SOCCER DATA

Build a football app with ChatGPT, Cursor or Lovable

Give your AI tool the Soccer Data reference at /llms.txt and a prompt with the API's rules, and keep the key on your server. Then let it write the code.

· 9 min read

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

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:

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:

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

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.

Try it with your own key

Fixtures, live scores, stats, xG, standings and odds for 1,100 competitions, in one REST API.

More guides