Every endpoint
Discovery
GETWhere the key stands/api/v1/meLists
GETList the lists/api/v1/listsGETGet one list/api/v1/lists/{listId}POSTCreate a list/api/v1/listsContacts
GETList contacts/api/v1/contactsGETGet a contact/api/v1/contacts/{contactId}POSTAdd or update one contact/api/v1/lists/{listId}/contactsPOSTImport contacts in bulk/api/v1/lists/{listId}/contacts/bulkPATCHUpdate a contact/api/v1/contacts/{contactId}POSTUnsubscribe a contact/api/v1/contacts/{contactId}/unsubscribePOSTResubscribe a contact/api/v1/contacts/{contactId}/resubscribePOSTOpt a contact out of a topic/api/v1/contacts/{contactId}/topics/{topicId}/opt-outPOSTResubscribe a contact to a topic/api/v1/contacts/{contactId}/topics/{topicId}/resubscribeTags
GETList tags/api/v1/tagsPOSTCreate a tag/api/v1/tagsPOSTTag a contact/api/v1/contacts/{contactId}/tagsDELETEUntag a contact/api/v1/contacts/{contactId}/tags/{tagId}Templates
GETList templates/api/v1/templatesGETGet a template/api/v1/templates/{templateId}POSTCreate a template/api/v1/templatesPATCHUpdate a template/api/v1/templates/{templateId}DELETEDelete a template/api/v1/templates/{templateId}POSTTest a template/api/v1/templates/{templateId}/test-sendGETBrowse the gallery/api/v1/template-galleryGETGet a gallery design/api/v1/template-gallery/{galleryTemplateId}POSTRender without sending/api/v1/renderCampaigns
POSTCreate a campaign/api/v1/campaignsGETList campaigns/api/v1/campaignsGETGet a campaign/api/v1/campaigns/{campaignId}PATCHEdit a draft campaign/api/v1/campaigns/{campaignId}POSTSend or schedule/api/v1/campaigns/{campaignId}/sendPOSTTest a campaign/api/v1/campaigns/{campaignId}/test-sendGETRead campaign stats/api/v1/campaigns/{campaignId}/statsSequences
POSTCreate a sequence/api/v1/sequencesGETList sequences/api/v1/sequencesGETGet a sequence/api/v1/sequences/{sequenceId}PATCHRename, activate or pause/api/v1/sequences/{sequenceId}DELETEDelete a sequence/api/v1/sequences/{sequenceId}GETWho is walking it/api/v1/sequences/{sequenceId}/enrollmentsGETList versions/api/v1/sequences/{sequenceId}/versionsPOSTOpen a draft version/api/v1/sequences/{sequenceId}/versionsPATCHChange a draft's trigger/api/v1/sequences/{sequenceId}/versions/{versionId}POSTPublish a version/api/v1/sequences/{sequenceId}/versions/{versionId}/publishPOSTRoll back to a version/api/v1/sequences/{sequenceId}/versions/{versionId}/rollbackDELETEDiscard a draft/api/v1/sequences/{sequenceId}/versions/{versionId}POSTAdd a step/api/v1/sequences/{sequenceId}/versions/{versionId}/nodesGETList steps/api/v1/sequences/{sequenceId}/versions/{versionId}/nodesPATCHEdit a step/api/v1/sequences/{sequenceId}/versions/{versionId}/nodes/{nodeId}DELETERemove a step/api/v1/sequences/{sequenceId}/versions/{versionId}/nodes/{nodeId}POSTMove a step/api/v1/sequences/{sequenceId}/versions/{versionId}/nodes/{nodeId}/moveGETList wires/api/v1/sequences/{sequenceId}/versions/{versionId}/edgesPOSTWire two steps/api/v1/sequences/{sequenceId}/versions/{versionId}/edgesDELETEUnwire two steps/api/v1/sequences/{sequenceId}/versions/{versionId}/edges/{edgeId}Settings
GETRead every setting/api/v1/settingsPATCHUpdate sending preferences/api/v1/settings/sendingGETRead the address book/api/v1/settings/identitiesPOSTAdd a sender address/api/v1/settings/identitiesPATCHEdit or promote an address/api/v1/settings/identities/{identityId}DELETERemove an address/api/v1/settings/identities/{identityId}PATCHUpdate sign-up settings/api/v1/settings/optinPOSTTest the confirmation email/api/v1/settings/optin/test-sendSending domains
GETList domains and records/api/v1/domainsPOSTDeclare a domain/api/v1/domainsGETGet one domain/api/v1/domains/{domainId}POSTVerify a domain/api/v1/domains/{domainId}/verifyDELETERemove a domain/api/v1/domains/{domainId}Authentication
Every/api/v1 request carries an API key in the x-api-key header. A key is created from the app
and belongs to one organization: that organization is the entire scope of the key.
bash
curl -H "x-api-key: $AGENTMAIL_API_KEY" \
"https://www.agentsmail.io/api/v1/tags"The key acts as the person who created it
A key is not a second identity with its own powers. It carries the rights of the member who created it, in the organization it was created for. A key made by anadmin can write; a key
made by a member can read. Nothing is granted through the API that the same person could not do
in the interface.
A key stops working when its owner leaves the organization. No orphan access survives a
departure: the key answers
401 from that moment, exactly as if it had been revoked. Revoking the
key of someone who left is therefore a cleanup, not a security fix.| Question | Decided by | Answer when it fails |
|---|---|---|
| Is the caller still a member, with which role? | The key's owner and their role | 401 / 403 |
| Does the resource belong to the key's organization? | The key's organization | 404 |
Who can do what
| Operation | Minimum role in the organization |
|---|---|
Reading anything, and POST /api/v1/render | member |
| Creating, updating, deleting, tagging, sending | admin |
POST /api/v1/render is the exception among the write verbs because it writes nothing: it renders
and returns, with no side effect.
Response envelope
| Case | Body |
|---|---|
| Success | {"success": true, "data": …} |
| Paginated list | {"success": true, "data": [...], "pagination": {"total", "page", "limit", "totalPages"}} |
| Error | {"error": "…"} |
| Validation error | {"error": "…", "details": [ … Zod issues … ]} |
| Status | When |
|---|---|
200 | Read or mutation done |
201 | Resource created |
202 | Send accepted, happening in the background — nothing is finished yet |
400 | Invalid parameters or body — details lists the offending fields |
401 | Missing or invalid key, or a key whose owner left the organization |
403 | The caller is a legitimate member, but their role is too low |
404 | Unknown resource, or a resource of another organization |
409 | State conflict — a tag name already taken, a campaign already sent |
429 | Rate limit of the key exceeded |
500 | Unexpected server error |
A resource of another organization answers
404, never 403. A 403 would confirm that the
id exists. Same response for an unknown id and for an id you are not allowed to read — you cannot
tell them apart, and that is the point. The 403 is reserved for the other question: you are in
the right organization, your role is too low.Rate limit
The limit is carried by the key, not by the IP. A key over its limit answers429 on every
endpoint. Back off and retry: nothing was done.
Where to start
Start with Discovery: two calls turn a bare key into a context — the organization it opens, and thelistId every audience endpoint asks for. Then read the audience — a campaign
without a list to send it to goes nowhere — and compose with templates and rendering. The whole path
from an empty organization to a sent newsletter is walked end to end at the bottom of
Campaigns.