Skip to main content

API Endpoints

Public Analytics / email delivery API endpoints. All paths below are relative to the base URL:

https://api.motorical.com/api/public/v1

For interactive testing, use the Swagger UI.

Motor Blocks

GET /motor-blocks

Lists all motor blocks you have access to. Use this first to discover your block IDs.

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks"

Response: Array of motor blocks with names, IDs, and basic info.


GET /motor-blocks/{id}/overview

One-call overview with totals, rates, reputation, rate limits, anomalies, and top domains.

Scopes: analytics.read, usage.read

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/overview"

Ideal for dashboards, health checks, and monitoring UIs that need a compact summary.


GET /motor-blocks/{id}/daily-summary

Daily rollups over a recent window (sent, delivered, bounced, complained + rates).

Parameters:

ParameterDescription
daysNumber of days to include (up to your retention window)
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/daily-summary?days=30"

GET /motor-blocks/{id}/rate-limits

Current usage against this Motor Block's effective ceiling — the lower of the block's configured limits and any active reputation-based throttle. This is not your plan ceiling; the plan drives the account-wide cap below.

Scopes: usage.read

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/rate-limits"

Response: burst, hourly, and daily objects (each used, limit, remaining, percentage, resetIn), plus:

FieldMeaning
limitsYour raw configured limits, unaffected by throttling — compare with daily.limit to see how far a throttle has lowered the ceiling
throttledtrue when a system throttle is currently lowering the effective ceiling
throttleReasonWhy, or null
timestampWhen the counters were read

A top-level warning field (outside data) appears only when counter data was temporarily unavailable; the bucket values are then ceiling-only estimates, not live counts. Note that GET /account/rate-limits carries its warning inside data.


GET /account/rate-limits

Current usage vs your account-wide hourly and daily send caps, aggregated across every Motor Block on your account. Unlike the endpoint above, this is not scoped to a single Motor Block — the token's bound motorBlockId only identifies which account it belongs to.

Scopes: usage.read

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/account/rate-limits"

Response:

{
"success": true,
"data": {
"source": "plan",
"timestamp": "2026-08-24T14:31:38.509Z",
"hourly": { "used": 46, "limit": 1500, "remaining": 1454, "resetIn": 1702 },
"daily": { "used": 611, "limit": 10000, "remaining": 9389, "resetIn": 34162 }
}
}

source is "plan" when the ceiling comes from your subscription, or "fallback" for accounts without an active product. A warning field appears (with hourly/daily values as best-effort fallback estimates, not live counts) only if usage data was temporarily unavailable. See Rate Limits & Retention for how this account-wide ceiling relates to the per-block one above and to POST /v1/send's enforcement.


GET /motor-blocks/{id}/metrics

Email performance data (sent, delivered, bounced, complained) with time-series breakdowns.

Parameters:

ParameterDescription
intervalhour or day (required)
fromStart time (ISO 8601)
toEnd time (ISO 8601)
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/metrics?interval=hour&from=2024-01-15T00:00:00Z&to=2024-01-16T00:00:00Z"

GET /motor-blocks/{id}/logs

Search individual email events and records.

Parameters:

ParameterDescription
querySearch filter (see query syntax below)
statussent, delivered, bounced, complained, deferred, failed
fromStart time (ISO 8601)
toEnd time (ISO 8601)
limitMax results (1–1000, default 100)
cursorFor pagination (use nextCursor from response)

Query Syntax:

  • recipient:user@example.com — specific email address
  • domain:gmail.com — all emails to Gmail
  • status:bounced — only bounced emails
  • metadata.campaign_id:newsletter-2024 — by campaign metadata
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/logs?query=domain:gmail.com&status=bounced&limit=50"

GET /motor-blocks/{id}/providers

Aggregate performance by mailbox provider (gmail, outlook, yahoo, other).

Parameters:

ParameterDescription
fromStart time (ISO 8601)
toEnd time (ISO 8601)
limitMax providers to return (default 20)
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/providers?from=2024-01-01T00:00:00Z&to=2024-01-08T00:00:00Z"

GET /motor-blocks/{id}/error-codes

Summarize SMTP response codes with human-friendly categories and hints.

Parameters: from, to, limit (default 50)

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/error-codes?from=2024-01-01T00:00:00Z&to=2024-01-08T00:00:00Z"

GET /motor-blocks/{id}/config

Read-only snapshot of a motor block's non-sensitive configuration and domain status.

Scopes: config.read

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/config"

GET /motor-blocks/{id}/domain-health

Summarized SPF/DKIM/DMARC and verification status for the attached domain.

Cache-first: the default response uses DB auth flags and returns quickly. Stale or never-checked domains schedule an async DNS refresh; the next call picks up updated flags. Pass refresh=true to wait for a live re-check (time-capped).

Scopes: config.read

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/domain-health"

GET /motor-blocks/{id}/deliverability

Deliverability matrix broken down by recipient domain. Shows delivery, bounce, complaint, and failure rates per domain.

Scopes: analytics.read

Parameters:

ParameterDescription
fromStart time (ISO 8601)
toEnd time (ISO 8601)
limitMax domains to return (1–200, default 50)
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/deliverability?from=2024-01-01T00:00:00Z&to=2024-01-08T00:00:00Z&limit=20"

Response: Array of domains with total, delivered, bounced, complained, failed, and deliveryRate per domain.


GET /motor-blocks/{id}/reputation

Reputation score (0–100) calculated from bounce, complaint, and failure rates over your retention window.

Scopes: analytics.read

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/reputation"

Response: score (0–100), windowDays, totals (total, delivered, bounced, complained, failed), and rates (bounceRate, complaintRate, failRate as percentages).


GET /motor-blocks/{id}/anomalies

Bounce spike detection over a 14-day window. Flags anomalies when today's bounce rate exceeds 2× the historical average by more than 2 percentage points.

Scopes: analytics.read

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/anomalies"

Response: anomalies array (empty if none detected; each entry has type, todayRate, avgRate), and windowDays.


Messages

GET /messages/{id}

GET /api/public/v1/messages/{id} — look up a single message by send id (UUID from POST /v1/send).

Scopes: logs.read

Parameters:

ParameterDescription
includePIItrue/false (requires logs.pii scope for unmasked recipients)
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/messages/$MESSAGE_ID"

Response:

FieldMeaning
idSend UUID (the same id used to look this message up)
smtpMessageIdRFC Message-ID header value, or null
currentOutcomeCurrent delivery outcome (e.g. queued, delivered, bounced, failed)
isTerminaltrue once the message has reached a final outcome
fromSender address
recipientRecipient address (masked unless includePII=true with logs.pii scope)
subjectMessage subject
queuedAtWhen the message was queued
processedAtWhen the message was processed, or null
deliveredAtWhen delivery was confirmed, or null
bouncedAtWhen a bounce was recorded, or null
smtpCodeSMTP response code from the receiving server, or null
smtpResponseFull SMTP response message, or null

GET /messages

Lookup a message by RFC Message-ID header. Not a collection endpointsmtpMessageId is required. To list recent sends use GET /motor-blocks/{id}/logs. To look up by send UUID use GET /messages/{id}.

Scopes: logs.read

ParameterRequiredDescription
smtpMessageIdYesRFC Message-ID (with or without angle brackets)
includePIINoRequires logs.pii
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/messages?smtpMessageId=$SMTP_MESSAGE_ID"

GET /messages/{id}/events

GET /api/public/v1/messages/{id}/events — canonical lifecycle events for a single message (queued → final result).

Scopes: logs.read

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/messages/$MESSAGE_ID/events"

Response: Lifecycle events from email_events (e.g. queueddelivered/bounced).

Use the messageId from your own logs or from /logs responses.

Webhooks

Full webhook CRUD is available. See the Webhooks reference for detailed examples including signature verification.

Scopes: All webhook endpoints require webhooks.manage.

GET /motor-blocks/{id}/webhooks

List all webhook endpoints for a motor block.

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks"

POST /motor-blocks/{id}/webhooks

Create a new webhook endpoint.

curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/webhook","events":["message.delivered"]}' \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks"

PUT /motor-blocks/{id}/webhooks/{webhookId}

Update an existing webhook (url, events, or enabled status).


DELETE /motor-blocks/{id}/webhooks/{webhookId}

Delete a webhook endpoint.


POST /motor-blocks/{id}/webhooks/{webhookId}/test

Queue a test delivery to verify your endpoint is working.


GET /motor-blocks/{id}/webhooks/{webhookId}/deliveries

View recent delivery attempts for a webhook.

Parameters: limit (1–200, default 50)


GET /motor-blocks/{id}/webhooks/{webhookId}/stats

High-level stats for webhook delivery (delivered, failed, queued, failure rate, latency).

curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks/$WEBHOOK_ID/stats?hours=24"

Parameters: hours (1–168, default 24)


Exports

See the Data Exports guide for a complete walkthrough.

GET /motor-blocks/{id}/exports/preview

Lightweight preview of recent logs without creating a background export job.

Scopes: analytics.read, logs.read

Parameters:

ParameterDescription
limitMax records to return (default 100, max 500)
includePIItrue/false (requires logs.pii scope for unmasked recipients)
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/exports/preview?limit=100"

POST /motor-blocks/{id}/exports

Create a background export job. Returns 202 Accepted with the job ID.

Scopes: analytics.read

curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jobType":"logs"}' \
"$BASE_URL/motor-blocks/$BLOCK_ID/exports"

GET /motor-blocks/{id}/exports

List recent export jobs (up to 50) for this motor block.

Scopes: analytics.read


GET /exports/{jobId}

Check the status of an export job. When complete, includes a download_url with a 15-minute download token.

Scopes: analytics.read


GET /exports/{jobId}/download

Download a completed export as NDJSON. Requires the token query parameter from the job status response.

curl -o export.ndjson \
"$BASE_URL/exports/$JOB_ID/download?token=$DOWNLOAD_TOKEN"

Email Sending (HTTP API)

These endpoints use a different base URL and authentication from the Public API v1 endpoints above.

Base URL: https://api.motorical.com

Authentication: Motor Block API Key via Authorization: ApiKey mk_live_... or X-Api-Key: mk_live_... header.

POST /v1/send

Send an email via the HTTP API. Requires a Motor Block API key with a verified domain and valid DNS setup (SPF + DKIM).

curl -X POST https://api.motorical.com/v1/send \
-H "Authorization: ApiKey mk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "sender@yourdomain.com",
"fromName": "Acme Billing",
"to": ["recipient@example.com"],
"subject": "Hello from Motorical",
"text": "Plain text body",
"html": "<p>HTML body</p>",
"dryRun": false
}'

Request Body:

FieldTypeRequiredDescription
fromstringYesBare sender address (must match motor block's verified domain). Do not put a display name here.
fromNamestringNoOptional From display name (max 78). Produces From: "Acme Billing" <sender@yourdomain.com>. Do not set a From custom header (DISALLOWED_HEADERS).
tostring or string[]YesRecipient address(es)
subjectstringYesEmail subject
textstringOne of text/htmlPlain text body
htmlstringOne of text/htmlHTML body
headersobjectNoCustom email headers (see Custom headers below)
dryRunbooleanNoValidate the send request without queueing mail or writing an email log.

Response (real send):

{
"success": true,
"data": {
"id": "3f7b1f2e-9c4a-4a1e-9f2b-7d1c8e5a0b93",
"acceptanceStatus": "queued",
"from": "sender@yourdomain.com",
"fromName": "Acme Billing",
"to": ["recipient@example.com"],
"subject": "Hello from Motorical",
"timestamp": "2026-08-25T12:00:00.000Z"
},
"message": "✅ Email queued for delivery successfully"
}

data.id is the send UUID — pass it to GET /messages/{id} and GET /messages/{id}/events. There is no messageId field and no status field on a real send.

Response (dryRun: true) has a different shape — no id (nothing was queued), plus extra validation metadata:

{
"success": true,
"dryRun": true,
"data": {
"status": "validated",
"acceptanceStatus": "validated",
"from": "sender@yourdomain.com",
"fromName": "Acme Billing",
"to": ["recipient@example.com"],
"subject": "Dry run check",
"domain": "yourdomain.com",
"motorBlockId": "0b6c1a44-1f2d-4c8e-9a3b-5e7d2c9f1a80",
"bodySize": 42,
"hasText": true,
"hasHtml": true,
"recipientCount": 1,
"timestamp": "2026-08-25T12:00:00.000Z"
},
"message": "Email payload validated; dryRun=true so no email was queued."
}

Response (idempotent replay) — when an Idempotency-Key matches a send from the last 24 hours, the original data.id is returned with "idempotent": true at the response root and no new message is queued.

Rate Limit / Quota Errors (429): An accepted send, and a send refused for a Motor Block or account ceiling, carry RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset headers. They are not present on dryRun: true responses, on the rate_limited_warmup refusal (which sets only Retry-After), or when the quota check itself is unavailable (Motorical fails open rather than emit placeholder numbers) — missing headers mean "unknown," never "unlimited." A refused send returns 429 with a Retry-After header and one of rate_limited_block, rate_limited_account, or rate_limited_warmup as the error field:

{
"success": false,
"error": "rate_limited_block",
"message": "Rate limit exceeded (daily)",
"retryAfterSeconds": 33953
}

See Rate Limits & Retention for the full contract, including which of these are enforced today vs. still rolling out, and GET /account/rate-limits for checking your account-wide standing ahead of time.

Domain Validation: The from address domain must match the motor block's assigned domain. The domain must be verified with DKIM and SPF configured.

:::tip Friendly From vs SMTP On HTTP /v1/send, use fromName for the display name. On SMTP submission (mail.motorical.com:2587 / :2465), set the display name in the message From: header (From: "Acme Billing" <sender@yourdomain.com>) — there is no JSON fromName field on SMTP. :::

Custom headers

The optional headers object sets additional message headers, which are applied to the outgoing message and DKIM-signed — for example In-Reply-To and References (to thread a reply into an existing conversation), Reply-To, List-Unsubscribe, or any custom X-* header.

For safety, reserved and spoofable headers are rejected with 400 (code: DISALLOWED_HEADERS):

From, Sender, Return-Path, To, Cc, Bcc, Subject, Date, Message-ID, MIME-Version, Content-*, DKIM-Signature, Received, Authentication-Results, ARC-*, and X-Motorical-*.

Use the from / fromName, to, and subject fields for those instead. Header values containing line breaks (CR/LF) are also rejected.

{
"from": "sender@yourdomain.com",
"fromName": "Acme Support",
"to": "recipient@example.com",
"subject": "Re: Your question",
"text": "Thanks for reaching out…",
"headers": {
"In-Reply-To": "<CABc123@mail.gmail.com>",
"References": "<CABc123@mail.gmail.com>",
"List-Unsubscribe": "<mailto:unsubscribe@yourdomain.com>"
}
}

Use dryRun: true before a production send to verify authentication, sender domain, DNS setup, recipient shape, subject, and body size without creating a queued message:

curl -X POST https://api.motorical.com/v1/send \
-H "Authorization: ApiKey mk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "sender@yourdomain.com",
"fromName": "Acme Billing",
"to": ["recipient@example.com"],
"subject": "Dry run check",
"text": "This validates only.",
"dryRun": true
}'

Dry-run responses return status: "validated" and include metadata such as sender domain, recipient count, body size, and echoed fromName. They do not include an id because nothing was queued.

GET /v1/status

Check the API status.

curl https://api.motorical.com/v1/status

POST /v1/test-email

Validate SMTP setup parameters for customer setup flows. This endpoint uses the same Motor Block API Key authentication as /v1/send.

curl -X POST https://api.motorical.com/v1/test-email \
-H "Authorization: ApiKey mk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"method": "starttls",
"host": "mail.motorical.com",
"port": 2587,
"username": "your_motor_block_username",
"password": "your_motor_block_password",
"to": "recipient@example.com",
"subject": "Motorical SMTP setup test",
"requireTLS": true
}'

Email Validation

POST /api/email/validate

Validate a single email address. No authentication required (rate limited).

Base URL: https://api.motorical.com

curl -X POST https://api.motorical.com/api/email/validate \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'

POST /api/email/validate/batch

Validate multiple email addresses at once. Requires authentication.

curl -X POST https://api.motorical.com/api/email/validate/batch \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"emails": ["user1@example.com", "user2@example.com"]}'

Events (Server-Sent Events)

GET /motor-blocks/{id}/events/stream

Real-time event stream of email delivery events.

Scopes: logs.read

curl -N -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/events/stream"

The token can also be passed via ?token= or ?access_token= query parameters for browser-friendly SSE connections.

Events are delivered as SSE with types: ready, keepalive, and message. Maximum 3 concurrent SSE connections per token.