
# API Keys API

These endpoints let you manage your account's API keys from code. They all operate on the calling account's own keys only.

There are two kinds of key, and they live on separate paths:

- **Your main key** — the single full-access key under **Settings → Integrations → API Key**. Look up its masked preview, check your rate-limit usage, rotate it, or revoke it. These are the `/api-keys/current`, `/api-keys/rotate` and `/api-keys/usage` endpoints below.
- **Scoped keys** — extra, named keys you create for a specific job, each limited to the parts of the API you choose. These are the `/api-keys` and `/api-keys/{id}` endpoints under [Scoped keys](#scoped-keys). Nothing about your main key changes when you create one; existing integrations carry on untouched.

All paths below are relative to the API base URL:

```
https://api.youraiconnector.com/v1
```

Every request must be authenticated. See [Authentication](authentication.md) for the four accepted methods. The examples here use the `X-API-Key` header (and one query-parameter form for cURL).

> **Read this first.** Rotating or revoking your key takes effect **immediately**. The moment either call succeeds, the old key stops working — every integration that still uses it starts getting `401` errors. Plan for it: rotate during a maintenance window and update all your integrations right away.

---

## Get current key metadata

Returns your active key: the full key in `api_key` when a retrievable copy exists, a masked preview (first 4 and last 4 characters), and, when available, the date it was created. `api_key` is `null` for keys created before retrievable copies were kept — rotate once and the new key can be shown again later.

`GET /api-keys/current`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/api-keys/current?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/api-keys/current", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/api-keys/current",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "api_key": "abcdEFGH1234ijkl5678MNOP9012qrst",
  "api_key_masked": "abcd...qrst",
  "created_at": "2026-06-01T10:00:00.000Z"
}
```

If the account has no API key, the response is `404` with `{ "success": false, "error": "No API key found for this account" }`.

---

## Get rate-limit usage

Returns your rate-limit usage for the current window: the per-window request limit, how many requests have been counted so far, how many remain, and when the window resets. Use this to build client-side throttling so your integration backs off before hitting `429` responses.

`GET /api-keys/usage`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/api-keys/usage" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/api-keys/usage", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/api-keys/usage",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "usage": {
    "limit": 300,
    "window_seconds": 60,
    "used": 37,
    "remaining": 263,
    "window_resets_at": "2026-06-09T12:01:00.000Z"
  }
}
```

If no requests have been recorded in the current window yet, usage is reported as zero and the response includes a `note` field explaining why.

---

## Rotate the key

Generates a new API key and invalidates the previous one in the same step. Use this if you suspect your key has leaked, or as part of a regular credential-rotation policy.

`POST /api-keys/rotate`

> **The new key is shown once.** It is returned in this response and cannot be retrieved in full afterwards — store it securely the moment you receive it. The previous key stops working the instant this call succeeds, so update every integration that used it.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/api-keys/rotate?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/api-keys/rotate", {
  method: "POST",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
// Save data.api_key now — it will not be shown again.
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/api-keys/rotate",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
# Save data["api_key"] now — it will not be shown again.
```

**Response**

```json
{
  "success": true,
  "api_key": "abcdEFGH1234ijkl5678MNOP9012qrst",
  "message": "API key rotated. The previous key is no longer valid. Store this key now — it will not be shown again."
}
```

---

## Revoke the key

Permanently deletes your account's API key. Revocation is immediate: every subsequent request that uses the revoked key — including integrations such as Make, Zapier, or custom scripts — is rejected with a `401`. To restore API access afterward, generate a new key from your account settings while signed in to the app.

`DELETE /api-keys/current`

> **There is no undo.** Unlike rotation, revocation does not hand you a replacement key. Only revoke when you intend to stop API access (for example, a leaked key you cannot immediately replace).

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/api-keys/current" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/api-keys/current", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/api-keys/current",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "revoked": true,
  "message": "API key revoked. All requests using it will be rejected immediately."
}
```

If the account has no key to revoke, the response is `404`.

---

## Scoped keys

A scoped key is an extra API key you create for one specific job, carrying only the access that job needs. The classic case: you want to point a client dashboard, a reporting tool, or an internal script at your account without handing over a key that could also send messages, change your AI agents, or buy a phone number.

The restriction travels with the key itself, so whoever holds it can only do what you allowed when you created it.

**What you can restrict**

| Field | What it means |
|---|---|
| `read_only` | `true` (the default) means only read requests are allowed. Any create, update or delete is refused. |
| `tags` | The list of API sections the key may use, written with the same section names you see in these docs and in the [API explorer](reference.md) — `Analytics`, `Campaigns`, `Contacts`, `Messages`, `Appointments`, and so on. An empty list means every section. |
| `sub_account_ids` | Which managed accounts the key may act on. Empty means your own account only; `["*"]` means any account you actually manage. Ownership is still checked on every request. |
| `rate_limit_per_min` | Requests per minute for this key, counted in its own budget so it cannot use up your other integrations' allowance. Defaults to `60`, and cannot be set above `300`. |

You can also give a key an `expires_at` date (ISO 8601, and it must be in the future). After that moment the key stops working on its own. Leave it out and the key never expires until you revoke it.

> **Denials fail closed.** If a request falls outside what the key allows, it is refused rather than let through: a write with a read-only key returns `403` with `error_code: "key_read_only"`, and anything outside the key's allowed sections returns `403` with `error_code: "key_scope_denied"`. If a scoped key gets an unexpected `403`, the endpoint you called is simply not inside its scopes — widen the key or use your main key.

> **Only the account owner manages keys.** These four endpoints require your main key, or an owner session in the app. A scoped key can never list, create, edit or revoke keys — including itself — so a restricted key can never be used to mint a wider one. Trying returns `403` with `error_code: "key_scope_denied"`. For the same reason, `API Keys` is not a section you can grant: asking for it returns `400` with `error_code: "invalid_scopes"`.

### List scoped keys

Returns the account's scoped keys, newest first (up to 200), including revoked ones so you can see what was withdrawn and when. Only masked previews come back — a scoped key's value is shown once, at creation, and is never retrievable afterwards.

`GET /api-keys`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/api-keys" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Response**

```json
{
  "success": true,
  "api_keys": [
    {
      "id": "key_9f2c1a7b4d6e8f0a1b2c3d4e5f60718a",
      "label": "Client dashboard - Acme",
      "key_preview": "abcd...qrst",
      "scopes": {
        "read_only": true,
        "tags": ["Analytics"],
        "sub_account_ids": [],
        "rate_limit_per_min": 60
      },
      "expires_at": null,
      "last_used_at": "2026-08-20T14:03:00.000Z",
      "created_at": "2026-08-14T09:12:00.000Z",
      "revoked_at": null,
      "revoked": false
    }
  ]
}
```

### Create a scoped key

Creates a new scoped key and returns its value **once**.

`POST /api-keys`

> **The key is shown once.** It is in this response and nowhere else, ever — there is no way to look it up again afterwards. Store it the moment you receive it. If you lose it, revoke it and create another.

**Body fields** — all optional:

| Field | Type | Notes |
|---|---|---|
| `label` | string | Your own name for the key, shown in the list and in Settings. |
| `scopes` | object | The four fields in the table above. Leave the whole object out and you get the safe default: read-only, limited to `Analytics`, your own account only, 60 requests per minute. |
| `expires_at` | ISO 8601 date | Optional expiry, must be in the future. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/api-keys" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Client dashboard - Acme",
    "scopes": {
      "read_only": true,
      "tags": ["Analytics"],
      "sub_account_ids": [],
      "rate_limit_per_min": 60
    }
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/api-keys", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    label: "Client dashboard - Acme",
    scopes: { read_only: true, tags: ["Analytics"] },
  }),
});
const data = await res.json();
// Save data.api_key now — it will not be shown again.
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/api-keys",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "label": "Client dashboard - Acme",
        "scopes": {"read_only": True, "tags": ["Analytics"]},
    },
)
data = res.json()
# Save data["api_key"] now — it will not be shown again.
```

**Response** — `201 Created`

```json
{
  "success": true,
  "api_key": "abcdEFGH1234ijkl5678MNOP9012qrst",
  "key": {
    "id": "key_9f2c1a7b4d6e8f0a1b2c3d4e5f60718a",
    "label": "Client dashboard - Acme",
    "key_preview": "abcd...qrst",
    "scopes": {
      "read_only": true,
      "tags": ["Analytics"],
      "sub_account_ids": [],
      "rate_limit_per_min": 60
    },
    "expires_at": null,
    "revoked": false
  },
  "message": "Store this key now — it is shown once and cannot be retrieved again."
}
```

A couple of details worth knowing when you build against this:

- **Omitting `scopes` is not the same as sending an empty `tags` list.** Leave `scopes` out entirely and you get the safe default (read-only, `Analytics` only). Send `"tags": []` on purpose and the key may use every section — that is read as a deliberate request for an unrestricted key.
- **`read_only` stays `true` unless you explicitly send `false`.** A typo or a missing flag can never accidentally produce a key that can write.

### Update a scoped key

Changes a key's label, scopes and/or expiry. Send any combination of the three; sending none of them returns `400`.

`PATCH /api-keys/{id}`

The `{id}` is the key's `id` from the list (the `key_...` value), never the key itself.

> **Scopes are replaced, not merged.** Whatever you send becomes the key's complete permission set. That is deliberate: narrowing a key can never silently leave the old, wider access in place. Always send the full `scopes` object you want, not just the field you are changing.

The key's value never changes. There is no rotate-in-place for a scoped key — to roll one, create a new key and revoke the old one, so a credential's access can never change under an integration still holding it.

**cURL**

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/api-keys/key_9f2c1a7b4d6e8f0a1b2c3d4e5f60718a" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Client dashboard - Acme (read-only)",
    "scopes": {
      "read_only": true,
      "tags": ["Analytics", "Campaigns"],
      "sub_account_ids": [],
      "rate_limit_per_min": 30
    }
  }'
```

**Response**

```json
{
  "success": true,
  "key": {
    "id": "key_9f2c1a7b4d6e8f0a1b2c3d4e5f60718a",
    "label": "Client dashboard - Acme (read-only)",
    "key_preview": "abcd...qrst",
    "scopes": {
      "read_only": true,
      "tags": ["Analytics", "Campaigns"],
      "sub_account_ids": [],
      "rate_limit_per_min": 30
    },
    "expires_at": null,
    "last_used_at": "2026-08-20T14:03:00.000Z",
    "created_at": "2026-08-14T09:12:00.000Z",
    "revoked_at": null,
    "revoked": false
  }
}
```

If there is no key with that id on your account, the response is `404`.

### Revoke a scoped key

Revocation is immediate: the very next request using that key is rejected with a `401`. Your main key and every other scoped key are unaffected.

`DELETE /api-keys/{id}`

The key stays in your list marked `"revoked": true`, so you keep the record of what existed and what it could reach. Revoking a key that is already revoked succeeds and changes nothing.

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/api-keys/key_9f2c1a7b4d6e8f0a1b2c3d4e5f60718a" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Response**

```json
{
  "success": true,
  "revoked": true,
  "id": "key_9f2c1a7b4d6e8f0a1b2c3d4e5f60718a",
  "message": "API key revoked. All requests using it will be rejected immediately."
}
```

---

## API Keys API errors

API-key endpoints return the standard error envelope:

```json
{
  "success": false,
  "error": "No API key found for this account"
}
```

On an API-key endpoint, a missing or invalid key returns `401` and an account with no key on file returns `404`. The shared codes every endpoint can return — `400`, `403` (your plan does not include API access), `429` (rate limit) and `500` — are listed with retry guidance in [Errors & Pagination](errors-and-pagination.md).

The scoped-key endpoints add a few named codes in the `error_code` field so you can tell the cases apart:

| `error_code` | Status | What happened |
|---|---|---|
| `key_read_only` | `403` | A read-only key attempted a write. |
| `key_scope_denied` | `403` | The key is not allowed on that endpoint or that managed account — or a scoped key tried to manage API keys, which is never permitted. |
| `invalid_scopes` | `400` | The requested scopes included the `API Keys` section. Keys cannot manage keys. |
| `404` | `404` | No key with that id on your account. |

---

## Next steps

- [Authentication](authentication.md) — the four ways to authenticate a request, and how key scopes are enforced.
- [Errors & Rate Limits](errors-and-pagination.md) — status codes and the 300 req/min limit.
