Send Tier API
The send tier is how you run a broadcast on your own sending domain. It is a separate surface from the mailbox API: mailbox routes read and send individual messages, while these routes onboard a sending domain, read the pool, and report deliverability per source.
All paths are under https://spideriq.ai/api/v1. These routes are tenant-scoped and authenticate with a personal access token or a dashboard session — not the client_id:api_key:api_secret job triple used elsewhere in this reference. See Authentication.
Reads need the member role. Enrolment and promotion need admin — both bind or arm an identity that sends real mail.
POST /dashboard/send/byo/check
Dry-run the DNS for a domain you want to send from. Writes nothing, stores nothing, and is safe to call repeatedly while you are still editing your zone.
Parameters
::table
Field | Type | Required | Notes
domain | string | yes | 3 to 253 characters, the domain you intend to send from
provider | string | no | defaults to `mailgun`
dkim_selector | string | no | defaults to `mailo`; must match the selector in your DKIM record
tracking_domain | string | no | the host you want click tracking rewritten throughExample
curl -X POST "https://spideriq.ai/api/v1/dashboard/send/byo/check" \
-H "Authorization: Bearer $SPIDERIQ_PAT" \
-H "Content-Type: application/json" \
-d '{"domain": "mail.example.com", "dkim_selector": "mailo"}'Response
Returns a DomainVerificationOut: domain, provider, verified, and one record block each for spf, dkim and tracking_cname.
Every record block carries kind, ok, name (the exact name queried), detail, expected and observed.
ok is tri-state and null is never a pass. true means the record was read and matched, false means it was read and did not match, and null means the zone could not be read at all. verified is true only when every check passed, never when merely none of them failed. A null tells you the answer is unknown, not that the record is wrong.
Errors:
::table
Status | When it fires | What to do
403 | the token resolves to no tenant | use a tenant-scoped PAT or a dashboard session
422 | `domain` is not a valid domain | read the message, which quotes the offending input
429 | more than 20 checks in a minute for this tenant | wait the seconds given in `Retry-After`
500 | an internal error | retry; if it persists the response body carries no detail by designPOST /dashboard/send/byo/enroll
Verify the domain, store your provider credential encrypted, and bind an existing mailbox as a sending source for your tenant. Returns 201.
Parameters
::table
Field | Type | Required | Notes
mailbox_id | integer | yes | the mailbox to bind as this domain's sending source
domain | string | yes | 3 to 253 characters
api_key | string | yes | your own provider API key; it is encrypted at rest
provider | string | no | defaults to `mailgun`
dkim_selector | string | no | defaults to `mailo`
tracking_domain | string | no | required if you want click tracking on
enable_tracking | boolean | no | see the warning below
api_base | string | no | your provider's API base, for non-default regionsExample
curl -X POST "https://spideriq.ai/api/v1/dashboard/send/byo/enroll" \
-H "Authorization: Bearer $SPIDERIQ_PAT" \
-H "Content-Type: application/json" \
-d '{
"mailbox_id": 42,
"domain": "mail.example.com",
"api_key": "'"$SPIDERIQ_SEND_PROVIDER_KEY"'",
"tracking_domain": "click.example.com",
"enable_tracking": true
}'Pass the key through an environment variable rather than typing it into a shell command, so it does not land in your shell history.
Response
Returns a DomainEnrollResponse: client_id, mailbox_id, sending_domain, tracking_domain, provider, vault_key_ref, state, tracking_enabled, the full verification block, an optional provider_tracking probe and a warnings array.
state is the source's warm-up state. A freshly enrolled domain starts warming rather than sending at full volume.
enable_tracking: false turns click tracking off for this domain. It does not fall back to a shared tracking host, and that is deliberate: inheriting one would rewrite your links through a host you do not control. The cost is that you get no clicked events for this source, so engagement-based features see replies only. The response says so in warnings rather than leaving you to discover it.
Errors:
::table
Status | When it fires | What to do
403 | no tenant in scope, or the token lacks the admin role | enrol with an admin token
409 | that mailbox already belongs to another tenant | pick a different mailbox; it is never re-tenanted silently
422 | the domain failed validation or verification did not pass | fix the record the `verification` block names, then retry
429 | the enrolment rate limit for this tenant | wait the seconds given in `Retry-After`
500 | an internal error | retry; the enrolment is ordered so a half-finished attempt is inertPOST /dashboard/send/sources/{mailbox_id}/promote
Promote a warming sending source to active. This is the only route that performs that transition, and until a source is active the send loop never claims it — a broadcast queued against a pool of warming sources refuses with no_sources.
Promotion also re-stamps the warm-up ramp: warmup_started_on is set to today in the source's own timezone, so a promoted source begins at day one of its curve rather than resuming at a rate implied by an older anchor.
Parameters
::table
Field | Type | Required | Notes
mailbox_id | integer | yes | path parameter, for example `42`; the warming source to promote. It must already have a capacity row in your poolExample
curl -X POST "https://spideriq.ai/api/v1/dashboard/send/sources/42/promote" \
-H "Authorization: Bearer $SPIDERIQ_PAT"Response
Returns a SourcePromoteResponse: mailbox_id, promoted, refusal, detail, source_id, sending_domain, state, warmup_started_on, daily_cap, effective_daily_cap, next_earliest_send_at, reputation_gate_overridden and notified.
Read effective_daily_cap, not daily_cap. The first is what the source may send today — on the default curve that is 8 on day one. The second is the ceiling it reaches at the end of the ramp, and sizing an audience against it will overcommit the send by an order of magnitude.
notified: false alongside promoted: true is not a failed promotion. The source is live; only the activation notification was not published.
This route cannot waive the reputation precondition. The request model does not carry that field at all. Waiving it is an operator action on a separate admin route, deliberately kept off the tenant surface so the check is not one conditional away from a tenant token.
Errors:
::table
Status | When it fires | What to do
403 | the token lacks the admin role | promote with an admin token; a member token can read the pool but not arm it
404 | no such source, or it belongs to another tenant | both cases answer identically on purpose, so this route cannot be used to probe for mailbox ids in other tenants
409 | the promotion was refused | read `refusal` and `detail`; see the table below. Do not retry
500 | an internal error, or the send database is unconfigured | `refusal` is `send_db_unconfigured`; this is an operator problemA 409 is an answer, not a transport failure. Each refusal describes a condition that another identical call cannot change:
::table
`refusal` | Meaning | Does retrying help
warmup_not_matured | the warm-up ramp has not run long enough yet | no, it is a date
reputation_input_required | the source has no reputation samples to judge | no, it needs real delivery history
source_not_warming | already active, or in a state that cannot be promoted | no, read the pool first
source_not_found | absent, or owned by another tenant | no
source_expired | past its `retire_after` date | no
promote_returned_false | the underlying transition did not apply | no, escalateGET /dashboard/send/deliverability/sources
List your sending pool: every mailbox acting as a sending source, its pacing state and caps, and its own bounce and complaint rates.
Example
curl "https://spideriq.ai/api/v1/dashboard/send/deliverability/sources" \
-H "Authorization: Bearer $SPIDERIQ_PAT"Response
A SendingSourceList. Each row carries the source's identity, its cap and current usage, its state, and complaint_pct / bounce_pct computed for that source alone.
Read metrics_available before you read a rate. It reports whether the per-source aggregation actually ran. A tenant with no mailboxes returns false, because there was nothing to aggregate; that is an honest "not computed", not a clean bill of health. A row whose provider events could not be attributed to a source is not counted, so a per-source number can be lower than the tenant-wide one.
Errors:
::table
Status | When it fires | What to do
401 | no credential was sent | send a PAT or a dashboard session cookie
403 | the credential resolves to no tenant | use a tenant-scoped token
422 | a query parameter is not an accepted value | correct the parameter; there is no silent defaultGET /dashboard/send/deliverability/summary
Funnel totals and rates for a time window.
Parameters
::table
Field | Type | Required | Notes
range | string | no | one of `24h`, `7d`, `30d`, `90d`Example
curl "https://spideriq.ai/api/v1/dashboard/send/deliverability/summary?range=7d" \
-H "Authorization: Bearer $SPIDERIQ_PAT"Response
Totals for dispatched, delivered, opened and clicked, the rates derived from them, negative outcomes, the thresholds in force, and an ingest health block.
Rates are a percentage of dispatched, which is read from the dispatch ledger rather than from a provider event. A window with dispatches but no provider feedback yet returns null for a rate, never 0.0 — so a quiet feedback channel cannot be misread as perfect deliverability.
Errors:
::table
Status | When it fires | What to do
401 | no credential was sent | send a PAT or a dashboard session cookie
403 | the credential resolves to no tenant | use a tenant-scoped token
422 | `range` is not one of the four accepted values | pass `24h`, `7d`, `30d` or `90d`; there is no silent defaultGET /dashboard/send/deliverability/timeseries
Daily bounce and complaint rates with the threshold lines drawn alongside them. Same range parameter and same auth as /summary.
Example
curl "https://spideriq.ai/api/v1/dashboard/send/deliverability/timeseries?range=30d" \
-H "Authorization: Bearer $SPIDERIQ_PAT"Response
One row per day in the window, each carrying that day's bounce_pct and complaint_pct plus the warn and hard threshold values, so the chart and the breaker agree on where the lines sit.
Errors:
::table
Status | When it fires | What to do
401 | no credential was sent | send a PAT or a dashboard session cookie
403 | the credential resolves to no tenant | use a tenant-scoped token
422 | `range` is not one of the four accepted values | pass `24h`, `7d`, `30d` or `90d`GET /dashboard/send/deliverability/undeliverable
Bounce, complaint and failure events, newest first, with a keyset cursor.
Parameters
::table
Field | Type | Required | Notes
kind | string | no | one of `all`, `bounce`, `complaint`, `failed`
range | string | no | one of `24h`, `7d`, `30d`, `90d`Example
curl "https://spideriq.ai/api/v1/dashboard/send/deliverability/undeliverable?kind=bounce&range=30d" \
-H "Authorization: Bearer $SPIDERIQ_PAT"Response
An UndeliverableList: the matching events newest first, each with its type, recipient, provider reason and timestamp, plus a next_cursor you pass back to page. A null cursor means you have reached the end.
Errors:
::table
Status | When it fires | What to do
401 | no credential was sent | send a PAT or a dashboard session cookie
403 | the credential resolves to no tenant | use a tenant-scoped token
422 | `kind` or `range` is not an accepted value | pass an accepted value; there is no silent defaultThe same surface from the CLI and from an agent
Every route above has a CLI verb and an MCP tool. See CLI Reference and MCP Tools.
For the walkthrough version of enrolment, with the DNS records explained, see Bring your own sending domain.