CUSTOMAGENTS
Developers

API Keys & Scopes

Account and per-agent API keys, the two scope namespaces, least-privilege defaults, rotation, IP allowlists, expiry, and the shown-once contract.

Two key types

CustomAgents issues two kinds of API key. They live in separate, non-overlapping scope namespaces and can never be mixed.

Key typePrefixReachesMint from
Accountca_acct_Your whole account — agents, contacts, messages, inboxes, webhooks, billing readsSettings → API Keys
Per-agentca_agt_Exactly one agent and nothing elseThe agent's Settings → API & MCP tab

A per-agent key is bound to a single agent server-side. It returns 403 on the account API and on every other agent. See Per-Agent API & MCP for the full isolation contract.

Both prefixes are intentionally identifiable so secret scanners (GitHub, GitGuardian, etc.) can flag a leaked key. Treat any ca_acct_… or ca_agt_… string as a live credential.

The shown-once contract

A key's plaintext secret is returned exactly once, at creation (or rotation), and is never retrievable again. We store only a SHA-256 hash. The dashboard and the list endpoints return metadata (name, prefix, scopes, last-used, expiry) but never the secret.

If you lose a key, you cannot recover it — rotate or revoke and issue a new one.

Access ceiling

Every API key — account or per-agent — is capped at member-level access regardless of who created it. A key can never perform owner/admin-only actions. In particular:

  • No key can mint another key (write:api_keys is never grantable to a key).
  • No key can mutate billing (write:billing is never grantable to a key).
  • * (god mode) is never grantable through the create path.

Key creation itself is an admin session action, not an API-key action.

Scopes

Scopes follow least privilege: a key holds only the verbs you grant. There are two disjoint namespaces.

Account scopes (resource:action)

Held only by ca_acct_ keys.

ScopeGrants
read:agentsList and read agents
write:agentsCreate, update, pause agents
read:contactsList and read contacts
write:contactsCreate and update contacts
read:messagesList conversations and read messages
write:messagesCreate drafts (does not send)
messages:sendSend real email/SMS — money-spending
read:inboxesRead inbox configuration
write:inboxesManage inboxes
read:webhooksList webhooks
write:webhooksCreate and delete webhooks
integrations:readRead connected integrations
integrations:manageConnect and disconnect integrations
read:domainsRead sending domains
write:domainsManage sending domains
read:billingRead subscription and usage
read:accountRead account stats, logs, and feed
read:activityRead activity timelines
trigger:agentsFire an agent (chat/voice/missions) — money-spending
read:api_keysList API-key metadata

messages:send is deliberately distinct from write:messages: a draft-only key cannot send real messages. trigger:agents is separated from reads so a read-only key cannot spend credits.

Agent scopes (agent:resource:action)

Held only by ca_agt_ keys. The agent: prefix guarantees these can never collide with an account scope.

ScopeGrants
agent:config:readRead this agent's configuration
agent:config:writeUpdate this agent's configuration
agent:conversations:readRead this agent's conversations
agent:activity:readRead this agent's activity
agent:triggerTrigger this agent via chat — money-spending

Never grantable

These can never be granted to any API key (the create path rejects them with 400):

  • * — wildcard / god mode
  • write:billing — billing mutations stay owner-only
  • write:api_keys — a key cannot mint a key

Wildcard matching

When a route or tool requires a scope, your granted scopes are matched literally plus two wildcard forms:

  • read:* satisfies any read:<resource> requirement.
  • *:agents satisfies any <verb>:agents requirement.

Legacy * keys satisfy everything (these are being phased out — re-scope when prompted).

Least-privilege defaults

If you create a key without specifying scopes, you get the minimal read-only set for that tier. Writes, sends, and triggers must be requested explicitly.

// Default account key scopes
["read:agents", "read:contacts", "read:account"]

// Default per-agent key scopes
["agent:config:read", "agent:conversations:read", "agent:activity:read"]

Lifecycle

Create

Dashboard: Settings → API Keys → New key. Pick a name and scopes; copy the secret immediately (shown once).

REST (account keys):

curl -X POST https://api.customagents.io/v1/api_keys \
  -H "Authorization: Bearer ca_acct_YOUR_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI deploy bot",
    "scopes": ["read:agents", "read:contacts"],
    "expiresAt": "2027-01-01T00:00:00Z",
    "allowedIps": ["203.0.113.10"]
  }'
{
  "id": "key_abc123",
  "name": "CI deploy bot",
  "keyType": "account",
  "keyPrefix": "ca_acct_",
  "scopes": ["read:agents", "read:contacts"],
  "key": "ca_acct_…",          // shown ONCE — store it now
  "expiresAt": "2027-01-01T00:00:00Z",
  "createdAt": "2026-06-17T10:30:00Z"
}

The key field is present only in this create/rotate response. It is never returned again by GET /v1/api_keys.

For per-agent key creation, see Per-Agent API & MCP — those keys are minted under /v1/agents/:agentId/api-keys, not here.

List

curl https://api.customagents.io/v1/api_keys \
  -H "Authorization: Bearer ca_acct_YOUR_ADMIN_KEY"

The response includes metadata only — never the secret or its hash.

Rotate

Rotation mints a successor secret while keeping the old one valid for a short grace window (rotationGraceUntil), so you can swap keys with zero downtime. After the grace window passes, the old key is rejected.

curl -X POST https://api.customagents.io/v1/api_keys/key_abc123/rotate \
  -H "Authorization: Bearer ca_acct_YOUR_ADMIN_KEY"
{
  "id": "key_abc123",
  "key": "ca_acct_…",                      // the NEW secret — shown once
  "rotationGraceUntil": "2026-06-18T10:30:00Z"
}

Revoke

Revocation is instant — the key stops working on the next request, no grace window.

# Deactivate (keep the row for audit)
curl -X POST https://api.customagents.io/v1/api_keys/key_abc123/deactivate \
  -H "Authorization: Bearer ca_acct_YOUR_ADMIN_KEY"

# Or delete entirely
curl -X DELETE https://api.customagents.io/v1/api_keys/key_abc123 \
  -H "Authorization: Bearer ca_acct_YOUR_ADMIN_KEY"

IP allowlist & expiry

  • allowedIps — an optional array of IPs. When set, requests from any other IP are rejected. Leave empty to allow any source.
  • expiresAt — an optional timestamp. After it passes, the key is rejected automatically.

Both are set at create time (and editable in the dashboard).

Spend caps

Keys holding a money-spending scope (messages:send, trigger:agents, or agent:trigger) can carry an optional credit ceiling (spendCapCredits). Scopes gate whether a key can spend; the spend cap bounds how much a leaked key can burn before it's cut off. Set spendCapCredits at create time, or leave it null for uncapped (subject to your plan's rate limits).

See also