Every /v1 endpoint, with parameters, pagination, errors and limits.
Base URL: https://api.agentboxd.com. All /v1 routes need Authorization: Bearer <api_key>. The dashboard calls the same routes with its session instead; see Dashboard and account API.
{ username?, display_name?, client_id?, metadata?, domain? }. Random readable username if omitted. Idempotent on client_id (200 with the existing inbox). domain is one of your verified domains (default agents.agentboxd.com). Send { ttl_seconds } (60–86 400) instead for a temporary inbox; username, domain and client_id are not allowed with it.
GET /v1/inboxes
List inboxes. Filter on your own fields with ?metadata.<key>=<value> (exact string match). Temporary inboxes are left out unless you pass ?include_temporary=true; ?temporary=true lists only them.
GET /v1/inboxes/:id
One inbox, with its metadata. Every inbox has temporary (boolean) and expires_at (ISO time, or null).
PATCH /v1/inboxes/:id
{ display_name?, metadata?, ttl_seconds? }. Metadata is merged; a null value deletes the key. See Metadata. ttl_seconds extends a temporary inbox, counted from now, up to 24 hours after it was created.
DELETE /v1/inboxes/:id
Soft delete. The address is never reused. On a temporary inbox this wipes its messages, attachments and threads right away.
{ to, cc?, bcc?, subject, text?, html?, attachments?: [{ filename, content_type, content_base64 }], labels? }. Returns 202 with status: "queued".
POST /v1/inboxes/:id/messages/:messageId/reply
{ text?, html?, attachments?, reply_all? }. Sets In-Reply-To and References, adds Re:, stays in the thread.
GET /v1/inboxes/:id/messages
Filters: labels=a,b (must have all), is_read, direction.
GET /v1/inboxes/:id/messages/wait
Long-poll. timeout (1–60 s, default 30), since (ISO, default now), from, subject, direction (default inbound). Returns { data: Message | null }: the oldest message after since, or null on timeout.
GET /v1/inboxes/:id/verification
Long-poll for a login code or magic link. timeout, since, from. Returns the newest matching message’s code, link and confidence, or null.
GET /v1/messages/:id
One message.
PATCH /v1/messages/:id
{ add_labels?, remove_labels?, is_read? }.
GET /v1/messages/:id/raw
Original MIME (message/rfc822, download only).
POST /v1/messages/:id/draft-reply
{ instructions? } (up to 2,000 characters). Returns { text, citations: [{ knowledge_id, title }], model }. Never sends. Needs AI processing full; see Reply drafts.
GET /v1/attachments/:id
Streams the file (download only, nosniff, sandbox CSP).
{ url, events?, inbox_ids?, enabled?, secret? }. The secret is returned only here.
GET /v1/webhooks · GET /v1/webhooks/:id
Returns secret_hint, never the secret.
PATCH /v1/webhooks/:id · DELETE /v1/webhooks/:id
Change events, scope or enabled; delete.
POST /v1/webhooks/:id/test
Sends a webhook.test event.
GET /v1/webhooks/:id/deliveries
Recent attempts: status, attempts, last status code and error, next retry.
GET /v1/webhooks/events
Public. Every event type with a description and an example payload. Webhooks accept payload: "envelope" for ids and headers only, no bodies or AI content.
?inbox_id=&direction=&kind=. Needs the lists:manage permission.
POST /v1/lists
{ inbox_id?, direction: "receive" | "send" | "reply", kind: "allow" | "block", pattern }. A pattern is an address (a@b.com) or a domain (@b.com, including subdomains). Without inbox_id it applies workspace-wide. Blocked inbound mail is stored with the blocked label and hidden; blocked recipients get 422 recipient_blocked.
?from=&to=&tz=&inbox_id=&bucket=hour|day, at most 90 days. Sent, received, delivered, bounced, complained and blocked per bucket, plus deliverability rates, inbound spam counts and resource totals. Needs metrics:read.
{ domain, receiving? } → 201 with status: "pending" and the DNS records to publish. How many depends on the plan (Free 1, Builder 10, Team 50; 403 plan_limit_domains past it). See Custom domains.
GET /v1/domains
List your domains.
GET /v1/domains/:id
One domain with its records and each record’s last check result.
POST /v1/domains/:id/verify
Checks the DNS records now and returns the domain. At most once every 10 seconds per domain.
PATCH /v1/domains/:id
{ receiving } turns receiving on or off.
DELETE /v1/domains/:id
409 domain_in_use while inboxes use the domain. ?force=true deletes those inboxes too.
Every message has text, html, extracted_text, headers, labels, status (queued | sent | delivered | bounced | complained | failed | received), attachment metadata, contact_id (inbound: the sender’s contact; outbound: the first recipient’s; or null), and ai.verification ({ code, link, confidence, jev_probability } or null). Inbound messages also get the JEV fields described below once they have been categorised.
Labels you may see on inbound mail: spf-fail and dmarc-fail (authentication failed), the ai:* labels below, and bounce-report on delivery-failure notices.
Shortly after an inbound message is stored, JEV reads the sender, subject, the first 3,000 characters of the text, our own SPF/DKIM/DMARC results and the attachment names, and answers six questions in one call. The results are merged into message.ai and a message.enriched webhook fires. message.received is never delayed by this. Bounce reports are not categorised.
One of support, sales, billing, verification, notification, newsletter, personal, other, with its confidence and the probability of each option.
risk.injection
Probability that the message tries to instruct an AI (ignore its rules, reveal data, act for the sender).
risk.phishing
Probability of credential harvesting, a scam or impersonation. The authentication results are part of the input.
needs_human
Probability that a person should handle it: legal matters, complaints, threats, sensitive personal data.
urgency
level is the most likely of low, normal, high, critical; score is the expected value from 0 to 3.
auto_reply
Probability of an out-of-office, auto-responder, ticket acknowledgement or bulk mail.
enrichment_error
Set instead of the fields above when enrichment gave up after its retries. The message is otherwise unaffected.
All probabilities are 0 to 1. A label is only added when a score passes its threshold, so a message without ai:phishing can still have a non-zero risk.phishing. Read the scores if you need a different cut-off.
Label
Added when
ai:<category>, e.g. ai:billing
Category confidence is 0.6 or more.
ai:uncertain
Category confidence is under 0.6 (instead of a category label).
ai:injection-risk
risk.injection is 0.8 or more.
ai:phishing
risk.phishing is 0.8 or more.
ai:needs-human
needs_human is 0.7 or more.
ai:auto-reply
auto_reply is 0.8 or more.
ai:urgent
urgency.level is high or critical.
Labels are only ever added; your own labels are never removed or reordered. Filter on them with GET /v1/inboxes/:id/messages?labels=ai:billing,ai:urgent (a message must have every label you list).
The action needs a workspace owner, for example changing AI processing.
403 ai_disabled
A reply draft was requested but AI processing is not full.
403 org_suspended
Sending is paused for the workspace (bounce rate over 5% or complaint rate over 0.1% in the last 7 days). Clean the recipient list, then write to abuse@agentboxd.com.
403 temporary_inbox_receive_only
Send or reply from a temporary inbox. They only receive.
403 domain_not_verified
Send from an inbox whose domain failed its DNS checks. Fix the records and check the domain again.
409 domain_taken · 409 domain_in_use
The domain is verified by another workspace; or you tried to delete a domain that inboxes still use.
413
Message or attachment over the size limit.
422 recipient_suppressed
The address hard-bounced or complained before.
402 plan_limit_emails
The plan’s monthly emails are used up. Receiving keeps working. See Plans and limits for every plan error.
403 plan_limit_inboxes
The plan’s inboxes are all in use.
429 daily_send_limit_exceeded
This inbox’s daily send limit reached. Resets at 00:00 UTC.
429 org_daily_limit_exceeded
The workspace’s daily send cap (from its plan) reached. Resets at 00:00 UTC.
429 rate_limited
Too many requests for this API key in a minute.
429 temporary_inbox_limit
25 temporary inboxes are already active in the workspace. Creating them is also limited to 60 an hour.
503 llm_unavailable
The drafting model didn’t answer. Nothing was sent; try again later.
The dashboard at agentboxd.com uses these routes with a session cookie (cm_session) instead of an API key. Requests that change data (POST, PATCH, DELETE) must send X-Requested-With: agentboxd-web, or they get 403 csrf. You don’t need them to build an agent; they are listed so nothing the dashboard does is hidden.
Method & path
Notes
POST /platform/auth/magic-link
{ email }. Always 204. Emails a sign-in link from login@agentboxd.com, valid 15 minutes, usable once. Rate-limited per address and per IP.
POST /platform/auth/verify
{ token } from the link. 200 { user, org } and sets the session cookie; 400 invalid_token if the link expired or was used.
GET /platform/auth/github
Starts GitHub sign-in (?next=/app). Not enabled at launch.
POST /platform/auth/logout
204. Ends the session.
GET /platform/me
{ user, org }, or 401 unauthorized. The org includes status, daily_send_limit, ai_processing and retention_days.
GET /platform/api-keys
API keys: name, prefix, created, last used, revoked. Never the secret.
POST /platform/api-keys
{ name }. 201 { api_key, key }: the full key is shown this once.
DELETE /platform/api-keys/:id
Revokes the key. 204.
GET /platform/usage
?days=30 (up to 90). Sends and receives today, the limits, the inbox count and a daily series.
PATCH /platform/org
{ name?, ai_processing?, retention_days? }. Owners only (403 forbidden otherwise). ai_processing is off, categorize or full. retention_days is 1 to 3650 days, capped at your plan’s retention, or null for the plan default. See Retention.