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

> ## Agent Instructions
> Unkey is two separate products. Compute builds, deploys, and runs apps behind a gateway. API Management issues API keys, enforces rate limits, manages identities and permissions, and reports usage. Say which product a page belongs to; a reader can use either without the other.
> Every Unkey API endpoint is an HTTP POST to https://api.unkey.com/v2/{service}.{procedure} with a root key in the Authorization: Bearer header. Root keys are workspace scoped.
> Error codes have the form err:{system}:{category}:{specific} and each has a page at /errors/{system}/{category}/{specific}.
> The word environment means production or preview in Compute. Rate limiting has four meanings on this site; the glossary lists them.

# Listing keys

> Page through the keys in a keyspace or find the ones a user owns.

Use `apis.listKeys` to list the keys in a keyspace, up to 100 per page. It's handy for a "your API keys" screen, for finding every key a user owns, or for auditing a keyspace. It only returns the full key for recoverable keys, and only when you ask.

<Note>
  You need a root key with the permissions listed on this page. Create one in the dashboard under **Settings > Root Keys**, and pass it as `Authorization: Bearer <root key>`. See [Permission reference](/docs/platform/root-keys/permissions-legacy) for every permission.
</Note>

The root key needs two permissions: `api.*.read_key` or `api.<api_id>.read_key`, **and** `api.*.read_api` or `api.<api_id>.read_api`. Adding `decrypt: true` additionally needs `api.*.decrypt_key` or `api.<api_id>.decrypt_key`. See [Root key permissions](/docs/platform/root-keys/permissions).

A missing permission isn't a 403. You get HTTP 404 [`err:unkey:data:api_not_found`](/docs/errors/unkey/data/api_not_found), "The requested API does not exist or has been deleted." If you see this for an ID you know is correct, check both permissions on the root key.

## Request

<ParamField body="apiId" type="string" required>
  The keyspace to list, by API ID.
</ParamField>

<ParamField body="limit" type="integer" default="100">
  Keys per page, 1 to 100.
</ParamField>

<ParamField body="cursor" type="string">
  The `pagination.cursor` from the previous response. Omit for the first page.
</ParamField>

<ParamField body="externalId" type="string">
  Only keys linked to the identity with this exact `externalId`. Use it to list one user's keys.
</ParamField>

<ParamField body="decrypt" type="boolean" default="false">
  Include `plaintext` for keys created with `recoverable: true`. Requires the decrypt permission and a keyspace with encrypted storage enabled. See [Recoverable keys](/docs/api-management/keys/recoverable-keys).
</ParamField>

```bash theme={"theme":"kanagawa-wave"}
curl -X POST https://api.unkey.com/v2/apis.listKeys \
  -H "Authorization: Bearer $UNKEY_ROOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "apiId": "api_...", "externalId": "user_123", "limit": 50 }'
```

## Response

`data` is an array of key objects, and `pagination` tells you whether to continue.

```json theme={"theme":"kanagawa-wave"}
{
  "meta": { "requestId": "req_..." },
  "data": [
    {
      "keyId": "key_...",
      "start": "sk_prod_abc1",
      "enabled": true,
      "name": "Production key",
      "createdAt": 1704067200000,
      "expires": 1735689600000,
      "meta": { "plan": "premium" },
      "credits": { "remaining": 950 },
      "identity": { "id": "id_...", "externalId": "user_123" }
    }
  ],
  "pagination": { "cursor": "...", "hasMore": true }
}
```

Each item has the same fields `keys.getKey` returns. `start` is the prefix and first few characters, so a user can recognize the key. `plaintext` appears only when `decrypt: true` worked for that key. [Looking up keys](/docs/api-management/keys/looking-up-keys) lists every field.

## Paging

Send `pagination.cursor` back until `hasMore` is false. Cursors expire, so don't store them.

```typescript list-all-keys.ts theme={"theme":"kanagawa-wave"}
let cursor: string | undefined;
do {
  const { data, pagination } = await unkey.apis.listKeys({
    apiId: "api_...",
    externalId: "user_123",
    cursor,
  });
  for (const key of data) {
    console.log(key.keyId, key.start);
  }
  cursor = pagination.hasMore ? pagination.cursor : undefined;
} while (cursor);
```

## In the dashboard

The keyspace's **Keys** tab shows the same list. You can filter by key ID, name, identity, and tags (is, contains, starts with, ends with). Select several rows to change their external ID, enable or disable them, or delete them in bulk.

<Frame>
  <img src="https://mintcdn.com/unkey/TjbnJStfcJRkiuek/images/dashboard/api-management--keyspaces-listing-keys--filter.png?fit=max&auto=format&n=TjbnJStfcJRkiuek&q=85&s=b7213bbd6801ba12bc5222899baf7bf7" alt="Keys tab with the filter menu open, offering Name, Identity, Key ID, and Tags" width="2560" height="1600" data-path="images/dashboard/api-management--keyspaces-listing-keys--filter.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/unkey/TjbnJStfcJRkiuek/images/dashboard/api-management--keyspaces-listing-keys--bulk-actions.png?fit=max&auto=format&n=TjbnJStfcJRkiuek&q=85&s=699df2395bde766a851fdb8e20a5edbe" alt="Keys tab with two keys selected and the bulk action bar offering Change External ID, Disable key, and Delete key" width="2560" height="1600" data-path="images/dashboard/api-management--keyspaces-listing-keys--bulk-actions.png" />
</Frame>
