Authentication

One header on every request, and a key that never leaves your server.

Send the key as a bearer token

Every request to https://api.sockodds.com/v1 needs your key in the Authorization header:

Authorization: Bearer <your key>

That header is the only place the API reads a key. A key in the query string or in any other header is ignored, and the request fails with 401. The WebSocket stream takes the same header on its upgrade request; see Streaming.

All the examples on this site read the key from the SOCKODDS_API_KEY environment variable, as the quickstart sets up.

Keep the key on your server

Your key is tied to your plan and your monthly allowance. Anyone who has it can spend that allowance.

  • Call the API from your own server, a serverless function or a scheduled job, never from code that runs in a browser or a mobile app. Anything shipped to a device can be read.
  • Never put the key in a URL. URLs end up in logs, browser history and analytics.
  • Keep it out of source control. Read it from an environment variable or your platform's secret store.
  • When your front end needs prices, have it call your own server, and have your server call SockOdds.
  • Never send your key to support. We will never ask for it.

Rotate a lost or leaked key

Sign in to your account and choose Rotate key. Rotation disables the old key straight away and shows the replacement once, so copy it before you leave the page. Update every server that uses the key: requests with the old one fail with 401 from that moment.

If you closed checkout before your key was shown, sign in and choose Recover a paid purchase.

401: the key was not accepted

A 401 means the API could not accept your key: the header is missing, it is not in the Bearer <key> form, or the key is unknown or has been rotated. The body is a problem object, as on every error:

curl -sS "https://api.sockodds.com/v1/sports" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
const response = await fetch("https://api.sockodds.com/v1/sports", {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
// Print the body whatever the status: on an error it is a problem object.
console.log(JSON.stringify(await response.json(), null, 2));
import json, os, urllib.error, urllib.request

request = urllib.request.Request(
    "https://api.sockodds.com/v1/sports",
    headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
try:
    with urllib.request.urlopen(request) as response:
        body = json.load(response)
except urllib.error.HTTPError as error:
    body = json.load(error)  # the problem object
print(json.dumps(body, indent=2))

Response from a local copy of the API with invented test prices

{
  "type": "https://api.sockodds.com/problems/401",
  "title": "Unauthorised",
  "status": 401,
  "detail": "Provide a valid bearer key.",
  "instance": "/v1/sports"
}

Check that the variable holds your current key, with no spaces or quotes around it. Retrying the same key will not help.

A 401 titled Access changed means your key was valid when the request arrived but was rotated, or its plan changed, before the response was sent. Retry with your active key.

403: your plan does not include this

A 403 means the key is fine but your plan does not cover what you asked for: a league outside the plan, or a Pro feature such as history and snapshots or streaming. Here a free Developer key asks for price history:

curl -sS "https://api.sockodds.com/v1/odds/history?event_id=ev_000ckk0nc1tg&from=2026-10-15T00:00:00Z&to=2026-10-15T08:00:00Z" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
const response = await fetch("https://api.sockodds.com/v1/odds/history?event_id=ev_000ckk0nc1tg&from=2026-10-15T00:00:00Z&to=2026-10-15T08:00:00Z", {
  headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
console.log(JSON.stringify(await response.json(), null, 2));
import json, os, urllib.error, urllib.request

request = urllib.request.Request(
    "https://api.sockodds.com/v1/odds/history?event_id=ev_000ckk0nc1tg&from=2026-10-15T00:00:00Z&to=2026-10-15T08:00:00Z",
    headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
try:
    with urllib.request.urlopen(request) as response:
        body = json.load(response)
except urllib.error.HTTPError as error:
    body = json.load(error)
print(json.dumps(body, indent=2))

Response from a local copy of the API with invented test prices

{
  "type": "https://api.sockodds.com/problems/403",
  "title": "Plan restriction",
  "status": 403,
  "detail": "History requires Pro access.",
  "instance": "/v1/odds/history?event_id=ev_000ckk0nc1tg&from=2026-10-15T00:00:00Z&to=2026-10-15T08:00:00Z"
}

The detail says what the plan is missing. Retrying will not change the answer; Pricing shows which plan includes it.

Bookmakers work differently. On a plan with a set list of bookmakers, prices from other bookmakers are left out of the response rather than refused, so a request still succeeds with fewer bookmakers.

Every other error is described in Errors.

Need help?FAQ · Email support

Search across the SockOdds documentation and catalogue.