Authentication
Every API request carries a bearer key:
Authorization: Bearer sk-hd-v1-...
A key is sk-hd-v1- followed by 64 hex characters. Keys issued before the prefix change
begin with sk-or-v1- and keep working; new keys always use sk-hd-v1-. Keys are created in your
account and shown once — only a hash is stored, so a lost key cannot be recovered,
only replaced. Treat a key like a password: keep it in a secret manager or an environment
variable, never in client-side code or a repository. A leaked key can be revoked from your account
at any time without affecting your other keys.
Keys, workspaces and limits
Keys belong to a workspace. Each key can carry its own spending limit with an optional reset window
(daily, weekly or monthly, in UTC), an expiry date, and an assigned organization member so usage is
attributed to the person holding it. When a key's limit is exhausted, requests with that key are
rejected with status 402 until the window resets or the limit is raised — other keys are
unaffected. The full account model is described in
Organizations, workspaces & keys.
Inspecting a key
GET /key returns the key you authenticate with — its label, limit and usage:
{
"data": {
"label": "sk-hd-v1-3f9a...",
"name": "production",
"limit": 10,
"limit_remaining": 4.5,
"limit_reset": "monthly",
"usage": 5.5,
"usage_daily": 0.61,
"usage_weekly": 2.4,
"usage_monthly": 5.5,
"is_free_tier": false,
"expires_at": null
}
}
Field-by-field reference: Key & activity.
Getting a key without asking for one
An application can obtain a key for a person's account without ever seeing their password, using the OAuth PKCE flow. The person approves on this site; the application receives a key it can use and the person can revoke.
Generate a random code_verifier (43 to 128 characters from A-Z a-z 0-9 - . _ ~), keep it, and send
the browser to the authorization page with its SHA-256:
https://staging.impossiblecarrot.cc/auth?callback_url=https://yourapp.example/callback&code_challenge=<S256 of the verifier>&code_challenge_method=S256
code_challenge_method is S256 and nothing else — a plain challenge travels in the address bar as
the very secret the exchange asks for. limit is optional: an exact decimal string of dollars, which
becomes the key's spending ceiling.
Approving returns the browser to callback_url with a one-time code on it. Declining returns to the
same address with error=access_denied. For a localhost callback, approval stays on this page and
shows a copyable code. Paste it into the CLI's waiting terminal, which may be on another computer.
heimdall-staging login --manual prints the approval URL without opening a browser or local port.
The optional response_mode=manual query parameter selects code display without a callback action.
A local or manual decline stays on the page with instructions to cancel the waiting terminal.
Trade the code for a key within ten minutes:
curl https://api-staging.impossiblecarrot.cc/api/v1/auth/keys \
-H "Content-Type: application/json" \
-d '{"code": "<the code>", "code_verifier": "<the verifier>", "code_challenge_method": "S256"}'
{
"key": "sk-hd-v1-...",
"user_id": "usr_01J8Z0X2QZ"
}
The code is consumed as it is read, so a second attempt with the same one is refused whether it is a
retry or a replay, as are a code past its ten minutes and a verifier that does not hash to the
challenge. All of them answer 400 with one message, because telling them apart would say which codes
exist. The key that comes back is listed on the person's account under the callback's
origin and can be revoked there like any other.
When a key is refused
A missing, malformed, unknown, revoked or expired key — or one whose owner is suspended — always gets the same response, so the error cannot be used to probe which keys exist:
{
"error": {
"code": 401,
"message": "Missing or invalid API key",
"metadata": { "error_type": "authentication" }
}
}
Trying it
curl https://api-staging.impossiblecarrot.cc/api/v1/key \
-H "Authorization: Bearer $HEIMDALL_API_KEY"
The catalogue endpoints — GET /models, GET /models/:author/:slug/endpoints, GET /providers,
GET /families and GET /stats/public — require no authentication. Unauthenticated requests are
rate-limited by client address; see Errors & rate limits.