To build a football app with ChatGPT, Cursor or Lovable, give the AI tool the Soccer Data reference at https://soccer-data.co/llms.txt, paste a prompt with the API's rules, and keep your API key on your server. With the real endpoints in front of it, the tool uses the exact paths and parameter names instead of guessing them. This guide has a ready-to-paste prompt, a worked league table page and the mistakes to watch for.
Note To have ChatGPT or Claude answer football questions while you chat, rather than write code, connect the MCP server instead.
What you need
- A Soccer Data API key. Create an account. Your first key is made at sign-up, and keys start with
sd_. You can create more on the API keys page. - An AI coding tool, such as ChatGPT, Cursor, Lovable, Claude Code or Codex.
- Somewhere server-side to call the API from: a route handler, a server function or an Edge Function. The key must never reach the browser.
Step 1: Give the AI tool the docs
llms.txt
The whole Soccer Data documentation, including every endpoint with its parameters, response fields and an example, is published as one plain-text file:
https://soccer-data.co/llms.txt
Paste that URL into your prompt, or save a copy inside your project so the tool can read it offline:
mkdir -p docs
curl -o docs/soccer-data-llms.txt https://soccer-data.co/llms.txt
Copy page
Every docs page and every endpoint reference page has a Copy page button that copies the page as Markdown, for an AI tool. Use it when the job only needs one part of the API: open League table or List matches, click Copy page, and paste it into the chat along with your prompt.
Where it goes in each tool
| Tool | Setup |
|---|---|
| ChatGPT | Paste the llms.txt link, attach the file, or paste a page copied with Copy page, along with the prompt below. Never paste your key into the chat. |
| Cursor | Save the reference to docs/soccer-data-llms.txt, create .env and put the rules in AGENTS.md. Mention @docs/soccer-data-llms.txt in chat. Don't add the reference file to .gitignore: Cursor leaves ignored files out of what it can see. |
| Lovable | Paste the rules into Project settings, under Knowledge. Store the key with the Add secret form, not in chat, and call the API from a server function or Edge Function. |
| Claude Code | Save the reference and .env in the project, put the rules in CLAUDE.md at the root, then run claude. Deny rules for ./.env and ./.env.* in .claude/settings.json stop it reading the key. |
The Build with AI page in your dashboard has a starter prompt with your base URL and the reference link filled in, plus ideas to build.
Step 2: Keep the API key on the server
Anyone who has your key can spend your requests. The safe setup is the same in every tool:
- Your frontend calls your own server route, and only that route calls Soccer Data. Anything that runs in a browser can be read in the page source or the network tab.
- Read the key from an environment variable,
SOCCER_DATA_KEY, and keep.envout of git (see below). - Avoid public variables. Anything starting with
NEXT_PUBLIC_orVITE_is bundled into browser code. - Use the platform's secrets store in Lovable and similar builders, since a project's
.envfile may be committed. - Send the key in the
X-Api-Keyheader (orAuthorization: Bearer). A key in the URL is ignored, and the request fails with401.
echo 'SOCCER_DATA_KEY=YOUR_API_KEY' >> .env
echo '.env' >> .gitignore
Step 3: Paste this prompt
Replace the last part with what you want to build. The rules are the ones the docs give for AI tools.
I'm building a football app with the Soccer Data API.
Read the reference first: https://soccer-data.co/llms.txt
Rules:
- Base URL: https://api.soccer-data.co/v1
- Send the key in the X-Api-Key header. Read it from the SOCCER_DATA_KEY
environment variable on the server. Never put it in browser code, in
NEXT_PUBLIC_ or VITE_ variables, or in git.
- The frontend calls my own server route; only that route calls Soccer Data.
- Ids are strings like team_... or competition_.... Look them up with the
search parameter on /competitions, /teams and /players. Never invent them.
- Dates use date_from and date_to (YYYY-MM-DD). There is no date parameter.
- /matches lists the latest kickoff first. For upcoming matches use
status=scheduled and sort=utc_date. status=live lists matches in play.
- Lists are paged with page and per_page (up to 100). Stop at meta.total_pages.
- Errors look like {"error": {"code", "message", "status_code"}}. Branch on code.
- 400 UNKNOWN_PARAMETER means a wrong parameter name: use the one the message suggests.
- On RATE_LIMITED (429) or 503, wait the seconds in Retry-After, then retry
a few times at most. Don't retry USAGE_LIMIT_EXCEEDED: it lasts until
midnight UTC.
- Cache responses. There are no webhooks, so poll live data only while a match is live.
- Check flags such as has_player_stats, xg_available, shotmap_available and
odds_available before calling endpoints that depend on them.
First test: fetch the Premier League (competition_id competition_705uv6jm8myn) and
print its current_season_id.
What I want to build:
A page that shows the current Premier League table: position, team,
played, goal difference and points.
Worked example: a league table page
A table belongs to one season of one competition, so the app needs two calls: the competition, to read its current_season_id, then that season's standings.
curl "https://api.soccer-data.co/v1/competitions/competition_705uv6jm8myn" \
-H "X-Api-Key: YOUR_API_KEY"
# data.current_season_id is season_hr1ie7yrsdmt (Premier League 26/27)
curl "https://api.soccer-data.co/v1/competitions/competition_705uv6jm8myn/seasons/season_hr1ie7yrsdmt/standings" \
-H "X-Api-Key: YOUR_API_KEY"
The standings come back in one response, one row per team in position order, with team, matches_played, wins, draws, losses, goals_for, goals_against, goal_difference and points. A good result from the prompt above looks like this Next.js route handler:
// app/api/table/route.js: runs on the server, so the key never reaches the browser
const API = "https://api.soccer-data.co/v1";
const PREMIER_LEAGUE = "competition_705uv6jm8myn";
const headers = { "X-Api-Key": process.env.SOCCER_DATA_KEY };
// Next.js: run on every request; the route does its own caching below.
export const dynamic = "force-dynamic";
let cached = null;
async function get(path) {
const res = await fetch(`${API}${path}`, { headers });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);
return body.data;
}
export async function GET() {
// Refresh at most once a minute, however many people open the page.
if (!cached || Date.now() - cached.at > 60_000) {
const competition = await get(`/competitions/${PREMIER_LEAGUE}`);
const table = await get(
`/competitions/${PREMIER_LEAGUE}/seasons/${competition.current_season_id}/standings`,
);
cached = {
at: Date.now(),
rows: table.map((row) => ({
position: row.position,
team: row.team.name,
played: row.matches_played,
goal_difference: row.goal_difference,
points: row.points,
})),
};
}
return Response.json(cached.rows);
}
The page then fetches /api/table and renders the rows. When you review what the tool wrote, check that it:
- reads
current_season_idrather than hard-coding a season id, so the table moves on to the new season by itself; - sends no
pageorper_pageto the standings endpoint, since tables aren't paged and the parameters are rejected; - caches the answer on the server, because every refresh spends two requests.
Variation: a live scores widget. Have the server route call /matches?competition_id=competition_705uv6jm8myn&status=live&per_page=100 and show each match's score and live.elapsed_minutes. Poll every 15 to 30 seconds only while matches are in play, and handle live fields being null in the first moments after kickoff. See Live matches.
Common mistakes AI tools make with this API
| Mistake | What you see | Fix |
|---|---|---|
| Inventing or hand-typing ids | 404 NOT_FOUND |
Look ids up with search on /competitions, /teams or /players, and copy them exactly. |
Inventing parameters, such as date=2026-10-10 |
400 UNKNOWN_PARAMETER, with the closest valid name and the accepted list |
Use date_from and date_to, both set to the same day. |
Leaving out sort=utc_date for upcoming matches |
The match furthest in the future comes first | Add sort=utc_date with status=scheduled. |
| Pairing a season with another competition | 400 SEASON_COMPETITION_MISMATCH |
Take seasons from that competition's /competitions/{competition_id}/seasons. |
| Asking for final stats while a match is live | 409 MATCH_IS_LIVE |
Use /matches/{match_id}/live-stats until full time. |
Counting only goal events |
Penalty goals are missing | Ask for event_type=goal,penalty_scored, and skip shoot-out kicks (period: "penalties"). |
| Calling xG, shot map or odds endpoints without checking | 404 for data the match doesn't have |
Check xg_available, shotmap_available and odds_available first. |
| Retry loops or polling faster than the live refresh | A day's requests spent in minutes, then 429 |
Cache, respect Retry-After, and poll only matches in play. |
| Putting the key in browser code | Anyone can copy it and spend your requests | Move the call to a server route. |
When a call fails, paste the full error JSON back to the tool. The message usually says exactly what to change, and UNKNOWN_PARAMETER errors don't count against your daily limit. Your usage page shows what's been spent.
Next steps
- Build with AI: the full setup for each AI coding tool.
- Build with AI dashboard page: the starter prompt with your details filled in.
- Quickstart: the same first requests, by hand.
- Authentication: keys, rotation and a safe server setup.
Frequently asked questions
Can ChatGPT build a football app?
Yes. Give ChatGPT the Soccer Data reference at https://soccer-data.co/llms.txt, or a docs page copied with Copy page, plus a prompt with the API's rules, and it can write the code. Keep your Soccer Data API key out of the chat and read it from an environment variable on your server.
Does Soccer Data have an llms.txt file?
Yes. https://soccer-data.co/llms.txt is the whole Soccer Data documentation as one plain-text file, with every endpoint's parameters, response fields and an example, so AI coding tools use the real paths and parameter names.
How do I keep a football API key safe in Lovable?
Store the Soccer Data key with Lovable's Add secret form, not in chat, and call the API from a server function or Edge Function. Never put the key in browser code or in NEXT_PUBLIC_ or VITE_ variables.
Why does my AI-generated code get 400 UNKNOWN_PARAMETER?
The AI tool sent a parameter the Soccer Data endpoint doesn't take, such as date instead of date_from and date_to. The error message suggests the closest valid name and lists every accepted parameter, so paste it back to the tool. These errors don't count against your daily limit.
How do I get team ids for a football app?
Search by name: Soccer Data's /teams?search=Arsenal&is_mens_team=true lists Arsenal's first team as team_f0ibhxdw7hs5, and /competitions and /players take search too. Ids never change, so look them up once, store them, and never let an AI tool invent one.
Can I call the Soccer Data API directly from the browser?
No. Anything that runs in a browser can be read by visitors, including your API key. Have your page call a route on your own server, and let only that route call Soccer Data with the key from an environment variable.