Skip to main content
Every request to the public API is authenticated by one API key on one header. There is no other credential, no session, and no signature: a key opens the door and nothing else does.

The header

Every request to the API carries your key in an Authorization header using the Bearer scheme. A missing header, a scheme other than Bearer, a key of the wrong shape, and a key we reject are all answered with 401.
Keep the key out of your source. Read it from the environment, as above.
The scheme must be Bearer. A missing header and a header using another scheme get the same refusal, because from our side they are the same thing: no key arrived.

The key format

A key is a non-secret prefix followed by 43 random characters from the base64url alphabet, which is A-Z, a-z, 0-9, - and _. The prefix carries the environment for two reasons. Secret scanners can recognise a leaked Uniblock key in a public repository, and a key pasted into the wrong configuration says out loud which environment it belongs to rather than failing with a puzzle. The prefix and the last four characters are not secret and stay visible in the dashboard, which is how you tell two keys apart after the fact. The other 39 characters are gone from our side the moment the key is shown.

Keys are shown once

The dashboard says it plainly, and this page says the same thing: a key appears once and we keep only a hash, so we cannot show it to you again or recover it for you. Copy it into your secret store before you leave the screen. There is no recovery path, and that is a property of the design rather than a missing feature: what we store is a one-way hash, so a leak of our database yields no working key, and the same fact is what stops us reading yours back. If you lose a key, mint a new one. Ending the lost one is covered under how a key ends below, and today that is not yet something you can do for yourself.

Keys belong to the workspace

A key is the workspace’s, not the person’s who minted it. Its spend lands on the workspace’s balance and its requests land in the workspace’s usage. That has a consequence worth knowing before you organise your team around it: removing the member who created a key does not, on its own, stop the key. Revoking is an explicit choice, made per key, at the moment of removal. See workspaces and members.

Five live keys per workspace

A workspace holds up to five live keys. An outstanding key reservation attached to an invitation occupies one of the five while it is outstanding, and frees it if the reservation is cancelled or lapses. When all five slots are held, minting refuses until a slot frees. Five is the ceiling on live keys, not on keys ever minted. A key that has been ended frees its slot.

How a key ends

The set of things that may ever end a key, meaning the key stops existing and its slot frees, is closed. There are three, and this is their status today: Expiry is a fourth way a key stops serving traffic, but it does not end the key in the sense above: an expired key still exists and still holds its slot, it is simply refused with code: key_expired. Nothing renews it, so the only way to keep serving traffic is a new key, and the old one continues to occupy a slot until it is separately revoked or replaced. Two things that do not end a key, stated because their absence is worth knowing: a role change never revokes a key, and neither does a workspace pause. A pause stops requests while the keys stay live, so lifting it needs no new key. We do not publish a figure for how quickly ending a key takes effect everywhere. The mechanism that would make a global timing promise true is not built, and quoting a number we have not measured would be worse than saying nothing.

The 401 responses

Refusals arrive in the OpenAI error envelope, so an OpenAI client parses them without changes. Every error on the API, at every status, is one JSON object with a single top-level error key. An OpenAI client parses it with the error handling it already has.
Error envelope
Branch on type and code. Both are stable. message is prose and may change. The type values are invalid_request_error, authentication_error, permission_error, not_found_error, rate_limit_error, api_error, insufficient_quota, and server_error. All four carry "type": "authentication_error". The first three share "code": "invalid_api_key"; the fourth carries its own code, key_expired, because the fix is different: an invalid key means the string is not a key here, while an expired key was valid and just needs replacing. Branch on code rather than on the message. The second is separated from the third on purpose. A key of the wrong shape is a configuration mistake, usually a key from another provider or a truncated paste, and saying so saves you an afternoon while telling an attacker nothing they do not already know about their own string. The third covers both a key that never existed and a key that was revoked, with one identical sentence. That is deliberate: confirming that a stolen key was once real is worth a great deal to whoever stole it and nothing to anybody else. Only our own logs tell the two apart.
401

Per-key rate limiting

A key carries a request rate limit, checked as the key is authenticated. Over it, the response is 429 with type rate_limit_error and code rate_limit_exceeded, and it carries a Retry-After header in seconds. The message names the wait:
429
This is the limit on your key’s request rate, which is ours to set and which you cannot raise yourself. It is a different refusal from your account spend rate cap, which you can raise, and the codes are distinct so your client can tell them apart. See rate limits and spend controls.
Every response carries the same id on two headers: x-uniblock-request-id, which is ours, and x-request-id, which is the one the official OpenAI SDKs surface on their error objects. Every error envelope repeats it as request_id. Quote it when you ask us about a request. Without it we are guessing at which of your requests you mean.

Handling keys well

  • Keep keys in a secret store or an environment variable, never in source control. The ub_live_ prefix exists partly so that scanners catch the mistake, but catching it after the fact is not the same as avoiding it.
  • Use one key per deployment or per service rather than one shared key, within the five-slot ceiling. Usage is attributed per key, so separate keys are what make a spending question answerable, and ending one then costs you one deployment rather than all of them.
  • We set no expiry on a key ourselves and publish no rotation requirement, so a key stays live until something on the list above ends it. You may give a key an expiry date of your own, and a key past it is refused with code key_expired. See rate limits and spend controls.