Get started
Authentication
API keys, scopes, the server-side-only rule, OAuth for MCP clients, and how the dashboard playground differs.
The authentication model is intentionally simple: create a key in the dashboard, keep it on your server, and send it with every request. This page covers the rules around that model — test versus live keys, what each key is permitted to do, and why a key must never reach a browser.
Every API request authenticates with an API key in the X-API-Key header:
curl https://api.sproutsdata.ai/v1/credits \
-H "X-API-Key: $SPROUTS_API_KEY"Send the key in X-API-Key and nowhere else. Authorization: Bearer on the REST API is reserved for OAuth tokens (what the MCP server and OAuth clients use). A key sent as a Bearer token is not read as a key: it gets the generic 401 auth.unauthorized.
Use cases#
Create a live key with only the scopes the integration uses, store it in your secret manager, and send it from server code only. It can do exactly what the integration needs, and can be revoked and reissued in seconds if it ever leaks.
Use a test key: same corpus, same responses, every charge recorded at zero cost. meta.environment: "test" on every response keeps test traffic identifiable.
Issue each service its own key for per-service spend in GET /v1/keys/usage, per-service revocation, and scopes trimmed to what each service actually does.
Key format and environments#
Keys carry a prefix that identifies the platform and environment:
| Prefix | Environment | Cost |
|---|---|---|
sk_dsp_live_ | live | Charged per action |
sk_dsp_test_ | test | Free — every call costs zero credits |
A test key searches the same real corpus and returns the same real records as a live key. It differs in exactly two ways:
- charges are recorded at zero cost;
- every response says
meta.environment: "test".
A key with any other prefix is rejected as 401 auth.invalid_key before any lookup happens.
Creating and revoking keys#
Keys are managed in the dashboard, or via the session-authenticated POST /v1/keys / GET /v1/keys / DELETE /v1/keys/{key_id} routes it uses.
- Create the key
In the dashboard, go to API Keys and create a key. Pick its environment, its scopes, and an expiry of 1–730 days. The dashboard pre-ticks
dsp.search.readanddsp.entities.readand pre-fills 90 days; change either before you create the key. There is no non-expiring key.The defaults differ on the key-creation route itself: a
POST /v1/keysrequest that leaves the fields out gets all five scopes and a 365-day expiry. - Copy the secret
The full secret is returned once and never again — it is stored as a hash. Listing keys afterwards shows only a truncated
display_key(sk_dsp_live_…PqSQ). - Store it server-side
Put it in your secret manager or an environment variable such as
SPROUTS_API_KEY, never in source control or browser code.
To rotate a key — because it leaked, or because it is about to expire — create its replacement the same way, switch your service over, then revoke the old one. Revocation takes effect immediately: the next request with a revoked key is a 401.
Keys are server-side only#
An API key works like a password: any caller who presents it can spend your tenant's entire balance. A key in browser JavaScript is a key in every visitor's devtools, so the API enforces the rule at the edge.
POST /v1/search Origin: https://app.example.com X-API-Key: sk_dsp_live_...
→ 403 {"code": "auth.browser_origin", "title": "API keys are server-side only"}
There is no allowlist to add your origin to. If you're building a browser UI, your server holds the key and your browser calls your server.
The generic 401#
Every pre-authentication failure on /v1/* — no credential at all, a key sent as a Bearer token, an expired dashboard session, a cookie that didn't travel — returns the same status, code, and sentence:
{
"type": "https://dashboard.sproutsdata.ai/errors/auth-unauthorized",
"title": "Unauthorized",
"status": 401,
"code": "auth.unauthorized",
"detail": "Authentication required. Send a valid `X-API-Key`, or sign in if you are using the dashboard. Quote the request_id if you need help.",
"instance": "/v1/search",
"request_id": "bd22794d0c5c06f1d3e7045e3c0e32ab"
}This is deliberate anti-oracle behavior: varying the answer by which headers were present would hand an anonymous caller a map of the auth model. The specific cause is logged against request_id, so support can tell you exactly what happened — quote the id.
One 401 code is specific, because it is only reachable after you presented a key in X-API-Key: auth.invalid_key (unknown, revoked, or expired key — including a wrong prefix) tells you the key itself failed. See the errors reference.