API key management end to end
The Developer surface in Devotel Orbit gathers four control families onto one page — the keys themselves, per-key IP allowlists, per-key usage limits, and per-key spend budgets. Each of those families has its own deep-dive guide, linked below, and together they cover every dial individually. What operators still assemble by hand, spread across those guides side-by-side, is the order of operations for one complete key lifecycle: mint the right key, add every control to it, and retire it without breaking a running integration. This guide is that one-shot recipe. It assumes nothing beyond an account; by the end you have a production key that is scoped, IP-locked, usage-bounded, and budget-aware — and you know exactly how to rotate it when the time comes.Why an umbrella guide
Five sibling guides each own one dial, and the recipe only works when all the dials are set in sequence:- Choose and scope API keys — which scopes a key carries.
- Prefix-scoped sandbox tokens — why the first exercise belongs on a
dv_test_key. - Per-API-key IP allowlist — bind the key to your source IPs.
- Per-API-key usage limits and usage budgets and threshold alerts — hard request caps and the browser-side badge.
- Per-API-key spend budgets — a money ceiling per key, alerted once per threshold.
Step 1 — Mint a scoped key (sandbox first, then production)
The first key you mint should be a sandbox key, not a production one. A sandbox key (mode: "test", prefix dv_test_sk_) can never deliver real traffic or bill your wallet, no matter which endpoint you aim it at — so your integration’s first run is free of side effects. Build the whole request shape against sandbox, then mint the production key with the same scope set and flip one environment variable.
Owner, admin, or developer scope. Mint a scoped production key:
201):
Cache-Control: no-store) — later reads show the masked prefix and four-character suffix. Move it into your secret manager before you close the tab.
Scope rules that save you a re-issue:
- Pick only the channels the integration touches.
messages:read+messages:writeabove is a complete messaging key — a:writegrant and the matching:readgrant are independent, and a sender that polls its own delivery status needs both. The full scope catalog is in Choose and scope API keys. - Name it for rotation, not for a person.
<env>-<surface>-<cohort>— for exampleprod-messaging-r2026q3— so the audit log shows which cohort you meant to retire. Person-names outlive the person. - Sandbox equivalent: same call with
"mode": "test"and atest-…name. The prefix swap alone changes the class.
Step 2 — Attach the security posture to the same key
Set all three controls now, before the key first ships — none of them is harder to add afterward, but the habit that keeps keys safe is attaching them in the same session as the mint. IP allowlist. Bind the key to the egress IPs your integration exits from. PATCH is replace-only — send the full list every time:[]) and null both clear the list, and the key has no self-lockout guard — verify each entry before you PATCH. Full rollout checklist and edge cases: Per-API-key IP allowlist.
Usage limits and alerts. Put a per-minute rate limit and a monthly request quota on the key — or rely on the org defaults — plus the advisory alert threshold that flags the key before it exhausts its quota. Set and read them per key in Per-API-key usage limits; add the browser-side threshold badge from usage budgets and threshold alerts.
Spend budget (money, not requests). Persist a monthly ceiling in minor currency units on the organization — this one is shared with your whole team, unlike the per-browser badge:
Step 3 — Read the usage ledger before you revoke
Before you retire any key, read its live usage so you know what you are about to cut off — and so a failed rotation is a configuration miss, not a billing surprise. Two reads cover it:effective_limits and a live usage.requests_this_minute / usage.requests_this_month pair — the same counters the rate-limit and quota surfaces enforce on. The second returns each budgeted key’s usage against its limit_minor. Read them before a scheduled revocation and again after a rotation cutover; a key that still shows traffic on the old suffix after the cutoff is a sign the runbook landed somewhere other than where you aimed it. Pair with the Request Logs console for the route-level view.
Step 4 — Rotate with zero downtime
Routine rotation never needs a maintenance window.POST …/:id/rotate in grace mode mints the replacement and keeps the old key valid for a window you choose (1–168 hours, default 24), so you can roll the new key service by service:
- Store the new plaintext now. This response is the only place it ever appears.
- Deploy the new key to every integration, on your own schedule, within the window. Both keys authenticate the same scopes until
oldKeyExpiresAt— the dual-valid overlap is the entire point. - Guard the window. While a key is mid-rotation the API rejects a second rotation attempt with
409— one rotation per grace window at a time. If you have changed course mid-window, wait for the cutoff or simply continue using the old key; it is valid untiloldKeyExpiresAteither way. A rotation cannot be un-cut-off, but it never needs to be — that is what the window is for. (The separatePOST …/:id/schedule-revokecutoff, which operators set when they want a known future retire date without a rotation, IS cancellable withPOST …/:id/cancel-revokewhile it is still in the future.) - Confirm the cutover in the ledger. After the cutoff, re-read
GET /api/v1/developer/governance: the old suffix should produce only401s. Anything2xxon it means an integration still holds the old key — flip it before the window closes.
oldKeyExpiresAt — and rotate again when you are ready. A scheduled revocation you want to abandon (POST …/:id/schedule-revoke) has a dedicated cancel (POST …/:id/cancel-revoke) while its cutoff is still in the future. The full state machine, both modes, and when to revoke instead: API key lifecycle and rotation.
Pitfalls that bite operators in production
- A partially scoped key fails silently for the channel you assumed. Halting at
messages:writebecause the send “worked” means the delivery-status poll 403s withINSUFFICIENT_SCOPEthe moment a worker pollsGET /messages/:id. Mint bothreadandwritefor a channel pair before you ship, and prove both halves over curl — the scoped call returns its real status, an out-of-scope call returns403. - A personalized share token is not an API key. Transcript, recording, and callback share links carry their own signed actor token (see Signed-token public share plane); the token is personalized to one recipient’s link and bound to one object, and rotating or revoking your API keys does not touch it. A tenant-owned scope on an API key is the only thing that gates program traffic. Never ship a share token where a scope belongs, and never ship an API key where a share link belongs.
- The moment a key becomes live is the mint, not the first send. A
mode: "live"key bills from its first request — there is no activation click and no warm-up window. Mint live keys inside the same change-window as the first deploy, and keep sandbox keys (dv_test_) out of any environment where a live route could receive them. *andadminscopes are script-side only. They never appear in the dashboard picker because a routine integration should never carry them; if your runbook asks for one, the runbook is wrong.
Wire it into your next three reads
- Choose and scope API keys for messaging, CCaaS agents, and the CDP — the scope table and the worked 403.
- API key lifecycle and rotation — the concept page this guide’s runbook applies.
- Prefix-scoped sandbox tokens — the pre-launch checklist that gates go-live.