AgentiSend API docs
Agent-first email API. Every error names the fix. 167 operations, 71 error codes and 48 event types, generated from the API itself at build. Version 0.1.0.
Guides
Start here
- Quickstart — Verify a domain, create a key, send the first email — in Node, Python or curl.
- Agents — MCP and the CLI — Connect Claude Code, Cursor, Windsurf, VS Code or any remote-MCP client to the same API your code uses.
- Support — How to open a request in the console, what Scale gets, and where replies land.
Control plane
- Budgets and the kill switch — Per-key spend ceilings, what happens when one is reached, and the one request that stops everything.
- Approvals — When a send waits for a person, how the queue behaves, and what an agent sees while it waits.
- Deliverability — The published thresholds, what happens at each one, and how to read the reputation panel.
- Trust and enforcement — The enforcement ladder, what each rung does, how to appeal, and what we will not do.
Integrating
- Webhooks — Signing, verification, the retry schedule, replay, and what happens to a dead letter.
- Domains and DNS — The records to publish, what verification checks, the US / EU region choice, and the customer return-path host.
- Templates — Storing a template, rendering it, versioning, and why the variables are declared.
- Migrating from Resend — What is identical, what the error names map onto, and the two things that behave differently on purpose.
- MCP server — Every tool with the scopes it needs, the rate-limit table, the JSON-RPC error codes, and what an agent cannot do.
Every endpoint
Each row carries the anchor the product links to. An error whose fix names an endpoint sends the reader to that anchor here.
accounts
- GET /accountThis account: lifecycle status, the reason it is in that state, and when it was created. Sandboxed accounts may only send to their own verified domains.
- DELETE /accountClose this account: revoke every key, pause every budget, suspend sending. Data stays readable and exportable until the retention window ends.
- GET /account/exportEverything this account owns, as a zip: emails.csv (inside your retention window), suppressions.csv and domains.json. No job, no wait.
- PATCH /account/onboardingConsole: the onboarding answers — your name, the company (which becomes the account name), the website, what you will send and roughly how much. Send completed: true to finish; only your name is required.
agent-actions
- GET /agent-actionsEvery held agent action, newest first — nothing waits invisibly. Filter by state to read the inbox or the audit trail.
- POST /agent-actions/{id}/approveExecute the held send through the normal accept path. A person signed in to the console decides; the action row records who and when.
- POST /agent-actions/{id}/rejectRefuse the held send. A person signed in to the console decides; the reason is preserved with the row.
api-keys
- GET /api-keysList API keys with 30-day request counts. Permission, domain scope and the key’s own budget ceiling are always returned (PRD F3); never the token.
- POST /api-keysCreate an API key. The token is shown exactly once.
- PATCH /api-keys/{id}Rename a key or change its domain scope and scopes. The token is unchanged — use POST /api-keys/:id/rotate for that.
- DELETE /api-keys/{id}Revoke an API key immediately.
- POST /api-keys/{id}/rotateRotate an API key. The new token is returned once; the token it replaces keeps working for grace_hours (0, 1 or 24).
automations
- GET /automationsEvery automation on the account.
- POST /automationsDraft an automation: a trigger plus ordered steps (email, webhook, A/B split).
- GET /automations/{id}One automation.
- PATCH /automations/{id}Edit a draft or disabled automation. Enabling snapshots a version — a running automation never executes a half-edited definition.
- POST /automations/{id}/disableStop firing. Versions stay for audit.
- POST /automations/{id}/enableSnapshot trigger+steps as an immutable version and start firing.
- GET /automations/{id}/runsEvery time this automation fired, newest first — the answer to "did it run?", which the engine used to throw away.
- GET /automations/{id}/runs/{run_id}One run with every step: what it did, what it returned, and the sentence for the ones that failed.
- GET /automations/{id}/versionsEvery immutable version, oldest first.
- GET /eventsEvery custom event this account has declared, with its typed schema and when it was last seen.
- POST /eventsIngest a custom event. Matching enabled automations fire synchronously (202 once enqueued).
- GET /events/{id}One event definition.
- PATCH /events/{id}Declare or re-declare an event’s fields. Adding a field to a strict event starts refusing payloads that omit it — that is the point.
- DELETE /events/{id}Forget an event definition. Ingest keeps accepting the event — it just stops being checked.
billing
- POST /billing/checkoutConsole: start a Stripe Checkout for a tier and billing term (monthly, or yearly once it is on sale).
- GET /billing/planThe plan in force, the 14-day Pro trial, the plans and terms on offer, and whether the signed-in person must choose a plan now.
- POST /billing/portalConsole: open the Stripe customer portal (plan changes, card, invoices).
- GET /billing/subscriptionConsole: the signed-in account’s plan and subscription state.
- POST /billing/trialConsole: start the 14-day Pro trial — no card, up to 3,000 emails, once per account. Owners and admins.
- POST /webhooks/stripeStripe → AgentiSend: signature-verified, deduplicated by event id.
broadcasts
- GET /broadcastsEvery broadcast, newest first — archived included.
- POST /broadcastsDraft a broadcast to a segment. Content comes from a template version or an inline body.
- GET /broadcasts/{id}One broadcast.
- PATCH /broadcasts/{id}Edit a DRAFT — or rename ANY broadcast. Sent content is immutable.
- POST /broadcasts/{id}/archiveArchive — reversible, any time, sent or draft.
- POST /broadcasts/{id}/cancelCancel a scheduled broadcast before it sends. It returns to draft, editable and re-schedulable.
- GET /broadcasts/{id}/messagesPer-member delivery state for this broadcast.
- POST /broadcasts/{id}/sendSend now. Content is SNAPSHOTTED: the segment is evaluated and every member rendered with their properties; the result is immutable.
contacts
- GET /contactsList contacts, newest first.
- POST /contactsCreate or update a contact by email. Properties are typed from their value and auto-created — nothing needs pre-declaring.
- GET /contacts/{id}One contact with its properties.
- PATCH /contacts/{id}Edit one contact by id: address, status, properties. Properties merge; an explicit null removes one.
- DELETE /contacts/{id}Delete one contact and its properties.
- GET /contacts/{id}/topicsWhat this contact has said about every topic. A topic they never answered reports the topic default, and says so.
- PATCH /contacts/{id}/topicsRecord this contact’s answer for one or more topics. Answers are absolute — nothing is inferred from what is left out.
dedicated-ips
- GET /dedicated-ipsThis account dedicated IPs with live warmup percentages.
- POST /dedicated-ipsProvision a dedicated IP for this account. It starts warming on the published curve.
- GET /dedicated-ips/rampThe PUBLISHED warmup curve — exactly how volume moves and when.
- GET /dedicated-ips/route-decision/{messageId}Which route this message would take RIGHT NOW — reproducible, the same math the send path uses.
- DELETE /dedicated-ips/{id}Release a dedicated IP. Traffic returns to shared automatically.
deliverability
- GET /deliverability/dmarcAggregate authentication reports for your domains. Aligned and failing volume per day, and every address sending as you, flagged when it is not one of ours.
- GET /deliverability/domainsEvery sending domain with its standing over the window, worst first — the failing domain is the first row, not one you have to find.
- GET /deliverability/domains/{id}Per-domain reputation: live rates over the rolling window, daily snapshots, the thresholds those rates are judged against, the bounce breakdown by class with its remediation, and the receiving domains rejecting the most.
- POST /deliverability/domains/{id}/snapshotPersist today rates as a snapshot row. Idempotent per domain per day.
domains
- GET /domainsList domains. Items omit records; GET /domains/:id has the DNS sheet.
- POST /domainsRegister a sending domain. Region is optional and defaults to us (Oregon); the stored value is returned.
- GET /domains/{id}One domain, with the DNS to publish and what each record last resolved to.
- PATCH /domains/{id}Turn click or open tracking on, or change the tracking subdomain. Name, region and return-path cannot change.
- DELETE /domains/{id}Remove a domain and its records. Messages already sent keep their history.
- GET /domains/{id}/connectDomain Connect: detect the DNS provider from the live nameservers and hand back the exact records to add.
- POST /domains/{id}/verifyRe-check every record and update the domain’s status.
emails
- GET /emailsList messages. Every console filter is a query param here (PRD F3).
- POST /emailsSend an email. Returns the message id; delivery happens on the queue.
- POST /emails/batchSend up to 500 emails. Each item succeeds or fails on its own — read data[i].status.
- POST /emails/bulk-cancelCancel many emails at once: by explicit ids or every scheduled/queued email before a time.
- GET /emails/export.csvThe message log as CSV — yours, take it with you.
- POST /emails/lintDeliverability lint without sending: a 0-100 placement score plus every finding with its fix.
- POST /emails/preflightRun EVERY send gate without sending: domain, sandbox, trust, suppression, budget, loop, content, verifier. The report matches what POST /emails would do, byte for byte — one code path.
- GET /emails/receivingMail received at this account’s domains, newest first. Filter by recipient, sender or date range.
- GET /emails/receiving/{id}One received email: headers, text and HTML bodies as data, and the attachments it carried.
- GET /emails/receiving/{id}/attachmentsWhat this received email carried. Ids are stable positions in the message.
- GET /emails/receiving/{id}/attachments/{aid}The bytes of one received attachment, as an inert download — always octet-stream, never the sender’s declared type.
- GET /emails/receiving/{id}/rawThe stored RFC 5322 source, byte for byte. Served as an attachment with sniffing off — it is someone else’s content.
- GET /emails/{id}Fetch one message with its current status and last event.
- PATCH /emails/{id}Move a scheduled email to a new time — the same act as POST /emails/:id/reschedule, under the verb a Resend integration already uses.
- GET /emails/{id}/attachmentsWhat this email carried. Ids are stable positions; bytes_available says whether the payload is still retrievable.
- GET /emails/{id}/attachments/{aid}The bytes of one attachment, exactly as they were sent. Served as an inert download.
- POST /emails/{id}/cancelCancel a scheduled or queued email. Already-sent mail is history, not cancellable.
- GET /emails/{id}/emlDownload this email as .eml.
- GET /emails/{id}/eventsEvery event recorded for one message, oldest first, with the provider detail.
- GET /emails/{id}/explainWhat happened to this email, the evidence, and the exact calls that fix it — machine-readable remediation.
- GET /emails/{id}/mimeThe exact RFC 5322 source of this email — what Resend never shows you.
- POST /emails/{id}/rescheduleMove an email to a new time. Works from scheduled AND canceled — a cancel is never terminal.
limits
- GET /limits/keysThe fleet: every key with its budget, ceiling, consumption, identity and current loop state.
- GET /limits/keys/{id}Budget, ceiling and current consumption for one key.
- PATCH /limits/keys/{id}Set or clear this key’s period budget and per-minute ceiling. An API key may lower its own; raising one is a person’s decision, made in the console.
- POST /limits/keys/{id}/killStop this key from sending, immediately. Takes effect on the next request.
- POST /limits/keys/{id}/resumeUndo the kill switch. A person signed in to the console does this; budgets and ceilings are unchanged.
- POST /limits/kill-allPause every key on this account at once. Takes effect on the next request.
- POST /limits/resume-allUndo the global kill switch. A person signed in to the console does this; budgets and ceilings are unchanged.
logs
meta
- GET /healthLiveness probe.
- GET /openapi.jsonOpenAPI 3.1 document generated from the same schemas that validate.
- GET /statusPublic per-component health. Every component reports what it actually checked; one that cannot be checked says so instead of claiming green.
- GET /status/historyDaily uptime per component over the last 90 days, aggregated from the scheduled probe's own samples. A day nobody measured reports no samples rather than 100%.
notifications
- GET /notificationsConditions this account should know about. Open rows are live; resolved ones ended. One row per condition, counted, never one per re-fire.
- GET /notifications/preferencesWhich channels each notification type uses. A type with no stored row is on for both.
- PATCH /notifications/preferencesTurn a notification type on or off per channel. The gate is the type — there is no severity to mute instead.
- POST /notifications/readMark notifications read. With no ids, every open row on the account is marked — the bell’s "Mark all read".
- POST /notifications/{id}/readMark one notification read. Reading it never resolves it — the condition ends the row, not the reader.
segments
- GET /segmentsEvery segment on the account.
- POST /segmentsCreate a dynamic segment. Rules AND together over properties and status.
- GET /segments/{id}One segment.
- PATCH /segments/{id}Rename or change the rules. Membership re-evaluates immediately.
- DELETE /segments/{id}Delete a segment. Contacts are untouched.
- GET /segments/{id}/membersThe segment evaluated RIGHT NOW — live member ids, never a snapshot.
support
- GET /supportSupport requests on this account, newest activity first.
- POST /supportOpen a support request.
- GET /support/attachments/{id}/downloadDownload an attachment on a request this account owns.
- GET /support/configTopics, hours and attachment limits for opening a support request.
- POST /support/uploadsStage files to attach to a support message. JSON base64; 5 files, 10 MB each, 25 MB total.
- GET /support/{ref}One support request and its public thread.
- POST /support/{ref}/csatRate a resolved support request, 1 to 5.
- POST /support/{ref}/messagesReply on a support request.
- POST /support/{ref}/reopenReopen a resolved request within 14 days.
- POST /support/{ref}/resolveMark a support request resolved.
suppressions
- GET /suppressionsEvery suppressed address or domain for this account (global platform rows included).
- POST /suppressionsSuppress an address or a whole domain (send "@example.com"). Idempotent — an existing row is returned.
- POST /suppressions/batch/addSuppress up to 500 addresses in one call. Each item succeeds or fails on its own — read data[i].status.
- POST /suppressions/batch/removeLift up to 500 suppressions by address. An address that was not suppressed reads not_found, not an error.
- DELETE /suppressions/{id}Remove one suppression row. A hard bounce you have fixed can be cleared with an API key; an unsubscribe or a spam complaint is a person’s to lift, in the console.
team
- POST /invite/{token}/acceptAccept an invitation. Requires a session signed in as the invited address; a person who has never signed in before is provisioned into the inviting account, not a new one.
- GET /team/invitesInvitations that have not been accepted, cancelled or expired.
- POST /team/invitesInvite an address at a role. The mail carries a link that only works while signed in as that address.
- DELETE /team/invites/{id}Cancel a pending invitation. The link in the email stops working immediately.
- GET /team/meYour membership on this account: your role, and the seats the plan allows.
- GET /team/membersEveryone on this account and the role each one holds. Owner, admin, viewer — three roles, and seats are never billed per seat.
- POST /team/membersAdd someone to this account. Membership begins when they accept, so this returns the pending invitation — the same call as POST /team/invites.
- PATCH /team/members/{id}Change a member’s role. The last owner cannot be demoted.
- DELETE /team/members/{id}Remove a member. Their sends and keys stay; only their access ends. The last owner cannot be removed.
templates
- GET /templatesList this account templates, newest first.
- POST /templatesCreate a template draft.
- GET /templates/{id}One template.
- PATCH /templates/{id}Edit the draft. Published templates are immutable.
- GET /templates/{id}/diffStructured diff between two versions, or between a version and the draft.
- POST /templates/{id}/duplicateCopy a template into a new editable draft. The copy carries no version history.
- POST /templates/{id}/publishSnapshot the draft as an immutable published version.
- POST /templates/{id}/renderRender a version with variables (defaults to the current published one; pass "draft" for the working copy). Unknown variables fail with the names listed.
- POST /templates/{id}/rollbackRollback creates a NEW draft from an older version and puts the template back into draft - history is never rewritten, and what is live does not move until that draft is published.
- GET /templates/{id}/versionsEvery immutable version, oldest first.
- GET /templates/{id}/versions/{n}One version.
topics
- GET /topicsEvery subscription topic on the account, newest first.
- POST /topicsCreate a subscription topic. default_subscribed=false makes it opt-in: silence means no.
- GET /topics/{id}One topic.
- PATCH /topics/{id}Rename a topic or change its default. Changing the default never rewrites an answer a contact already gave.
- DELETE /topics/{id}Delete a topic and every recorded answer about it.
trust
- POST /trust/appealFile an appeal against the current standing. The reason is recorded verbatim; a human answers by sla_deadline_at.
- GET /trust/remediationThe remediation checklist ticks for this account. Per account, not per browser — the work one person does is done for everyone on it.
- POST /trust/remediationTick or untick one remediation item. Returns the whole checklist.
- GET /trust/standingThis account’s standing, machine-readable. An agent can query it and back off before enforcement does it for them.
- GET /trust/thresholdsThe published enforcement thresholds and the ladder they drive. Same constants the ladder acts on, so nothing that draws a line has to hard-code one.
usage
webhooks
- GET /webhooksList endpoints. Secrets are never returned after creation.
- POST /webhooksRegister an endpoint. The signing secret is returned exactly once.
- GET /webhooks/{id}Fetch one endpoint.
- PATCH /webhooks/{id}Update url, subscribed events, or the disabled flag.
- DELETE /webhooks/{id}Delete an endpoint and its queued deliveries.
- GET /webhooks/{id}/dead-lettersDeliveries this endpoint exhausted every retry on, with the payload to inspect.
- GET /webhooks/{id}/deliveriesEvery delivery to this endpoint, with each attempt and what the receiver answered (A9).
- POST /webhooks/{id}/replayRe-send past events to this endpoint, by time range and/or a single event id (PRD B2).
- POST /webhooks/{id}/rotate-secretMint a new signing secret. Returned once; the old one stops verifying immediately.
Bounce classes
The four classes every bounce lands in, and the one action that changes each number. The Deliverability screen links a bounce row straight to these ids.
- hard
- The receiving server says the address does not exist. It never will. These addresses are already suppressed. Remove them at the source so the same list stops producing them — GET /suppressions lists every address we are holding back.
- soft
- A temporary refusal — mailbox full, greylisting, or the receiver was briefly unavailable. Nothing to clean. We retry these; a soft bounce only becomes your problem if the same address keeps soft-bouncing for days, at which point it is treated as hard.
- block
- The receiver refused the message on reputation or content, not on the address. This is the class that ends in a blocklist. Check the blocklists panel below, then cut volume on this domain until the rate falls. Recipients who never asked for the mail are the usual cause.
- policy
- The address is real and the receiver would take mail from you, but not this message — size, attachment type, or our own suppression list. Shrink the message or drop the attachment and send again. If the reason is suppression, the address is on a list you can inspect with GET /suppressions.
Catalogues
- Error catalogue — 71 codes, each with a fix.
- Event catalogue — 48 types.
- openapi.json — the document these pages are generated from.