Skip to main content

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)​

CredentialShape / how obtainedUse forDo not use for
Dashboard JWTAuthorization: Bearer … from login / set-password (~15m access + 7d refreshToken)/api/auth/*, /api/domains*, /api/developer/sandbox*, web handoffPOST /v1/send, Public Analytics /api/public/v1/*
Motor Block mk_live_ApiKey from sandbox provision or Motor Block keys UIPOST /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 grantMint 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:

MethodPath
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.

MethodPath
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 surfaceBase URLAuth mechanismPrimary use
Dashboard / onboarding APIshttps://api.motorical.com/api/auth, /api/domains, /api/developer/sandboxDashboard JWT (Bearer)Signup, domains, sandbox, check-dns
Public Analytics APIhttps://api.motorical.com/api/public/v1Short-lived bearer token, or MCP OAuth/Delegation grantLogs, analytics, configuration, webhooks, exports, SSE, domain-health, domain management (account-wide)
Token minting APIhttps://api.motorical.com/api/publicAccount API Key or dashboard JWTCreate short-lived public API bearer tokens
HTTP Send APIhttps://api.motorical.com/v1/sendMotor Block API KeySend production email over HTTPS
Email validationhttps://api.motorical.com/api/email/validateNo auth for single validationValidate 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.

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​

  1. Go to Settings -> API Access in your Motorical dashboard
  2. Select a Motor Block
  3. Check the permissions you need
  4. 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​

ScopeAccess
logs.readSearch and read email delivery logs
analytics.readAccess metrics, providers, error codes, daily summaries
usage.readView rate limits and usage counters
config.readRead motor block configuration, domain list, and domain health
config.manageAdd, verify, and check DNS for sending domains (persists to the domain; implies config.read)
logs.piiAccess unmasked recipient email addresses in logs
webhooks.readList webhook endpoints, deliveries, and stats
webhooks.manageCreate, update, delete, and test webhook endpoints (implies webhooks.read)
tip

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​

StatusMeaningFix
401 UnauthorizedToken missing, invalid, or expiredGenerate a new token
403 ForbiddenToken lacks the required scopeRegenerate with the needed scope
403 ForbiddenMotor Block ID mismatchToken is bound to a different Motor Block
401 UnauthorizedAPI key format is invalidUse an ak_live_... account key for token minting or an mk_live_... Motor Block key for sending
403 ForbiddenSend domain mismatch or DNS incompleteSend 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.

EndpointURL
Discoveryhttps://motorical.com/.well-known/oauth-authorization-server
JWKShttps://motorical.com/.well-known/jwks.json
Authorizehttps://motorical.com/api/oauth2/authorize
Tokenhttps://motorical.com/api/oauth2/token

Requirements:

  • PKCE S256 is mandatory. plain is rejected.
  • Client registration is by Client ID Metadata Document — your client_id is an HTTPS URL serving a JSON document whose own client_id field equals that URL. Dynamic Client Registration is not supported.
  • The resource parameter 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.

ServerURIScopes
All tools/v1/motorical/mcpsend:transactional read:analytics read:domains manage:domains manage:sandbox read:webhooks manage:webhooks
Transactional/v1/motorical_transactional/mcpsend:transactional read:analytics
Analytics/v1/motorical_analytics/mcpread:analytics
Domains/v1/motorical_domains/mcpread:domains manage:domains
Sandbox/v1/motorical_sandbox/mcpmanage:sandbox
Webhooks/v1/motorical_webhooks/mcpread: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.