Skip to main content

Documentation Index

Fetch the complete documentation index at: https://unkey.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

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 { data } = await unkey.keys.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 { data } = await unkey.keys.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 { data } = await unkey.keys.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

Set up roles & permissions

Create your authorization structure

Full example

See a complete implementation
Last modified on May 6, 2026