> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Choose and scope API keys for messaging, CCaaS agents, and the CDP

> Pick the right scope set for each integration — messaging-only, agent management, CDP ingest — name it so rotation is obvious, and verify the narrow key in request logs.

# Choose which scopes your API key needs

The question every integration starts with is not "how do I get a key?" — it is "which scopes does this key actually need?" Devotel Orbit mints per-scope keys so a messaging worker cannot read contacts, an agent manager cannot send messages, and a leaked key loses everything except its own surface. This guide maps the three commonest integration types — messaging, CCaaS AI agents, and CDP ingestion — onto exact scope sets, gives you a naming convention that encodes the rotation schedule, shows a real scoped key working (and being rejected) over curl, and points at the request log you audit it through. For the channel-by-channel catalog of scopes, see [Integrate with the Orbit REST API](/guides/api-integration) and the full scope picker under **Settings → API Keys**.

## Read the scope table first — what each product surface needs

Every endpoint in the [API integration guide's channel table](/guides/api-integration#channels-covered-by-the-api) maps to exactly one scope family. Read the table before you mint, and tick only the rows your integration touches:

| Product surface                                         | Scope family                                 | What it gates                                                                |
| ------------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
| Send + read messages (SMS, WhatsApp, RCS, email, Viber) | `messages:read` / `messages:write`           | `POST /messages/<channel>`, `GET /messages`, `GET /messages/:id`             |
| Manage + view AI agents (CCaaS voice + chat agents)     | `agents:read` / `agents:write`               | `POST /agents`, `GET /agents`, runs, configuration                           |
| Ingest + read CDP events and profiles                   | `contacts:read` / `contacts:write`           | `/cdp/events/recent`, `/cdp/profiles/*`, trait + event reads                 |
| Place + read voice calls                                | `voice:read` / `voice:write`                 | `/voice/calls`, recordings, transcripts                                      |
| Manage + read templates                                 | `templates:read` / `templates:write`         | WhatsApp / RCS / email templates and approval status                         |
| Manage + read webhooks                                  | `webhooks:read` / `webhooks:write`           | Endpoint CRUD and delivery logs                                              |
| Read billing + usage                                    | `billing:read`                               | Balance, invoices, usage analytics                                           |
| Read analytics                                          | `analytics:read`                             | Deliverability, goals, scheduled reports (read-only — no write scope exists) |
| Manage + read numbers                                   | `numbers:read` / `numbers:write`             | Search, purchase, release, port, lookup                                      |
| Manage + read campaigns                                 | `campaigns:read` / `campaigns:write`         | Campaign CRUD and runs                                                       |
| Manage + read flows                                     | `flows:read` / `flows:write`                 | Automation flows and execution                                               |
| Manage + read conversations (inbox)                     | `conversations:read` / `conversations:write` | Threads, replies, assignment, handoff                                        |
| Manage + read inbox settings                            | `inbox:read` / `inbox:write`                 | Macros, routing rules, SLAs, saved views                                     |
| Manage + read files                                     | `files:read` / `files:write`                 | Upload, list, download, delete                                               |
| Manage + read integrations                              | `integrations:read` / `integrations:write`   | Install, configure, remove integrations                                      |
| Manage + read knowledge bases                           | `knowledge:read` / `knowledge:write`         | Create, ingest, and query agent knowledge                                    |
| Manage + read settings                                  | `settings:read` / `settings:write`           | Webhooks, channels, sender IDs, account config                               |
| Send verifications                                      | `verify:write`                               | One-shot OTP / silent-auth flows                                             |
| Manage + read BYO-SMPP credentials                      | `smpp:read` / `smpp:write`                   | `/messaging/smpp/*` bind credential CRUD                                     |

A `:write` scope is not a superset of `:read` — the two are independent grants. Mint both when the integration writes and then reads back its own writes (a messaging worker polling `GET /messages/:id` for delivery status needs `messages:write` to send **and** `messages:read` to poll). The dashboard picker at **Settings → API Keys → Create key** presents the same list as checkboxes; `*` (wildcard) and `admin` are intentionally absent from the picker and only ever mintable script-side — never for a routine integration.

## Pick the right key for the job

Three shapes cover the integrations most teams mint first. Start with the narrowest set that works — you can always re-issue with one more scope; you cannot take back the blast radius of a broad key already leaked.

**Messaging-only sender.** A worker that POSTs sends and polls their delivery:

* `messages:write` — the send itself.
* `messages:read` — the status poll.

That pair is everything. No contacts, no templates, no billing. If the send is template-driven (a WhatsApp campaign fragment), add `templates:read` — not `templates:write`, unless the worker itself submits templates for approval.

**Agent management (CCaaS).** An orchestration layer that builds, tunes, and inspects AI voice/chat agents:

* `agents:write` — create, update, and configure agents.
* `agents:read` — list agents and their runs.
* `knowledge:read` — if the agent pulls from a knowledge base the orchestrator only queries.
* `knowledge:write` — add only if the same worker also manages the knowledge base content.

Do not give an agent-management key `conversations:write` or `messages:write` by default — the agent runtime uses its own internal credential path, and a management key that can also reply as a human agent widens the leak surface without adding capability the orchestrator needs.

**CDP ingest.** A pipeline pushing events into the customer data platform and the read side that audits it:

* `contacts:write` — trait and event writes against profiles.
* `contacts:read` — `/cdp/profiles/*`, `/cdp/events/recent`, and the identity-resolution reads.

The first-party event stream itself (the segment-style `/cdp/v1/:ingest_id/track` family) is authenticated per-ingest-source, not through this scope set — a separate ingest credential minted from the CDP source catalog. The scope pair above is what your *read-side* integration needs for the dashboard-shaped surfaces (`GET /cdp/events/recent`, `GET /cdp/profiles/...`). Mint them separately: the ingest credential has no API-dashboard surface and the API key has no ingest surface, so a leaked key of either kind cannot pivot into the other lane.

## Name keys so the rotation schedule is visible

A label is the only metadata a key carries that survives to an audit log, a request-log row, or a leaked-secret paste. Encode the environment, the surface, and the rotation cadence in the name itself so anyone (including future-you at 3 a.m.) can see what a key is for and when it should retire without opening the dashboard:

```
<env>-<surface>-<rotation-tag>
```

Worked examples:

| Name                      | Reads as                                                                                    |
| ------------------------- | ------------------------------------------------------------------------------------------- |
| `prod-messaging-r2026q3`  | Production, messaging-scoped, rotation cohort Q3 2026                                       |
| `staging-agents-r0907`    | Staging, agent-management, rotated 09-07                                                    |
| `prod-cdp-ingest-r2026q3` | Production, CDP event/read, same rotation cohort                                            |
| `test-sms-sandbox-r`      | Sandbox keys keep the `test-` prefix — never confuse a `dv_test_sk_` with a live credential |

Two rules make the convention load-bearing:

1. **One surface per key.** The surface in the name must be one row of the scope table — never `prod-everything`. If two workers need two surfaces, mint two keys; the per-key rate-limits and budget guides below assume that shape.
2. **The rotation tag is the cohort, not the person.** `r2026q3` is re-minted across all keys in that cohort on the same day; a person-name (`sarah-prod`) survives the person leaving and outlives its meaning. Owner / admin / developer roles can rotate; the cutover is the grace flow in the next section.

## Wire the neighboring guides as your next three steps

A scoped key is the first of four lines you should draw before production. The other three each have their own guide; set them in this order once the key is minted:

1. **[Per-API-key IP allowlist](/guides/api-key-ip-allowlist)** — bind the key to your egress IPs so a leaked copy is useless outside your network. Cheapest possible mitigation against the paste-site scenario.
2. **[Per-API-key usage limits and spend alerts](/guides/api-key-usage-limits)** — the rate limit and monthly quota that contain one noisy integration (or one compromised key) instead of your whole workspace's throughput.
3. **[Per-API-key usage budgets and threshold alerts](/guides/api-key-usage-budget-alerts)** — the dashboard tripwire: a warning badge at 80% and an exceeded badge at 100% of a budget you set per browser, layered on top of the API-level alert the whole team sees.

The fourth line — the actual key-retirement mechanics — is the rotation section below.

## Rotate as a how-to (the lifecycle concept, applied)

The [API key lifecycle and rotation](/concepts/api-key-rotation-lifecycle) concept page defines the states and the two modes; here is the cutover runbook as concrete steps against a key named `prod-messaging-r2026q3`:

1. **List first**, so you target the right id — `GET /api/v1/settings/api-keys` returns labels, prefixes, suffixes, and expiry metadata; secrets are never listed.
2. **Rotate with a grace window** (never immediate, for routine rotation):

```bash theme={null}
curl -X POST https://orbit.devotel.io/api/v1/settings/api-keys/key_01H8X/rotate \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"grace_period_hours": 24}'
```

```json theme={null}
{
  "data": {
    "id": "key_01H9Q2",
    "key": "dv_live_sk_9f3c…new-plaintext…",
    "oldKeyExpiresAt": "2026-09-05T10:24:00.000Z",
    "rotationMode": "grace"
  },
  "meta": { "requestId": "req_9c2fa1" }
}
```

3. **Copy `key` into your secret manager now** — this response is the only place the plaintext ever appears; later reads only show the masked prefix and suffix.
4. **Deploy the new key** to every integration. The old one keeps authenticating against the same scopes until `oldKeyExpiresAt`, so you can roll service by service (the dual-valid window is the entire point of grace mode).
5. **Confirm the cutover in request logs** — once `oldKeyExpiresAt` passes, any request still carrying the old suffix fails `401`. Grep your access logs for the old suffix ahead of the cutoff if zero surprise is the bar.

For a leaked key the same endpoint with `"mode": "immediate"` mints the replacement and kills the old key in one transaction — that is the incident path, not the routine one. Full mode semantics, the cutoff math, and the rotate-vs-revoke decision table live in the [concept page](/concepts/api-key-rotation-lifecycle).

## Worked example: a messaging-only key, verified over curl

Mint a key with exactly `messages:write` and `messages:read` in the dashboard, then prove both halves — the scoped surface answers, everything else 403s:

```bash theme={null}
# 1. The scoped surface works.
curl https://api.orbit.devotel.io/api/v1/messages/sms \
  -H "X-API-Key: dv_live_sk_KEY_WITH_MESSAGES_SCOPE" \
  -H "Content-Type: application/json" \
  -d '{"from": "+16572262362", "to": "+15555551234", "body": "hello"}'
# -> 202 Accepted

# 2. A surface outside the scope is rejected.
curl https://api.orbit.devotel.io/api/v1/cdp/events/recent \
  -H "X-API-Key: dv_live_sk_KEY_WITH_MESSAGES_SCOPE"
```

```json theme={null}
{
  "error": {
    "code": "INSUFFICIENT_SCOPE",
    "message": "API key missing required scope. Needs one of: contacts:read. Re-issue this key with the required scope from Settings → API Keys.",
    "status": 403
  },
  "meta": {
    "request_id": "req_01HXYZ...",
    "timestamp": "2026-09-07T10:23:00Z"
  }
}
```

That rejection is the whole point of scoping: the key carried no credential that `/cdp/events/recent` accepts, so read on those rows never happens under this key. Acceptance for any new key you mint is exactly this pair — the scoped call returns its real status, and one deliberate out-of-scope call returns `403 INSUFFICIENT_SCOPE`. Do not do step 2 with the wildcard scope: a `*` key answers both calls and that is the smell you are verifying against.

## Audit surface and what to grep in request logs

Verify a key is behaving the way you scoped it in the [Request Logs console](/guides/api-request-logs-console) — filter by the API key prefix and read what it actually sent, not what you expect it sent. Three checks catch the recurring misuse patterns:

* **Scope creep.** The key should only ever appear on the routes its scopes imply. A `messaging-only` key hitting `/contacts` or `/cdp/events/recent` means someone is re-using it for work it was never meant for — grep the request path by that key and diff against the scope table above.
* **Out-of-scope `403`s.** Every `INSUFFICIENT_SCOPE` under one key id is an attempted access the key does not have. A trickle is noise from a misconfigured client; a burst is either a worker fetching the wrong env's key or someone probing what the key can reach. The request log records the `403` even though the caller saw only the error code.
* **The old suffix after rotation.** After a grace cutover the only rows a retired suffix should produce are `401` — anything `2xx` against the old suffix post-cutoff means the cutoff did not land where your runbook assumed. Re-check the `oldKeyExpiresAt` the rotation response returned.

For the org-wide billing-side view of the same discipline — which keys are spending, not just which keys are authenticating — pair the request log with [Per-API-key spend budgets](/guides/per-api-key-spend-budgets).

## Related references

* [Integrate with the Orbit REST API](/guides/api-integration) — base URLs, authentication, channel and rate-limit tables
* [API key lifecycle and rotation](/concepts/api-key-rotation-lifecycle) — the state machine and the two rotation modes
* [Per-API-key IP allowlist](/guides/api-key-ip-allowlist)
* [Per-API-key usage limits and spend alerts](/guides/api-key-usage-limits)
* [Per-API-key usage budgets and threshold alerts](/guides/api-key-usage-budget-alerts)
* [Per-API-key spend budgets](/guides/per-api-key-spend-budgets)
* [Request Logs console](/guides/api-request-logs-console)
