keys.verifyKey from your own server. One call runs expiry, permissions, rate limits, and credits, then returns data.valid and a code. The HTTP status stays 200 when the key fails, and your server returns 401 or 429 to the caller. On Compute, the API key authentication policy runs the same checks at the gateway and returns 401, 403 or 429 itself.
What the check tells you
Readdata.code. These are the outcomes you usually branch on. The full list, including the status to return, is under Code values.
Verify an API key for your public API by calling
keys.verifyKey with the key your user sent. This is the API Management endpoint. Use it when your own server, not the Compute gateway, decides whether the request is allowed.
Send the key exactly as your user sent it. Unkey runs the checks set on the key, plus any you add to the request, and returns one answer. The response is HTTP 200 for every outcome, including an invalid key, a rate limit, and a used-up credit balance. Read data.valid and data.code, not the status code. Your server then returns 401 or 429 to the caller. See Rate limiting for Retry-After.
Create a root key with the permissions listed on this page under Root Keys in the dashboard sidebar. CLI commands can instead use a session from unkey login with the required permissions. See Permission reference for every permission.
unkey:v1:<workspace_id>:projects/<project_id>/keyspaces/<keyspace_id>/keys/*#verify for the key’s keyspace. Use the keyspace’s project ID and keyspace ID, not its API ID. Without it you get a 200 with code: NOT_FOUND, not a permission error. A missing or invalid root key gets HTTP 401. See Root key permissions.
Request
The key your user sent, 1 to 512 characters, including its prefix. Any change to the string gives
NOT_FOUND. You don’t pass an API ID because the key already belongs to one keyspace.1 to 5 keyspace IDs (
ks_...), each up to 100 characters, matched exactly. The key must belong to one of them or the result is NOT_FOUND. Use it when one root key verifies several keyspaces but an endpoint should only accept keys from some of them. An empty array is HTTP 400.A permission query, 1 to 1000 characters, such as
documents.read AND (billing.read OR billing.write). AND binds tighter than OR, and parentheses group. Fails the verification with INSUFFICIENT_PERMISSIONS when the key’s direct and role-derived permissions don’t satisfy it. A malformed query is HTTP 400 err:user:bad_request:permissions_query_syntax_error. Asterisks inside slugs are matched literally. See Permission queries.credits.cost (integer, 0 to 1,000,000,000,000) is how many credits this verification spends. Omitting the object spends 1 on keys that have credits configured and nothing on unlimited keys. cost: 0 checks the key without spending. See Credits and refill.Rate limits to check on this call. Each entry names a limit configured on the key or its identity (
name, 3 to 255 characters) and may pass cost (default 1). Naming a limit that doesn’t exist on the key or its identity fails the whole request with HTTP 412 err:unkey:application:precondition_failed. An entry that also carries both limit and duration defines an inline, key-scoped limit for this call instead. Limits with autoApply: true are checked whether or not you list them.An inline limit must be 1 or more and duration at least 1000 milliseconds. Values below that aren’t rejected with an error. Instead the verification skips rate limiting entirely, including the auto-applied limits, and can still return VALID. Check these two values in your own code before you send them.Up to 20 strings of up to 512 characters, recorded with the verification for analytics only. They never affect the verdict. See Metadata and tags.
Up to 256 characters. Lets Unkey match a key imported from another provider’s hash format on its first use and convert it to the native hash. See Migrating existing keys.
Order of checks
Checks run in this order and stop at the first failure. The order decides whichcode you get, and whether a rate limit or credits get used up.
- Existence. The key must exist, its workspace and keyspace must not be deleted, and your root key must be allowed to verify keys in that keyspace. Anything else is
NOT_FOUND. - Keyspace restriction. If the request lists
keyspacesand the key’s keyspace isn’t one of them, the result isNOT_FOUNDwith no key details. - Workspace. A disabled workspace produces
FORBIDDEN. - Enabled. A key with
enabled: falseproducesDISABLED. - Expiration. A key whose
expiresis in the past producesEXPIRED. - Permissions. If the request carries a
permissionsquery, the key must satisfy it, or the result isINSUFFICIENT_PERMISSIONS. - Rate limits. Auto-applied limits and limits named in the request are checked together. If any is exceeded, the result is
RATE_LIMITED, and the other limits aren’t used up. - Credits. Only if everything above passed, the cost is deducted. Not enough credits gives
USAGE_EXCEEDEDand nothing is deducted.
Response
One of
VALID, NOT_FOUND, FORBIDDEN, INSUFFICIENT_PERMISSIONS, USAGE_EXCEEDED, RATE_LIMITED, DISABLED, EXPIRED. Code values explains each one.Credits remaining after this call. Omitted for unlimited keys. After
USAGE_EXCEEDED it’s the unchanged balance.When the key is linked to an identity:
id, externalId, the identity’s meta, and its ratelimits (each with id, name, limit, duration, autoApply).One entry per rate limit that was checked:
id (empty for inline limits), name, limit, duration, remaining, reset (Unix milliseconds), exceeded, and autoApply.Code values
Why some failures say NOT_FOUND
Unkey never tells a caller that a key exists unless they’re allowed to verify it. So you get a 200 withcode: NOT_FOUND when the key belongs to another workspace, its keyspace was deleted, your root key has no verify permission for its keyspace, or your keyspaces list leaves it out. The response looks the same in every case. If you get NOT_FOUND for a key you know exists, check your root key’s permissions first.
How quickly changes take effect
A change to a key (disable, delete, new expiry, new permissions or roles) takes about 10 seconds to reach verification, and a few verifications just after that can still see the old state. Plan incident runbooks around a little more than 10 seconds, not an instant cutoff. Credit balances change instantly. Rate limit counts sync across regions within moments, soremaining is approximate. See How rate limiting works.
What gets recorded
Every verification, whatever its outcome, is recorded in analytics with its outcome, key, identity, tags, credits spent, and latency. Verifications made with a root key also produce akey.verify audit log event. See Analytics and Audit logs.