Authentication
Send your API key with every request, and keep it on your server.
Every request needs an API key. You send it in a header, we check it, and the request counts against your plan.
Sending your key
Put the key in the X-Api-Key header:
curl "https://api.soccer-data.co/v1/competitions?per_page=1" \
-H "X-Api-Key: YOUR_API_KEY"
const res = await fetch("https://api.soccer-data.co/v1/competitions?per_page=1", {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
console.log(res.status, await res.json());
import os, requests
res = requests.get(
"https://api.soccer-data.co/v1/competitions",
params={"per_page": 1},
headers={"X-Api-Key": os.environ["SOCCER_DATA_KEY"]},
)
print(res.status_code, res.json())
$response = Http::withHeaders(['X-Api-Key' => env('SOCCER_DATA_KEY')])
->get('https://api.soccer-data.co/v1/competitions', ['per_page' => 1]);
If your HTTP client is already set up for bearer tokens, Authorization: Bearer YOUR_API_KEY works too. Use one or the other.
Note Keys only work in a header. A key added to the URL as a query parameter is ignored, and the request fails with
401.
Managing keys
Your first key is made for you when you sign up. Create more, and revoke them, on the API keys page of your dashboard.
- Keys start with
sd_. - Give each key a name, such as the app or environment that uses it, so you can tell them apart later.
- We show a new key in full only once. Copy it before you leave the page.
- You can hold up to 10 active keys at a time.
- All your keys share your account's per-minute and daily limits. See Rate limits.
Revoking and rotating
Revoking a key stops it at once; any request that still uses it gets 401. If a key might have leaked, rotate it without downtime:
- Create a new key.
- Put the new key on your server and deploy.
- Revoke the old key.
Keeping your key safe
Your key is tied to your account and your plan. Anyone who has it can spend your requests.
- Call the API from your server. If you have a website or a mobile app, have it call a route on your own server, and let only that route talk to Soccer Data. Anything that runs in a browser can be read by visitors in the page source or the network tab.
- Read the key from an environment variable. Don't hard-code it, and add
.envto.gitignore. - Watch out for public variables. Variables that start with
NEXT_PUBLIC_orVITE_are bundled into browser code, so they are no place for a key. - Use your platform's secrets store. In tools like Lovable, use the Add secret form and call the API from a server function, since the project's
.envfile may be committed. - Keep it out of sight. Don't paste the key into logs, screenshots, support tickets or AI chats.
Environment variables
# .env (keep this file out of git)
SOCCER_DATA_KEY=YOUR_API_KEY
// config.js
export const apiKey = process.env.SOCCER_DATA_KEY;
if (!apiKey) throw new Error("SOCCER_DATA_KEY is not set");
In Node.js 20.6 or later, node --env-file=.env app.js loads the file for you; on older versions use the dotenv package. In Python, python-dotenv does the same job before you read os.environ["SOCCER_DATA_KEY"].
A server route in Next.js
This route handler keeps the key on the server. Your page calls /api/next-fixture, and only the server talks to Soccer Data:
// app/api/next-fixture/route.js
export async function GET() {
const params = new URLSearchParams({
team_id: "team_f0ibhxdw7hs5",
status: "scheduled",
sort: "utc_date",
per_page: "1",
});
const res = await fetch(`https://api.soccer-data.co/v1/matches?${params}`, {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
return Response.json(await res.json(), { status: res.status });
}
When something goes wrong
Authentication problems use the same error shape as everything else:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Send your API key in the X-Api-Key header. You can create one in your dashboard.",
"status_code": 401
}
}
| Status | Code | What it means | What to do |
|---|---|---|---|
| 401 | UNAUTHORIZED |
No key was sent. | Add the X-Api-Key header. |
| 401 | UNAUTHORIZED |
The key is wrong or has been revoked. | Check for missing characters or extra spaces, or create a new key. |
| 403 | PLAN_UPGRADE_REQUIRED |
The endpoint isn't part of your plan. Odds need a paid plan. | Upgrade on the plans page, or use another endpoint. |
| 429 | RATE_LIMITED or USAGE_LIMIT_EXCEEDED |
You've hit the per-minute or daily limit. | Wait the number of seconds in Retry-After. |
Note Too many requests with invalid keys from the same address within a minute get a
429 RATE_LIMITEDfor a short while. Fix the key, then wait forRetry-After.
Once your key is accepted, every response, errors included, carries your rate limit headers. Rate limits explains them.
Next steps
- Quickstart: your first requests.
- Errors: every error code.
- Build with AI: set up an AI coding tool without exposing your key.
# Authentication
Send your API key with every request, and keep it on your server.
Every request needs an API key. You send it in a header, we check it, and the request counts against your plan.
## Sending your key
Put the key in the `X-Api-Key` header:
```bash
curl "https://api.soccer-data.co/v1/competitions?per_page=1" \
-H "X-Api-Key: YOUR_API_KEY"
```
```javascript
const res = await fetch("https://api.soccer-data.co/v1/competitions?per_page=1", {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
console.log(res.status, await res.json());
```
```python
import os, requests
res = requests.get(
"https://api.soccer-data.co/v1/competitions",
params={"per_page": 1},
headers={"X-Api-Key": os.environ["SOCCER_DATA_KEY"]},
)
print(res.status_code, res.json())
```
```php
$response = Http::withHeaders(['X-Api-Key' => env('SOCCER_DATA_KEY')])
->get('https://api.soccer-data.co/v1/competitions', ['per_page' => 1]);
```
If your HTTP client is already set up for bearer tokens, `Authorization: Bearer YOUR_API_KEY` works too. Use one or the other.
> **Note:** Keys only work in a header. A key added to the URL as a query parameter is ignored, and the request fails with `401`.
## Managing keys
Your first key is made for you when you sign up. Create more, and revoke them, on the [API keys](/dashboard/keys) page of your dashboard.
- Keys start with `sd_`.
- Give each key a name, such as the app or environment that uses it, so you can tell them apart later.
- We show a new key in full only once. Copy it before you leave the page.
- You can hold up to 10 active keys at a time.
- All your keys share your account's per-minute and daily limits. See [Rate limits](/docs/concepts/rate-limits).
### Revoking and rotating
Revoking a key stops it at once; any request that still uses it gets `401`. If a key might have leaked, rotate it without downtime:
1. Create a new key.
2. Put the new key on your server and deploy.
3. Revoke the old key.
## Keeping your key safe
Your key is tied to your account and your plan. Anyone who has it can spend your requests.
- **Call the API from your server.** If you have a website or a mobile app, have it call a route on your own server, and let only that route talk to Soccer Data. Anything that runs in a browser can be read by visitors in the page source or the network tab.
- **Read the key from an environment variable.** Don't hard-code it, and add `.env` to `.gitignore`.
- **Watch out for public variables.** Variables that start with `NEXT_PUBLIC_` or `VITE_` are bundled into browser code, so they are no place for a key.
- **Use your platform's secrets store.** In tools like Lovable, use the Add secret form and call the API from a server function, since the project's `.env` file may be committed.
- **Keep it out of sight.** Don't paste the key into logs, screenshots, support tickets or AI chats.
### Environment variables
```bash
# .env (keep this file out of git)
SOCCER_DATA_KEY=YOUR_API_KEY
```
```javascript
// config.js
export const apiKey = process.env.SOCCER_DATA_KEY;
if (!apiKey) throw new Error("SOCCER_DATA_KEY is not set");
```
In Node.js 20.6 or later, `node --env-file=.env app.js` loads the file for you; on older versions use the `dotenv` package. In Python, `python-dotenv` does the same job before you read `os.environ["SOCCER_DATA_KEY"]`.
### A server route in Next.js
This route handler keeps the key on the server. Your page calls `/api/next-fixture`, and only the server talks to Soccer Data:
```javascript
// app/api/next-fixture/route.js
export async function GET() {
const params = new URLSearchParams({
team_id: "team_f0ibhxdw7hs5",
status: "scheduled",
sort: "utc_date",
per_page: "1",
});
const res = await fetch(`https://api.soccer-data.co/v1/matches?${params}`, {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
return Response.json(await res.json(), { status: res.status });
}
```
## When something goes wrong
Authentication problems use the same error shape as everything else:
```json
{
"error": {
"code": "UNAUTHORIZED",
"message": "Send your API key in the X-Api-Key header. You can create one in your dashboard.",
"status_code": 401
}
}
```
| Status | Code | What it means | What to do |
|---|---|---|---|
| 401 | `UNAUTHORIZED` | No key was sent. | Add the `X-Api-Key` header. |
| 401 | `UNAUTHORIZED` | The key is wrong or has been revoked. | Check for missing characters or extra spaces, or create a new key. |
| 403 | `PLAN_UPGRADE_REQUIRED` | The endpoint isn't part of your plan. Odds need a paid plan. | Upgrade on the [plans page](/dashboard/subscription), or use another endpoint. |
| 429 | `RATE_LIMITED` or `USAGE_LIMIT_EXCEEDED` | You've hit the per-minute or daily limit. | Wait the number of seconds in `Retry-After`. |
> **Note:** Too many requests with invalid keys from the same address within a minute get a `429 RATE_LIMITED` for a short while. Fix the key, then wait for `Retry-After`.
Once your key is accepted, every response, errors included, carries your rate limit headers. [Rate limits](/docs/concepts/rate-limits) explains them.
## Next steps
- [Quickstart](/docs/quickstart): your first requests.
- [Errors](/docs/concepts/errors): every error code.
- [Build with AI](/docs/build-with-ai): set up an AI coding tool without exposing your key.