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โ
| Scope | Allows |
|---|---|
campaigns:read | List campaigns, get a campaign, list recipients |
campaigns:write | Create, 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โ
| Status | Description |
|---|---|
draft | Being prepared; editable and deletable |
scheduled | Queued to send at scheduled_at; editable, can be unscheduled back to draft |
sending | Delivery in progress (emails are sent asynchronously) |
sent | Delivery finished; read-only |
cancelled | Cancelled; 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:
| Value | Description |
|---|---|
all_customers | All active customers with an email address |
selected_customers | Hand-picked customers (managed in the EquipDash UI) |
filter | Customers 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"
}
| Key | Type | Description |
|---|---|---|
v | integer | Filter format version. Omit for legacy v1; set 2 for the current format |
preset | string | v2 only: recent, booked_two_years, season, locals, opted_in or last_season (see below). Ignores all other keys except the season parameters |
product_ids | array | Rental product IDs. v2: customer has any booking with any of these products |
experience_ids | array | Experience IDs. v2: OR-combined with product_ids |
date_filter_type | string | dateBooked (booking creation date) or bookingDate (booking start date) |
date_from | string | Range start (YYYY-MM-DD, supplier timezone) |
date_to | string | Range end (YYYY-MM-DD, supplier timezone) |
not_booked_since | string | v2 only: exclude customers with any booking after this date (YYYY-MM-DD) |
city | string | v2 only: match the customer's city (case-insensitive) |
Presetsโ
| Key | Description |
|---|---|
recent | Customers with a booking in the last 90 days |
booked_two_years | Customers 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_season | Customers whose latest booking was 6โ18 months ago (win-back audience) |
locals | Customers whose city matches your business location |
opted_in | Customers 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 |
season | Customers 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_recipientscounts 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-Unsubscribeand 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:
| Tag | Replaced 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:
| Key | Description |
|---|---|
plain | Simple text-focused layout |
announcement | Announcement layout |
promo_blast | Promotion-focused layout |
season_opener | Season kickoff layout |
win_back | Win-back layout |
Notesโ
channelis alwaysemail; SMS is planned but not yet availablescheduled_atis 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
skippedin stats - Campaigns are scoped to your account: requesting another account's campaign ID returns
404
API Endpointsโ
| Method | Endpoint | Description | Scope Required |
|---|---|---|---|
GET | /v1/campaigns | List campaigns | campaigns:read |
GET | /v1/campaigns/{id} | Get a campaign with stats | campaigns:read |
GET | /v1/campaigns/{id}/recipients | List campaign recipients | campaigns:read |
POST | /v1/campaigns | Create a campaign (draft) | campaigns:write |
PATCH | /v1/campaigns/{id} | Update a campaign | campaigns:write |
DELETE | /v1/campaigns/{id} | Delete a campaign | campaigns:write |
POST | /v1/campaigns/{id}/send | Send a campaign now | campaigns:write |
POST | /v1/campaigns/{id}/schedule | Schedule a campaign | campaigns:write |
POST | /v1/campaigns/{id}/unschedule | Cancel a scheduled send | campaigns:write |
POST | /v1/campaigns/{id}/duplicate | Duplicate a campaign | campaigns:write |
Related Resourcesโ
- Promotions - Attach promo codes to campaigns in the EquipDash UI
- Customers - Campaign audiences are resolved from your customers