# Per-customer API keys

Issue an API key per customer that only reaches their profile, make it read-only or bar whole areas of the API, and rotate it without downtime.

Your main backend can use one full-access key for every customer. Some parts of your system should not: a worker that runs customer-supplied automations, a reporting export, an AI agent acting for one tenant, a script you hand to an agency. Give those a key scoped to one customer's profile, so the worst a bug or a leak can do is limited to that customer.

## Create a key for one customer

Call `POST /v1/api-keys` with `scope: "profiles"` and the customer's profile id. Use your full-access, read-write key for this call: scoped and read-only keys cannot create keys.

```bash cURL
curl -X POST https://api.pozy.ai/v1/api-keys \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "cus_8f3a2 scheduler",
    "scope": "profiles",
    "profileIds": ["6720f3b1c9e4a85d2f0b7c14"],
    "disabledResourceGroups": ["ads", "telephony", "money", "billing", "webhooks", "commerce", "creator_campaigns"]
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/api-keys", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "cus_8f3a2 scheduler",
    scope: "profiles",
    profileIds: [customer.pozyProfileId],
    disabledResourceGroups: ["ads", "telephony", "money", "billing", "webhooks", "commerce", "creator_campaigns"],
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
await secrets.put(`pozy/${customer.id}`, data.apiKey.key); // shown once
```
```python Python
import os, requests

resp = requests.post(
    "https://api.pozy.ai/v1/api-keys",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "name": "cus_8f3a2 scheduler",
        "scope": "profiles",
        "profileIds": ["6720f3b1c9e4a85d2f0b7c14"],
        "disabledResourceGroups": ["ads", "telephony", "money", "billing", "webhooks", "commerce", "creator_campaigns"],
    },
)
key = resp.json()["apiKey"]["key"]  # shown once: store it in your secret manager
```

Store the returned `key` in your secret manager straight away. It is never shown again; lists only include `keyPreview`.

A key can cover several profiles. A customer with two brands can get one key with both profile ids.

## Pick the right restrictions

Combine the three controls to match the job:

| Job | `scope` | `permission` | `disabledResourceGroups` |
| --- | --- | --- | --- |
| Post scheduler for one customer | `profiles` | `read-write` | everything except `publishing` and `accounts` |
| Customer-facing analytics dashboard | `profiles` | `read` | `messages`, `contacts`, `billing`, `webhooks`, `money` |
| Support inbox for one customer | `profiles` | `read-write` | everything except `messages`, `engagement`, `contacts`, `accounts` |
| AI assistant that only reads | `profiles` | `read` | `billing`, `money`, `webhooks` |
| Short-lived access for a contractor | `profiles` | as needed | as needed, plus `expiresIn: 7` |

The full list of groups and what each covers is in [Authentication](https://pozy.ai/docs/getting-started/authentication#disabled-resource-groups).

## What a scoped key can and cannot do

A profile-scoped key:

- Can do anything its permission and groups allow, as long as every `profileId` in the request is in its scope.
- Gets `403` with `code: "profile_out_of_scope"` when a request names any other profile.
- Gets `403` with `code: "account_out_of_scope"` when a request names an account that lives in any other profile, for example an `accountId` in a post's `platforms`.
- Sees only its own profiles in `GET /v1/profiles`.
- Can move an account only into a profile in its scope.
- Cannot list, create or delete API keys (`403`, `code: "insufficient_permissions"`).
- Cannot read workspace-wide billing or usage (`403`, `code: "profile_scoped_key"`), but can read usage for its own profile with `GET /v1/usage?profileId=...`.

> **Always pass profileId on lists:**
> 
> Pass the customer's `profileId` on every list call made with a scoped key, such as accounts, posts, conversations and analytics. It keeps results inside the tenant and makes the scope check explicit.

## Rate limits are shared

Scoped keys do not add throughput. Every key in the workspace draws from the same per-minute budget ([rate limits](https://pozy.ai/docs/getting-started/rate-limits)). If one customer runs a large import, queue it on your side so it cannot use up the budget your other customers need.

## Rotate a customer's key

There is no update endpoint for keys. To change a key's settings or rotate it:

1. Create a new key with the settings you want.
2. Switch the customer's service to it.
3. Delete the old key with `DELETE /v1/api-keys/{keyId}`.

Deleting takes effect on the next request. Keep the key `id` next to the stored secret so you can revoke the right one later.

## Clean up

When a customer leaves, revoke their keys as part of offboarding ([build a platform](https://pozy.ai/docs/build#offboard-a-customer)). Use `GET /v1/api-keys` to find keys whose `profileIds` include the profile you are deleting.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/auth/verify`](https://pozy.ai/docs/api/verifyCredential) | Verify credential |
| [`GET /v1/api-keys`](https://pozy.ai/docs/api/listApiKeys) | List keys |
| [`POST /v1/api-keys`](https://pozy.ai/docs/api/createApiKey) | Create key |
| [`DELETE /v1/api-keys/{keyId}`](https://pozy.ai/docs/api/deleteApiKey) | Delete key |
