API reference

Base URL https://api.avelto.dev. Every request needs an Authorization header with a bearer key, av_live_… or av_test_…. Bodies and responses are JSON with snake_case fields. Field tables on this page are generated from the same schemas the API validates with.

Authentication

Create keys in the dashboard. The prefix tells you the mode: av_live_ keys send real email, av_test_ keys run the pipeline without sending.

Scopes

A key carries a list of scopes, and a route it does not have one for answers 403 naming the scope it needed. Keys created before scopes existed hold the 7 standard ones, and a key created without naming any still does: that is what the dashboard calls Full access. Narrowing is the deliberate act, not the default.

ScopeCovers
emails:sendSend email
emails:readRead the email log
domains:manageAdd and remove domains
webhooks:manageManage webhook endpoints
suppressions:manageChange the suppression list
templates:manageCreate and edit templates
account:manageExport the account's data and erase recipients
account:settingsChange the account name and alert settings. Owner only.
keys:manageCreate and revoke API keys, never with more access than this one. Owner only.

Two scopes are different. account:settings and keys:manage let a key change the account’s name and alert settings and create and revoke other keys: what an AI agent setting the account up needs, and what the dashboard’s Key for an AI agent holds. Only the account owner can grant them, in the dashboard and by asking for them by name. They are never part of Full access or of a key created without a list, and a key created through the API keys routes never holds them. See Let an AI agent set up your account.

The split worth knowing is emails:send against emails:read. A key that can send and cannot read the log back is the one to put in an application server, and the dashboard offers it as a preset.

The mode is a boundary on reads as well as sends. A test key sees test emails and their events and deliveries; a live email is a 404, and ?mode=live does not change that. It also gets a 403 live_key_required on the calls that live sending depends on: removing a domain, creating, changing or deleting a template, creating, changing or deleting a webhook endpoint, reading or changing the suppression list, changing the account settings, exporting the account and erasing a recipient. It can still add and read domains, read templates and endpoints, and send with a template in test mode, and a test key with keys:manage creates test keys only. Idempotency keys are kept per mode. A live key reads both modes. See Test mode.

curl
curl https://api.avelto.dev/v1/emails \  -H "Authorization: Bearer av_live_..."

Pagination

Lists take limit (1 to 100, default 20) and cursor, and return { data, next_cursor }. Pass next_cursor back as cursor; it is null on the last page.

Rate limits

Between 10 and 50 requests a second per key depending on plan (see Plans and limits). Over that, the API returns 429 rate_limited with a Retry-After header. New accounts can send 100 emails a day for their first 7 days, each recipient across to, cc and bcc counting as one.

Emails

Send, fetch, list and cancel emails. The key's mode decides the mode of the emails it creates, and also what it can read: a test key sees test emails only, and a live email is a 404 to it.

POST/v1/emailsSend an email

Send an Idempotency-Key header to make retries safe. A replay returns the existing id with status 200. Keys are kept per mode, so a test send never replays a live one, and keys starting with batch: are reserved for batches.

Provide html, text or both. At most 50 recipients across to, cc and bcc.

Errors: 403 domain_not_verified, 403 sandbox_recipient_not_allowed, 403 account_paused, 422 recipient_suppressed (live sends only), 429 plan_limit, 429 rate_limited.

Request body

FieldTypeDescription
fromrequiredstringSender. a@b.com or Name <a@b.com>. The domain must be verified on your account, or be your sandbox domain.
torequiredstring | string[]One recipient or a list. At most 50 recipients across to, cc and bcc.
ccoptionalstring | string[]Carbon-copy recipients.
bccoptionalstring | string[]Blind-carbon-copy recipients.
reply_tooptionalstringAddress replies go to.
subjectoptionalstringSubject line. Line breaks are rejected.
htmloptionalstringHTML body. Provide html, text or both.
textoptionalstringPlain-text body. Provide html, text or both.
headersoptionalobject<string, string>Extra headers. Names reserved for the envelope, MIME structure or signing are rejected.
tagsoptionalstring[]Up to 10 tags of letters, numbers, _ - : . for filtering and reporting.
attachmentsoptionalobject[]Up to 10 files, 7 MB in total after decoding.
attachments[].filenamerequiredstringFile name shown to the recipient. No quotes or slashes.
attachments[].contentoptionalstringFile contents, base64-encoded.
attachments[].urloptionalstring (url)Endpoint to POST events to. Must be https in production.
attachments[].content_typeoptionalstringMIME type, for example application/pdf.
template_idoptionalstring (uuid)
template_slugoptionalstring
variablesoptionalobject<string, string | number | boolean | null>
scheduled_atoptionalstring (ISO 8601)Send at this time instead of now. Must be in the future and within 30 days.
unsubscribe_urloptionalstring (url)

Response 201

FieldTypeDescription
idstring (uuid)Unique id.
POST/v1/emails/batchSend up to 100 emails

Each message is validated, limited and queued exactly as a single send is. results[i] lines up with messages[i].

200 when every message was accepted, 422 when none was, 207 when it was mixed.

An Idempotency-Key makes the whole batch replayable: each message is stored with batch:<key>:<index>, a prefix a single send's key may not use. Each message counts separately against your plan and rate limit.

Request body

FieldTypeDescription
messagesrequiredobject[]
messages[].fromrequiredstringSender. a@b.com or Name <a@b.com>. The domain must be verified on your account, or be your sandbox domain.
messages[].torequiredstring | string[]One recipient or a list. At most 50 recipients across to, cc and bcc.
messages[].ccoptionalstring | string[]Carbon-copy recipients.
messages[].bccoptionalstring | string[]Blind-carbon-copy recipients.
messages[].reply_tooptionalstringAddress replies go to.
messages[].subjectoptionalstringSubject line. Line breaks are rejected.
messages[].htmloptionalstringHTML body. Provide html, text or both.
messages[].textoptionalstringPlain-text body. Provide html, text or both.
messages[].headersoptionalobject<string, string>Extra headers. Names reserved for the envelope, MIME structure or signing are rejected.
messages[].tagsoptionalstring[]Up to 10 tags of letters, numbers, _ - : . for filtering and reporting.
messages[].attachmentsoptionalobject[]Up to 10 files, 7 MB in total after decoding.
messages[].attachments[].filenamerequiredstringFile name shown to the recipient. No quotes or slashes.
messages[].attachments[].contentoptionalstringFile contents, base64-encoded.
messages[].attachments[].urloptionalstring (url)Endpoint to POST events to. Must be https in production.
messages[].attachments[].content_typeoptionalstringMIME type, for example application/pdf.
messages[].template_idoptionalstring (uuid)
messages[].template_slugoptionalstring
messages[].variablesoptionalobject<string, string | number | boolean | null>
messages[].scheduled_atoptionalstring (ISO 8601)Send at this time instead of now. Must be in the future and within 30 days.
messages[].unsubscribe_urloptionalstring (url)

Response 200

FieldTypeDescription
resultsdiscriminatedunion[]
acceptedinteger
failedinteger
GET/v1/emailsList emails

Newest first. Pass next_cursor back as cursor for the next page.

Query

FieldTypeDescription
limitoptionalintegerPage size, 1 to 100. Defaults to 20.
cursoroptionalstringCursor from a previous page.
statusoptional"queued" | "scheduled" | "sent" | "delivered" | "bounced" | "complained" | "failed" | "cancelled"Only emails in this status.
tagoptionalstringOnly emails carrying this tag.
modeoptional"live" | "test"Filter by mode. A live key defaults to live and may ask for test; a test key always gets test, whatever this says.
qoptionalstring

Response 200

FieldTypeDescription
dataobject[]Items on this page.
data[].idstring (uuid)Unique id.
data[].mode"live" | "test"live or test, taken from the API key that created the email.
data[].fromstringSender. a@b.com or Name <a@b.com>. The domain must be verified on your account, or be your sandbox domain.
data[].tostring[]One recipient or a list. At most 50 recipients across to, cc and bcc.
data[].ccstring[]Carbon-copy recipients.
data[].bccstring[]Blind-carbon-copy recipients.
data[].reply_tostring | nullAddress replies go to.
data[].subjectstringSubject line. Line breaks are rejected.
data[].tagsstring[]Up to 10 tags of letters, numbers, _ - : . for filtering and reporting.
data[].status"queued" | "scheduled" | "sent" | "delivered" | "bounced" | "complained" | "failed" | "cancelled"Current state.
data[].scheduled_atstring (ISO 8601) | nullSend at this time instead of now. Must be in the future and within 30 days.
data[].created_atstring (ISO 8601)When the record was created.
data[].updated_atstring (ISO 8601)When the record last changed.
data[].matchobject | null
data[].match.field"recipient" | "subject" | "message_id" | "email_id"
data[].match.valuestringRecord value.
next_cursorstring | nullPass as cursor to fetch the next page. Null on the last page.
GET/v1/emails/:idGet an email

Includes the bodies, headers, attachment metadata and every event so far.

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
mode"live" | "test"live or test, taken from the API key that created the email.
fromstringSender. a@b.com or Name <a@b.com>. The domain must be verified on your account, or be your sandbox domain.
tostring[]One recipient or a list. At most 50 recipients across to, cc and bcc.
ccstring[]Carbon-copy recipients.
bccstring[]Blind-carbon-copy recipients.
reply_tostring | nullAddress replies go to.
subjectstringSubject line. Line breaks are rejected.
tagsstring[]Up to 10 tags of letters, numbers, _ - : . for filtering and reporting.
status"queued" | "scheduled" | "sent" | "delivered" | "bounced" | "complained" | "failed" | "cancelled"Current state.
scheduled_atstring (ISO 8601) | nullSend at this time instead of now. Must be in the future and within 30 days.
created_atstring (ISO 8601)When the record was created.
updated_atstring (ISO 8601)When the record last changed.
matchobject | null
match.field"recipient" | "subject" | "message_id" | "email_id"
match.valuestringRecord value.
htmlstring | nullHTML body. Provide html, text or both.
textstring | nullPlain-text body. Provide html, text or both.
headersobject<string, string>Extra headers. Names reserved for the envelope, MIME structure or signing are rejected.
unsubscribe_urlstring | null
idempotency_keystring | null
source"api" | "smtp"
attachmentsobject[]Up to 10 files, 7 MB in total after decoding.
attachments[].filenamestringFile name shown to the recipient. No quotes or slashes.
attachments[].content_typestringMIME type, for example application/pdf.
attachments[].sizeintegerDecoded size in bytes.
domain_idstring (uuid) | nullThe verified domain the email was sent from, or null for the sandbox domain.
ses_message_idstring | nullProvider message id once the email has been handed off.
errorstring | nullFailure reason, when status is failed.
eventsobject[]Every state change for this email, oldest first.
events[].idstring (uuid)Unique id.
events[].type"email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled"Event type.
events[].payloadobject<string, any>Event-specific details.
events[].occurred_atstring (ISO 8601)When the event happened.
POST/v1/emails/:id/cancelCancel a scheduled email

409 not_scheduled unless the email's status is scheduled.

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
status"cancelled"Current state.
POST/v1/emails/validateValidate an address before sending

Nothing is sent or stored, so call it from a signup form on every submit. Works with a test key; needs the emails:send scope.

checks.suppressed is only looked up for a live key. A test key always gets false, which is also what a test send would see: test sends are not checked against the suppression list.

undeliverable means a send would fail or be refused; risky means it may deliver but deserves a look, and suggestion carries a corrected address when the domain looks mistyped.

It cannot tell you whether the mailbox exists. The large providers accept every address and bounce later, so nobody can without sending.

Request body

FieldTypeDescription
emailrequiredstring

Response 200

FieldTypeDescription
emailstring
normalizedstring | null
result"deliverable" | "risky" | "undeliverable"
reason"invalid_syntax" | "suppressed" | "no_mail_server" | "disposable" | "possible_typo" | "role_address" | nullWhy the address was suppressed.
checksobject
checks.syntaxboolean
checks.mail_serverboolean | null
checks.disposableboolean
checks.roleboolean
checks.free_providerboolean
checks.suppressedboolean
suggestionstring | null

Domains

Sending domains and the DNS records that verify them.

POST/v1/domainsAdd a domain

Returns the ownership TXT record to publish first; the DKIM records follow once it is found. A domain whose ownership is not proven within 7 days is removed.

409 conflict if the domain is already added. 409 domain_unavailable if it is part of a domain on another account, is reserved, or is already set up elsewhere. 403 plan_limit when your plan's domain limit is reached.

Request body

FieldTypeDescription
namerequiredstringDomain name, for example mail.example.com.

Response 201

FieldTypeDescription
idstring (uuid)Unique id.
namestringDomain name, for example mail.example.com.
regionstringRegion the domain identity lives in.
status"pending" | "verified" | "failed"Current state.
ownership_verifiedbooleanWhether the ownership TXT record has been found. A domain still unproven 7 days after it was added is removed.
dns_recordsobject[]Records to publish before the domain can verify. The ownership TXT record comes first; the DKIM records appear once it is found.
dns_records[].type"CNAME" | "TXT" | "MX"Event type.
dns_records[].namestringDomain name, for example mail.example.com.
dns_records[].valuestringRecord value.
dns_records[].purpose"ownership" | "dkim" | "spf" | "dmarc"What the record is for.
health"unknown" | "healthy" | "action_needed"
health_changed_atstring (ISO 8601) | null
dns_checksobject[] | null
dns_checks[].type"CNAME" | "TXT" | "MX"Event type.
dns_checks[].namestringDomain name, for example mail.example.com.
dns_checks[].valuestringRecord value.
dns_checks[].purpose"ownership" | "dkim" | "spf" | "dmarc"What the record is for.
dns_checks[].idstringUnique id.
dns_checks[].state"found" | "missing" | "mismatch"
dns_checks[].foundstring | null
dns_checked_atstring (ISO 8601) | null
created_atstring (ISO 8601)When the record was created.
GET/v1/domainsList domains

Response 200

FieldTypeDescription
dataobject[]Items on this page.
data[].idstring (uuid)Unique id.
data[].namestringDomain name, for example mail.example.com.
data[].regionstringRegion the domain identity lives in.
data[].status"pending" | "verified" | "failed"Current state.
data[].ownership_verifiedbooleanWhether the ownership TXT record has been found. A domain still unproven 7 days after it was added is removed.
data[].dns_recordsobject[]Records to publish before the domain can verify. The ownership TXT record comes first; the DKIM records appear once it is found.
data[].dns_records[].type"CNAME" | "TXT" | "MX"Event type.
data[].dns_records[].namestringDomain name, for example mail.example.com.
data[].dns_records[].valuestringRecord value.
data[].dns_records[].purpose"ownership" | "dkim" | "spf" | "dmarc"What the record is for.
data[].health"unknown" | "healthy" | "action_needed"
data[].health_changed_atstring (ISO 8601) | null
data[].dns_checksobject[] | null
data[].dns_checks[].type"CNAME" | "TXT" | "MX"Event type.
data[].dns_checks[].namestringDomain name, for example mail.example.com.
data[].dns_checks[].valuestringRecord value.
data[].dns_checks[].purpose"ownership" | "dkim" | "spf" | "dmarc"What the record is for.
data[].dns_checks[].idstringUnique id.
data[].dns_checks[].state"found" | "missing" | "mismatch"
data[].dns_checks[].foundstring | null
data[].dns_checked_atstring (ISO 8601) | null
data[].created_atstring (ISO 8601)When the record was created.
GET/v1/domains/:idGet a domain

Re-checks verification on every call while the domain is pending: the ownership record first, then DKIM, and updates ownership_verified, dns_records and status.

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
namestringDomain name, for example mail.example.com.
regionstringRegion the domain identity lives in.
status"pending" | "verified" | "failed"Current state.
ownership_verifiedbooleanWhether the ownership TXT record has been found. A domain still unproven 7 days after it was added is removed.
dns_recordsobject[]Records to publish before the domain can verify. The ownership TXT record comes first; the DKIM records appear once it is found.
dns_records[].type"CNAME" | "TXT" | "MX"Event type.
dns_records[].namestringDomain name, for example mail.example.com.
dns_records[].valuestringRecord value.
dns_records[].purpose"ownership" | "dkim" | "spf" | "dmarc"What the record is for.
health"unknown" | "healthy" | "action_needed"
health_changed_atstring (ISO 8601) | null
dns_checksobject[] | null
dns_checks[].type"CNAME" | "TXT" | "MX"Event type.
dns_checks[].namestringDomain name, for example mail.example.com.
dns_checks[].valuestringRecord value.
dns_checks[].purpose"ownership" | "dkim" | "spf" | "dmarc"What the record is for.
dns_checks[].idstringUnique id.
dns_checks[].state"found" | "missing" | "mismatch"
dns_checks[].foundstring | null
dns_checked_atstring (ISO 8601) | null
created_atstring (ISO 8601)When the record was created.
DELETE/v1/domains/:idRemove a domain

Needs a live key.

Response 204

Empty body.

Templates

A stored subject and body with {{variables}} in them. Send one by passing template_id or template_slug instead of a body. Needs the templates:manage scope. A live send renders the template as it is now, so creating, changing, deleting and restoring need a live key; a test key can read templates and send them in test mode.

POST/v1/templatesCreate a template

The slug is derived from the name unless you set one. 409 conflict if a slug you chose is taken.

At most 100 templates per account; past that, 403 plan_limit. Each keeps its last 50 versions.

Request body

FieldTypeDescription
namerequiredstringDomain name, for example mail.example.com.
subjectrequiredstringSubject line. Line breaks are rejected.
htmloptionalstringHTML body. Provide html, text or both.
textoptionalstringPlain-text body. Provide html, text or both.
slugoptionalstring

Response 201

FieldTypeDescription
idstring (uuid)Unique id.
namestringDomain name, for example mail.example.com.
slugstring
subjectstringSubject line. Line breaks are rejected.
htmlstring | nullHTML body. Provide html, text or both.
textstring | nullPlain-text body. Provide html, text or both.
variablesstring[]
versioninteger
created_atstring (ISO 8601)When the record was created.
updated_atstring (ISO 8601)When the record last changed.
GET/v1/templatesList templates

Response 200

FieldTypeDescription
dataobject[]Items on this page.
data[].idstring (uuid)Unique id.
data[].namestringDomain name, for example mail.example.com.
data[].slugstring
data[].subjectstringSubject line. Line breaks are rejected.
data[].htmlstring | nullHTML body. Provide html, text or both.
data[].textstring | nullPlain-text body. Provide html, text or both.
data[].variablesstring[]
data[].versioninteger
data[].created_atstring (ISO 8601)When the record was created.
data[].updated_atstring (ISO 8601)When the record last changed.
GET/v1/templates/:idGet a template

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
namestringDomain name, for example mail.example.com.
slugstring
subjectstringSubject line. Line breaks are rejected.
htmlstring | nullHTML body. Provide html, text or both.
textstring | nullPlain-text body. Provide html, text or both.
variablesstring[]
versioninteger
created_atstring (ISO 8601)When the record was created.
updated_atstring (ISO 8601)When the record last changed.
PATCH/v1/templates/:idUpdate a template

Only the fields you pass change. null clears a body part. Every save keeps a version.

Request body

FieldTypeDescription
nameoptionalstringDomain name, for example mail.example.com.
slugoptionalstring
subjectoptionalstringSubject line. Line breaks are rejected.
htmloptionalstring | nullHTML body. Provide html, text or both.
textoptionalstring | nullPlain-text body. Provide html, text or both.

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
namestringDomain name, for example mail.example.com.
slugstring
subjectstringSubject line. Line breaks are rejected.
htmlstring | nullHTML body. Provide html, text or both.
textstring | nullPlain-text body. Provide html, text or both.
variablesstring[]
versioninteger
created_atstring (ISO 8601)When the record was created.
updated_atstring (ISO 8601)When the record last changed.
DELETE/v1/templates/:idDelete a template

Response 204

Empty body.

GET/v1/templates/:id/versionsList versions

Newest first. Every save is kept.

Response 200

FieldTypeDescription
dataobject[]Items on this page.
data[].versioninteger
data[].namestringDomain name, for example mail.example.com.
data[].subjectstringSubject line. Line breaks are rejected.
data[].htmlstring | nullHTML body. Provide html, text or both.
data[].textstring | nullPlain-text body. Provide html, text or both.
data[].created_bystring | null
data[].created_atstring (ISO 8601)When the record was created.
POST/v1/templates/:id/restoreRestore a version

Writes the old version forward as a new one, so the history stays append-only.

Request body

FieldTypeDescription
versionrequiredinteger

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
namestringDomain name, for example mail.example.com.
slugstring
subjectstringSubject line. Line breaks are rejected.
htmlstring | nullHTML body. Provide html, text or both.
textstring | nullPlain-text body. Provide html, text or both.
variablesstring[]
versioninteger
created_atstring (ISO 8601)When the record was created.
updated_atstring (ISO 8601)When the record last changed.

Suppressions

Addresses this account will not send live mail to. Every entry is a real recipient, so all three routes need a live key.

GET/v1/suppressionsList suppressions

Query

FieldTypeDescription
limitoptionalintegerPage size, 1 to 100. Defaults to 20.
cursoroptionalstringCursor from a previous page.

Response 200

FieldTypeDescription
dataobject[]Items on this page.
data[].idstring (uuid)Unique id.
data[].email_addressstring | nullThe suppressed address.
data[].reason"bounce" | "complaint" | "manual"Why the address was suppressed.
data[].created_atstring (ISO 8601)When the record was created.
next_cursorstring | nullPass as cursor to fetch the next page. Null on the last page.
POST/v1/suppressionsAdd a suppression

Stored with reason manual. 409 conflict if already suppressed.

Request body

FieldTypeDescription
email_addressrequiredstring (email)The suppressed address.

Response 201

FieldTypeDescription
idstring (uuid)Unique id.
email_addressstring | nullThe suppressed address.
reason"bounce" | "complaint" | "manual"Why the address was suppressed.
created_atstring (ISO 8601)When the record was created.
DELETE/v1/suppressions/:emailRemove a suppression

URL-encode the address: @ becomes %40.

Response 204

Empty body.

Webhooks

Endpoints that receive signed event POSTs, and the log of every delivery attempt.

POST/v1/webhooksCreate an endpoint

Needs a live key: an endpoint receives live events as well as test ones.

The secret is returned only here. In production the URL must be https and public.

At most 10 endpoints per account; past that, 403 plan_limit.

Request body

FieldTypeDescription
urlrequiredstring (url)Endpoint to POST events to. Must be https in production.
eventsoptional("email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled")[]Event types to deliver. Defaults to all eight: email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.rejected, email.failed and email.cancelled. Defaults to ["email.sent","email.delivered","email.delivery_delayed","email.bounced","email.complained","email.rejected","email.failed","email.cancelled"].

Response 201

FieldTypeDescription
idstring (uuid)Unique id.
urlstringEndpoint to POST events to. Must be https in production.
events("email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled")[]Event types to deliver. Defaults to all eight: email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.rejected, email.failed and email.cancelled.
enabledbooleanWhether deliveries are being attempted.
created_atstring (ISO 8601)When the record was created.
secretstringSigning secret. Returned once, at creation.
GET/v1/webhooksList endpoints

Response 200

FieldTypeDescription
dataobject[]Items on this page.
data[].idstring (uuid)Unique id.
data[].urlstringEndpoint to POST events to. Must be https in production.
data[].events("email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled")[]Every state change for this email, oldest first.
data[].enabledbooleanWhether deliveries are being attempted.
data[].created_atstring (ISO 8601)When the record was created.
GET/v1/webhooks/:idGet an endpoint

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
urlstringEndpoint to POST events to. Must be https in production.
events("email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled")[]Every state change for this email, oldest first.
enabledbooleanWhether deliveries are being attempted.
created_atstring (ISO 8601)When the record was created.
PATCH/v1/webhooks/:idChange an endpoint

Needs a live key. Pass events, enabled or both; anything you leave out stays as it is.

The URL cannot be changed: a new URL is a new endpoint, with its own secret and history. Create one and delete the old one.

Request body

FieldTypeDescription
eventsoptional("email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled")[]Event types to deliver from now on. Replaces the list.
enabledoptionalbooleanfalse stops deliveries to the endpoint; true starts them again.

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
urlstringEndpoint to POST events to. Must be https in production.
events("email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled")[]Event types to deliver from now on. Replaces the list.
enabledbooleanfalse stops deliveries to the endpoint; true starts them again.
created_atstring (ISO 8601)When the record was created.
DELETE/v1/webhooks/:idDelete an endpoint

Needs a live key.

Response 204

Empty body.

GET/v1/webhooks/:id/deliveriesList deliveries

Query

FieldTypeDescription
limitoptionalintegerPage size, 1 to 100. Defaults to 20.
cursoroptionalstringCursor from a previous page.

Response 200

FieldTypeDescription
dataobject[]Items on this page.
data[].idstring (uuid)Unique id.
data[].endpoint_idstring (uuid)The webhook endpoint this delivery belongs to.
data[].email_event_idstring (uuid) | nullThe event being delivered.
data[].event_type"email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled"Type of the event being delivered.
data[].email_idstring (uuid) | nullThe email the event belongs to.
data[].status"pending" | "delivered" | "failed"Current state.
data[].attemptsintegerDelivery attempts so far, up to 8.
data[].last_errorstring | nullError from the most recent failed attempt.
data[].response_codeinteger | null
data[].duration_msinteger | null
data[].is_testboolean
data[].replay_ofstring (uuid) | null
data[].created_atstring (ISO 8601)When the record was created.
next_cursorstring | nullPass as cursor to fetch the next page. Null on the last page.
POST/v1/webhooks/:id/deliveries/:deliveryId/retryRetry a delivery

Queues a failed delivery again and returns it.

Only a delivery whose status is failed: a delivered one, or one still being retried, gets 409 conflict.

At most 100 retries per endpoint per hour; past that, 429 rate_limited with retry-after.

Response 200

FieldTypeDescription
idstring (uuid)Unique id.
endpoint_idstring (uuid)The webhook endpoint this delivery belongs to.
email_event_idstring (uuid) | nullThe event being delivered.
event_type"email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled"Type of the event being delivered.
email_idstring (uuid) | nullThe email the event belongs to.
status"pending" | "delivered" | "failed"Current state.
attemptsintegerDelivery attempts so far, up to 8.
last_errorstring | nullError from the most recent failed attempt.
response_codeinteger | null
duration_msinteger | null
is_testboolean
replay_ofstring (uuid) | null
created_atstring (ISO 8601)When the record was created.

Account

The account as an integrator sees it: what the key can do, the sandbox and its allowed recipients, and what is set up.

GET/v1/accountDescribe the account

Any scope; API key only. Read this first when integrating: it says what exists and what the next step is.

Response 200

FieldTypeDescription
accountobject
account.idstring (uuid)Unique id.
account.namestringDomain name, for example mail.example.com.
account.plan"free" | "pro" | "growth" | "enterprise"
account.plan_stepstring | null
keyobject
key.mode"live" | "test"live or test, taken from the API key that created the email.
key.scopesstring[]
sandboxobject
sandbox.domainstring
sandbox.recipientsstring[]
sandbox.simulatorobject
sandbox.simulator.deliveredstring
sandbox.simulator.bouncedstring
sandbox.simulator.complainedstring
domainsobject[]
domains[].idstring (uuid)Unique id.
domains[].namestringDomain name, for example mail.example.com.
domains[].status"pending" | "verified" | "failed"Current state.
webhooksobject[]
webhooks[].idstring (uuid)Unique id.
webhooks[].urlstringEndpoint to POST events to. Must be https in production.
webhooks[].enabledbooleanWhether deliveries are being attempted.
templatesobject[]
templates[].idstring (uuid)Unique id.
templates[].slugstring
templates[].namestringDomain name, for example mail.example.com.
docs_urlstring
GET/v1/account/settingsGet the account settings

Needs account:settings, which only the account owner can grant (see Scopes).

Response 200

FieldTypeDescription
namestringThe account's name.
alertsobjectWhere alert emails go and which alerts are on, as the dashboard's Alerts section shows them.
alerts.notify_emailstring | nullThe address alerts go to, or null for the account owner.
alerts.notify_email_verifiedbooleanWhether that address has confirmed by the link sent to it. Until it has, alerts keep going to the owner.
alerts.domain_healthboolean
alerts.bounce_rateboolean
alerts.complaint_rateboolean
alerts.usage_80boolean
alerts.api_key_createdboolean
PATCH/v1/account/settingsChange the account settings

Needs account:settings and a live key. Pass name, alerts or both.

alerts.api_key_created can be switched on but not off: that alert is how the owner hears about a key they did not make, so only the owner can turn it off, in the dashboard. Either refusal is a 403 forbidden and changes nothing. Plans, payment, the team and sign-in are not settings this route can change.

Request body

FieldTypeDescription
nameoptionalstringA new name for the account.
alertsoptionalobjectAlert settings to change. Anything you leave out stays as it is.
alerts.notify_emailoptionalstring (email) | nullWhere alerts go: null for the account owner, or the address of someone who signs in to the account. An API key cannot send the alerts to an outside address; the owner can, in the dashboard.
alerts.domain_healthoptionalboolean
alerts.bounce_rateoptionalboolean
alerts.complaint_rateoptionalboolean
alerts.usage_80optionalboolean
alerts.api_key_createdoptionalboolean

Response 200

FieldTypeDescription
namestringA new name for the account.
alertsobjectAlert settings to change. Anything you leave out stays as it is.
alerts.notify_emailstring | nullWhere alerts go: null for the account owner, or the address of someone who signs in to the account. An API key cannot send the alerts to an outside address; the owner can, in the dashboard.
alerts.notify_email_verifiedboolean
alerts.domain_healthboolean
alerts.bounce_rateboolean
alerts.complaint_rateboolean
alerts.usage_80boolean
alerts.api_key_createdboolean

API keys

Keys that a key with `keys:manage` lists, creates and revokes: how an AI agent setting the account up hands your application a narrower key of its own, such as a send-only key for the production server. Never more than the key that asks: a key made here holds a subset of the caller's scopes, never `account:settings` and `keys:manage`.

GET/v1/api-keysList API keys

Needs keys:manage. Never returns a key itself, only its prefix. A test key sees only test keys.

Response 200

FieldTypeDescription
dataobject[]The account's keys.
data[].idstring (uuid)Unique id.
data[].namestringThe key's name, for recognising it later.
data[].mode"live" | "test"live or test. The prefix of the key says the same.
data[].prefixstringThe first characters of the key, enough to recognise it. The whole key is never returned again.
data[].scopes("emails:send" | "emails:read" | "domains:manage" | "webhooks:manage" | "suppressions:manage" | "templates:manage" | "account:manage" | "account:settings" | "keys:manage")[]What the key may do. See Scopes above.
data[].created_by_key_idstring (uuid) | nullThe key that created this one through this API, or null when a person made it in the dashboard.
data[].last_used_atstring (ISO 8601) | nullWhen the key last authenticated a request, or null.
data[].created_atstring (ISO 8601)When the record was created.
data[].revoked_atstring (ISO 8601) | nullWhen the key was revoked, or null while it works.
POST/v1/api-keysCreate an API key

Needs keys:manage. 403 forbidden when a requested scope is one the calling key does not hold, or is an owner-only scope; the message names it. 403 live_key_required when a test key asks for a live key.

The same limit as creating keys in the dashboard: 20 an hour per account, then 429 rate_limited.

The new key records which key created it in created_by_key_id, and the dashboard shows it. If the account has the new API key alert on, the owner is emailed, with the name of the key that created it.

Request body

FieldTypeDescription
namerequiredstringThe key's name, for recognising it later.
moderequired"live" | "test"live or test. The prefix of the key says the same.
scopesrequired("emails:send" | "emails:read" | "domains:manage" | "webhooks:manage" | "suppressions:manage" | "templates:manage" | "account:manage" | "account:settings" | "keys:manage")[]What the key may do. See Scopes above.

Response 201

FieldTypeDescription
idstring (uuid)Unique id.
namestringThe key's name, for recognising it later.
mode"live" | "test"live or test. The prefix of the key says the same.
prefixstringThe first characters of the key, enough to recognise it. The whole key is never returned again.
scopes("emails:send" | "emails:read" | "domains:manage" | "webhooks:manage" | "suppressions:manage" | "templates:manage" | "account:manage" | "account:settings" | "keys:manage")[]What the key may do. See Scopes above.
created_by_key_idstring (uuid) | nullThe key that created this one through this API, or null when a person made it in the dashboard.
last_used_atstring (ISO 8601) | nullWhen the key last authenticated a request, or null.
created_atstring (ISO 8601)When the record was created.
revoked_atstring (ISO 8601) | nullWhen the key was revoked, or null while it works.
keystringThe full key. Returned only here; store it at once.
DELETE/v1/api-keys/:idRevoke an API key

Needs keys:manage. Revokes a key the calling key created, or the calling key itself. Any other id is a 404, so a key cannot learn which other keys exist or switch off the ones your application uses.

Requests using the revoked key fail at once. Revoking is permanent: create a new key instead.

Response 204

Empty body.

Data rights

Answer a data subject access request or an erasure request. Both need the `account:manage` scope and a live key.

GET/v1/account/exportExport everything held for the account

Needs account:manage and a live key.

One JSON document, not paginated: account, users, keys, domains, emails with bodies, events, suppressions and webhooks. It is streamed as it is read, so a large account starts downloading at once.

At most 3 exports per account per hour (429 rate_limited after that, with Retry-After), and one at a time (409 conflict while another is running).

Response 200

Empty body.

POST/v1/recipients/eraseErase a recipient

Needs account:manage and a live key.

Removes the address everywhere it is held: the recipients, reply-to, subject and headers of emails, their events (bounce diagnostics included), the webhook deliveries made for them and their logs, and abuse samples. Emails sent only to them are deleted, as is the stored body of every email sent to them. Keeps only a hashed suppression entry so it is never sent to again.

Request body

FieldTypeDescription
email_addressrequiredstring (email)The suppressed address.

Response 200

FieldTypeDescription
recipient_hashstring
emails_redactedinteger
emails_deletedinteger
events_redactedinteger
suppressedboolean

Webhook payload

The body POSTed to your endpoints, signed with Avelto-Signature: t=<unix seconds>,v1=<hex> where v1 is the HMAC-SHA256 of <t>.<body>. See Webhooks.

FieldTypeDescription
idstring (uuid)Event id. The same id is sent again on every retry; use it to de-duplicate.
type"email.queued" | "email.scheduled" | "email.sent" | "email.delivered" | "email.delivery_delayed" | "email.bounced" | "email.complained" | "email.rejected" | "email.failed" | "email.cancelled"Event type.
created_atstring (ISO 8601)When the record was created.
dataobjectItems on this page.
data.email_idstring (uuid)The email the event belongs to.
data.mode"live" | "test"live or test, taken from the API key that created the email.
data.fromstringSender. a@b.com or Name <a@b.com>. The domain must be verified on your account, or be your sandbox domain.
data.tostring[]One recipient or a list. At most 50 recipients across to, cc and bcc.
data.subjectstringSubject line. Line breaks are rejected.
data.tagsstring[]Up to 10 tags of letters, numbers, _ - : . for filtering and reporting.
data.statusstringThe email's status after this event.
data.detailsobject<string, any>Event-specific details such as bounce type or diagnostic code.

Errors

Every error response has this shape. The code is stable; the message is for people.

FieldTypeDescription
errorobjectFailure reason, when status is failed.
error.code"validation_error" | "unauthorized" | "forbidden" | "live_key_required" | "not_found" | "conflict" | "domain_unavailable" | "rate_limited" | "internal_error" | "sandbox_recipient_not_allowed" | "domain_not_verified" | "recipient_suppressed" | "not_scheduled" | "plan_limit" | "account_paused" | "account_suspended"One of the codes below.
error.messagestringHuman-readable explanation.
error.detailsanyExtra context, for example the failing fields of a validation error.
CodeStatusWhen
validation_error400The body or query failed validation. details lists the issues.
unauthorized401Missing, malformed or revoked API key.
forbidden403A key without the scope for the call, or a key asking to create a key with a scope it does not hold or may never grant. The message names the scope.
live_key_required403A test key on an action that live sending depends on: removing a domain, managing templates or webhook endpoints, reading or changing suppressions, changing the account settings, creating a live key, exporting the account or erasing a recipient.
not_found404No such email, domain, template, template version, webhook, delivery, suppression or API key on this account, or an API key this key did not create.
conflict409The resource already exists: the domain or suppression is already added, or a template slug you chose is already taken.
domain_unavailable409The domain cannot be added: it is part of a domain on another account, is reserved, or is already set up elsewhere. Contact support if it belongs to you.
rate_limited429More than the per-key request rate (between 10 and 50 a second depending on plan). Honour Retry-After.
internal_error500Something went wrong on our side. Safe to retry with an idempotency key.
sandbox_recipient_not_allowed403Sending from the sandbox domain to anyone other than the account owner's verified email.
domain_not_verified403from uses a domain that is not added and verified on this account.
recipient_suppressed422A recipient is on the suppression list. details.suppressed lists the addresses.
not_scheduled409Cancel was called on an email whose status is not scheduled.
plan_limit429 / 403Monthly email limit or first-week daily cap reached (429), or the plan's domain limit reached (403).
account_paused403Sending is paused on the account, usually after a high bounce or complaint rate.
account_suspended403The account has been suspended by us. The message says why, and the dashboard has a button to write to us.