# API keys

A key is a bearer credential scoped to one team. It is shown once, stored as a hash, and can be narrowed to a single domain.

## Permissions

| Permission | Reaches | Use it for |
| --- | --- | --- |
| `full_access` | Every endpoint, including creating and revoking other keys. | Your server, when it genuinely needs to manage domains and keys. |
| `sending_access` | Sending only. Any other endpoint answers 401 restricted_api_key. | Anything whose job is to send mail — which is most things. |

A `sending_access` key may also carry `domain_id`, which pins it to one verified domain: a request whose `from` is on any other domain is refused. A `full_access` key carrying `domain_id` is rejected at creation rather than silently ignored.

## Handling the token

- The token starts with `rk_` and is returned by the create response and nowhere else. We store only its hash, so a lost token cannot be recovered — mint a new key and revoke the old one.
- Keep it in an environment variable or a secret manager. Every example on this site reads it from the environment for that reason.
- `last_used_at` on the list endpoint tells you whether a key is still in use before you revoke it.

> If a key is exposed, revoke it first and investigate second. Revocation takes effect on the next request, and the row is kept so your audit history still reads correctly.

## Endpoints

### `POST /api-keys`

Mint a key and read its token — once.

#### Body

| Field | Type | Description |
| --- | --- | --- |
| `name` (required) | string | What the key is for, up to 255 characters. It appears in the dashboard and in audit records. |
| `permission` | string | `full_access` (the default) reaches every endpoint. `sending_access` may only send. |
| `domain_id` | string | Restrict the key to one verified domain. Allowed only with `sending_access`; a `full_access` key carrying it is refused. |

Create an API key:

```sh
curl -X POST "https://api.rasket.com/api-keys" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "billing worker",
  "permission": "sending_access",
  "domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34"
}'
```

```ts
const response = await fetch("https://api.rasket.com/api-keys", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "billing worker",
    permission: "sending_access",
    domain_id: "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34"
  }),
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.post(
    "https://api.rasket.com/api-keys",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
    json={
    "name": "billing worker",
    "permission": "sending_access",
    "domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34"
  },
)

id = response.json()["id"]
```

#### Response `201`

```json
{
  "id": "a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75",
  "token": "rk_7Hq2Lm9Pu8jzPde0IgxLd6GncfBAepfJBd0Kh8oOOL8dKLzdocJ"
}
```

- `token` is returned by this response and never again. Store it before you close the connection; we keep only its hash.
- A key inherits the team it was created in. It cannot reach another team's data.

### `GET /api-keys`

Every key on the team, without its token.

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `limit` | integer | How many items to return, 1–100. Defaults to 20. |
| `after` | string | Return the page that follows this item ID. Mutually exclusive with `before`. |
| `before` | string | Return the page that precedes this item ID. Mutually exclusive with `after`. |
| `status` | string | `active` (the default: every key that is not revoked, suspended ones included), `revoked` or `all`. |
| `search` | string | Keep keys whose name contains this text, ignoring case. `%` and `_` are ordinary characters here. A blank value is refused. |
| `permission` | string | Keep only `full_access` or only `sending_access` keys. |

List API keys:

```sh
curl -X GET "https://api.rasket.com/api-keys" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/api-keys", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const data = await response.json();
```

```python
import os

import requests

response = requests.get(
    "https://api.rasket.com/api-keys",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

print(response.json())
```

#### Response `200`

```json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75",
      "name": "billing worker",
      "created_at": "2026-09-09T09:11:07.552Z",
      "last_used_at": "2026-09-09T10:14:02.118Z",
      "permission": "sending_access",
      "domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
      "key_prefix": "rk_7Hq2Lm9P",
      "status": "active",
      "last_used_request_log_id": "0198f4c1-0000-7000-8000-000000000000"
    }
  ]
}
```

- `last_used_at` is refreshed at most once a minute while a key is in use, so it tells you whether a key is still in use before you revoke it.

### `PATCH /api-keys/{api_key_id}`

Change the name. Nothing else about a key is editable.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `api_key_id` (required) | string | The key's ID. |

#### Body

| Field | Type | Description |
| --- | --- | --- |
| `name` (required) | string | The new name. |

Rename an API key:

```sh
curl -X PATCH "https://api.rasket.com/api-keys/a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "billing worker (eu)"
}'
```

```ts
const response = await fetch("https://api.rasket.com/api-keys/a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "billing worker (eu)"
  }),
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.patch(
    "https://api.rasket.com/api-keys/a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
    json={
    "name": "billing worker (eu)"
  },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "api_key",
  "id": "a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75"
}
```

- Permission and domain restriction are fixed at creation. To change either, create a new key and revoke this one.

### `DELETE /api-keys/{api_key_id}`

Stop the key working, immediately and permanently.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `api_key_id` (required) | string | The key's ID. |

Revoke an API key:

```sh
curl -X DELETE "https://api.rasket.com/api-keys/a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"
```

```ts
const response = await fetch("https://api.rasket.com/api-keys/a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
    "User-Agent": "acme-billing/1.0",
  },
});

const { id } = await response.json();
```

```python
import os

import requests

response = requests.delete(
    "https://api.rasket.com/api-keys/a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75",
    headers={
        "Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
        "User-Agent": "acme-billing/1.0",
    },
)

id = response.json()["id"]
```

#### Response `200`

```json
{
  "object": "api_key",
  "id": "a4d2f0c8-5b31-4e7a-9c62-8f0b1d4e6a75",
  "deleted": true
}
```

- The row is kept so your audit history stays readable; only the credential stops working.
- A revoked key answers `403 restricted_api_key`, which is a different answer from an unknown key's `401 invalid_api_key`.
