iris API v1
build on iris - discover contacts, draft personalized messages with AI, and orchestrate multi-channel outreach from your own apps.
getting started
create an API key
go to settings → API keys in your iris dashboard and click create key. give it a name and pick scopes, then copy the key immediately - it is shown only once.
store your key securely. keys begin with is_live_ and cannot be retrieved after creation. if you lose it, revoke and create a new one.
base URL
https://api.iris-ai.devall timestamps in API responses use ISO 8601 format in UTC (e.g. 2026-04-29T12:00:00.000Z).
make your first request
curl https://api.iris-ai.dev/campaigns \
-H "Authorization: Bearer is_live_YOUR_KEY"quick example
create a campaign, discover contacts, check connected channels, and send a message - the minimum end-to-end flow.
step 1 - create a campaign
curl -X POST https://api.iris-ai.dev/campaigns \
-H "Authorization: Bearer is_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "April DJ outreach - Brooklyn",
"objective_type": "venue_dj",
"location": "Brooklyn, NY",
"radius_miles": 10,
"venue_types": ["bar", "club", "lounge"],
"keywords": ["live music", "dj nights"],
"max_results": 50,
"outreach_channels": ["email", "instagram", "twitter"],
"followup_days": [3, 7, 14],
"status": "draft"
}'
# response: { "success": true, "data": { "id": "<campaign_id>", ... } }step 2 - discover contacts (synchronous)
curl -X POST https://api.iris-ai.dev/discovery/search \
-H "Authorization: Bearer is_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"keywords": ["bar", "lounge"],
"location": "Brooklyn, NY",
"radius_miles": 10,
"max_results": 50,
"campaign_id": "<campaign_id>",
"enrich_emails": true
}'
# returns synchronously - { "data": { "contacts": [...], "total_results": 50, ... } }step 3 - check connected channels
curl https://api.iris-ai.dev/channels/availability \
-H "Authorization: Bearer is_live_YOUR_KEY"
# email is always available; twitter and instagram require an OAuth-connected accountstep 4 - send a message (or approve a draft)
# direct send to an existing conversation:
curl -X POST https://api.iris-ai.dev/channels/send \
-H "Authorization: Bearer is_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "email",
"conversation_id": "<conversation_id>",
"recipient_id": "<contact_id>",
"subject": "quick question about djing at {{venue}}",
"content": "hi {{first_name}}, ...",
"variables": { "first_name": "Alex", "venue": "The Bell House" }
}'
# OR - list AI-drafted messages waiting for approval and approve one:
curl "https://api.iris-ai.dev/messages/pending?campaign_id=<campaign_id>" \
-H "Authorization: Bearer is_live_YOUR_KEY"
curl -X POST https://api.iris-ai.dev/messages/<id>/approve \
-H "Authorization: Bearer is_live_YOUR_KEY"note: /channels/send requires a conversation_id and recipient_id that already exist. campaign activation creates these automatically. for AI-drafted message review, use the message-approval flow instead.
authentication & rate limits
authentication methods
the iris API accepts three authentication headers. send any one - the API tries them in order:
| method | header | use case |
|---|---|---|
| API key | Authorization: Bearer is_live_… | accounted users (this section) |
| MPP | Authorization: Payment … | AI agents using Stripe credits - see agent payments |
| x402 | X-PAYMENT: … | AI agents paying USDC per call - see agent payments |
bearer token format
Authorization: Bearer is_live_YOUR_KEYinvalid or revoked keys return 401 with error code APIKEY_001. missing scope returns 403 with APIKEY_004.
rate limits
| scope | limit | window | purpose |
|---|---|---|---|
| global (per key) | 120 req | 1 minute | overall API request limit |
| burst (per key) | 20 req | 1 second | burst request protection |
| campaign mutations | 30 req | 1 minute | POST/PATCH/DELETE on /campaigns |
| contact mutations | 60 req | 1 minute | POST/PATCH/DELETE on /contacts |
| AI generation | 20 req | 1 minute | POST /ai/generate, /ai/analyze |
| discovery searches | 5 req | 1 hour | POST /discovery/search |
when rate-limited you receive a 429 response with a Retry-After header (seconds):
{
"success": false,
"error": {
"code": "RATE_001",
"message": "too many requests. please try again in 12 seconds.",
"requestId": "req_abc123"
},
"retryAfter": 12,
"limit": 60,
"remaining": 0
}response headers
X-Request-ID is set on every response. The X-RateLimit-* headers are set on 429 rate-limited responses, alongside Retry-After.
| header | description |
|---|---|
| X-RateLimit-Limit | requests allowed per window |
| X-RateLimit-Remaining | requests remaining in window |
| X-RateLimit-Reset | Unix timestamp in milliseconds when window resets |
| X-Request-ID | correlation ID for support and Sentry - included on every response |
scopes
API keys can be created with specific scopes to limit their permissions. defense-in-depth - even if a key is compromised, it can only perform operations within its granted scopes. use * for full access on trusted integrations.
available scopes
| scope | description |
|---|---|
| read | view all resources (campaigns, contacts, conversations, messages, analytics, billing) |
| write | create, update, and (soft-)delete business data (campaigns, contacts, conversations, notifications). DELETE routes gate on this scope — iris uses soft-deletes everywhere, so there is no separate `delete` scope. |
| generate | costly external API calls (Claude AI generation, Apify discovery searches) |
| send | send and approve outbound messages (email, instagram DM, twitter DM) |
| admin | manage API keys and webhooks |
missing scope error
requests without the required scope receive a 403 response:
{
"success": false,
"error": {
"code": "APIKEY_004",
"message": "API key missing required scope: write",
"requestId": "req_abc123"
}
}recommended scopes by use case
| use case | scopes |
|---|---|
| AI agents / MCP | read, write, generate, send |
| read-only dashboards | read |
| inbound automation | read, write, send |
| full access (admin tools) | * (wildcard) |
audit logging
destructive and sensitive operations performed via the API are automatically written to api_audit_log with the API key ID, user ID, action, resource ID, and a metadata JSON blob.
audited operations
| action | description |
|---|---|
| create_campaign | creates a new campaign envelope |
| update_campaign | modifies campaign metadata or status |
| delete_campaign | soft-deletes a campaign |
| create_contact / bulk_create_contacts | creates one or many contacts |
| update_contact | modifies contact data |
| delete_contact / bulk_delete_contacts | soft-deletes contacts |
| create_key | issues a new API key (raw value shown once) |
| revoke_key | permanently revokes an API key |
| create_webhook / update_webhook / delete_webhook | manages webhook endpoint registration |
| test_webhook | delivers a synthetic event to a webhook URL |
| send_channel_message | sends a message via /channels/send |
| approve_message / reject_message / retry_message | human-in-the-loop message decisions |
| discover_contacts / discover_campaign_contacts | runs a discovery search |
| generate_ai_content / generate_campaign_content / analyze_ai | AI generation and analysis runs |
idempotency
mutating endpoints (POST /campaigns, POST /agent/credits) accept an Idempotency-Key request header. on a timed-out network retry, the second call with the same key returns the cached response without re-executing — critical for agent-pay endpoints where re-execution would double-charge USDC or double-charge a Stripe credit pack.
idempotency is opt-in: requests without the header execute fresh. cached responses ship for 24 hours and only 2xx outcomes are cached (errors re-execute on retry).
curl -X POST https://api.iris-ai.dev/campaigns \
-H "Authorization: Bearer is_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-unique-key-12345" \
-d '{"name": "Q2 outreach", "objective_type": "product_promotion"}'key requirements
- 1–255 characters, allowlist
[A-Za-z0-9._-] - scoped per-(api-key, endpoint) — same key value across different endpoints is safe
- invalid keys (too long, bad chars) → 400 SERVER_BAD_REQUEST (don't retry — fix the value)
- concurrent retry while first call still in-flight → 409 SERVER_CONFLICT with Retry-After: 1
replay detection
replayed responses carry the Idempotent-Replayed: true response header so the client can distinguish a fresh execution from a cached replay.
MCP server
the model context protocol (MCP) lets AI agents interact with external tools. the iris MCP server gives agents like Claude Code and Cursor the ability to manage campaigns, contacts, conversations, messages, AI drafting, discovery, channels, webhooks, and more - 66 tools across 13 categories, all from your editor.
install
npx -y -p @jclvsh/iris iris-mcpthe package ships two binaries - iris-mcp (MCP server, used below) and iris (CLI).
Claude Code
{
"mcpServers": {
"iris": {
"command": "npx",
"args": [
"-y",
"-p",
"@jclvsh/iris",
"iris-mcp"
],
"env": {
"IRIS_API_KEY": "is_live_YOUR_API_KEY"
}
}
}
}add to .claude/settings.json
Cursor
{
"mcpServers": {
"iris": {
"command": "npx",
"args": [
"-y",
"-p",
"@jclvsh/iris",
"iris-mcp"
],
"env": {
"IRIS_API_KEY": "is_live_YOUR_API_KEY"
}
}
}
}add to .cursor/mcp.json
available tools
| category | tools | count |
|---|---|---|
| campaigns | list, create, get, update, delete, set status, list contacts, target profiles, usage, analyze | 11 |
| contacts | list, create, get, update, delete, bulk import, bulk delete, conversations | 8 |
| conversations | list, get, get messages, mark read, engagement | 5 |
| messages | list pending, approve, reject, retry, feedback, bulk | 6 |
| AI | generate, generate campaign, analyze, usage | 4 |
| discovery | search, search campaign, get job, list sources | 4 |
| analytics | overview, campaigns, performance, response time, contact types | 5 |
| channels | list accounts, availability, send | 3 |
| notifications | list, update, delete, read all | 4 |
| webhooks | list, create, get, update, delete, test, deliveries, rotate secret | 9 |
| keys | me, list, create, revoke | 4 |
| billing | list plans | 1 |
| agent credits | balance, purchase pack | 2 |
recommended scopes for AI agents: read, write, read, write, read, send, generate, generate, send.
for machine-readable API references, see api.iris-ai.dev/llms.txt (plain text for LLMs) and api.iris-ai.dev/openapi.json (OpenAPI 3.1 spec).
CLI
the iris CLI lets you manage campaigns, contacts, messages, and more from your terminal. same API and same shape as the MCP server - just a different interface.
install
npm install -g @jclvsh/irisor run directly with npx: npx @jclvsh/iris campaigns list
commands
| group | commands | count |
|---|---|---|
| campaigns | list, create, get, update, delete, contacts, usage, analyze | 8 |
| contacts | list, create, get, update, delete, bulk-import, bulk-delete | 7 |
| conversations | list, get, messages, mark-read, engagement | 5 |
| messages | pending, approve, reject, retry, feedback, bulk-approve, bulk-reject | 7 |
| ai | generate, generate-campaign, analyze, usage | 4 |
| discovery | search, search-campaign, jobs, sources, get-job | 5 |
| analytics | overview, campaigns, performance, response-time, contact-types, summary | 6 |
| channels | accounts, availability, send, list | 4 |
| notifications | list, update, delete, read-all, mark-read | 5 |
| webhooks | list, create, get, update, delete, test, deliveries, rotate-secret | 8 |
| keys | me, list, create, revoke, rotate, rename | 6 |
| billing | plans, current | 2 |
| credits | balance, purchase, history | 3 |
70 total commands.
examples
# list your campaigns
iris campaigns list
# create a campaign
iris campaigns create --name "Q2 outreach" --objective product_promotion
# discover contacts and add to a campaign
iris discovery search --keywords "coffee shop" --location "Brooklyn, NY" --campaign <id>
# review pending message drafts
iris messages pending --campaign <id>
# approve a draft
iris messages approve <message-id>
# check your API key
iris keys me
# table output
iris --format table campaigns listrun iris --help or iris campaigns --help for the full command reference.
API reference
all endpoints are relative to https://api.iris-ai.dev. send Authorization: Bearer is_live_… on every request. responses follow the shape { success, data, meta? } on success and { success: false, error: { code, message, requestId } } on error.
authentication
verify your API key and view its metadata.
get information about the currently authenticated API key
API keys
programmatically manage API keys. all routes in this group (except `/keys/me`) require an API key with the `admin` scope. `/keys/me` returns metadata for the currently-authenticated key and works without any scope.
list all API keys for your account
create a new API key. the raw key is only returned once.
rename an API key. only the display label is updated; the secret, scopes, and expiry are untouched.
revoke an API key. this action is irreversible.
rotate an API key — revokes the old key and creates a new one with the same name and scopes. the new raw key is returned exactly once. update stored credentials immediately — the old key stops authenticating as soon as this returns. cannot rotate the key being used for the request itself (would lock the caller out mid-rotation).
campaigns
create and manage outreach campaigns.
list all campaigns
create a new campaign
get a specific campaign with full details
update a campaign
soft-delete a campaign (sets status to 'deleted')
list contacts in a campaign
get campaign usage statistics
trigger AI product analysis for a campaign
list target profiles for a campaign
create a target profile for a campaign
delete a target profile
contacts
manage contacts and import leads.
list all contacts
create a single contact. idempotent on email — if a contact with the same email already exists for this user, returns 201 with the existing record (merging any new fields you supply) instead of 409.
get a specific contact
update a contact
soft-delete a contact (sets deleted_at timestamp)
bulk import contacts (up to 500 per request)
bulk soft-delete contacts by ID
get contact usage for the current billing period
get conversations for a specific contact
conversations
manage conversations across all channels.
list all conversations
get a conversation with details
get messages for a conversation
mark a conversation as read
get engagement metrics across conversations
messages
manage message approval workflow and sending.
list messages pending approval
approve a pending message for sending
reject a pending message
retry a failed message
submit feedback to revise a message with AI
bulk approve or reject messages
AI
AI-powered content generation and analysis.
generate AI content (email subject + body)
generate campaign-level outreach content (email sequence, templates) from an inline brief. content-first: pass the strategy directly, no campaign id required
analyze an inbound message (email or DM) for intent, sentiment, key points, and a suggested reply
get AI generation counts for the current billing period, broken down by generation_type. iris does not enforce a separate AI quota - generations are bounded implicitly by the campaign envelope, so no limit field is exposed.
discovery
search for and discover new contacts.
run a synchronous contact discovery search across configured sources (Google Maps, social platforms via Apify)
run discovery search using campaign target profiles
get the status of a discovery job
list available discovery data sources
analytics
campaign performance metrics and reporting.
get overall analytics overview across all campaigns
get analytics for a specific campaign
get performance metrics over time
get average response time metrics
get contact type distribution
billing
view billing plans and subscription information.
get available billing plans and their features
channels
manage connected accounts and multi-channel messaging.
list connected social accounts
check which channels are available for messaging
send a message through a specific channel into an existing conversation
notifications
manage user notifications.
list notifications
update a notification (e.g. mark as read)
delete a notification
mark all notifications as read
webhooks
manage webhook endpoints for receiving real-time event notifications. all routes in this group require an API key with the `admin` scope (round-6 audit closed a privilege-escalation gap where a non-admin key could create or rotate webhooks).
list all webhook endpoints
create a new webhook endpoint
get a specific webhook endpoint
update a webhook endpoint
delete a webhook endpoint
send a test event to a webhook endpoint
list recent webhook deliveries
rotate the signing secret for a webhook. the new secret is returned once and the old secret is invalidated immediately. update HMAC verification on the receiver side before the next delivery, otherwise signature checks will fail.
agent credits
credit-based payments for AI agents using x402 (USDC on Base) or MPP (Stripe). 1 credit = 1 campaign (30 contacts, 240 messages, AI drafting bundled in). no subscription required.
check the agent's current credit balance
purchase 5 credits with x402 or MPP payment
webhook delivery
register a webhook via POST /webhooks with an HTTPS URL and an event filter. iris signs every delivery with HMAC-SHA256 using the secret returned at creation.
event types
| event | fired when |
|---|---|
| campaign.created | a new campaign was created |
| campaign.updated | a campaign was updated |
| campaign.deleted | a campaign was soft-deleted |
| contact.created | a new contact was created or imported |
| contact.updated | a contact was updated |
| contact.deleted | a contact was soft-deleted |
| conversation.created | a new conversation was started |
| conversation.reply_received | a reply was received in a conversation |
| message.sent | a message was sent |
| message.delivered | a message was confirmed delivered |
| message.opened | a message was opened by the recipient |
| message.bounced | a message bounced |
| message.approved | a pending message was approved |
| message.rejected | a pending message was rejected |
| discovery.completed | a discovery search completed successfully |
| discovery.failed | a discovery search failed |
payload format
every delivery wraps the event-specific data in an envelope:
{
"id": "evt_a1b2c3d4e5f6...",
"event": "campaign.created",
"timestamp": "2026-04-29T12:00:00.000Z",
"created_at": "2026-04-29T12:00:00.000Z",
"data": { "id": "uuid", "name": "Q2 outreach", "status": "draft", "objective_type": "product_promotion" }
}headers
| header | description |
|---|---|
| Content-Type | application/json |
| X-Iris-Signature | HMAC-SHA256 hex digest of {timestamp}.{body} |
| X-Iris-Timestamp | Unix epoch seconds at delivery time — included in the signed payload to prevent replay. (The event-payload timestamp field is ISO-8601 — different format, different purpose.) |
| X-Iris-Delivery | unique delivery ID for idempotency. (Use this as the dedup key on your side — read the event type from the body's event field.) |
| User-Agent | Iris-Webhooks/1.0 |
signature verification
verify the X-Iris-Signature header using your webhook secret to ensure the request is from iris:
import crypto from "node:crypto";
// in your webhook handler
export async function POST(request: Request) {
const signature = request.headers.get("x-iris-signature");
const timestamp = request.headers.get("x-iris-timestamp");
const body = await request.text();
// reconstruct the signed payload
const signed = `${timestamp}.${body}`;
const expected = crypto
.createHmac("sha256", process.env.IRIS_WEBHOOK_SECRET)
.update(signed)
.digest("hex");
// timing-safe compare
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(body);
// dispatch on event.event - "campaign.created", "message.delivered",
// "conversation.reply_received", etc. (full list at /docs/api#webhooks)
return new Response("ok");
}retry behavior
- up to 3 retries with exponential backoff (via QStash)
- 30-second timeout per delivery attempt
- return 2xx to acknowledge - any other status code triggers retry
- use the
X-Iris-Deliveryheader as an idempotency key to avoid processing the same event twice
agent payments
autonomous AI agents can use the iris API without an account or subscription via two payment protocols: x402 (crypto, USDC on Base) and MPP (Stripe via the Machine Payments Protocol).
iris does not offer an agent free tier. every agent campaign requires payment - the free user plan is generous enough that agents bypassing payment would cannibalize subscription revenue.
how it works
- •each campaign creation costs $2.25 via x402 (USDC) or 1 credit via MPP ($2.40 effective from a 5-credit pack)
- •discovery, AI drafting, and message sends run automatically inside the campaign workflow as internal jobs (30 contacts, 240 messages allotment) — agents never call
/discovery/search,/ai/generate, or/channels/senddirectly - •payment proofs identify the agent - settlement only occurs on the campaign-creation request
- •agent-created campaigns are owned by an internal system user and can only be operated on by the agent identity
protocols
| x402 (crypto) | MPP (Stripe) | |
|---|---|---|
| header | X-PAYMENT: <base64> | Authorization: Payment <base64url> |
| payment method | USDC on Base | Stripe charge |
| settlement | on-chain transfer (per-campaign) | credit pack purchase, then 1 credit/campaign |
| 402 challenge | PAYMENT-REQUIRED header + JSON body | WWW-Authenticate: Payment header + JSON body |
| identity | wallet address | payer credential source |
supported endpoints
| endpoint | charge | notes |
|---|---|---|
POST /campaigns | $2.25 x402 / 1 credit MPP | creates the campaign envelope |
POST /agent/credits | $12.00 MPP (pack) | buys 5 credits via Stripe - MPP only |
these are the only two endpoints that accept agent auth (x402 / MPP). discovery, AI drafting, and message sends are not separately callable — they run automatically inside the campaign workflow as internal QStash jobs. all other endpoints (reads, listings, analytics, webhooks, keys, plus /discovery/search, /ai/generate, /channels/send) require an API key — no agent auth path. issue a key from /settings/api.
example flow
1. request with no payment proof
curl -X POST https://api.iris-ai.dev/campaigns \
-H "Content-Type: application/json" \
-d '{
"name": "Q2 outreach",
"objective_type": "product_promotion",
"outreach_channels": ["email"]
}'2. 402 response (includes both challenges)
the body contains both x402 and mpp challenge objects, plus a credits alternative for MPP agents.
HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: <base64-encoded x402 v2 envelope>
WWW-Authenticate: Payment realm="www.iris-ai.dev", challenge_id="...", price_cents=240, ...
Access-Control-Expose-Headers: PAYMENT-REQUIRED, PAYMENT-RESPONSE, X-PAYMENT-RESPONSE, WWW-Authenticate
{
"error": {
"code": "PAYMENT_001",
"message": "payment required: $2.25 USDC (x402) or $2.40 via Stripe (MPP)"
},
"x402": {
"version": 2,
"scheme": "exact",
"network": "eip155:8453",
"asset": "USDC",
"amount": "2.25",
"receiver": "0x...",
"resource": "POST /campaigns"
},
"mpp": {
"version": 1,
"method": "stripe",
"intent": "charge",
"currency": "usd",
"amount_cents": 240,
"resource": "POST /campaigns"
},
"credits": {
"purchase_endpoint": "POST /agent/credits",
"packs": [{ "id": "pack_5", "credits": 5, "price_usd": "12.00" }]
}
}3a. pay with x402 (per-campaign USDC)
curl -X POST https://api.iris-ai.dev/campaigns \
-H "Content-Type: application/json" \
-H "X-PAYMENT: <base64-encoded EIP-3009 signed payload>" \
-d '{
"name": "Q2 outreach",
"objective_type": "product_promotion",
"outreach_channels": ["email"]
}'3b. pay with MPP (deduct from credit pack)
curl -X POST https://api.iris-ai.dev/campaigns \
-H "Content-Type: application/json" \
-H "Authorization: Payment <base64url-encoded mppx credential>" \
-d '{
"name": "Q2 outreach",
"objective_type": "product_promotion",
"outreach_channels": ["email"]
}'SDKs: the @x402/client npm package handles x402 signing, and mppx handles MPP. both follow the same retry-on-402 pattern: catch the 402, sign the challenge, replay the request.
credit packs
MPP (Stripe) agents purchase credit packs to prepay for campaigns in bulk. instead of paying per-campaign individually, buy 5 credits for $12.00 in a single Stripe charge. credits are deducted automatically when creating campaigns - no payment credential needed for each request after purchase.
credit packs are MPP-only. x402 (crypto) agents already have low transaction fees on Base and pay per-campaign in USDC instead - no pack needed.
pricing
| pack | credits | price | per campaign |
|---|---|---|---|
| pack_5 | 5 | $12.00 | $2.40 |
how it works
- check balance -
GET /agent/creditsreturns your current credit balance and the available pack. - purchase -
POST /agent/creditswith an MPP credential for $12.00 adds 5 credits. - auto-deduction - when you create a campaign with credits available, 1 credit is deducted instead of charging Stripe again.
- out of credits - if credits run out, the next campaign returns 402 with purchase instructions. buy another pack to continue.
- refunds - if campaign creation fails after credit deduction, the credit is refunded automatically.
- no expiration - credits never expire.
purchase example
send an MPP credential for $12.00 (1200 cents) to the purchase endpoint:
curl -X POST https://api.iris-ai.dev/agent/credits \
-H "Content-Type: application/json" \
-H "Authorization: Payment <base64url-credential-for-1200-cents>" \
-d '{ "pack": "pack_5" }'
# response:
# {
# "success": true,
# "data": {
# "balance": 5,
# "credits_added": 5,
# "pack": "pack_5"
# }
# }check balance
curl https://api.iris-ai.dev/agent/credits \
-H "Authorization: Payment <base64url-credential>"
# response:
# {
# "success": true,
# "data": {
# "balance": 3,
# "identity_id": "uuid"
# }
# }error handling
error response shape
every error response includes a stable code, a human-readable message, and a requestId for support correlation:
{
"success": false,
"error": {
"code": "CAMP_001",
"message": "campaign not found",
"requestId": "req_a1b2c3d4"
}
}error codes
authentication
| code | HTTP | meaning |
|---|---|---|
| AUTH_001 | 401 | unauthorized - invalid or missing API key |
| AUTH_002 | 403 | forbidden - insufficient permissions |
| AUTH_003 | 401 | session expired |
API keys
| code | HTTP | meaning |
|---|---|---|
| APIKEY_001 | 401 | invalid or missing API key |
| APIKEY_002 | 401 | API key expired |
| APIKEY_003 | 401 | API key revoked |
| APIKEY_004 | 403 | scope not granted for this operation |
| APIKEY_005 | 429 | API key creation limit reached |
| APIKEY_006 | 404 | API key not found |
campaigns
| code | HTTP | meaning |
|---|---|---|
| CAMP_001 | 404 | campaign not found |
| CAMP_002 | 500 | campaign creation failed |
| CAMP_003 | 500 | campaign update failed |
| CAMP_005 | 409 | campaign already active |
| CAMP_007 | 400 | campaign has no contacts |
| CAMP_008 | 400 | invalid campaign status transition |
| CAMP_009 | 429 | campaign limit reached for billing period |
contacts
| code | HTTP | meaning |
|---|---|---|
| CONTACT_001 | 404 | contact not found |
| CONTACT_003 | 400 | invalid email address |
| CONTACT_007 | 500 | bulk import failed |
| CONTACT_009 | 429 | contact limit reached for billing period |
conversations
| code | HTTP | meaning |
|---|---|---|
| CONVERSATION_001 | 404 | conversation not found |
| CONVERSATION_002 | 500 | conversation update failed |
| CONVERSATION_003 | 500 | message send failed |
messages
| code | HTTP | meaning |
|---|---|---|
| MESSAGE_001 | 500 | message send failed |
| MESSAGE_004 | 409 | message already approved |
| MESSAGE_005 | 404 | message not found |
| MESSAGE_015 | 429 | message limit reached for billing period |
AI
| code | HTTP | meaning |
|---|---|---|
| AI_001 | 503 | AI service unavailable |
| AI_002 | 500 | AI generation failed |
| AI_003 | 500 | AI analysis failed |
| AI_004 | 429 | AI rate limit exceeded |
| AI_005 | 400 | AI content filtered |
discovery
| code | HTTP | meaning |
|---|---|---|
| DISCOVERY_001 | 500 | discovery job failed |
| DISCOVERY_002 | 404 | discovery job not found |
| DISCOVERY_006 | 429 | discovery rate limited |
| DISCOVERY_008 | 429 | discovery quota exceeded |
webhooks
| code | HTTP | meaning |
|---|---|---|
| HOOK_001 | 400 | invalid webhook signature |
| HOOK_003 | 400 | invalid webhook payload |
| WEBHOOK_001 | 404 | webhook not found |
agent payments
| code | HTTP | meaning |
|---|---|---|
| PAYMENT_001 | 402 | payment required — agent must send X-PAYMENT (x402) or Authorization: Payment (MPP) header |
| PAYMENT_002 | 402 | x402 payment verification failed |
| PAYMENT_003 | 402 | MPP credential verification failed |
| PAYMENT_004 | 500 | settlement failed after payment was charged — contact support |
| PAYMENT_005 | 402 | insufficient credit balance — purchase a credit pack via POST /agent/credits |
| PAYMENT_006 | 400 | invalid credit pack id |
| PAYMENT_007 | 500 | credit purchase failed — if payment was charged, contact support |
billing
| code | HTTP | meaning |
|---|---|---|
| BILLING_004 | 402 | payment failed (Stripe charge declined) |
| BILLING_010 | 429 | plan usage limit reached — upgrade to continue (alias of BILLING_USAGE_EXCEEDED) |
| BILLING_011 | 402 | active subscription required for this operation |
rate limiting
| code | HTTP | meaning |
|---|---|---|
| RATE_001 | 429 | too many requests |
| RATE_002 | 429 | API rate limit exceeded |
server
| code | HTTP | meaning |
|---|---|---|
| SERVER_001 | 500 | internal server error |
| SERVER_002 | 400 | request validation failed (also returned for invalid Idempotency-Key) |
| SERVER_003 | 404 | resource not found |
| SERVER_004 | 405 | method not allowed |
| SERVER_005 | 422 | invalid input (validation failed) |
| SERVER_006 | 500 | database error |
| SERVER_008 | 504 | request timeout |
| SERVER_009 | 503 | service temporarily unavailable |
| SERVER_016 | 409 | conflict — concurrent retry with same Idempotency-Key in flight |
every response includes an X-Request-ID header and a requestId field on errors - quote it when filing a support ticket.