> ## Documentation Index
> Fetch the complete documentation index at: https://docs.odds-api.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Authenticate Odds-API.io requests using API keys. Includes code examples for cURL, Python, JavaScript and best practices for secure credentials.

## API Key Authentication

Odds-API.io uses API key authentication to secure access to the API. Your API key identifies your account and tracks your usage.

### Getting Your API Key

1. Sign up for free at [odds-api.io](https://odds-api.io)
2. Check your email for the API key
3. Copy and securely store your API key

<Warning>
  **Keep your API key secure!** Never share it publicly, commit it to version control, or expose it in client-side code.
</Warning>

### Using Your API Key

Include your API key as a query parameter in all authenticated requests:

```
https://api.odds-api.io/v3/events?apiKey=YOUR_API_KEY&sport=football
```

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.odds-api.io/v3/events?apiKey=YOUR_API_KEY&sport=football"
  ```

  ```javascript JavaScript theme={null}
  const apiKey = process.env.ODDS_API_KEY;

  const response = await fetch(
    `https://api.odds-api.io/v3/events?apiKey=${apiKey}&sport=football`
  );
  const data = await response.json();
  ```

  ```python Python theme={null}
  import os
  import requests

  api_key = os.environ.get('ODDS_API_KEY')

  response = requests.get(
      'https://api.odds-api.io/v3/events',
      params={
          'apiKey': api_key,
          'sport': 'football'
      }
  )
  data = response.json()
  ```

  ```php PHP theme={null}
  <?php
  $apiKey = getenv('ODDS_API_KEY');

  $url = 'https://api.odds-api.io/v3/events?' . http_build_query([
      'apiKey' => $apiKey,
      'sport' => 'football'
  ]);

  $response = file_get_contents($url);
  $data = json_decode($response, true);
  ```
</CodeGroup>

## Security Best Practices

### Use Environment Variables

Store your API key in environment variables instead of hardcoding it:

<CodeGroup>
  ```bash .env theme={null}
  ODDS_API_KEY=your_api_key_here
  ```

  ```javascript .env.local (Next.js) theme={null}
  ODDS_API_KEY=your_api_key_here
  ```

  ```python .env theme={null}
  ODDS_API_KEY=your_api_key_here
  ```
</CodeGroup>

### Server-Side Only

<Warning>
  Never expose your API key in client-side JavaScript code. Always make API calls from your backend server.
</Warning>

**Bad Practice:**

```javascript theme={null}
// ❌ DON'T DO THIS - API key exposed in browser
const apiKey = 'a7f3k9m2p5q8r1t4w6x9z2b5c8';
fetch(`https://api.odds-api.io/v3/events?apiKey=${apiKey}`);
```

**Good Practice:**

```javascript theme={null}
// ✅ Use a backend proxy
// Frontend
const response = await fetch('/api/odds/events');

// Backend (Node.js/Express)
app.get('/api/odds/events', async (req, res) => {
  const apiKey = process.env.ODDS_API_KEY;
  const response = await fetch(
    `https://api.odds-api.io/v3/events?apiKey=${apiKey}&sport=football`
  );
  const data = await response.json();
  res.json(data);
});
```

### Rotate Keys Regularly

For enhanced security:

* Rotate your API keys periodically
* Generate separate keys for development and production
* Immediately revoke compromised keys

## Rate Limiting

API requests are rate-limited based on your subscription plan:

| Plan    | Requests per Hour | Additional Packages Available |
| ------- | ----------------- | ----------------------------- |
| Starter | 5,000             | +10K/20K/30K per hour         |
| Growth  | 5,000             | +10K/20K/30K per hour         |
| Pro     | 5,000             | +10K/20K/30K per hour         |

**Additional Request Packages:**

* +10K requests/hour: £139/month
* +20K requests/hour: £229/month
* +30K requests/hour: £309/month
* Custom volumes available on request

### Monitoring Rate Limits

The API returns rate limit information in response headers with every request:

```
x-ratelimit-limit: 99999999
x-ratelimit-remaining: 99999862
x-ratelimit-reset: 2025-10-03T18:07:41Z
```

* **x-ratelimit-limit**: Total requests allowed in the current window
* **x-ratelimit-remaining**: Requests remaining in the current window
* **x-ratelimit-reset**: ISO 8601 timestamp when the rate limit resets

When you exceed the rate limit, you'll receive a `429 Too Many Requests` response:

```json theme={null}
{
  "error": "Rate limit exceeded. Please try again later."
}
```

### Best Practices for Rate Limiting

1. **Monitor response headers** to track your usage and remaining requests
2. **Implement exponential backoff** when you receive 429 errors
3. **Cache responses** when appropriate to reduce API calls
4. **Use the multi-odds endpoint** to fetch odds for multiple events in a single request

<CodeGroup>
  ```javascript JavaScript - Rate Limit Handling theme={null}
  async function fetchWithRetry(url, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
      const response = await fetch(url);

      // Log rate limit info
      console.log('Rate limit:', response.headers.get('x-ratelimit-remaining'),
                  '/', response.headers.get('x-ratelimit-limit'));
      console.log('Resets at:', response.headers.get('x-ratelimit-reset'));

      if (response.status === 429) {
        const resetTime = new Date(response.headers.get('x-ratelimit-reset'));
        const waitTime = Math.max(resetTime - Date.now(), Math.pow(2, i) * 1000);

        console.log(`Rate limited. Waiting ${waitTime}ms...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }

      return response.json();
    }

    throw new Error('Max retries exceeded');
  }
  ```

  ```python Python - Rate Limit Handling theme={null}
  import time
  import requests
  from datetime import datetime

  def fetch_with_retry(url, max_retries=3):
      for i in range(max_retries):
          response = requests.get(url)

          # Log rate limit info
          print(f"Rate limit: {response.headers.get('x-ratelimit-remaining')} / {response.headers.get('x-ratelimit-limit')}")
          print(f"Resets at: {response.headers.get('x-ratelimit-reset')}")

          if response.status_code == 429:
              reset_time = datetime.fromisoformat(response.headers.get('x-ratelimit-reset').replace('Z', '+00:00'))
              wait_time = max((reset_time - datetime.now()).total_seconds(), 2 ** i)

              print(f"Rate limited. Waiting {wait_time}s...")
              time.sleep(wait_time)
              continue

          return response.json()

      raise Exception('Max retries exceeded')
  ```
</CodeGroup>

## Error Responses

The API uses standard HTTP status codes:

| Status Code | Description                               |
| ----------- | ----------------------------------------- |
| 200         | Success                                   |
| 400         | Bad Request - Invalid parameters          |
| 401         | Unauthorized - Invalid or missing API key |
| 403         | Forbidden - API key doesn't have access   |
| 404         | Not Found - Resource doesn't exist        |
| 429         | Too Many Requests - Rate limit exceeded   |
| 500         | Internal Server Error                     |

All errors return a JSON response with an error message:

```json theme={null}
{
  "error": "Invalid API key provided"
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Make your first API call
  </Card>

  <Card title="Best Practices" icon="lightbulb" href="/guides/best-practices">
    Learn optimization techniques
  </Card>
</CardGroup>
