API Authentication
Motorical uses three distinct credential types. Pick the one that matches the surface you are calling — mixing them is the most common agent failure mode.
Canonical credential map (agents)
| Credential | Shape / how obtained | Use for | Do not use for |
|---|---|---|---|
| Dashboard JWT | Authorization: Bearer … from login / set-password (~15m access + 7d refreshToken) | /api/auth/*, /api/domains*, /api/developer/sandbox*, web handoff | POST /v1/send, Public Analytics /api/public/v1/* |
Motor Block mk_live_ | ApiKey from sandbox provision or Motor Block keys UI | POST /v1/send (Authorization: ApiKey mk_live_… or X-Api-Key) | Domain ops, sandbox APIs, minting public tokens |
Account ak_live_ → short-lived Public bearer, or MCP OAuth/Delegation grant | Mint via POST /api/public/token/account-key, or OAuth consent | /api/public/v1/* (logs, analytics, webhooks, domain-health, and domain add/list/verify/check-dns — account-wide, no Motor Block) | POST /v1/send |
Send-readiness shortcut: after DNS is live, use POST /api/public/v1/domains/{id}/check-dns (Public bearer or MCP OAuth/Delegation, account-wide) or POST /api/domains/{id}/check-dns (dashboard JWT) — either flips the flags. Do not expect GET /api/public/v1/motor-blocks/{id}/domain-health to accept a dashboard JWT — that route wants a Public API bearer (ak_live_ mint) or an OAuth/Delegation grant.
Canonical docs: this page · machine journey authLifecycle · https://docs.motorical.com/llms-full.txt § Authentication
Onboarding Auth & Domains (dashboard JWT)
These routes use a dashboard JWT (or are unauthenticated for signup). Exact paths:
| Method | Path |
|---|---|
POST | /api/auth/register |
POST | /api/auth/verify-code |
POST | /api/auth/set-password |
POST | /api/auth/login |
POST | /api/auth/refresh |
POST | /api/auth/web-handoff |
POST | /api/domains |
POST | /api/domains/{id}/verify |
POST | /api/domains/{id}/check-dns |
Domains, account-wide (Public API bearer or MCP OAuth/Delegation)
Same handlers as the dashboard routes above, reached without a dashboard JWT — no Motor Block required, since domains are account-wide and a Motor Block is created from a verified domain, not the reverse.
| Method | Path |
|---|---|
GET | /api/public/v1/domains |
POST | /api/public/v1/domains |
POST | /api/public/v1/domains/{id}/verify |
POST | /api/public/v1/domains/{id}/check-dns |
Motorical's email delivery API and Public Analytics API use distinct key types.
Motorical exposes more than one customer-facing API surface. Use the authentication method that matches the endpoint you are calling.
| API surface | Base URL | Auth mechanism | Primary use |
|---|---|---|---|
| Dashboard / onboarding APIs | https://api.motorical.com/api/auth, /api/domains, /api/developer/sandbox | Dashboard JWT (Bearer) | Signup, domains, sandbox, check-dns |
| Public Analytics API | https://api.motorical.com/api/public/v1 | Short-lived bearer token, or MCP OAuth/Delegation grant | Logs, analytics, configuration, webhooks, exports, SSE, domain-health, domain management (account-wide) |
| Token minting API | https://api.motorical.com/api/public | Account API Key or dashboard JWT | Create short-lived public API bearer tokens |
| HTTP Send API | https://api.motorical.com/v1/send | Motor Block API Key | Send production email over HTTPS |
| Email validation | https://api.motorical.com/api/email/validate | No auth for single validation | Validate email address syntax/domain |
Public Analytics API tokens are scoped, bound to a single Motor Block, and expire after 5 minutes by default.
Getting a Public API Bearer Token
Use a bearer token for endpoints under https://api.motorical.com/api/public/v1.
Recommended: Account API Key
For server-to-server integrations, use a full Account API Key to mint short-lived public API tokens. The raw key value has this shape:
ak_live_<prefix>_<secret>
Copy the full key when you create it. A masked dashboard display value or key label is not usable for API calls.
curl -X POST https://api.motorical.com/api/public/token/account-key \
-H "Authorization: ApiKey ak_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"motorBlockId": "YOUR_MOTOR_BLOCK_ID",
"scopes": ["logs.read", "analytics.read", "usage.read"],
"ttlSeconds": 300
}'
You can also pass the same account key as X-Api-Key: ak_live_<prefix>_<secret>. Create account API keys in Settings -> Account API Keys in your dashboard. Scopes granted by the token are intersected with the scopes allowed on the key itself.
::::caution Account keys and Motor Block keys are different
Use AK_API_KEY=ak_live_... only for token minting. Use MK_API_KEY=mk_live_... only for the HTTP Send API (/v1/send). A mk_live_... key will fail on /api/public/token/account-key, and an ak_live_... key will fail on /v1/send.
::::
Dashboard JWT Flow
If you already have a dashboard session JWT, you can mint a token with POST /api/public/token:
curl -X POST https://api.motorical.com/api/public/token \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"motorBlockId": "YOUR_MOTOR_BLOCK_ID",
"scopes": ["logs.read", "analytics.read", "usage.read"],
"ttlSeconds": 300
}'
This flow is mainly used by the Motorical dashboard and trusted internal tools. Customer server integrations should prefer the Account API Key flow above so they do not depend on a user login session.
Via the Dashboard
- Go to Settings -> API Access in your Motorical dashboard
- Select a Motor Block
- Check the permissions you need
- Generate a short-lived token
Using the Token
Include the token in the Authorization header of every request:
Authorization: Bearer <TOKEN>
For browser-friendly Server-Sent Events only, GET /events/stream also accepts ?token= or ?access_token= query parameters. Prefer the Authorization header everywhere else.
HTTP Send API Authentication
POST https://api.motorical.com/v1/send does not use the public bearer token or the Account API Key. It uses a full Motor Block API Key tied to the sending Motor Block:
mk_live_<prefix>_<secret>
curl -X POST https://api.motorical.com/v1/send \
-H "Authorization: ApiKey mk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "sender@yourdomain.com",
"to": ["recipient@example.com"],
"subject": "Hello from Motorical",
"text": "Plain text body"
}'
The send endpoint also accepts X-Api-Key: mk_live_YOUR_KEY. The from domain must match the Motor Block's verified domain and must have SPF and DKIM configured.
For production tests and examples, use a real sender address on your verified domain. Do not rely on placeholder senders such as test@yourdomain.com unless that is an intentional, monitored address for your account.
Permission Scopes
| Scope | Access |
|---|---|
logs.read | Search and read email delivery logs |
analytics.read | Access metrics, providers, error codes, daily summaries |
usage.read | View rate limits and usage counters |
config.read | Read motor block configuration, domain list, and domain health |
config.manage | Add, verify, and check DNS for sending domains (persists to the domain; implies config.read) |
logs.pii | Access unmasked recipient email addresses in logs |
webhooks.read | List webhook endpoints, deliveries, and stats |
webhooks.manage | Create, update, delete, and test webhook endpoints (implies webhooks.read) |
For most integrations, start with logs.read, analytics.read, and usage.read. Add config.read if you need domain health or configuration endpoints.
Token Format
Tokens are signed HS256 JWTs containing:
- Motor Block ID binding
- Granted scopes
- User and client identifiers
- Expiration timestamp (60–900 seconds)
- Issuer:
auth.motorical.com - Audience:
smtp.motorical.com
Common Errors
| Status | Meaning | Fix |
|---|---|---|
401 Unauthorized | Token missing, invalid, or expired | Generate a new token |
403 Forbidden | Token lacks the required scope | Regenerate with the needed scope |
403 Forbidden | Motor Block ID mismatch | Token is bound to a different Motor Block |
401 Unauthorized | API key format is invalid | Use an ak_live_... account key for token minting or an mk_live_... Motor Block key for sending |
403 Forbidden | Send domain mismatch or DNS incomplete | Send from the verified domain attached to the Motor Block and complete SPF/DKIM setup |
OAuth 2.1 for AI agents (MCP)
Agent clients authorize through OAuth 2.1 with PKCE rather than pasted API keys.
| Endpoint | URL |
|---|---|
| Discovery | https://motorical.com/.well-known/oauth-authorization-server |
| JWKS | https://motorical.com/.well-known/jwks.json |
| Authorize | https://motorical.com/api/oauth2/authorize |
| Token | https://motorical.com/api/oauth2/token |
Requirements:
- PKCE
S256is mandatory.plainis rejected. - Client registration is by Client ID Metadata Document — your
client_idis an HTTPS URL serving a JSON document whose ownclient_idfield equals that URL. Dynamic Client Registration is not supported. - The
resourceparameter is required on both the authorization and token requests, and must be the canonical URI of the MCP server you intend to call. Tokens are bound to that audience and are rejected elsewhere. - Access tokens are RS256, issuer
https://motorical.com, and expire in 1 hour. Refresh tokens rotate on every use.
Users manage granted connections in the dashboard; revoking one takes effect immediately and does not affect their own login sessions.
MCP servers and what each one can do
Motorical serves MCP over Streamable HTTP at https://mcp.motorical.com. Each
server is a separate URI, a separate token audience, and a separate consent —
so connecting the narrow one genuinely limits what an agent can do, rather than
just showing it fewer tools.
| Server | URI | Scopes |
|---|---|---|
| All tools | /v1/motorical/mcp | send:transactional read:analytics read:domains manage:domains manage:sandbox read:webhooks manage:webhooks |
| Transactional | /v1/motorical_transactional/mcp | send:transactional read:analytics |
| Analytics | /v1/motorical_analytics/mcp | read:analytics |
| Domains | /v1/motorical_domains/mcp | read:domains manage:domains |
| Sandbox | /v1/motorical_sandbox/mcp | manage:sandbox |
| Webhooks | /v1/motorical_webhooks/mcp | read:webhooks manage:webhooks |
read:domains and read:webhooks grant only the read-only tools on each server
(e.g. motorical_domain_list, motorical_webhook_list); write tools
(motorical_domain_add, motorical_webhook_create, etc.) still require the
manage:* scope.
Each publishes RFC 9728 metadata at
https://mcp.motorical.com/.well-known/oauth-protected-resource<path>. An
unauthenticated request returns 401 with a WWW-Authenticate header naming
that document, so a client can discover where to authorize without being told
in advance.
A call needing a scope the grant lacks returns 403 with
error="insufficient_scope" and every missing scope in one challenge.
logs.pii is never granted to an MCP client.