> ## 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.

# Verifying Permissions

> Verify that an API key has the required permissions during key verification. Use permission queries to enforce fine-grained access control.

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:

<CodeGroup>
  ```bash cURL theme={"theme":"kanagawa-wave"}
  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"
    }'
  ```

  ```typescript TypeScript theme={"theme":"kanagawa-wave"}
  try {
    const { data } = await unkey.keys.verifyKey({
      key: "sk_...",
      permissions: "documents.read",
    });

    if (!data.valid) {
      if (data.code === "INSUFFICIENT_PERMISSIONS") {
        // Key is valid but lacks the permission
        return Response.json(
          { error: "Insufficient permissions" },
          { status: 403 },
        );
      }
      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 });
  }
  ```
</CodeGroup>

## Permission query syntax

Unkey supports logical operators for complex permission checks:

### Single permission

```json theme={"theme":"kanagawa-wave"}
{ "permissions": "documents.read" }
```

Key must have `documents.read`.

### AND (all required)

```json theme={"theme":"kanagawa-wave"}
{ "permissions": "documents.read AND documents.write" }
```

Key must have **both** permissions.

### OR (any required)

```json theme={"theme":"kanagawa-wave"}
{ "permissions": "admin OR editor" }
```

Key must have **at least one** of the permissions.

### Complex queries with parentheses

```json theme={"theme":"kanagawa-wave"}
{ "permissions": "admin OR (documents.read AND documents.write)" }
```

Key must have `admin` **OR** have both `documents.read` and `documents.write`.

### Real-world example

```typescript theme={"theme":"kanagawa-wave"}
// 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:

```json theme={"theme":"kanagawa-wave"}
{
  "meta": { "requestId": "req_..." },
  "data": {
    "valid": true,
    "code": "VALID",
    "keyId": "key_...",
    "permissions": ["documents.read", "documents.write", "users.view"]
  }
}
```

Failed permission check:

```json theme={"theme":"kanagawa-wave"}
{
  "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:

<Steps>
  <Step title="Verify the key">
    Verify without permission requirements to get the key's permissions list.

    ```typescript theme={"theme":"kanagawa-wave"}
    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 });
    }
    ```
  </Step>

  <Step title="Load your data">
    Query your database for the resource.

    ```typescript theme={"theme":"kanagawa-wave"}
    const document = await db.documents.find(documentId);
    ```
  </Step>

  <Step title="Check permissions manually">
    Use the permissions array and your data to make the decision.

    ```typescript theme={"theme":"kanagawa-wave"}
    const canDelete = 
      permissions.includes("admin") ||
      (permissions.includes("documents.delete") && 
       document.ownerId === data.identity?.externalId);

    if (!canDelete) {
      return forbidden();
    }
    ```
  </Step>
</Steps>

## Wildcard permissions

Permissions support wildcards for broader access:

```typescript theme={"theme":"kanagawa-wave"}
// 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

<AccordionGroup>
  <Accordion title="Use specific permissions, not broad ones">
    Instead of `admin`, define specific permissions like `users.delete`,
    `billing.manage`. This gives you more control and better audit trails.
  </Accordion>

  <Accordion title="Check permissions at the API layer">
    Don't just check in the UI, always verify permissions server-side during API
    requests.
  </Accordion>

  <Accordion title="Use roles for common access patterns">
    Instead of attaching 10 permissions to every key, create a role and attach
    that. Easier to manage and update.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Set up roles & permissions" icon="shield" href="/platform/apis/features/authorization/roles-and-permissions">
    Create your authorization structure
  </Card>

  <Card title="Full example" icon="code" href="/platform/apis/features/authorization/example">
    See a complete implementation
  </Card>
</CardGroup>
