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 type | Prefix | Reaches | Mint from |
|---|---|---|---|
| Account | ca_acct_ | Your whole account — agents, contacts, messages, inboxes, webhooks, billing reads | Settings → API Keys |
| Per-agent | ca_agt_ | Exactly one agent and nothing else | The 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_keysis never grantable to a key). - No key can mutate billing (
write:billingis 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.
| Scope | Grants |
|---|---|
read:agents | List and read agents |
write:agents | Create, update, pause agents |
read:contacts | List and read contacts |
write:contacts | Create and update contacts |
read:messages | List conversations and read messages |
write:messages | Create drafts (does not send) |
messages:send | Send real email/SMS — money-spending |
read:inboxes | Read inbox configuration |
write:inboxes | Manage inboxes |
read:webhooks | List webhooks |
write:webhooks | Create and delete webhooks |
integrations:read | Read connected integrations |
integrations:manage | Connect and disconnect integrations |
read:domains | Read sending domains |
write:domains | Manage sending domains |
read:billing | Read subscription and usage |
read:account | Read account stats, logs, and feed |
read:activity | Read activity timelines |
trigger:agents | Fire an agent (chat/voice/missions) — money-spending |
read:api_keys | List 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.
| Scope | Grants |
|---|---|
agent:config:read | Read this agent's configuration |
agent:config:write | Update this agent's configuration |
agent:conversations:read | Read this agent's conversations |
agent:activity:read | Read this agent's activity |
agent:trigger | Trigger 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 modewrite:billing— billing mutations stay owner-onlywrite: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 anyread:<resource>requirement.*:agentssatisfies any<verb>:agentsrequirement.
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
API Reference
Full REST API documentation for CustomAgents. Create agents, send messages, manage contacts, and configure webhooks.
Connect your account to an IDE (MCP)
Use your CustomAgents account from Claude Code, Cursor, Claude Desktop, or VS Code over the Model Context Protocol. Endpoint, IDE config, tool catalog, and security.