Marketing
Endpoints for the Marketing tools: your campaign dashboard, audience and
segments, campaigns, templates, automations (“journeys”) and sender identity.
See Marketing for the full feature guide. Every
endpoint here is operator (client role) and scoped to your own
account - send an Authorization: Bearer token from the
Authentication flow.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose | Auth |
|---|---|---|---|
| GET | /api/my-marketing/summary |
Dashboard: KPIs, funnel, recent and scheduled sends | operator |
| GET | /api/my-marketing/usage |
Per-channel send allowance for a billing period | operator |
| GET | /api/my-marketing/audience |
Preview who a send would reach | operator |
| GET | /api/my-marketing/audience-insights |
Richer reach and engagement analytics | operator |
| GET | /api/my-marketing/segments |
List saved audience segments | operator |
| POST | /api/my-marketing/segments |
Save a segment | operator |
| PUT | /api/my-marketing/segments/:id |
Update a saved segment | operator |
| DELETE | /api/my-marketing/segments/:id |
Delete a saved segment | operator |
| POST | /api/my-marketing/segments/preview |
Preview sample guests for ad-hoc rules | operator |
| POST | /api/my-marketing/segments/reach |
Live reach count for ad-hoc rules | operator |
| GET | /api/my-marketing/segment-attrs |
Fields and operators available to build a segment | operator |
| GET | /api/my-marketing/campaigns |
List campaigns | operator |
| POST | /api/my-marketing/campaigns |
Create a draft campaign | operator |
| GET | /api/my-marketing/campaigns/:id |
Read one campaign, with recipients | operator |
| PUT | /api/my-marketing/campaigns/:id |
Update a draft campaign | operator |
| DELETE | /api/my-marketing/campaigns/:id |
Delete a campaign | operator |
| POST | /api/my-marketing/campaigns/:id/duplicate |
Clone a campaign into a new draft | operator |
| POST | /api/my-marketing/campaigns/:id/send |
Send or schedule a campaign | operator |
| POST | /api/my-marketing/campaigns/:id/pause |
Pause a sending/scheduled campaign | operator |
| POST | /api/my-marketing/campaigns/:id/resume |
Resume a paused campaign | operator |
| POST | /api/my-marketing/campaigns/:id/cancel |
Cancel a campaign | operator |
| GET | /api/my-marketing/templates |
List email templates | operator |
| POST | /api/my-marketing/templates |
Create a template | operator |
| GET | /api/my-marketing/templates/:id |
Read one template | operator |
| PUT | /api/my-marketing/templates/:id |
Update a template | operator |
| DELETE | /api/my-marketing/templates/:id |
Delete a template | operator |
| POST | /api/my-marketing/templates/:id/duplicate |
Clone a template | operator |
| GET | /api/my-marketing/journeys |
List automations | operator |
| POST | /api/my-marketing/journeys |
Create an automation | operator |
| PATCH | /api/my-marketing/journeys/:id |
Edit an automation, or toggle it on/off | operator |
| DELETE | /api/my-marketing/journeys/:id |
Delete an automation | operator |
| PATCH | /api/my-marketing/journeys/:id/venues/:venueId |
Per-venue on/off override | operator |
| POST | /api/my-marketing/images |
Upload an image for templates/campaigns | operator |
| POST | /api/my-marketing/preview-render |
Render final campaign HTML | operator |
| POST | /api/my-marketing/test-send |
Send a test email | operator |
| GET | /api/my-marketing/settings |
Marketing-wide settings (double opt-in) | operator |
| POST | /api/my-marketing/settings |
Update marketing-wide settings | operator |
| GET | /api/my-marketing/sender-identity |
The address campaigns send from | operator |
| GET | /api/my-operator/marketing |
Operator-level sender defaults | operator |
| POST | /api/my-marketing/aimy/quietfill-draft |
Aimy: draft a campaign for a quiet slot | operator, Aimy add-on |
| POST | /api/my-marketing/aimy/section |
Aimy: generate an email section from a prompt | operator, Aimy add-on |
Dashboard summary
Section titled “Dashboard summary”GET /api/my-marketing/summary is the Marketing dashboard: send/delivery/open/click
totals, a funnel through to reservations and revenue, your best-performing
and upcoming scheduled campaigns, automation health, a per-channel
breakdown, and a daily/weekly/monthly series for charting. days is 7,
30, 90 or 365 (default 30).
curl "https://thesidedoor.co/api/my-marketing/summary?days=30" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-marketing/summary?days=30", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-marketing/summary?days=30");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "kpi": { "sent": 4200, "delivered": 4100, "opened": 1800, "clicked": 420, "open_rate": 44, "click_rate": 10, "revenue_pence": 86000 }, "recent": [{ "id": "cmp_1", "name": "Summer launch", "status": "sent", "sent_at": "2026-07-01T09:00:00Z" }], "scheduled": [], "channels": { "email": { "sent": 3800 }, "sms": { "sent": 400 }, "push": { "sent": 0 } }}Usage and allowance
Section titled “Usage and allowance”GET /api/my-marketing/usage returns how much of this billing period’s
per-channel send allowance you’ve used. period defaults to the current
period.
curl "https://thesidedoor.co/api/my-marketing/usage" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch("https://thesidedoor.co/api/my-marketing/usage", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` },});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-marketing/usage");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "channel": "email", "used": 3800, "allowance": 10000, "remaining": 6200 }Hitting 0 remaining doesn’t block this read - it blocks the send endpoints
elsewhere in the API.
Audience preview and insights
Section titled “Audience preview and insights”GET /api/my-marketing/audience previews who a send would reach for a given
audience type (all, recent, lapsed, venue or segment), with
totals, how many have opted out, and a short sample of recipients. GET /api/my-marketing/audience-insights returns the richer analytics version of
the same scope: suppression, estimated volume, historical open/click rates
and a new-vs-returning breakdown. Both take months, days, venueId,
segmentId and channel (email, sms or push) to narrow the audience.
curl "https://thesidedoor.co/api/my-marketing/audience?type=recent&months=3&channel=email" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-marketing/audience?type=recent&months=3&channel=email", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-marketing/audience?type=recent&months=3&channel=email");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "totalGuests": 1840, "deliverable": 1790, "optedOut": 50, "eligibleEstimate": 1790, "sample": [{ "name": "Sam Lee", "contact": "sam@example.com" }], "credits": { "used": 3800, "allowance": 10000, "remaining": 6200 }}sample shows real guest names and contact details, so swap in your own
fake values when trying this - don’t publish a real recipient list.
Segments
Section titled “Segments”GET /api/my-marketing/segments lists saved audience segments. GET /api/my-marketing/segment-attrs returns the schema used to build one: the
fields you can filter on, the comparison operators available for each, and
supporting dropdown data (guest tags, loyalty tags, months, weekdays) -
useful if you’re building your own segment-rule editor against the segments
API.
curl "https://thesidedoor.co/api/my-marketing/segments" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch("https://thesidedoor.co/api/my-marketing/segments", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` },});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-marketing/segments");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "data": [{ "id": "seg_1", "name": "Big spenders", "rules": [{ "field": "total_spend_pence", "op": "gte", "value": 50000 }] }] }POST /api/my-marketing/segments saves a reusable segment - send { "name", "match": "all" | "any", "rules": [...] }, where each rule names a field
from /segment-attrs, an op, and a value. PUT /api/my-marketing/segments/:id updates a saved segment’s name or rules;
DELETE /api/my-marketing/segments/:id removes it. Before saving, POST /api/my-marketing/segments/preview takes the same { match, rules } shape
for an ad-hoc (unsaved) rule set and returns a reach estimate plus up to 20
sample guests; POST /api/my-marketing/segments/reach returns just the live
count, useful while a rule editor is still being adjusted.
curl -X POST "https://thesidedoor.co/api/my-marketing/segments" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Big spenders", "match": "all", "rules": [{"field": "total_spend_pence", "op": "gte", "value": 50000}]}'
curl -X POST "https://thesidedoor.co/api/my-marketing/segments/reach" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"match": "all", "rules": [{"field": "total_spend_pence", "op": "gte", "value": 50000}]}'const segment = await fetch("https://thesidedoor.co/api/my-marketing/segments", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Big spenders", match: "all", rules: [{ field: "total_spend_pence", op: "gte", value: 50000 }], }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-marketing/segments");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "name" => "Big spenders", "match" => "all", "rules" => [["field" => "total_spend_pence", "op" => "gte", "value" => 50000]], ]),]);$segment = json_decode(curl_exec($ch), true);{ "ok": true, "id": "seg_1" }{ "reach": 214, "sample": [{ "name": "Sam Lee", "contact": "sam@example.com" }] }Campaigns
Section titled “Campaigns”GET /api/my-marketing/campaigns lists campaigns with their results, sortable
and searchable with q, sort, dir, channel, status, limit (max
200) and page. GET /api/my-marketing/campaigns/:id reads one campaign in
full, including its recipients (up to 500) and, for an A/B test, each
variant’s performance.
curl "https://thesidedoor.co/api/my-marketing/campaigns?status=sent&limit=50" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"
curl "https://thesidedoor.co/api/my-marketing/campaigns/cmp_1" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const list = await fetch( "https://thesidedoor.co/api/my-marketing/campaigns?status=sent&limit=50", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());
const one = await fetch( "https://thesidedoor.co/api/my-marketing/campaigns/cmp_1", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-marketing/campaigns?status=sent&limit=50");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$list = json_decode(curl_exec($ch), true);{ "data": [ { "id": "cmp_1", "name": "Summer launch", "channel": "email", "status": "sent", "total_recipients": 1800, "sent_count": 1800, "opened_count": 790, "clicked_count": 180 } ]}{ "id": "cmp_1", "name": "Summer launch", "channel": "email", "subject": "Summer's here", "status": "sent", "recipients": [{ "contact": "sam@example.com", "status": "opened", "open_count": 1 }]}contact on a recipient row is a real guest email or phone - fake it in your
own examples.
POST /api/my-marketing/campaigns creates a draft - send { "name", "subject", "channel": "email" | "sms" | "push", "body_html" (or body_text), "template_id"? }, plus ab for an A/B test and sender
overrides (from_name, reply_to, …) if you don’t want the account
defaults. PUT /api/my-marketing/campaigns/:id updates a campaign with the
same body shape, but only while it’s still a draft (409 otherwise). POST /api/my-marketing/campaigns/:id/duplicate clones a campaign’s content and
settings into a fresh draft (not its audience or stats). DELETE /api/my-marketing/campaigns/:id removes a campaign (409 while it’s
sending).
POST /api/my-marketing/campaigns/:id/send sends now, or schedules it with
{ "scheduled_at" }; pass segment to target an audience the same way as
audience preview. It enforces your plan’s
send limits (402 with over_limit if you’re out of allowance). Once
sending or scheduled, POST .../pause pauses it, POST .../resume restarts
or re-schedules it, and POST .../cancel cancels it for good.
curl -X POST "https://thesidedoor.co/api/my-marketing/campaigns" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Summer launch", "subject": "Summer'"'"'s here", "channel": "email", "body_html": "<p>...</p>"}'
curl -X POST "https://thesidedoor.co/api/my-marketing/campaigns/cmp_1/send" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"segment": {"type": "all"}}'const draft = await fetch("https://thesidedoor.co/api/my-marketing/campaigns", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Summer launch", subject: "Summer's here", channel: "email", body_html: "<p>...</p>", }),}).then((r) => r.json());
await fetch(`https://thesidedoor.co/api/my-marketing/campaigns/${draft.id}/send`, { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ segment: { type: "all" } }),});$ch = curl_init("https://thesidedoor.co/api/my-marketing/campaigns");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "name" => "Summer launch", "subject" => "Summer's here", "channel" => "email", "body_html" => "<p>...</p>", ]),]);$draft = json_decode(curl_exec($ch), true);{ "ok": true, "id": "cmp_1" }{ "ok": true, "sending": true, "recipients": 1800 }A scheduled send instead returns { "ok": true, "scheduled": true, "recipients": 1800 }.
Templates
Section titled “Templates”GET /api/my-marketing/templates lists your reusable email templates. GET /api/my-marketing/templates/:id reads one, including its body_html. The
design field on a template is the visual builder’s own internal model -
treat it as opaque and edit templates through the portal rather than
hand-authoring it.
curl "https://thesidedoor.co/api/my-marketing/templates" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch("https://thesidedoor.co/api/my-marketing/templates", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` },});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-marketing/templates");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "data": [{ "id": "tpl_1", "name": "Welcome email", "thumbnail_url": "https://...", "system_key": null }] }POST /api/my-marketing/templates creates a template - send { "name", "body_html", "design"?, "thumbnail_url"? }. PUT /api/my-marketing/templates/:id updates one; DELETE /api/my-marketing/templates/:id deletes one (400 if it’s one of
Sidedoor’s non-deletable default templates). POST /api/my-marketing/templates/:id/duplicate clones a template under a new id.
curl -X POST "https://thesidedoor.co/api/my-marketing/templates" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Welcome email", "body_html": "<p>...</p>"}'const template = await fetch("https://thesidedoor.co/api/my-marketing/templates", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Welcome email", body_html: "<p>...</p>" }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-marketing/templates");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["name" => "Welcome email", "body_html" => "<p>...</p>"]),]);$template = json_decode(curl_exec($ch), true);{ "ok": true, "id": "tpl_1" }Automations (journeys)
Section titled “Automations (journeys)”GET /api/my-marketing/journeys lists your automations: triggered sends
like a welcome email or a post-visit review request, each with its trigger,
channel(s) and send counts, plus any per-venue on/off overrides.
curl "https://thesidedoor.co/api/my-marketing/journeys" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch("https://thesidedoor.co/api/my-marketing/journeys", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` },});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-marketing/journeys");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "data": [ { "id": "jrn_1", "name": "Post-visit review request", "channel": "email", "trigger_type": "post_visit", "active": true, "sent_count": 920, "opened_count": 410 } ]}POST /api/my-marketing/journeys creates an automation - send { "name", "trigger_type", "channels": { "email"?, "sms"?, "push"? }, ... } plus the
timing fields for that trigger (e.g. days/hours after the trigger
fires). PATCH /api/my-marketing/journeys/:id edits an automation’s name,
content or timing, or just toggles it with { "active": boolean }. DELETE /api/my-marketing/journeys/:id deletes an automation and any per-venue
overrides it has. If an automation’s global switch is off, PATCH /api/my-marketing/journeys/:id/venues/:venueId with { "active": true }
turns it on for one venue only (409 if the global switch is already on -
turn that off first).
curl -X POST "https://thesidedoor.co/api/my-marketing/journeys" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Post-visit review request", "trigger_type": "post_visit", "days": 1, "channels": {"email": true}}'const journey = await fetch("https://thesidedoor.co/api/my-marketing/journeys", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Post-visit review request", trigger_type: "post_visit", days: 1, channels: { email: true }, }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-marketing/journeys");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "name" => "Post-visit review request", "trigger_type" => "post_visit", "days" => 1, "channels" => ["email" => true], ]),]);$journey = json_decode(curl_exec($ch), true);{ "ok": true, "id": "jrn_1" }Settings and sender identity
Section titled “Settings and sender identity”GET /api/my-marketing/settings returns double_optin - whether a new
email subscriber must confirm before you can send to them; POST /api/my-marketing/settings sets it with { "double_optin": boolean }. GET /api/my-marketing/sender-identity returns the address your campaigns send
from: sending_address, from_name and reply_to. GET /api/my-operator/marketing is the operator-wide counterpart - the same kind
of sender defaults, plus your SMS sending number and its compliance status,
read via your general operator-settings permissions rather than the
marketing permission family above.
curl "https://thesidedoor.co/api/my-marketing/sender-identity" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"
curl "https://thesidedoor.co/api/my-operator/marketing" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const identity = await fetch( "https://thesidedoor.co/api/my-marketing/sender-identity", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());
const operator = await fetch( "https://thesidedoor.co/api/my-operator/marketing", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-marketing/sender-identity");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$identity = json_decode(curl_exec($ch), true);{ "sending_address": "the-blue-room@mail.thesidedoor.co", "from_name": "The Blue Room", "reply_to": "hello@blueroom.example" }{ "from_name": "The Blue Room", "from_email": "the-blue-room@mail.thesidedoor.co", "reply_to": "hello@blueroom.example", "sms_from_number": "+44...", "sms_compliance_status": "approved"}curl -X POST "https://thesidedoor.co/api/my-marketing/settings" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"double_optin": true}'await fetch("https://thesidedoor.co/api/my-marketing/settings", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ double_optin: true }),});$ch = curl_init("https://thesidedoor.co/api/my-marketing/settings");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["double_optin" => true]),]);$data = json_decode(curl_exec($ch), true);{ "ok": true, "double_optin": true }Images, previewing and test sends
Section titled “Images, previewing and test sends”POST /api/my-marketing/images uploads an image for use in a template or
campaign - send a base64 data URL (JPEG, PNG, GIF or WebP, 3MB max) as
{ "image": "data:image/png;base64,..." } and get back a hosted url to
reference in your HTML. POST /api/my-marketing/preview-render renders a
campaign’s final HTML exactly as a real send would - merge fields resolved
and the compliance footer added, with tracking disabled - from { "body_html", "subject", "preview_text" }, so you can show an accurate
preview without actually sending anything. POST /api/my-marketing/test-send sends a one-off test email of { "subject", "body_html", "reply_to"? } to an address (to, defaulting to your own
account email), with merge fields resolved against sample data.
curl -X POST "https://thesidedoor.co/api/my-marketing/preview-render" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"body_html": "<p>Hi {{first_name}}</p>", "subject": "Summer'"'"'s here", "preview_text": "Don'"'"'t miss out"}'
curl -X POST "https://thesidedoor.co/api/my-marketing/test-send" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"subject": "Summer'"'"'s here", "body_html": "<p>Hi {{first_name}}</p>"}'const rendered = await fetch("https://thesidedoor.co/api/my-marketing/preview-render", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ body_html: "<p>Hi {{first_name}}</p>", subject: "Summer's here", preview_text: "Don't miss out", }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-marketing/preview-render");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "body_html" => "<p>Hi {{first_name}}</p>", "subject" => "Summer's here", "preview_text" => "Don't miss out", ]),]);$rendered = json_decode(curl_exec($ch), true);{ "ok": true, "url": "https://assets.thesidedoor.co/marketing/img_1.png" }{ "ok": true, "html": "<html>...</html>", "subject": "Summer's here" }{ "ok": true, "sent_to": "you@example.com" }Aimy-assisted content
Section titled “Aimy-assisted content”Two endpoints use the Aimy add-on (metered, see Aimy, your portal
assistant) to help write marketing content. POST /api/my-marketing/aimy/quietfill-draft drafts a campaign to fill a specific
quiet slot - send { "venue", "weekday", "band" } (the day and time band
you want to fill) and get back a draft plus token usage. POST /api/my-marketing/aimy/section turns a plain-language instruction into one
email-safe HTML section for the campaign composer - send { "prompt" }
(600 characters max). Both return 403 if the add-on isn’t enabled or
you’re out of allowance for the period.
curl -X POST "https://thesidedoor.co/api/my-marketing/aimy/section" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"prompt": "A short section inviting guests back for happy hour on Fridays"}'const section = await fetch("https://thesidedoor.co/api/my-marketing/aimy/section", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ prompt: "A short section inviting guests back for happy hour on Fridays", }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-marketing/aimy/section");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["prompt" => "A short section inviting guests back for happy hour on Fridays"]),]);$section = json_decode(curl_exec($ch), true);{ "html": "<table>...</table>", "tokens": 180, "cached": false, "usage": { "allowance": 5000, "used": 1380, "remaining": 3620 }}quietfill-draft returns the same tokens/cached/usage shape alongside
draft, the generated campaign content for that slot.