API & Integrations
Everything programmatic lives behind one kind of credential: an API key
from Settings → API keys (/settings/api), scoped to exactly what it
may do. Keys are server-to-server credentials – never ship one in browser
code, a mobile app, or anything a customer can inspect.
Authentication
Every request carries the key in a header – either works:
Authorization: Bearer theaicmo_...
X-Api-Key: theaicmo_...
Keys are shown once at creation. Each key carries scopes (ticked when you
create it) and per-minute/per-day rate limits; 429 responses include a
Retry-After header.
Send a service message
Scope: send:transactional. Receipts, password resets, notifications –
delivered on your transactional identity and never blocked by marketing
opt-outs (a receipt is not a promotion).
curl -X POST https://theaicmo.com/api/send \
-H "X-Api-Key: theaicmo_..." \
-H "Content-Type: application/json" \
-d '{
"channel": "email",
"to": "customer@example.com",
"subject": "Your order shipped",
"html": "<p>It is on the way.</p>",
"idempotency_key": "order-1042-shipped"
}'
Parameters
| Field | Notes |
|---|---|
channel | email, sms, whatsapp, push, or inbox |
to | The recipient – email address or E.164 phone, per channel |
subject | Email only; optional when a library_item_id carries one |
html / text | Inline email content (either or both) |
library_item_id | Send an email built in the platform instead of inline content – see below |
external_id | Your own customer id, used for contact matching and suppression |
category | receipt, order, delivery, payout, security, account, support, legal, other |
client_id | Which business profile sends – required when the key isn't bound to one and the account has several |
idempotency_key | Exactly-once: the same key replays the original result, never a second send |
allow_unknown | true sends to a recipient who is not a contact yet – see below |
Sends go to known customers by default. The recipient must match a
contact on the business profile (by email, phone, or external_id);
anyone else answers 403 unknown_recipient. For flows where the send
legitimately precedes the contact record – a brand-new customer's first
receipt or OTP – pass "allow_unknown": true to override for that one
request:
curl -X POST https://theaicmo.com/api/send \
-H "X-Api-Key: theaicmo_..." \
-H "Content-Type: application/json" \
-d '{
"channel": "email",
"to": "brand-new-customer@example.com",
"subject": "Welcome – your account is ready",
"html": "<p>Your login is ready.</p>",
"allow_unknown": true,
"idempotency_key": "signup-8812-welcome"
}'
Blocked lists, hard suppressions and GDPR erasures apply either way: an
identity erased on request answers 410 identity_erased on every
channel, override or not.
Send an email from your library
Emails you built in the platform – the email designer, the campaign generator, journey drafts – live in your library and can be sent by reference instead of pasting HTML:
curl -X POST https://theaicmo.com/api/send \
-H "X-Api-Key: theaicmo_..." \
-H "Content-Type: application/json" \
-d '{
"channel": "email",
"to": "customer@example.com",
"library_item_id": "<the library item id>",
"idempotency_key": "welcome-cust-8812"
}'
- The item's own subject line is used when it carries one; a
subjectin the request overrides it. Provide one or the other. - Personalization renders for the recipient exactly as a journey send
would render it –
{{p.*}}tokens and survey links resolve to this person before delivery. - Brand-locked. The item must belong to the same business profile
the key resolves to; anything else is refused with
403 wrong_profile. Accounts with several profiles passclient_idexplicitly. A key bound to one brand can never send another brand's asset. - Pass either
library_item_idor inlinehtml/text– not both. - This endpoint stays service-only. An item sent by reference must still be a service message; the content check flags marketing copy riding a service send, exactly as it does for inline content.
The item id is shown on the item's detail panel in the library ("API id" – click to copy).
Trigger a journey
Scope: journeys:trigger. Start a published journey for one person from
your own systems – "order shipped", "deposit made", "ticket resolved".
curl -X POST https://theaicmo.com/api/journeys/trigger \
-H "X-Api-Key: theaicmo_..." \
-H "Content-Type: application/json" \
-d '{
"journey_id": "<the journey id>",
"email": "customer@example.com",
"event": { "order_number": "1042", "amount": 89.90 },
"idempotency_key": "order-1042-shipped"
}'
Event properties (up to 20) reach your message copy as personalization. Every entry door still applies: suppression, contact status, and the journey's own re-entry rules. The journey's id and a ready-made curl live on the journey's trigger settings in the builder.
Loyalty: balance, rewards, redemption
Scope: loyalty. Render points and tier inside your product, list the
rewards shop, and redeem – full walkthrough in the
Loyalty Program guide.
curl -H "X-Api-Key: theaicmo_..." \
"https://theaicmo.com/api/loyalty/balance?identity=customer@example.com"
Redemptions are idempotent (same idempotency_key returns the original
receipt) and can never overdraw a balance.
Run a tool
Scope: tools:execute. Any generation tool, programmatically – same
tools, same credits as in the product.
curl -X POST https://theaicmo.com/api/execute-tool-api \
-H "Authorization: Bearer theaicmo_..." \
-H "Content-Type: application/json" \
-d '{
"tool_id": "social-post-generator",
"parameters": {
"productName": "Aurora Coffee",
"platform": "linkedin",
"variations": 2
}
}'
The response carries the generated output plus credits_used and
credits_remaining. Tool ids and per-tool credit prices are listed on
each tool's page in the product.
Errors
| Status | Meaning |
|---|---|
| 401 | Missing or invalid key |
| 402 | Insufficient credits (tools) or insufficient points (loyalty) |
| 403 | Key lacks the required scope, the resource belongs to a different business profile (wrong_profile), or the recipient is not a known contact (unknown_recipient – see allow_unknown) |
| 404 | Resource not found or not yours |
| 409 | Idempotency conflict – same key, different payload |
| 410 | Identity erased on request (GDPR) – cannot be messaged or enrolled |
| 422 | No verified sending identity, or the referenced library item has no sendable content |
| 429 | Rate limited – honor Retry-After |
Error bodies are JSON with a stable error code and a human message.