# Manage API Keys

List, revoke, and delegate API keys to keep your Blnk deployment secure over time.

After you create a scoped key, you'll audit what's active, revoke keys you no longer need, and rotate keys before they expire.

If you haven't created a key yet, start with [Scoped API keys](/operating/api-keys/overview).

Listing, revoking, and delegating keys requires the master key or a scoped key with the matching `api-keys:*` scopes. See [Scopes](/operating/api-keys/scopes) for permissions and [Owner context](/operating/api-keys/owner-context) for which keys a caller can manage.

## List keys

List keys for an owner to see what's active. The plaintext `key` value is never returned. You get metadata such as name, scopes, expiry, and last-used timestamp.

<CodeGroup>
```bash cURL wrap
curl -X GET "http://localhost:5001/api-keys?owner=payments-team" \
  -H "X-blnk-key: <api-key>"
```

```typescript TypeScript wrap
const response = await blnk.ApiKeys.list({
  owner: 'payments-team',
});
```

```go Go wrap
keys, _, _ := client.ApiKeys.List(&blnkgo.ListApiKeysOptions{
    Owner: "payments-team",
})
```

```python Python wrap
response = blnk.api_keys.list({
  "owner": "payments-team",
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.apiKeys().list(
    ListApiKeysOptions.create()
        .owner("payments-team"));
```
</CodeGroup>

```json 200 OK wrap
[
  {
    "api_key_id": "api_key_879f0ecb-e29f-4137-801b-1048366381db",
    "name": "Payments Service",
    "owner_id": "payments-team",
    "scopes": ["transactions:write", "balances:read"],
    "expires_at": "2027-06-13T00:00:00Z",
    "created_at": "2026-06-13T10:30:00Z",
    "last_used_at": "2026-06-13T14:22:00Z"
  }
]
```

## Revoke a key

Revoke a key when it's no longer needed or you suspect it was exposed.

<CodeGroup>
```bash cURL wrap
curl -X DELETE "http://localhost:5001/api-keys/api_key_879f0ecb-e29f-4137-801b-1048366381db?owner=payments-team" \
  -H "X-blnk-key: <api-key>"
```

```typescript TypeScript wrap
const response = await blnk.ApiKeys.delete(
  'api_key_879f0ecb-e29f-4137-801b-1048366381db',
  {
    owner: 'payments-team',
  },
);
```

```go Go wrap
resp, _ := client.ApiKeys.Delete(
    "api_key_879f0ecb-e29f-4137-801b-1048366381db",
    &blnkgo.DeleteApiKeysOptions{
        Owner: "payments-team",
    },
)
```

```python Python wrap
response = blnk.api_keys.delete(
  "api_key_879f0ecb-e29f-4137-801b-1048366381db",
  {
    "owner": "payments-team",
  },
)
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.apiKeys().delete(
    "api_key_879f0ecb-e29f-4137-801b-1048366381db",
    DeleteApiKeyOptions.create()
        .owner("payments-team"));
```
</CodeGroup>

A successful revoke returns `204 No Content` with an empty body. The key stops working on the next request.

<Warning>
Revoking a key takes effect immediately. Deploy a replacement key before revoking the old one.
</Warning>

## Delegate key creation

<Info>Available on Blnk Core 0.14.3 and later.</Info>

A scoped key with `api-keys:write` can create narrower keys for its own owner, as long as it only grants scopes it already holds. 

See [Owner context](/operating/api-keys/owner-context) for inheritance and cross-owner rules.

<CodeGroup>
```bash cURL wrap
curl -X POST "http://localhost:5001/api-keys" \
  -H "X-blnk-key: <team-admin-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Nightly Reconciliation Job",
    "owner": "payments-team",
    "scopes": ["reconciliation:read"],
    "expires_at": "2027-01-01T00:00:00Z"
  }'
```

```typescript TypeScript wrap
const response = await blnk.ApiKeys.create({
  name: 'Nightly Reconciliation Job',
  owner: 'payments-team',
  scopes: ['reconciliation:read'],
  expires_at: '2027-01-01T00:00:00Z',
});
```

```go Go wrap
expiresAt, _ := time.Parse(time.RFC3339, "2027-01-01T00:00:00Z")
apiKey, _, _ := client.ApiKeys.Create(blnkgo.CreateApiKeyRequest{
    Name: "Nightly Reconciliation Job",
    Owner: "payments-team",
    Scopes: []string{"reconciliation:read"},
    ExpiresAt: expiresAt,
})
```

```python Python wrap
response = blnk.api_keys.create({
  "name": "Nightly Reconciliation Job",
  "owner": "payments-team",
  "scopes": ["reconciliation:read"],
  "expires_at": "2027-01-01T00:00:00Z",
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.apiKeys().create(
    CreateApiKeyData.create()
        .name("Nightly Reconciliation Job")
        .owner("payments-team")
        .scopes(List.of("reconciliation:read"))
        .expiresAt("2027-01-01T00:00:00Z"));
```
</CodeGroup>

## Rotate a key

<Steps>
<Step title="Create the replacement key">
  Create a new key with the same scopes or tighter ones:

<CodeGroup>
```bash cURL wrap
curl -X POST "http://localhost:5001/api-keys" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Payments Service",
    "owner": "payments-team",
    "scopes": ["transactions:write", "balances:read"],
    "expires_at": "2027-06-13T00:00:00Z"
  }'
```

```typescript TypeScript wrap
const response = await blnk.ApiKeys.create({
  name: 'Payments Service',
  owner: 'payments-team',
  scopes: ['transactions:write', 'balances:read'],
  expires_at: '2027-06-13T00:00:00Z',
});
```

```go Go wrap
expiresAt, _ := time.Parse(time.RFC3339, "2027-06-13T00:00:00Z")
apiKey, _, _ := client.ApiKeys.Create(blnkgo.CreateApiKeyRequest{
    Name: "Payments Service",
    Owner: "payments-team",
    Scopes: []string{"transactions:write", "balances:read"},
    ExpiresAt: expiresAt,
})
```

```python Python wrap
response = blnk.api_keys.create({
  "name": "Payments Service",
  "owner": "payments-team",
  "scopes": ["transactions:write", "balances:read"],
  "expires_at": "2027-06-13T00:00:00Z",
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.apiKeys().create(
    CreateApiKeyData.create()
        .name("Payments Service")
        .owner("payments-team")
        .scopes(List.of("transactions:write", "balances:read"))
        .expiresAt("2027-06-13T00:00:00Z"));
```
</CodeGroup>

Copy the plaintext `key` value from the response immediately. You won't see it again.
</Step>

<Step title="Update your applications">
  Deploy the new key to your secret manager or environment variables. Verify the service works with the new key.
</Step>

<Step title="Revoke the old key">
  Delete the old key:

<CodeGroup>
```bash cURL wrap
curl -X DELETE "http://localhost:5001/api-keys/api_key_879f0ecb-e29f-4137-801b-1048366381db?owner=payments-team" \
  -H "X-blnk-key: <api-key>"
```

```typescript TypeScript wrap
const response = await blnk.ApiKeys.delete(
  'api_key_879f0ecb-e29f-4137-801b-1048366381db',
  {
    owner: 'payments-team',
  },
);
```

```go Go wrap
resp, _ := client.ApiKeys.Delete(
    "api_key_879f0ecb-e29f-4137-801b-1048366381db",
    &blnkgo.DeleteApiKeysOptions{
        Owner: "payments-team",
    },
)
```

```python Python wrap
response = blnk.api_keys.delete(
  "api_key_879f0ecb-e29f-4137-801b-1048366381db",
  {
    "owner": "payments-team",
  },
)
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.apiKeys().delete(
    "api_key_879f0ecb-e29f-4137-801b-1048366381db",
    DeleteApiKeyOptions.create()
        .owner("payments-team"));
```
</CodeGroup>

Confirm your applications no longer reference the old key.
</Step>

<Step title="Audit">
  List keys for the owner and confirm only the expected keys remain active:

<CodeGroup>
```bash cURL wrap
curl -X GET "http://localhost:5001/api-keys?owner=payments-team" \
  -H "X-blnk-key: <api-key>"
```

```typescript TypeScript wrap
const response = await blnk.ApiKeys.list({
  owner: 'payments-team',
});
```

```go Go wrap
keys, _, _ := client.ApiKeys.List(&blnkgo.ListApiKeysOptions{
    Owner: "payments-team",
})
```

```python Python wrap
response = blnk.api_keys.list({
  "owner": "payments-team",
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.apiKeys().list(
    ListApiKeysOptions.create()
        .owner("payments-team"));
```
</CodeGroup>
</Step>
</Steps>

## Security best practices

- Review your key list regularly. Look for keys with broad scopes, keys that haven't been used recently, and keys approaching their expiry date.
- Create a separate key for each service or environment.
- Set expiration dates and grant the minimum scopes. See [Scopes](/operating/api-keys/scopes) before each create.
- Store keys in a secret manager. Never commit them to version control.

## Error handling

<Info>
Structured errors are available from Blnk Core 0.15.0 and later.
</Info>

When a list, create, or revoke request fails validation or owner checks, Blnk returns `400 Bad Request` or `404 Not Found`.

| Code | When it happens |
| :-- | :-- |
| `APIKEY_INVALID` | The create request failed validation. |
| `APIKEY_OWNER_REQUIRED` | The master key was used to create or list keys without an `owner` parameter. |
| `APIKEY_NOT_FOUND` | The key ID is not found in the caller's owner context. |

```json 400 Bad Request wrap
{
  "error": "owner is required",
  "error_detail": {
    "code": "APIKEY_OWNER_REQUIRED",
    "message": "owner is required"
  }
}
```

To resolve the error:

| Code | What to do |
| :-- | :-- |
| `APIKEY_OWNER_REQUIRED` | Add the `owner` query parameter or body field when using the master key. |
| `APIKEY_INVALID` | Fix the validation issue in the create request (missing fields, invalid scopes, or malformed dates). |
| `APIKEY_NOT_FOUND` | Verify the key ID and that it belongs to the owner you're acting on. See [Owner context](/operating/api-keys/owner-context#error-handling). |

Delegation and cross-owner errors are covered in [Owner context](/operating/api-keys/owner-context#error-handling). Permission errors for missing scopes are covered in [Scopes](/operating/api-keys/scopes#error-handling).

<RelatedTopics
  items={[
    { title: "API keys overview", href: "/operating/api-keys/overview" },
    { title: "Scopes", href: "/operating/api-keys/scopes" },
    { title: "Owner context", href: "/operating/api-keys/owner-context" },
    { title: "Create API key", href: "/reference/create-api-key" },
  ]}
/>