v0.5.7.0 introduced a two-tier credential model so an AI agent can mint, rotate, and revoke its own scoped API keys without human intervention. The bootstrap step stays in the portal; everything after that is API-driven.
A service key is a root credential for credential management. It never places calls, never sends SMS, never moves money. Its only job is to call /v1/keys on your behalf so an agent can manage its own credentials at runtime.
Both credential types authenticate the same way on the wire: Authorization: Bearer <key>. The prefix tells the auth dispatcher which tier you’re on.
Service keys cannot mint other service keys via API. This is enforced at the schema level (there is no parent_service_key_id column on the service keys table), so no API surface can ever expose that path. The pattern mirrors Stripe restricted keys and Exa service keys: bootstrap is one-way through a human surface, by design.
Child API keys carry their own scope. The service key that mints them does not relax any account-level guardrails. A child key is the intersection of the requested scope and what the account allows.
line_id scopes a child key to a single line. The line must exist, be owned by the same account as the service key, match the service key’s environment, and not be released. Otherwise POST /v1/keys returns 404 not_found on the line (without sticky-caching, so retries succeed once the binding is fixed). Omit line_id for an account-wide child key.
monthly_cap_cents is per-key and independent of the account cap. Spend on a child key counts against both its own cap and the account cap, whichever hits first.
Service keys themselves are limited to the six /v1/keys credential-management endpoints. Calling any data-plane endpoint with a service key returns 401 invalid_api_key. The tier dispatcher rejects the wrong-tier token before the handler runs.
The API accepts exactly four permission tiers on POST /v1/keys and PATCH /v1/keys/{id}. A fifth value, legacy_full, appears on responses for keys minted before v0.5.7.0 but cannot be set via the API.
line_id. Setting line_id on a child key scopes every call and message it sends to that one line. Useful for one-agent-per-line topologies. PATCH with line_id: null to unbind (account-wide). Omitting line_id on POST creates an account-wide key.
monthly_cap_cents. Per-key spend cap in cents (minimum 1, maximum 1,000,000). When a child key hits its cap, calls return 402 agent_cap_exceeded until the cycle rolls. The account cap still applies on top, so a child cap never lets you exceed the account budget.
Bootstrap a service key in the portal, save its plaintext as SAPERLY_SERVICE_KEY, then have your agent mint, rotate, and revoke child keys at runtime.
Rotate when you suspect compromise. The old plaintext stops working immediately and a new one is returned:
Revoke when you’re done. Child revocation takes effect immediately, with no grace window. Calls and SMS already accepted before revocation continue to completion; only new requests are rejected.
Every key operation writes an append-only row to compliance_events. You can revoke a key but you cannot erase the event that created it. Every child API key carries created_by_service_key_id, so any call placed with a child key traces back to the service key that minted the credential.
Rotation cadence. Rotate long-lived account API keys on a 90-day cadence. Rotate service keys immediately on any team-member offboarding. Child keys minted by service keys can rotate on every deploy if your runtime supports it. That’s the whole point of the model.
Storage. Store service keys as SAPERLY_SERVICE_KEY in your secrets manager. Store the regular API key your agent runtime uses as SAPERLY_API_KEY. Never commit either to source control. Never log the plaintext.
When to use each tier.
line_id-bound and cap-limited, so blast radius is bounded even if it leaks.If a key leaks. Rotate or revoke immediately, then audit. The audit feed at GET /v1/audit (called with a regular API key, not the service key) shows every key lifecycle event and every call traced back to the credential that placed it. For a leaked child key, revoke just that one. For a leaked service key, rotate it in the portal. Existing child keys keep working, but rotate any you suspect were minted under the compromise window.
Service keys are limited to the six /v1/keys credential-management endpoints. Calling any of these with a service key returns 401 invalid_api_key:
POST /v1/calls)POST /v1/calls/conversation)POST /v1/messages)POST/PATCH/DELETE /v1/lines)GET /v1/audit, which requires a regular API key)For the full bootstrap-to-revoke walkthrough, see Agent onboarding. For idempotency rules, error codes, and the audit chain, see the v0.5.7.0 changelog entry.