Authentication
API keys, scopes, key safety and request ids for API v1.
API key
Every endpoint on this page is authenticated with an API key in the x-api-key header (not Authorization: Bearer):
curl -sS "$API_BASE_URL/$PATH" -H "x-api-key: $API_KEY"A missing key returns 401 with message: "Missing API key"; a key that is not active returns 401 with message: "Invalid API key". A key without the required scope returns 403 with details.required and details.granted.
Keys are created and regenerated in the dashboard. A regenerated key replaces the previous one immediately. The dashboard playground runs with your active key, so its calls count towards the same usage and limits.
Keep your API key server-side
Treat the key like a password:
- Call the API from your server or an edge function and proxy the data to the browser. Never ship the key in frontend code or a public repository.
- Rotate a key from the dashboard if it leaks. Regenerating a key deactivates the previous one immediately.
- One key per environment (development, staging, production) keeps usage and rate limits separate.
Scopes
Every v1 endpoint accepts the umbrella scope assets:read, which is on every key by default. Four fine-grained scopes exist for keys that should only read one kind of data:
| Scope | Grants |
|---|---|
assets:read | Everything on this surface. |
assets:profile:read | include=profile on asset detail. |
assets:risk:read | include=risk on asset detail and GET /v1/assets/risk-summary. |
assets:ohlcv:read | include=ohlcv on asset detail. |
assets:markets:read | include=markets on asset detail. |
A request that asks for an include the key cannot read returns 403 with details.required and details.granted.
Request IDs
Every response carries an x-request-id header. When you report a problem, include the request URL, the response body (with your key redacted), the x-request-id, and the UTC time of the call.
Example client
interface ApiErrorEnvelope {
error: { _tag: string; message: string; details?: Record<string, unknown> };
}
function isApiError(value: unknown): value is ApiErrorEnvelope {
const error = (value as { error?: { _tag?: unknown; message?: unknown } } | null)?.error;
return typeof error?._tag === "string" && typeof error.message === "string";
}
export async function getJson<T>(path: string, apiKey: string): Promise<T> {
const res = await fetch(`https://api.tokens.xyz${path}`, {
headers: { "x-api-key": apiKey },
});
const json: unknown = await res.json().catch(() => null);
if (!res.ok) {
if (isApiError(json)) throw new Error(`${json.error._tag}: ${json.error.message}`);
throw new Error(`HTTP ${res.status}`);
}
return json as T;
}