Authentication
Two identities, and they are not interchangeable.
The API key
This is the one for integrations. It lives in the database, carries no default expiry, and travels in the Authorization: Bearer header.
curl "$SPAVIK_BASE_URL/v1/models" -H "Authorization: Bearer sk-spv-api-..."The API also accepts it in X-API-Key. Both forms are equivalent; the libraries use the first.
| Prefix | What it is | Can predict |
|---|---|---|
sk-spv-api- | API key | yes |
sk-spv-admin- | Admin key | no |
Keys issued before September 2026 started with ssk-. They are no longer recognised: create a new key from your workspace.
An admin key cannot predict: it answers ADMIN_KEY_CANNOT_PREDICT. And an API key cannot mint others.
The web session
This is the one for interfaces. A magic link goes out by email, is exchanged for a token pair, and the access token travels in Authorization: Bearer.
curl -X POST $SPAVIK_BASE_URL/v1/auth/magic-link/request \
-H 'Content-Type: application/json' -d '{"email":"you@example.com"}'The link and the code go out by email only, never in the response. That is what stops a third party who can trigger the send from opening the session.
The access token is short-lived. A long-running application must renew it along the way, with POST /v1/auth/refresh, without asking for a new link.
The refresh token is single use
Replaying it signals that a copy is circulating: the API then revokes the whole token family for the account. Serialise your renewals, do not fire them in parallel from several requests.
The separation is by route, not by header
An API key opens the engine and usage routes. It opens neither the account routes, which require a web session, nor the administration routes, which require an admin key. The API says so plainly rather than leaving you to guess.
| What you present | Where | Answer |
|---|---|---|
| A valid API key | /v1/models, /v1/forecast, /v1/usage | 200, it works |
| A valid API key | /v1/auth/me and account routes | 401 AUTH_REQUIRED, a web session is required |
| A valid API key | /v1/api-keys, /v1/webhooks | 403 ADMIN_KEY_REQUIRED |
| An unknown key | anywhere | 401 API_KEY_INVALID |
| An expired access token | anywhere | 401 TOKEN_INVALID |
Rotation without downtime
POST /v1/api-keys/{keyId}/rotate creates a twin key and gives the old one an end date, seven days by default. Both work during the migration. grace_days: 0 revokes the old one immediately, for a compromised secret.