Authentication
API key classes, how they are presented, and what each one may do.
Every request that touches an account carries an API key as a bearer token. The catalogue endpoints are the exception: they are public and take no credential at all.
Authorization: Bearer sk-or-v1-...
The scheme match is case-insensitive, so bearer is accepted as well as
Bearer.
Two classes of key
The prefix is the class, and the class decides what the key may call.
| Prefix | Class | May call |
|---|---|---|
| sk-or-v1- | Inference | POST /chat/completions, POST /completions, GET /generation, GET /key |
| sk-or-mgmt- | Management | The /keys administration endpoints, GET /credits, GET /key |
They are not interchangeable in either direction, and that is deliberate rather than an accident of how the guards were written. A management key administers credentials and reads the account's balance, so it is the key most likely to be held by a person or sitting in a deployment pipeline; letting it also spend money on inference would mean the most privileged credential is also the one in the most places. An inference key runs in application code, so it can spend against the balance but cannot read the account balance or mint another key.
GET /key is the one endpoint both classes reach, and it reports on whichever
credential made the request.
What a key looks like
A key is its prefix followed by 64 lowercase hex characters — 32 bytes of randomness. Only the SHA-256 hash of the full string is stored; the plaintext is returned once, at creation, and cannot be retrieved afterwards. If it is lost the key has to be replaced.
The hash is plain, single-pass, lowercase-hex SHA-256 with no pepper and no key derivation function. That is a compatibility property rather than an oversight: key hashes appear in URLs on the management endpoints, and a client that computes one locally has to arrive at the same string we did. What makes it safe is the 256 bits of entropy in the key body — there is no dictionary to run against it.
Because the plaintext is gone, each key also stores a masked preview so a human can tell rows apart. It is the prefix, three characters, an ellipsis, and the last four:
sk-or-v1-42a...fe98
Getting a key
Sign in and go to Account. The API keys panel there creates an inference key, shows you the plaintext once, and lists your existing keys so you can revoke one. Copy the key when it is shown — that is the only time it exists in readable form, and the panel says so beside it.
Keys created that way are inference keys. A management key is not something the account page mints; it is issued by whoever operates the deployment.
The /keys endpoints can also mint a key programmatically, for a caller that
already holds a management key. We are not publishing a sample of that call
here: every sample in these docs is executed before it ships, and a minting call
cannot be executed without creating a real credential on a real account.
Checking a key
GET /key describes the credential that authenticated the request, including
what it has spent and any cap it is under. This response came back from an
inference key:
curl -s "https://api.impossiblecarrot.cc/api/v1/key" -H "Authorization: Bearer $CP_API_KEY"
{
"data": {
"label": "sk-or-v1-42a...fe98",
"limit": 5,
"limit_remaining": 4.9224569,
"limit_reset": "monthly",
"include_byok_in_limit": false,
"is_free_tier": false,
"is_management_key": false,
"is_provisioning_key": false,
"creator_user_id": null,
"usage": 0.0775431,
"usage_daily": 0.0000064,
"usage_weekly": 0.0775431,
"usage_monthly": 0.0775431,
"byok_usage": 0,
"byok_usage_daily": 0,
"byok_usage_weekly": 0,
"byok_usage_monthly": 0,
"rate_limit": { "requests": -1, "interval": "", "note": "This field is deprecated and safe to ignore." },
"expires_at": null
}
}
limit is a spend cap on this key alone, and limit_reset names the window it
is enforced in — here, a $5 monthly cap with $4.92 left. limit_remaining
compares the cap against spend in that same window, so a monthly cap is always
compared against this month.
rate_limit is a deprecated field kept for wire compatibility. It always reports
-1 and means nothing; the note says so in the payload itself.
What a rejection looks like
The status separates the two questions a caller actually has: do you know who I am, and am I allowed.
401 — the credential was missing.
{"error":{"code":401,"message":"Missing Authentication header","metadata":{"error_type":"authentication"}}}
401 — the credential was presented and is not one of ours.
{"error":{"code":401,"message":"Invalid credentials","metadata":{"error_type":"authentication"}}}
A credential that is not shaped like one of our keys at all gets this same response. Key format is not something a caller can probe by watching the API disagree with itself.
403 — the credential is valid, and is the wrong class. This is an inference key asking for the account balance:
{"error":{"code":403,"message":"Only management keys can perform this operation","metadata":{"error_type":"permission_denied"}}}
A 403 always means the key authenticated successfully. Reaching for a different key will not help unless it is of the other class.