# Authentication

Every HelixOdds API request carries one credential: your **client token**.

```
Authorization: Bearer LI-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

The token identifies your account. Everything the API returns is scoped to it —
there is no account parameter, and a `client_id` in a query string or a header
is ignored. If a request returns someone else's data, that is a defect, not a
configuration option.

## Two credentials, two jobs

These are easy to confuse and must never be swapped.

| | HelixOdds client token | Your provider credential |
|---|---|---|
| What it is | identity | entitlement to upstream data |
| Who issues it | HelixOdds | your data provider |
| Where it goes | the `Authorization` header on every API request | nowhere — you paste it once in the panel |
| Rotating it | changes nothing about your subscription or provider | changes nothing about your subscription |

Your provider credential is **never sent on an API request**. You give it to us
once; we hold it encrypted and use it only when retrieving data on your behalf.
No endpoint returns it, and no log line contains it.

## Getting a token

The token is shown **once**, when the account is created or when it is rotated.
It is stored as a salted hash, so it cannot be recovered afterwards — if it is
lost, rotate it and update your integration.

The panel shows a prefix (`LI-abcd••••••`) so you can tell which token an
account is using without revealing it.

## Rotating

Rotation issues a new token and invalidates the old one immediately. There is no
overlap window, so plan a short cutover: fetch the new token, deploy it, and the
old one stops working at the moment of rotation rather than at the moment you
finish deploying.

Rotation does **not** touch your subscription, your features, your allowed
origins or your provider connection. It replaces a credential, nothing else.

## Allowed origins

If your account declares allowed origins, a request that carries an `Origin`
header must match one of them. This exists for browser integrations.

A server-to-server call sends no `Origin` header and is served normally. The
policy restricts browsers; it is not a second authentication.

CORS reflects exactly the one origin that matched. `*` is never returned: a
wildcard combined with a bearer token would let any page spend your quota from a
visitor's browser.

## What the API tells you, and what it does not

| Situation | Code | Why |
|---|---|---|
| No token, or a token we do not recognise | `INVALID_API_TOKEN` | Identical for an unknown token and one that never existed. Confirming that a token was once real helps an attacker. |
| Your account is suspended | `CLIENT_SUSPENDED` | You own the token, so you are told why it stopped working. |
| Your account is revoked | `CLIENT_REVOKED` | Final. Revocation is not reversible. |
| Your subscription lapsed | `SUBSCRIPTION_REQUIRED` | Data routes close; `/account` stays open so you can see the expiry. |
| An add-on is not enabled | `FEATURE_NOT_ENABLED` | Distinct from a missing subscription on purpose. |
| Origin not in your allowlist | `ORIGIN_NOT_ALLOWED` | |

## Rate limits

Limits are per **account**, not per address: one of your servers cannot throttle
another of your servers, and adding addresses does not raise your ceiling.

A refusal returns `429` with `RATE_LIMITED` and a `Retry-After` header in
seconds. Honour it; retrying immediately spends the next window too.

## Request ids

Every response carries `X-Request-Id`, and every error body repeats it as
`error.request_id`. Quote it when you report a problem — it is the single key
that finds the request in our logs.

## Example

```bash
curl -sS https://api.helixodds.com/api/v1/account \
  -H "Authorization: Bearer LI-EXAMPLE-TOKEN-DO-NOT-USE"
```

```json
{
  "data": {
    "client_id": "a1b2c3d4e5f60718",
    "status": "ACTIVE",
    "api_token_prefix": "LI-EXAM••••••",
    "products": [
      { "product": "market_engine", "active": true, "expires_at": "2026-11-08T00:00:00.000Z" }
    ]
  }
}
```

See also: [Errors](errors.md) · [Security model](security-model.md) ·
[Quickstart](integration-quickstart.md)
