Skip to content

Security

This page describes the security properties of the API that you can rely on when you design your integration, and the practices that keep your keys and your data safe on your side.

  • A key looks like bel_live_<prefix>_<secret> (or bel_test_… for sandbox keys). The secret part is 32 random bytes.
  • Belarel stores only a SHA-256 hash of the key, never the key itself. Each call is matched by hashing the key it presents.
  • The full key is shown once, when it is created — in the dashboard, or in the token field of the POST /v1/keys response. It cannot be retrieved later; if you lose it, create a new key and revoke the old one.
  • Listings show only the key’s prefix (for example bel_live_k3x9a0qz), which is enough to recognize a key in logs without exposing it.

An unknown, revoked, expired or disabled key always gets the same answer:

{
"error": {
"message": "Invalid, revoked, or expired API key",
"type": "authentication_error",
"code": "invalid_api_key",
"param": null
}
}

The status is 401. The API never tells these cases apart, so a leaked or guessed key cannot be probed for its state. A request with no key, or with a bearer that is not a Belarel key, gets the same 401 invalid_api_key code on every endpoint, with the message Missing or malformed API key. Only the public model catalog, GET /v1/models sent with no Authorization header, answers without a key.

State Effect Reversible
active The key can call the API within its scopes and caps. —
disabled Every call answers 401 invalid_api_key. Yes
revoked Every call answers 401 invalid_api_key. No
Expired (expires_at passed) Every call answers 401 invalid_api_key. —

Set expires_at on keys that only need to live for a project or a test campaign.

Each key carries the scopes it needs, and nothing more:

Scope Grants
inference POST /v1/chat/completions, POST /v1/responses, keyed GET /v1/models
embeddings POST /v1/embeddings
usage:read GET /v1/usage, GET /v1/credits, GET /v1/requests/{id}
keys:manage GET/POST /v1/keys, PATCH/DELETE /v1/keys/{id}

A call outside the key’s scopes answers 403 scope_denied. Any valid key can read itself with GET /v1/key.

Beyond scopes, a key can be restricted to a list of models, and capped in USD per day and per month, in requests and tokens per minute, in calls in flight, and in output tokens per request. A key never sees or calls a model outside your organization’s catalog; a model your organization cannot see answers 404 model_not_found, exactly like a model that does not exist.

A key with keys:manage can create sub-keys — one per customer, environment or service — through POST /v1/keys:

  • A sub-key can never exceed its parent: scopes, models, caps, rates and expiry must all fit inside the parent’s. Otherwise the API answers 400 child_exceeds_parent.
  • A sub-key never gets keys:manage, and there is only one level: sub-keys cannot create sub-keys.
  • A sub-key inherits its parent’s environment, mode and payment mode, and can be stricter than its parent on data handling, never looser.
  • A key lists and manages only its own sub-keys; any other key id answers 404. Likewise, GET /v1/requests/{id} only finds requests made by the key itself or its sub-keys.

If one customer’s sub-key leaks, you disable or revoke that sub-key alone.

Every call is admitted, priced and reserved before it reaches a model. If the API cannot decide whether a call is allowed — a check that cannot be completed, a catalog that cannot be read — it refuses with 503 and a Retry-After header rather than serving the call unchecked. Refusals for money, policy or rate are always 4xx.

When a model’s provider refuses a request, its explanation is passed on only when you can act on it — a request it cannot serve as written — and only after links and anything shaped like a key or token are removed. Other provider failures are described in Belarel’s own words.

An Idempotency-Key is scoped to the API key that sent it, so two keys can never read each other’s stored answers. A key that has been revoked never gets a replay: the API key is checked before the idempotency store is consulted.

To recognize a repeated request, Belarel keeps a fingerprint of its body. The fingerprint is a keyed hash (HMAC-SHA256 with a secret held only by the server), for every key including zero-retention keys, so a stored fingerprint cannot be used to confirm a guessed prompt. If that secret is not available, idempotent calls are refused with 503 rather than fingerprinted without it. See Idempotency.

Your own provider keys are validated with the provider before they are stored, encrypted (AES-256-GCM), never returned after saving, and read only by the inference service at the moment it serves a call. See Bring your own key.

Every webhook Belarel sends is signed with HMAC-SHA256 using a secret shared only between Belarel and your organization, and the timestamp is part of the signed string, so you can reject replays. Deliveries go only to https:// endpoints on public addresses, and redirects are not followed. See Webhooks for verification code.

  • Keep keys on the server. Never ship a Belarel key in a browser, a mobile app or a public repository. Call the API from your backend.
  • Always use HTTPS and the base URL https://api.belarel.com/v1.
  • Store keys in a secret manager or environment variable (BELAREL_API_KEY), never in code.
  • One key per workload. Separate keys make it possible to revoke one without stopping everything, and keep usage attribution clean.
  • Rotate by creating the new key, deploying it, then revoking the old one.
  • Keep tags free of personal data. Tags and the metadata field are recorded with every call.