# API keys

**You will:** Give each integration a key with only the access it needs, and replace it when it leaks.

**You need:**
- Access to the dashboard to create, rotate and revoke keys
- subaccounts:write to issue keys for a subaccount

A key is how your code proves who it is: `Authorization: Bearer lel_live_...`. Every key belongs to the organization, never to a person, and carries a list of **scopes** (what it may do).

The examples use this client:

```ts
import { Lelemon } from '@lelemon-ai/sdk';

const lelemon = new Lelemon({ apiKey: process.env.LELEMON_API_KEY });
```

## Rules that always apply

- No key can create, change, rotate or revoke keys. Keys are managed only from the dashboard, with the session of a person. A key that could mint keys would be the most valuable one to steal, so a person with a login always stands behind that power.
- The secret is shown once, when the key is created or rotated. Lelemon stores only a hash, so a lost secret cannot be recovered: rotate or replace the key.

## Choose the scopes

Each scope is written `resource:action`, such as `agents:write` or `cases:read`. `GET /v2/scopes` lists all of them and the [scope catalog](/basics/scopes) says what each one allows. `GET /v2/me` returns the scopes of the key you are using.

Give a key the scopes of the job and nothing else:

| Job | Scopes |
| --- | --- |
| A server that only reads conversations | `cases:read`, `contacts:read` |
| A chat in your app | `contacts:write`, `cases:read`, `cases:write`, `cases:converse` |
| A script that configures agents | `agents:read`, `agents:write`, `pipelines:write`, `knowledge:write` |
| A webhook consumer | `webhooks:read`, `webhooks:write` |

Two groups cost money or send your data outside Lelemon, so they are separate scopes: `cases:converse` writes as the contact, which runs the agent and spends model tokens; `actions:write` and `webhooks:write` decide where conversation data goes.

## Manage keys in the dashboard

Create, rotate and revoke keys under Settings, Integrations. Rotation issues a new key with the same name, scopes and expiry window, and lets the old secret keep working for a grace period (24 hours by default) so you can deploy without downtime. Revoking stops a key on the next request, even inside that grace period. A revoked key answers `401 api_key_invalid`.

If a secret leaked: revoke it first and rotate afterwards. Rotating leaves the old secret working for the grace period.

### Allowed sites

A key with `actions:write` decides where your agents send data, so it must name the sites it may call. A key takes between 1 and 10 addresses, each a valid `http` or `https` origin. An action whose URL is on another site answers `403 action_origin_not_allowed`.

## Keys for subaccounts

A platform acts on a subaccount with its own key and the `Lelemon-Subaccount: sub_...` header. When your customer needs direct access, issue a key that sees only that subaccount with `POST /v2/subaccounts/{id}/api-keys`. Its scopes cannot go beyond yours and never include `subaccounts:*`. It takes `expiresInDays` (`30`, `90` or `365`) and ignores `Idempotency-Key`, so the secret is never stored.

```bash title="cURL"
curl https://api.lelemon.cl/v2/subaccounts/$SUBACCOUNT_ID/api-keys \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  --json '{ "name": "Customer dashboard", "scopes": ["cases:read", "contacts:read"], "expiresInDays": 90 }'
```

```ts title="Node.js"
const subKey = await lelemon.subaccounts.apiKeys.create('sub_...', {
  name: 'Customer dashboard',
  scopes: ['cases:read', 'contacts:read'],
  expiresInDays: 90,
});
```


More on subaccounts in [Run Lelemon for your customers](/guides/platforms#keys-inside-a-subaccount).

## Errors you will meet

| Error | Cause |
| --- | --- |
| `401 api_key_invalid` | The key is missing, wrong or revoked |
| `403 scope_missing` | The key lacks the scope of the route; `detail` names it |
| `403 action_origin_not_allowed` | An action calls a site that is not in `allowedActionOrigins` |
| `400 invalid_request` | An unknown scope, or `actions:write` without allowed sites |

All codes are in the [error catalog](/errors).

## Good practice

- One key per system and per environment, named after it. A leaked key then costs one rotation, not a migration.
- Keep keys on your server. Anyone can read a key shipped in a browser or a mobile app.
- Give each key the scopes of its job, and an expiry when it is for a person or a contractor.
- Rotate on a schedule with a short grace period, and check the last use in the dashboard to confirm the old key went quiet before it expires.
- Never log the secret. The `prefix` is enough to find a key.

## What's next

- [Authentication and scopes](/basics/authentication) for the header and the model.
- [Run Lelemon for your customers](/guides/platforms) for subaccounts.
