# 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
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
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
{ "permissions": "documents.read" }
```

Key must have `documents.read`.

### AND (all required)

```json
{ "permissions": "documents.read AND documents.write" }
```

Key must have **both** permissions.

### OR (any required)

```json
{ "permissions": "admin OR editor" }
```

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

### Complex queries with parentheses

```json
{ "permissions": "admin OR (documents.read AND documents.write)" }
```

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

### Real-world example

```typescript
// 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
{
  "meta": { "requestId": "req_..." },
  "data": {
    "valid": true,
    "code": "VALID",
    "keyId": "key_...",
    "permissions": ["documents.read", "documents.write", "users.view"]
  }
}
```

Failed permission check:

```json
{
  "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
    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
    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
    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
// 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>
