Security
Authentication
Issue, scope, rotate and revoke MainAI API keys.
Updated 2026-10-25 · MainAI Compute docs
The inference API uses bearer keys. Portal endpoints use the browser session cookie instead. The two are separate: an API key cannot read the portal, and a portal session cannot call /v1.
Send the keyLink to this section
curl -s https://cloud.mainai.dk/v1/models \
-H "Authorization: Bearer $MAINAI_API_KEY"Send the header on every /v1 request. Keys start with sk-mai_. The API accepts only that prefix; an upstream-style key or a missing Bearer scheme returns 401 invalid_api_key.
Key anatomyLink to this section
| Field | Meaning |
|---|---|
name | Human label, 64 characters maximum. |
models | Allow-list. Empty means every public model. A denied model returns 403 model_not_allowed. |
rpmLimit | Requests per minute for this key. |
tpmLimit | Tokens per minute for this key. |
budgetCents / budgetPeriod | Hard key budget in EUR cents, monthly or total. |
expiresAt | Optional absolute expiry. |
prefix / last4 | Display hints. The full key is shown once. |
name
- Meaning
- Human label, 64 characters maximum.
models
- Meaning
- Allow-list. Empty means every public model. A denied model returns
403 model_not_allowed.
rpmLimit
- Meaning
- Requests per minute for this key.
tpmLimit
- Meaning
- Tokens per minute for this key.
budgetCents / budgetPeriod
- Meaning
- Hard key budget in EUR cents, monthly or total.
expiresAt
- Meaning
- Optional absolute expiry.
prefix / last4
- Meaning
- Display hints. The full key is shown once.
The portal stores a routing-token hash, the prefix and the last four characters. The plaintext key never comes back from GET /api/portal/keys.
Create a keyLink to this section
Use Keys → Create key in the portal. The request is a session-authenticated mutation:
curl -s https://cloud.mainai.dk/api/portal/keys \
-H "Content-Type: application/json" \
-H "Origin: https://cloud.mainai.dk" \
-H "Cookie: $PORTAL_COOKIE" \
-d '{
"name": "production-api",
"models": ["mainai-coder", "mainai-cyber"],
"rpmLimit": 600,
"budgetCents": 20000,
"budgetPeriod": "monthly"
}'The 201 body contains key metadata and plaintextKey. Copy it immediately. Owner, admin and developer roles can create keys; billing and viewer roles can read them.
RotationLink to this section
- 01
Create the successor
New key, same allow-list, same limits. Keep the name distinct, for example
production-api-2026-11. - 02
Deploy the new value
Roll the secret to every worker and environment. Leave the old key active.
- 03
Observe
Watch Usage until the old key's
lastUsedAtstops moving for a full deploy window. - 04
Revoke the predecessor
Delete it in the portal or call
DELETE /api/portal/keys/:id. Revocation is synchronous.
Rotate on a schedule your team can keep, and immediately after a suspected leak. There is no automatic rotation in the MVP.
Revocation and suspensionLink to this section
- Deleting a key sets its status to
revoked, removes its routing credential and zeroes its budget. - Requests with a revoked key return
401 invalid_api_key. - Suspending the organization blocks all of its keys with
403 org_suspended, even though key records remain active. - Closing an organization makes its keys unusable.
Status codesLink to this section
| Status | code | Action |
|---|---|---|
| 401 | invalid_api_key | Check the Bearer scheme, prefix, revocation and expiry. |
| 403 | model_not_allowed | Add the model to the key allow-list or use an allowed model. |
| 403 | org_suspended | Resolve billing or policy with the organization owner. |
| 429 | rate_limit_exceeded | Back off. See Rate limits. |
401
codeinvalid_api_key- Action
- Check the
Bearerscheme, prefix, revocation and expiry.
403
codemodel_not_allowed- Action
- Add the model to the key allow-list or use an allowed model.
403
codeorg_suspended- Action
- Resolve billing or policy with the organization owner.
429
coderate_limit_exceeded- Action
- Back off. See Rate limits.
Portal sessionsLink to this section
Portal and /api calls use an HttpOnly, Secure, SameSite=Lax session cookie. Production uses the __Host-mc_session name. Mutating /api routes check the Origin header; a cross-origin request without a matching origin is rejected with 403 csrf.
IdempotencyLink to this section
Idempotency-Key is accepted on /v1 inference routes and forwarded upstream. Use a stable UUID when a client may retry a non-streaming POST. Streaming retries are best treated as a new generation.