About this page · Updated 2026-10-27 15:04 UTC
What this page is: Customer-facing Marketing Communications Block API endpoints for lists, contacts, templates, campaigns, suppressions, tracking, analytics, and data controls.
How far to trust this page: the build checked 8 names, paths, tools, scopes and error codes against the real product (0 do not match). Reviewed claim by claim on 2026-10-22 (documentation truth audit).
Updated 2026-10-27 15:04 UTCLatest change: Communications MCP: manage workspaces and webhooksRecently updated pages
Communications API Reference
Use the Communications API to manage workspaces, contact lists, subscribers, templates, campaigns, consent, suppressions, and reporting. The base URL is:
https://motorical.com/comm-api/api
Start here
For normal Communications operations you need two values:
- An account API key. In the Motorical dashboard, open Settings → Account
API Keys, create a key, and copy the
ak_live_...value when it is shown. - A workspace ID (also called the tenant ID). In the dashboard, open Comm → Workspaces and use the copy button beside the workspace ID.
You can also discover the workspace ID through the API. This bootstrap request needs only the account API key:
export MOTORICAL_API_KEY="ak_live_YOUR_KEY"
curl -sS "https://api.motorical.com/api/comm/tenant" \
-H "Authorization: ApiKey $MOTORICAL_API_KEY" \
| jq .
The response contains data.tenantId for the default workspace and a
data.workspaces array for every workspace on the account:
{
"success": true,
"data": {
"tenantId": "3f6c1a2e-0000-0000-0000-000000000000",
"workspaces": [
{
"id": "3f6c1a2e-0000-0000-0000-000000000000",
"name": "Default"
}
]
}
}
Save the id of the workspace you want and make your first Communications call:
export COMM_TENANT_ID="3f6c1a2e-0000-0000-0000-000000000000"
curl -sS "https://motorical.com/comm-api/api/lists" \
-H "Authorization: ApiKey $MOTORICAL_API_KEY" \
-H "X-Tenant-Id: $COMM_TENANT_ID" \
-H "Accept: application/json" \
| jq .
jq . pretty-prints the JSON. Do not put a trailing \ after the final command
line. Keep real API keys out of source code, screenshots, and support messages.
Authentication rules
Nearly every customer operation requires both headers:
Authorization: ApiKey ak_live_YOUR_KEY
X-Tenant-Id: YOUR_WORKSPACE_ID
Authorizationis either an account API key (ApiKey ak_live_..., created in the dashboard under Settings → Account API Keys) or a dashboard JWT (Bearer ...).X-Tenant-Idis your workspace ID. “Tenant” and “workspace” refer to the same value in the API.- Account-scoped workspace-management operations are the exception: they use the
account API key but do not require
X-Tenant-Id. The health check requires neither header.
| Situation | Result |
|---|---|
X-Tenant-Id header missing | 400 Missing X-Tenant-Id |
X-Tenant-Id sent without valid Authorization | 401 for an existing, active workspace id (an unknown or inactive id returns 403 first). The workspace header alone is never accepted. |
| Workspace ID does not belong to the authenticated account | 403 |
Campaign create or schedule with a Motor Block that is not marketing type | 400 (error message names the block's type) |
| Campaign create or schedule with a Motor Block not assigned to the workspace | 400 code MOTOR_BLOCK_NOT_ASSIGNED |
| Campaign create or schedule with a Motor Block owned by another account | 403 code MOTOR_BLOCK_FORBIDDEN |
| Another workspace's campaign, list or template id | 404 (the same answer as a missing one) |
| A malformed id in the path | 400 code INVALID_ID |
An account API key plus a workspace is the way to use a marketing Motor Block: marketing blocks have no SMTP login or API key of their own. See Sending with a manual marketing block.
How to read endpoint syntax
{id}is a placeholder. Replace it with the real ID and remove the braces. For campaign IDabc-123,/campaigns/{id}becomes/campaigns/abc-123.- Query parameters follow
?, for example/campaigns?compact=true. GETreads data,POSTcreates or starts an action,PATCHchanges selected fields,PUTreplaces a value or collection, andDELETEremoves data.- Send
Content-Type: application/jsononly when the request has a JSON body. - The tables below show the path after
https://motorical.com.
Internal provisioning routes and public tracking link callbacks are intentionally excluded from this reference. They are used by Motorical services and email links, not by customer API integrations.
Workspace discovery and management
These account-scoped calls require Authorization: ApiKey ak_live_... but do
not require X-Tenant-Id, except for current/motor-blocks as noted.
| Method | Endpoint | Purpose |
|---|---|---|
GET | https://api.motorical.com/api/comm/tenant | Provision if needed and return the default workspace ID plus all workspaces. |
GET | /comm-api/api/workspaces | List workspaces. |
POST | /comm-api/api/workspaces | Create a workspace. |
PATCH | /comm-api/api/workspaces/{id} | Rename a workspace or make it the default. |
GET | /comm-api/api/workspaces/{id}/motor-blocks | List Motor Blocks assigned to a workspace. |
POST | /comm-api/api/workspaces/{id}/motor-blocks/from-domain | Find or create the companion marketing Motor Block for a sending domain. |
POST | /comm-api/api/workspaces/{id}/motor-blocks/add | Find or create the marketing Motor Block for a verified sending domain and add it to the workspace (never replaces or removes; dry_run: true changes nothing). |
PUT | /comm-api/api/workspaces/{id}/motor-blocks | Replace a workspace's Motor Block assignments. |
GET | /comm-api/api/workspaces/current/motor-blocks | List marketing Motor Blocks for the workspace in X-Tenant-Id; this call requires both headers. |
GET | /comm-api/api/workspaces/{id}/settings/consent | Read click-as-consent settings. |
PATCH | /comm-api/api/workspaces/{id}/settings/consent | Change click-as-consent settings. |
GET | /comm-api/api/workspaces/{id}/settings/open-tracking | Read open-tracking settings. |
PATCH | /comm-api/api/workspaces/{id}/settings/open-tracking | Change open-tracking settings. |
GET | /comm-api/api/workspaces/{id}/settings/unsubscribe | Read the unsubscribe landing page of a workspace. |
PATCH | /comm-api/api/workspaces/{id}/settings/unsubscribe | Set or clear it (details). |
Lists and Contacts
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/lists | List contact lists. |
POST | /comm-api/api/lists | Create a list. |
POST | /comm-api/api/lists/from-filter | Create a smart list from filters. |
PATCH | /comm-api/api/lists/{id} | Update a list. |
DELETE | /comm-api/api/lists/{id} | Delete a list. |
DELETE | /comm-api/api/lists | Bulk delete lists. |
GET | /comm-api/api/lists/{id} | One list as a card: description, kind, provenance, computed counts, used_by. |
POST | /comm-api/api/lists/{id}/contacts/bulk | Add up to 500 contacts as JSON, with dry_run and a result for every row. |
POST | /comm-api/api/lists/{id}/contacts/bulk-remove | Take contacts off a list by id (contactIds), or everything matching a search; membership only, the contacts are never deleted. |
POST | /comm-api/api/lists/derive | Make a list from other lists (union, intersection, difference). |
GET | /comm-api/api/lists/compare | Compare two lists (a, b): only in one, in both. |
GET | /comm-api/api/lists/{id}/relations | Where a list came from and what was made from it. |
GET | /comm-api/api/map | The workspace at a glance: lists, campaigns, templates and their connections. |
GET | /comm-api/api/readiness | The first thing missing before a campaign can be sent. |
GET | /comm-api/api/lists/{id}/contacts | List contacts in a list. |
POST | /comm-api/api/lists/{id}/contacts | Add contacts to a list. |
DELETE | /comm-api/api/lists/{id}/contacts/{contactId} | Remove one contact from a list. |
POST | /comm-api/api/lists/{id}/contacts/import | Import contacts from CSV. |
GET | /comm-api/api/lists/{id}/contacts/export | Export contacts from a list. |
POST | /comm-api/api/contacts/upsert | Create or update a contact. |
DELETE | /comm-api/api/contacts/{id} | Delete a contact. |
POST | /comm-api/api/contacts/{id}/unsubscribe | Unsubscribe a contact. |
PUT | /comm-api/api/contacts/{id}/resubscribe | Resubscribe a contact. |
Templates
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/templates | List templates. |
POST | /comm-api/api/templates/validate | Validate and preview a template without saving. Warnings marked blocksScheduling tell you scheduling would be refused. |
POST | /comm-api/api/templates | Create a template. |
GET | /comm-api/api/templates/{id} | Get template content. |
PATCH | /comm-api/api/templates/{id} | Update a template. |
DELETE | /comm-api/api/templates/{id} | Delete a template. |
DELETE | /comm-api/api/templates | Bulk delete templates. |
POST | /comm-api/api/templates/{id}/duplicate | Duplicate a template; the copy remembers its parent (derived_from). |
POST | /comm-api/api/templates/preview | Render a template for a contact or a sample, with and without a name. |
GET | /comm-api/api/templates/rules | The template rulebook, generated from the validator's constants. |
POST | /comm-api/api/templates/lint | Findings (closed vocabulary) with locations and ready fixes. |
POST | /comm-api/api/templates/fix | Apply the safe fixes in memory; never saves. |
POST | /comm-api/api/templates/check-links | Every link with tracking status and problems. |
GET | /comm-api/api/templates/starters | The seven starter skeletons and their slots. |
GET | /comm-api/api/templates/starters/{key} | One starter. |
POST | /comm-api/api/templates/starters/{key}/render | Render a starter with the brand kit; saves nothing. |
POST | /comm-api/api/templates/from-starter | Create a template from a starter and the brand kit. |
GET | /comm-api/api/settings/brand-kit | Get the workspace brand kit. |
PATCH | /comm-api/api/settings/brand-kit | Update the brand kit (partial; null clears a field). |
Campaigns
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/campaigns | List campaigns. Supports compact=true. |
POST | /comm-api/api/campaigns | Create a campaign. |
GET | /comm-api/api/campaigns/{id} | Get campaign details. |
PATCH | /comm-api/api/campaigns/{id}/settings | Update campaign settings; what can change depends on the status. |
POST | /comm-api/api/campaigns/{id}/schedule | Schedule a draft: re-checks and compiles it, then sends now or at scheduled_at. |
POST | /comm-api/api/campaigns/{id}/cancel | Cancel a draft, scheduled or sending campaign. Final. |
POST | /comm-api/api/campaigns/{id}/duplicate | Copy a campaign of any status into a new draft. |
POST | /comm-api/api/campaigns/{id}/test-send | Send the campaign as a test to the account owner only (details). |
DELETE | /comm-api/api/campaigns/{id} | Move a campaign to the recycle bin (a scheduled one is cancelled). |
GET | /comm-api/api/campaigns/deleted | List deleted campaigns. |
PATCH | /comm-api/api/campaigns/{id}/restore | Restore a deleted campaign. |
DELETE | /comm-api/api/campaigns/{id}/permanent | Permanently delete a campaign. |
DELETE | /comm-api/api/campaigns/cleanup | Cleanup old deleted campaigns. |
POST | /comm-api/api/campaigns/{id}/compile | Optional preview of the compiled message and audience. Scheduling compiles by itself. |
GET | /comm-api/api/campaigns/{id}/events | List campaign delivery events. |
GET | /comm-api/api/campaigns/{id}/stats | Campaign statistics, including unsubscribed. |
GET | /comm-api/api/campaigns/{id}/report | Per-recipient delivery report: the source the other result endpoints read. |
GET | /comm-api/api/campaigns/{id}/analytics | Campaign analytics summary. |
GET | /comm-api/api/campaigns/{id}/click-breakdown | URL-level click analytics. |
GET | /comm-api/api/campaigns/{id}/recipients | Campaign recipient list. |
GET | /comm-api/api/campaigns/{id}/recipient-consent | Campaign recipient consent status. |
GET | /comm-api/api/campaigns/{id}/clicks | Campaign click events. |
Recipients
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/recipients | Search recipients across lists/campaigns. |
GET | /comm-api/api/recipients/campaigns | Campaign recipient grouping data. |
GET | /comm-api/api/recipients/facets | Values and counts available for recipient filtering. |
POST | /comm-api/api/recipients/filter | Filter recipients with criteria. |
POST | /comm-api/api/recipients/export | Export filtered recipients. |
POST | /comm-api/api/recipients/bulk-delete | Soft-delete recipients in bulk. |
POST | /comm-api/api/recipients/bulk-delete-by-filter | Soft-delete all recipients matching a filter. |
POST | /comm-api/api/recipients/bulk-move | Move recipients between lists. |
POST | /comm-api/api/recipients/restore | Restore recipients in bulk. |
GET | /comm-api/api/recipients/deleted | List deleted recipients. |
PATCH | /comm-api/api/recipients/{id}/restore | Restore one deleted recipient. |
DELETE | /comm-api/api/recipients/{id}/permanent | Permanently delete a recipient. |
POST | /comm-api/api/recipients/bulk-permanent-delete | Permanently delete several deleted recipients; body { "recipient_ids": [...] }, returns { deleted_count }. Irreversible. |
DELETE | /comm-api/api/recipients/cleanup | Cleanup old deleted recipients. |
Suppressions
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/suppressions | List suppressions. |
POST | /comm-api/api/suppressions | Add a suppression (account-wide). |
DELETE | /comm-api/api/suppressions/{id} | Remove a suppression. A spam-complaint one needs acknowledge_complaint=true. |
POST | /comm-api/api/suppressions/bulk | Bulk import suppressions. |
GET | /comm-api/api/suppressions/stats | Suppression statistics. |
POST | /comm-api/api/suppressions/import-bounces | Import bounces into suppressions. |
Tracking and Unsubscribe Analytics
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/tracking/events | Search visible tracking events. |
GET | /comm-api/api/tracking/export | Export the same visible tracking-event population. |
GET | /comm-api/api/tracking/stats | Counts for the same visible tracking-event population. |
GET | /comm-api/api/unsubscribe-events | List unsubscribe events. |
GET | /comm-api/api/unsubscribe-analytics | Unsubscribe analytics. |
GET | /comm-api/api/unsubscribe-trends | Unsubscribe trends. |
Consent and tracking settings
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/settings/consent | Read click-as-consent setting for the current workspace. |
PATCH | /comm-api/api/settings/consent | Change click-as-consent setting for the current workspace. |
GET | /comm-api/api/settings/open-tracking | Read open-tracking setting for the current workspace. |
PATCH | /comm-api/api/settings/open-tracking | Change open-tracking setting for the current workspace. |
GET | /comm-api/api/settings/unsubscribe | Read the landing page recipients are sent to after unsubscribing. |
PATCH | /comm-api/api/settings/unsubscribe | Set or clear that landing page (details). |
GET | /comm-api/api/consent-events | List consent events. |
GET | /comm-api/api/consent-events/analytics | Consent-event analytics. |
The three tracking endpoints share event_type, campaign_id, contact_email,
date_from, and date_to filters. Comma-separate multiple event types or campaign
IDs. They hide an earlier queued lifecycle row after the same campaign recipient
has a later outcome, so summary counts match the event list and CSV export. Tracking
stats cover all visible events when no date filter is supplied; pass days for an
explicit rolling-day window.
All three also accept view (chronological | recipient, default chronological
-- omitting it keeps today's raw per-event behavior). view=recipient collapses
each campaign recipient to one row at their furthest send-funnel stage (queued,
sent, delivered-or-bounced, opened, clicked); complained and
unsubscribed are returned as independent boolean flags on that row rather than
competing funnel stages, since a recipient can complain or unsubscribe at any point
after delivery regardless of whether they also opened or clicked. In this view,
event_type filters on the recipient's current stage (or flag), not on raw
per-event rows -- exporting is exactly the current view and filters, so a
by-recipient export and a chronological export of the same search never mix rows
from the other population. /stats in recipient view returns by_status (the
non-overlapping funnel breakdown) plus separate complained/unsubscribed counts,
in place of the raw by_type breakdown.
Webhooks
Webhooks tell your server when a campaign completes, a message is delivered, bounces or draws a complaint, or a contact unsubscribes or gives consent. {id} must be your own workspace ID, the same value as X-Tenant-Id. Full guide: Webhooks.
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/workspaces/{id}/webhooks | List the workspace's webhooks. |
POST | /comm-api/api/workspaces/{id}/webhooks | Create a webhook; the signing secret is shown once. |
PATCH | /comm-api/api/workspaces/{id}/webhooks/{webhookId} | Change url, events or enabled. |
DELETE | /comm-api/api/workspaces/{id}/webhooks/{webhookId} | Delete a webhook. |
POST | /comm-api/api/workspaces/{id}/webhooks/{webhookId}/test | Queue a test event. |
Analytics and Data Controls
| Method | Endpoint | Purpose |
|---|---|---|
GET | /comm-api/api/analytics/overview | Communications analytics overview. |
POST | /comm-api/api/gdpr/export | Email a contact their GDPR data export. |
GET | /comm-api/api/gdpr/export | Deprecated form of the same call (it sends mail, so use POST). |
GET | /comm-api/api/health | Communications API health check. |
Response Shape
Most Communications endpoints return:
{
"success": true,
"data": {}
}
Failures return success: false, a sentence in error, and (for the errors a program should react to) a stable code:
{
"success": false,
"error": "This campaign has no eligible recipients (lists are empty, or everyone is unsubscribed, suppressed or not yet eligible).",
"code": "EMPTY_AUDIENCE"
}
Branch on code, not on the text of error. The campaign codes are in Campaigns: Errors; the template codes (TEMPLATE_INVALID, TYPE_IMMUTABLE) are in Templates; the suppression and unsubscribe rules are in Suppressions.
Retrying safely
The create-style calls POST /campaigns, POST /campaigns/{id}/schedule and POST /campaigns/{id}/duplicate accept an Idempotency-Key header. Repeating a request with the same key and body replays the first answer instead of doing the work twice (response header Idempotent-Replay: true). Details: Campaigns: Retrying safely.
Rules that apply to every call
- Campaign and settings writes check ownership. A template, list, Motor Block or campaign from another workspace is refused, never partly used.
- Campaigns have a status machine.
completedandcancelledare final; duplicate a campaign to send it again (states). - The From address is bound to the Motor Block's domain (create rules).
- Merge variables are a closed set. A template that uses a
{{tag}}outside it is refused at scheduling (UNKNOWN_MERGE_VARIABLE, with the offending tags invariables); see Merge variables. Check withPOST /templates/validatefirst. - Every campaign needs
{{unsubscribe_url}}in its template body or scheduling is refused (UNSUBSCRIBE_LINK_MISSING). You can send recipients to your own page afterwards (landing page) and be told about each unsubscribe by webhook. - Suppressions are account-wide and a spam-complaint suppression needs an explicit acknowledgement to remove (details).