---
url: https://docs.dataflair.ai/dataflair/toplist-api/authentication.md
description: >-
  Static token and HMAC key exchange for the Toplist API, with request and
  response examples.
---

# Auth token endpoint

This page documents the concrete request/response mechanics for both Toplist API auth modes. See
[Authentication](/guide/authentication) first for which mode to pick and the shared bearer-header
pattern.

## Option A: Static token (recommended for periodic sync)

Use your issued static token directly as the bearer token on every request. No exchange call is
required:

```bash
curl "https://{your-tenant-domain}/api/v1/toplists?template_id=55" \
  -H "Authorization: Bearer dfp_live_9f2c1a..." \
  -H "Accept: application/json"
```

The token itself carries no built-in short expiry (unlike Option B). It stays valid until the
credential's own `expires_at` (if any) or until it's revoked on DataFlair's side.

## Option B: HMAC token exchange

Exchange a key + secret pair for a short-lived (1 hour) signed bearer token:

```bash
curl -X POST https://{your-tenant-domain}/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"key": "dfk_live_...", "secret": "dfs_..."}'
```

Success response:

```jsonc
{
  "token": "eyJrZXkiOiJkZmtfbGl2ZV8uLi4iLCJ0cyI6MTc1MzE0ODAwMCwic2lnIjoiLi4uIn0=",
  "type": "bearer",
  "expires_in": 3600,
  "site": { "id": 7, "name": "Example Operator", "code": "EXOP", "domain": "example-operator.com" },
  "scopes": ["toplist:read", "brand:read"]
}
```

Use the returned `token` as the bearer token on subsequent requests. When it expires (1 hour),
exchange again: there is no refresh-token step, just repeat the same call.

* This exchange endpoint is throttled to **10 requests/minute per calling IP**, separately from the
  data-endpoint rate limit. Exchanging a token in a tight retry loop will get you throttled
  quickly, so cache the token for its full `expires_in` rather than re-exchanging on every call.
* A wrong key or wrong secret both return the same `401 invalid_credentials` error (deliberately,
  see [Error handling](/guide/error-handling)).

## Scopes

Every credential (either auth mode) carries a list of scopes, by default `["toplist:read",
"brand:read"]`. Each endpoint requires one specific scope; a credential missing it gets a
`403 insufficient_scope` response, not partial/filtered data.

## IP allowlisting

See [Authentication](/guide/authentication#ip-allowlisting) for what IP allowlisting is and how a
mismatch surfaces on this endpoint specifically: a `403 ip_not_allowed` on the exchange call itself,
distinct from the `401` you'd get on a data endpoint (see [Error handling](/guide/error-handling)).
