# Error codes

Whenever you receive an unexpected response or experience unexpected behavior, check the HTTP response code and use this guide to troubleshoot the issue.

### Quick reference

| Code  | Error                 | When It Happens                       | Quick Fix                                |
| ----- | --------------------- | ------------------------------------- | ---------------------------------------- |
| `200` | OK                    | Request succeeded                     | No action needed ✅                       |
| `400` | Bad Request           | Malformed request, invalid parameters | Check request syntax and parameters      |
| `401` | Unauthorized          | Missing or invalid API token          | Verify your API token                    |
| `403` | Forbidden             | Accessing unauthorized resource       | Check your plan access                   |
| `404` | Not Found             | Resource doesn't exist                | Verify the ID or endpoint                |
| `429` | Too Many Requests     | Rate limit exceeded                   | Reduce request frequency or upgrade plan |
| `500` | Internal Server Error | Server-side issue                     | Contact support if persistent            |

{% hint style="info" %}
💡 **Pro tip:** Always implement proper error handling in your code to catch and handle these errors gracefully.
{% endhint %}

### Detailed error explanations

Click an error code below for detailed troubleshooting information:

#### 200: OK ✅

**What it means:** Your request was successful and data has been returned.

**Response structure:**

```json
{
  "data": { ... },
  "subscription": [ ... ],
  "rate_limit": { ... },
  "timezone": "UTC"
}
```

**Best practices:**

* Always check that `data` field exists before processing
* Store `rate_limit` information to monitor your usage
* Handle empty data arrays appropriately

📖 **Learn more:** [Understanding API Responses](https://docs.sportmonks.com/v3/api/syntax)

#### 400: Bad request ❌

**What it means:** There's a problem with how your request is formatted or the parameters you're using.

**Common causes:**

**1. Invalid query parameters**

```bash
# ❌ Wrong
GET /fixtures?invalid_param=123

# ✅ Correct
GET /fixtures?api_token=YOUR_TOKEN&filters=fixtureLeagues:501
```

**2. Malformed include syntax**

```bash
# ❌ Wrong - space in include
GET /fixtures/123?include=participants, events

# ✅ Correct - no spaces
GET /fixtures/123?include=participants;events
```

**3. Invalid filter syntax**

```bash
# ❌ Wrong - incorrect format
GET /fixtures?filters=league=501

# ✅ Correct - proper format
GET /fixtures?filters=fixtureLeagues:501
```

**4. Invalid timezone**

```bash
# ❌ Wrong - invalid timezone
GET /fixtures?timezone=NewYork

# ✅ Correct - proper timezone format
GET /fixtures?timezone=America/New_York
```

**Example error response:**

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "filters": [
      "The filters format is invalid."
    ]
  }
}
```

**How to fix:**

1. **Check parameter spelling** - Use exact names from documentation
2. **Verify filter syntax** - See [Filter Tutorial](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/filter-and-select-fields)
3. **Validate include format** - Use semicolons, no spaces
4. **Test with minimal parameters** - Remove optional params to isolate issue

**Code example (Error handling):**

```javascript
try {
  const response = await fetch(url);
  const data = await response.json();
  
  if (response.status === 400) {
    console.error('Bad Request:', data.message);
    console.error('Errors:', data.errors);
    // Fix your request parameters
  }
} catch (error) {
  console.error('Request failed:', error);
}
```

📖 **Related:** [Request Options](https://docs.sportmonks.com/v3/api/request-options) | [Filter Tutorial](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/filter-and-select-fields)

#### 401: Unauthorized 🔒

**What it means:** Your API token is missing, invalid, or expired.

**Common causes:**

**1. Missing API token**

```bash
# ❌ Wrong - no token
GET https://api.sportmonks.com/v3/football/fixtures

# ✅ Correct - token included
GET https://api.sportmonks.com/v3/football/fixtures?api_token=YOUR_TOKEN
```

**2. Token typo or extra spaces**

```bash
# ❌ Wrong - extra space
?api_token= YOUR_TOKEN

# ✅ Correct - no spaces
?api_token=YOUR_TOKEN
```

**3. Using test token in production**

```bash
# ❌ Wrong environment
Production URL + Test Token = 401 Error

# ✅ Correct - matching environment
Production URL + Production Token
```

**4. Revoked or expired token**

* Token was regenerated in MySportmonks
* Token was manually revoked
* Account subscription expired

**Example error response:**

```json
{
  "error": "Unauthenticated.",
  "message": "Please provide a valid API token"
}
```

**How to fix:**

1. **Verify your token:**
   * Go to [MySportmonks Dashboard](https://my.sportmonks.com)
   * Navigate to API tokens section
   * Copy the correct token (don't type it manually)
2. **Test with cURL:**

```bash
curl "https://api.sportmonks.com/v3/football/fixtures?api_token=YOUR_TOKEN"
```

3. **Check token format in code:**

```javascript
// ✅ Correct implementation
const API_TOKEN = process.env.SPORTMONKS_TOKEN; // From environment variable
const url = `https://api.sportmonks.com/v3/football/fixtures?api_token=${API_TOKEN}`;
```

4. **Common mistakes to avoid:**
   * ❌ Hardcoding tokens in frontend code (security risk)
   * ❌ Including quotes around the token
   * ❌ Adding spaces before or after the token
   * ❌ Using header authentication (use query parameter instead)

**Security best practices:**

```javascript
// ✅ Good - Server-side only
// backend/api.js
const SPORTMONKS_TOKEN = process.env.SPORTMONKS_TOKEN;

// ❌ Bad - Exposed in frontend
// frontend/app.js
const SPORTMONKS_TOKEN = 'abcd1234...'; // Visible to users!
```

📖 **Related:** [Authentication Guide](https://docs.sportmonks.com/v3/welcome/authentication) | [Best Practices](https://docs.sportmonks.com/v3/welcome/best-practices)

#### 403: Forbidden 🚫

**What it means:** Your API token is valid, but you don't have permission to access this resource.

**Common Causes:**

**1. Resource not in your plan**

```bash
# Your plan: Free Plan (Danish & Scottish leagues only)
GET /fixtures?filters=fixtureLeagues:8 # Premier League
# Result: 403 Forbidden
```

**2. Feature not available in your tier**

```bash
# Your plan: Basic (no xG access)
GET /fixtures/123?include=xGFixture
# Result: 403 Forbidden
```

**3. Accessing premium endpoints**

```bash
# Predictions endpoint requires add-on
GET /predictions/probabilities/fixtures/123
# Result: 403 if you don't have Predictions add-on
```

**Example Error Response:**

```json
{
  "error": "Access Denied",
  "message": "This resource is not available in your current subscription.",
  "resource": "xGFixture",
  "plan_required": "Standard or higher"
}
```

**How to fix:**

1. **Check your plan access:**
   * Go to [MySportmonks Dashboard](https://my.sportmonks.com)
   * Review "My Subscriptions" section
   * Check which leagues and features are included
2. **Verify resource availability:**

```javascript
// Check if resource is accessible
try {
  const response = await fetch(url);
  
  if (response.status === 403) {
    console.log('Resource not available in your plan');
    console.log('Upgrade at: https://www.sportmonks.com/football-api/#plans-pricing');
  }
} catch (error) {
  console.error(error);
}
```

3. **Common 403 scenarios:**

| Scenario              | Plan needed        | Solution                          |
| --------------------- | ------------------ | --------------------------------- |
| Access Premier League | Standard+          | Upgrade or filter to your leagues |
| Use xG data           | Standard+          | Upgrade or remove xG includes     |
| Access Predictions    | Predictions add-on | Add predictions to your plan      |
| Live odds             | Odds package       | Subscribe to odds package         |

4. **Workarounds (if you can't upgrade):**
   * Filter requests to only leagues in your plan
   * Remove premium includes from requests
   * Check [Data Features by League](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/data-features-per-league)

📖 **Related:** [Plan Features](https://www.sportmonks.com/football-api/#plans-pricing) | [Data Features per League](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/data-features-per-league)

#### 404: Not found 🔍

**What it means:** The resource you're trying to access doesn't exist.

**Common Causes:**

**1. Invalid ID**

```bash
# ❌ Wrong - fixture doesn't exist
GET /fixtures/99999999999

# ✅ Correct - use valid fixture ID
GET /fixtures/18535517
```

**2. Wrong endpoint path**

```bash
# ❌ Wrong - typo in endpoint
GET /v3/football/fixture/123  # "fixture" should be "fixtures"

# ✅ Correct - proper endpoint
GET /v3/football/fixtures/123
```

**3. Resource was deleted**

```bash
# Fixture was removed from database
# (e.g., cancelled match, placeholder fixture)
GET /fixtures/12345
# Result: 404 Not Found
```

**Example error response:**

```json
{
  "error": "Not Found",
  "message": "The requested resource could not be found.",
  "resource_type": "fixture",
  "resource_id": "99999999"
}
```

**How to fix:**

1. **Verify the ID exists:**

```javascript
// Search for the resource first
const searchResponse = await fetch(
  'https://api.sportmonks.com/v3/football/fixtures/search/Celtic?api_token=YOUR_TOKEN'
);
const results = await searchResponse.json();

// Then use a valid ID from results
const fixtureId = results.data[0].id;
```

2. **Check endpoint spelling:**

```javascript
// ✅ Correct endpoints
/v3/football/fixtures/{id}
/v3/football/teams/{id}
/v3/football/players/{id}
/v3/football/leagues/{id}

// ❌ Common mistakes
/v3/football/fixture/{id}   // Missing 's'
/v3/soccer/fixtures/{id}    // Wrong sport name
/v3/football/match/{id}     // Wrong entity name
```

3. **Handle 404 gracefully:**

```javascript
async function getFixture(fixtureId) {
  try {
    const response = await fetch(
      `https://api.sportmonks.com/v3/football/fixtures/${fixtureId}?api_token=YOUR_TOKEN`
    );
    
    if (response.status === 404) {
      console.log('Fixture not found');
      return null; // Return null instead of throwing error
    }
    
    return await response.json();
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}
```

4. **Use ID finder tool:**
   * Visit [ID Finder](https://my.sportmonks.com/resources/id-finder)
   * Search for teams, players, leagues, etc.
   * Get valid IDs for your requests

📖 **Related:** [API Structure](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/api-structure-and-navigation) | [Endpoints Reference](https://docs.sportmonks.com/v3/endpoints-and-entities/endpoints)

#### 429: Too many requests ⏱️

**What it means:** You've exceeded your plan's rate limit.

**Common causes:**

**1. Making requests too quickly**

```javascript
// ❌ Wrong - rapid fire requests
for (let i = 0; i < 1000; i++) {
  fetch(url); // Hitting rate limit!
}

// ✅ Correct - with delay
for (let i = 0; i < 1000; i++) {
  await fetch(url);
  await sleep(100); // 100ms delay
}
```

**2. Inefficient API usage**

```javascript
// ❌ Wrong - separate requests
const team1 = await fetch('/teams/53');
const team2 = await fetch('/teams/62');
const fixture = await fetch('/fixtures/123');
// 3 API calls

// ✅ Correct - use includes
const fixture = await fetch('/fixtures/123?include=participants');
// 1 API call with all data
```

**3. Not caching reference data**

```javascript
// ❌ Wrong - fetching types every time
for (const stat of statistics) {
  const type = await fetch(`/types/${stat.type_id}`); // Wasteful!
}

// ✅ Correct - fetch once and cache
const allTypes = await fetch('/types');
const typesMap = new Map(allTypes.data.map(t => [t.id, t]));
// Use cached types for lookups
```

**Example error response:**

```json
{
  "error": "Too Many Requests",
  "message": "Rate limit of 3000 requests per hour exceeded.",
  "retry_after": 1847,
  "rate_limit": {
    "remaining": 0,
    "total": 3000,
    "resets_in": "30:47"
  }
}
```

**How to fix:**

**1. Check your rate limit:** Every successful response includes rate limit info:

```json
{
  "data": { ... },
  "rate_limit": {
    "resets_in_seconds": 1847,
    "remaining": 2847,
    "requested_entity": "Fixture"
  }
}
```

**2. Implement rate limiting:**

```javascript
class RateLimiter {
  constructor(maxRequests, perSeconds) {
    this.maxRequests = maxRequests;
    this.perSeconds = perSeconds;
    this.requests = [];
  }
  
  async throttle() {
    const now = Date.now();
    this.requests = this.requests.filter(time => now - time < this.perSeconds * 1000);
    
    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = (this.perSeconds * 1000) - (now - oldestRequest);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
    
    this.requests.push(Date.now());
  }
}

// Usage
const limiter = new RateLimiter(3000, 3600); // 3000 requests per hour

async function makeRequest(url) {
  await limiter.throttle();
  return fetch(url);
}
```

**3. Implement retry with exponential backoff:**

```javascript
async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url);
      
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
        console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
        await sleep(retryAfter * 1000);
        continue;
      }
      
      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await sleep(Math.pow(2, i) * 1000);
    }
  }
}
```

**4. Optimisation strategies:**

| Strategy             | Savings | How                                      |
| -------------------- | ------- | ---------------------------------------- |
| Use includes         | 50-80%  | Combine related data in one request      |
| Cache reference data | 30-50%  | Store types, states, leagues locally     |
| Batch operations     | 40-60%  | Use multi-ID endpoints where available   |
| Smart polling        | 30-40%  | Only poll live matches, reduce frequency |

**Example - Before & after optimisation:**

```javascript
// ❌ Before: 50 API calls for 10 fixtures
for (const fixtureId of fixtureIds) {
  const fixture = await fetch(`/fixtures/${fixtureId}`);
  const team1 = await fetch(`/teams/${fixture.team1_id}`);
  const team2 = await fetch(`/teams/${fixture.team2_id}`);
  const events = await fetch(`/fixtures/${fixtureId}/events`);
  const stats = await fetch(`/fixtures/${fixtureId}/statistics`);
}

// ✅ After: 2 API calls for 10 fixtures
const fixtures = await fetch(
  `/fixtures/multi/${fixtureIds.join(',')}?include=participants;events;statistics.type`
);
// Then fetch types once and cache
const types = await fetch('/types');
```

**5. Monitor your usage:**

```javascript
function logRateLimit(response) {
  const remaining = response.rate_limit.remaining;
  const total = response.rate_limit.resets_in_seconds;
  
  console.log(`Rate limit: ${remaining} requests remaining`);
  
  if (remaining < 100) {
    console.warn('⚠️ Low on API calls! Optimize your requests.');
  }
}
```

📖 **Related:** [Rate Limiting Guide](https://docs.sportmonks.com/v3/api/rate-limit) | [Includes Tutorial](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/includes) | [Best Practices](https://docs.sportmonks.com/v3/welcome/best-practices)

#### 500: Internal server error 🔧

**What it means:** Something went wrong on our servers.

**When this happens:**

* Temporary server issue
* Database connectivity problem
* Unexpected data format causing server error
* Service maintenance or deployment

**Example error response:**

```json
{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred. Our team has been notified.",
  "error_id": "err_abc123xyz"
}
```

**How to handle:**

**1. Implement retry logic:**

```javascript
async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url);
      
      if (response.status === 500) {
        if (i < maxRetries - 1) {
          console.log(`Server error. Retrying (${i + 1}/${maxRetries})...`);
          await sleep(Math.pow(2, i) * 1000); // Exponential backoff
          continue;
        }
      }
      
      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }
}
```

**2. Check API status:**

* Visit [Sportmonks Status Page](https://status.sportmonks.com) (if available)
* Check for maintenance announcements
* Verify if issue is widespread or isolated

**3. Contact support (if persistent):**

```javascript
if (response.status === 500) {
  const errorDetails = {
    timestamp: new Date().toISOString(),
    endpoint: url,
    error_id: response.error_id,
    request_id: response.headers.get('X-Request-ID')
  };
  
  console.error('Persistent 500 error:', errorDetails);
  // Send to support: support@sportmonks.com
}
```

**4. Graceful degradation:**

```javascript
async function getFixtures(fallbackData) {
  try {
    const response = await fetch(url);
    
    if (response.status === 500) {
      console.warn('Server error. Using cached data.');
      return fallbackData; // Use cached or default data
    }
    
    return await response.json();
  } catch (error) {
    console.error('Complete failure:', error);
    return fallbackData;
  }
}
```

📖 **Related:** [Best Practices](https://docs.sportmonks.com/v3/welcome/best-practices) | [Contact Support](https://www.sportmonks.com/contact-support/)

### Common error scenarios

#### Scenario 1: "Data is missing"

**Problem:** Response is 200 OK but data is empty or missing fields.

**Not an error code issue!** This is usually because:

* Resource not in your plan → Check [plan access](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/data-features-per-league)
* Missing includes → Add necessary includes to request
* Filtering too strict → Review [filter syntax](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/filter-and-select-fields)

#### Scenario 2: CORS Errors (Frontend)

**Problem:** "CORS policy: No 'Access-Control-Allow-Origin' header"

**Solution:** This isn't an API error - use a backend proxy:

```javascript
// ❌ Don't do this - frontend direct call
fetch('https://api.sportmonks.com/v3/football/fixtures?api_token=YOUR_TOKEN')

// ✅ Do this - call your backend
fetch('/api/fixtures') // Your backend proxies to Sportmonks
```

📖 **Learn more:** [CORS Best Practices](https://docs.sportmonks.com/v3/welcome/best-practices#cors-and-frontend-security)

#### Scenario 3: Timeout errors

**Problem:** Request times out with no response.

**Solutions:**

* Reduce includes complexity
* Add pagination to large requests
* Check your network connectivity
* Increase timeout threshold in your code

### Error handling code examples

#### Complete Example (JavaScript/Node.js)

{% tabs %}
{% tab title="JavaScript" %}

```javascript
class SportmonksAPI {
  constructor(apiToken) {
    this.apiToken = apiToken;
    this.baseURL = 'https://api.sportmonks.com/v3/football';
  }
  
  async request(endpoint, params = {}) {
    const url = new URL(`${this.baseURL}${endpoint}`);
    url.searchParams.append('api_token', this.apiToken);
    
    Object.entries(params).forEach(([key, value]) => {
      url.searchParams.append(key, value);
    });
    
    try {
      const response = await fetch(url.toString());
      const data = await response.json();
      
      // Handle different status codes
      switch (response.status) {
        case 200:
          return { success: true, data: data.data };
          
        case 400:
          throw new Error(`Bad Request: ${data.message || 'Invalid parameters'}`);
          
        case 401:
          throw new Error('Unauthorized: Check your API token');
          
        case 403:
          throw new Error(`Forbidden: ${data.message || 'Resource not in your plan'}`);
          
        case 404:
          throw new Error(`Not Found: Resource doesn't exist`);
          
        case 429:
          const retryAfter = data.retry_after || 60;
          throw new Error(`Rate Limited: Retry after ${retryAfter} seconds`);
          
        case 500:
          throw new Error('Server Error: Try again later or contact support');
          
        default:
          throw new Error(`Unexpected error: ${response.status}`);
      }
    } catch (error) {
      console.error('API Error:', error.message);
      throw error;
    }
  }
}

// Usage
const api = new SportmonksAPI(process.env.SPORTMONKS_TOKEN);

try {
  const fixtures = await api.request('/fixtures', {
    filters: 'fixtureLeagues:501',
    include: 'participants'
  });
  console.log(fixtures);
} catch (error) {
  // Handle error appropriately in your app
  console.error('Failed to fetch fixtures:', error);
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests
import time
from typing import Optional, Dict, Any

class SportmonksAPI:
    def __init__(self, api_token: str):
        self.api_token = api_token
        self.base_url = 'https://api.sportmonks.com/v3/football'
    
    def request(self, endpoint: str, params: Optional[Dict] = None, retry_count: int = 0) -> Dict[str, Any]:
        """Make API request with error handling"""
        url = f"{self.base_url}{endpoint}"
        
        # Add API token to params
        if params is None:
            params = {}
        params['api_token'] = self.api_token
        
        try:
            response = requests.get(url, params=params, timeout=30)
            
            # Handle different status codes
            if response.status_code == 200:
                return {'success': True, 'data': response.json().get('data')}
            
            elif response.status_code == 400:
                error_data = response.json()
                raise ValueError(f"Bad Request: {error_data.get('message', 'Invalid parameters')}")
            
            elif response.status_code == 401:
                raise PermissionError('Unauthorized: Check your API token')
            
            elif response.status_code == 403:
                error_data = response.json()
                raise PermissionError(f"Forbidden: {error_data.get('message', 'Resource not in your plan')}")
            
            elif response.status_code == 404:
                raise LookupError('Not Found: Resource does not exist')
            
            elif response.status_code == 429:
                if retry_count < 3:
                    retry_after = response.json().get('retry_after', 60)
                    print(f"Rate limited. Retrying after {retry_after} seconds...")
                    time.sleep(retry_after)
                    return self.request(endpoint, params, retry_count + 1)
                else:
                    raise Exception('Rate limit exceeded. Max retries reached.')
            
            elif response.status_code == 500:
                if retry_count < 2:
                    wait_time = 2 ** retry_count
                    print(f"Server error. Retrying after {wait_time} seconds...")
                    time.sleep(wait_time)
                    return self.request(endpoint, params, retry_count + 1)
                else:
                    raise Exception('Server error persists. Contact support.')
            
            else:
                raise Exception(f"Unexpected error: HTTP {response.status_code}")
        
        except requests.exceptions.Timeout:
            raise TimeoutError('Request timed out')
        
        except requests.exceptions.ConnectionError:
            raise ConnectionError('Failed to connect to API')

# Usage
api = SportmonksAPI(os.environ.get('SPORTMONKS_TOKEN'))

try:
    fixtures = api.request('/fixtures', {
        'filters': 'fixtureLeagues:501',
        'include': 'participants'
    })
    print(f"Found {len(fixtures['data'])} fixtures")
except Exception as error:
    print(f"Error: {error}")
```

{% endtab %}
{% endtabs %}

### See also

* [Rate Limiting Guide](https://docs.sportmonks.com/v3/api/rate-limit) - Understand and optimize rate limits
* [Authentication](https://docs.sportmonks.com/v3/welcome/authentication) - Setting up API access
* [Best Practices](https://docs.sportmonks.com/v3/welcome/best-practices) - Build robust applications
* [Request Options](https://docs.sportmonks.com/v3/api/request-options) - Available query parameters
* [Contact Support](https://www.sportmonks.com/contact-support/) - Get help when needed

\
**Need help?** Contact <support@sportmonks.com> or use the chat widget.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sportmonks.com/v3/api/error-codes.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
