Skip to main content

Campaigns API

The Campaigns API lets you create, schedule, and send marketing email campaigns to your customers โ€” promo blasts, season openers, and win-back emails.

Scopesโ€‹

ScopeAllows
campaigns:readList campaigns, get a campaign, list recipients
campaigns:writeCreate, update, delete, send, schedule, unschedule, and duplicate campaigns

Scopes are explicit: a key with only campaigns:write cannot read campaigns. Grant both scopes for full access.

Campaign Lifecycleโ€‹

StatusDescription
draftBeing prepared; editable and deletable
scheduledQueued to send at scheduled_at; editable, can be unscheduled back to draft
sendingDelivery in progress (emails are sent asynchronously)
sentDelivery finished; read-only
cancelledCancelled; read-only, but deletable

Allowed transitions: draft โ†’ scheduled โ†’ sending โ†’ sent, and scheduled โ†’ draft via unschedule. A draft can also be sent immediately with the send endpoint.

State violations return 422 with error code invalid_state, for example:

{
"error": {
"code": "invalid_state",
"message": "Cannot edit a campaign that has already been sent."
},
"meta": {
"request_id": "req_abc123"
}
}

Audience Typesโ€‹

Every campaign targets one of three audiences via audience_type:

ValueDescription
all_customersAll active customers with an email address
selected_customersHand-picked customers (managed in the EquipDash UI)
filterCustomers matching the audience_filter JSON criteria

Audience Filterโ€‹

When audience_type is filter, audience_filter controls who receives the campaign. Two formats are supported.

v1 (legacy)โ€‹

A filter without a v key is a legacy v1 filter: all criteria must match in the same booking.

{
"product_ids": [1, 5],
"experience_ids": [10],
"date_filter_type": "bookingDate",
"date_from": "2026-06-01",
"date_to": "2026-08-31"
}

v2โ€‹

Set "v": 2 for the current format โ€” either a smart-audience preset or a custom filter with independent criteria.

Preset:

{
"v": 2,
"preset": "last_season"
}

Custom:

{
"v": 2,
"product_ids": [1, 5],
"experience_ids": [10],
"date_filter_type": "dateBooked",
"date_from": "2026-01-01",
"date_to": "2026-03-31",
"not_booked_since": "2025-06-01",
"city": "Sydney"
}
KeyTypeDescription
vintegerFilter format version. Omit for legacy v1; set 2 for the current format
presetstringv2 only: recent, booked_two_years, season, locals, opted_in or last_season (see below). Ignores all other keys except the season parameters
product_idsarrayRental product IDs. v2: customer has any booking with any of these products
experience_idsarrayExperience IDs. v2: OR-combined with product_ids
date_filter_typestringdateBooked (booking creation date) or bookingDate (booking start date)
date_fromstringRange start (YYYY-MM-DD, supplier timezone)
date_tostringRange end (YYYY-MM-DD, supplier timezone)
not_booked_sincestringv2 only: exclude customers with any booking after this date (YYYY-MM-DD)
citystringv2 only: match the customer's city (case-insensitive)

Presetsโ€‹

KeyDescription
recentCustomers with a booking in the last 90 days
booked_two_yearsCustomers with a booking made in the last 2 years (your timezone). Leaves out customers who were imported or added by hand and never booked
last_seasonCustomers whose latest booking was 6โ€“18 months ago (win-back audience)
localsCustomers whose city matches your business location
opted_inCustomers whose latest answer to marketing emails is yes (ticked at checkout, signed up on your website, or resubscribed). Someone who said yes and later unsubscribed is left out, and so is anyone who was never asked. Email campaigns only
seasonCustomers with a booking that starts inside a season. Send season_id (one of your seasons) or year (for example 2026) with it

Who gets oneโ€‹

  • Each email address gets one copy, even when several customer records share it (letter case and spaces are ignored). total_recipients counts addresses.
  • An unsubscribe from any of those records stops marketing email to all of them.
  • A customer with no saved language gets your default booking language when the campaign is offered in it, otherwise English.
  • Every campaign email carries List-Unsubscribe and one-click unsubscribe headers, so the inbox shows its own Unsubscribe button.

An unknown preset is refused with 422 Unprocessable Entity. If you send audience_filter without audience_type, the campaign is saved as a filter audience, never as all customers.

Merge Tagsโ€‹

email_subject and email_body support five merge tags, replaced per recipient at send time:

TagReplaced with
{customer_name}The customer's name
{supplier_name}Your business name
{promo_code}Code of the attached promo code (empty if none attached)
{promo_discount}Discount of the attached promo code, e.g. 25% (empty if none attached)
{survey_link}Public URL of the linked survey (empty if none linked)

Promo codes and surveys are attached in the EquipDash UI, not via the API.

Languagesโ€‹

Write email_subject, email_body and cta_text in English. When your booking page offers more languages (Settings > Localization > Booking widget languages), each customer gets the email in their own language: the language the customer last used on your booking page, when your booking page offers it, otherwise English.

  • The email is translated once per language, right before it sends, and reused for every customer who reads that language. Unchanged text is not translated again.
  • Merge tags and links stay exactly as you wrote them.
  • The template's own words (for example the footer and the unsubscribe link) follow the same language.
  • If a translation is not available, that customer gets the English.

There is no API field for this: it applies to every email campaign, including ones created through the API. To check or change a translation, open the campaign's Message tab in EquipDash and pick the language above the subject.

Email Templatesโ€‹

template_key selects the branded email layout:

KeyDescription
plainSimple text-focused layout
announcementAnnouncement layout
promo_blastPromotion-focused layout
season_openerSeason kickoff layout
win_backWin-back layout

Notesโ€‹

  • channel is always email; SMS is planned but not yet available
  • scheduled_at is accepted and returned in UTC (ISO 8601)
  • Emails are delivered asynchronously by a background job; sending can take a few minutes for large audiences
  • Recipients who opted out of marketing emails are skipped and counted as skipped in stats
  • Campaigns are scoped to your account: requesting another account's campaign ID returns 404

API Endpointsโ€‹

MethodEndpointDescriptionScope Required
GET/v1/campaignsList campaignscampaigns:read
GET/v1/campaigns/{id}Get a campaign with statscampaigns:read
GET/v1/campaigns/{id}/recipientsList campaign recipientscampaigns:read
POST/v1/campaignsCreate a campaign (draft)campaigns:write
PATCH/v1/campaigns/{id}Update a campaigncampaigns:write
DELETE/v1/campaigns/{id}Delete a campaigncampaigns:write
POST/v1/campaigns/{id}/sendSend a campaign nowcampaigns:write
POST/v1/campaigns/{id}/scheduleSchedule a campaigncampaigns:write
POST/v1/campaigns/{id}/unscheduleCancel a scheduled sendcampaigns:write
POST/v1/campaigns/{id}/duplicateDuplicate a campaigncampaigns:write
  • Promotions - Attach promo codes to campaigns in the EquipDash UI
  • Customers - Campaign audiences are resolved from your customers