Skip to main content
When verifying a key, you can check if it has specific permissions. If the key lacks the required permissions, verification fails with code: INSUFFICIENT_PERMISSIONS.

Basic permission check

Pass a permission string to verify:
curl -X POST https://api.unkey.com/v2/keys.verifyKey \
  -H "Authorization: Bearer $UNKEY_ROOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "sk_...",
    "permissions": "documents.read"
  }'

Permission query syntax

Unkey supports logical operators for complex permission checks:

Single permission

{ "permissions": "documents.read" }
Key must have documents.read.

AND (all required)

{ "permissions": "documents.read AND documents.write" }
Key must have both permissions.

OR (any required)

{ "permissions": "admin OR editor" }
Key must have at least one of the permissions.

Complex queries with parentheses

{ "permissions": "admin OR (documents.read AND documents.write)" }
Key must have admin OR have both documents.read and documents.write.

Real-world example

// User wants to delete a document
// They need: admin OR (documents.delete AND owner of this document)

try {
  const { meta, data } = await verifyKey({
    key: request.apiKey,
    permissions: "admin OR documents.delete",
  });

  if (!data.valid) {
    return Response.json({ error: data.code }, { status: 401 });
  }

  // Key is valid - continue with your API logic
} catch (err) {
  console.error(err);
  return Response.json({ error: "Internal error" }, { status: 500 });
}

Response structure

Successful verification with permissions:
{
  "meta": { "requestId": "req_..." },
  "data": {
    "valid": true,
    "code": "VALID",
    "keyId": "key_...",
    "permissions": ["documents.read", "documents.write", "users.view"]
  }
}
Failed permission check:
{
  "meta": { "requestId": "req_..." },
  "data": {
    "valid": false,
    "code": "INSUFFICIENT_PERMISSIONS",
    "keyId": "key_..."
  }
}

Manual permission checking

Sometimes you need to check permissions after loading data from your database (e.g., checking if the user owns a resource). In these cases:
1

Verify the key

Verify without permission requirements to get the key’s permissions list.
try {
  const { meta, data } = await verifyKey({
    key: "sk_...",
  });

  if (!data.valid) {
    return unauthorized();
  }

  const permissions = data.permissions ?? [];
  // Continue with your API logic
} catch (err) {
  console.error(err);
  return Response.json({ error: "Internal error" }, { status: 500 });
}
2

Load your data

Query your database for the resource.
const document = await db.documents.find(documentId);
3

Check permissions manually

Use the permissions array and your data to make the decision.
const canDelete = 
  permissions.includes("admin") ||
  (permissions.includes("documents.delete") && 
   document.ownerId === data.identity?.externalId);

if (!canDelete) {
  return forbidden();
}

Wildcard permissions

Permissions support wildcards for broader access:
// Key has: ["documents.*"]
// This grants: documents.read, documents.write, documents.delete, etc.

try {
  const { meta, data } = await verifyKey({
    key: "sk_...",
    permissions: "documents.read",  // ✅ Passes (matched by documents.*)
  });

  if (!data.valid) {
    return Response.json({ error: data.code }, { status: 401 });
  }

  // Key is valid - continue with your API logic
} catch (err) {
  console.error(err);
  return Response.json({ error: "Internal error" }, { status: 500 });
}
Common wildcard patterns:
  • * — All permissions (use carefully!)
  • documents.* — All document permissions
  • api.v1.* — All v1 API permissions

Best practices

Instead of admin, define specific permissions like users.delete, billing.manage. This gives you more control and better audit trails.
Don’t just check in the UI — always verify permissions server-side during API requests.
Instead of attaching 10 permissions to every key, create a role and attach that. Easier to manage and update.

Next steps

Last modified on February 16, 2026