SplashSend API

The SplashSend REST API lets your own systems work with your people, lists, custom events, orders, products and data sets.

Authentication: every request needs a key or a token as a Bearer token: Authorization: Bearer sk_live_…. An app for your own account (a private app) uses its key (sk_live_…). Make one under Apps, then Apps you've made, or in Setup → API keys. An app for every SplashSend account (a public app) uses the access token (ssa_…) each account gives it when it connects with OAuth 2.1 and PKCE: see Connecting a public app. A key or a token belongs to one account and carries scopes. Each endpoint below names the scope it needs, and a request without it gets a 403 with the code forbidden. A key made under Apps is refused when it's sent from a web page (a request with an Origin header), with the code key_in_web_page: call SplashSend from a server.

Test mode: a new private app starts in test mode, and so does a public app on its maker's own account. Its requests work, but nothing it does starts a drip, sends an email or sends a webhook until it's switched to live on the app's page in SplashSend.

People access: a few fields show who someone is, and are sent only to a key with contacts:read. They are an order's email, an event's email, a list member's email and name, and data-set columns holding email addresses. Without that scope those fields are left out, and filtering events by email gets a 403. A write's response still shows an address the request itself sent.

Idempotency: POST /contacts, POST /contacts/import, POST /events, POST /orders, POST /lists, POST /data and POST /data/{key}/rows accept an Idempotency-Key header. Send a new key with each new request, and the same key when you retry it. A retry with the same key and the same request, body byte for byte, gets the first response back, and nothing is done twice. The same key on a different request gets a 422 (idempotency_key_reused). A retry sent while the first request is still running gets a 409 (idempotency_in_progress) with Retry-After: 5. A request that fails doesn't keep its key, so you can retry it with the same one. Keys are kept for 7 days.

Rate limit: 100 requests a minute per key, and per public app on each account. Over that you get a 429 (rate_limited) with Retry-After: 60. A request refused for a missing scope still counts.

Body size: a request body can be up to 256KB, or 1MB for POST /contacts/import and 4MB for POST /data/{key}/rows. The limit applies to the bytes received, with or without a Content-Length header. A larger body gets a 413 (payload_too_large).

Errors: every error has the body { "error": { "code": "…", "message": "…" } }. The message says what went wrong in plain words, and the codes are listed below.

Webhooks: SplashSend can also send events to your own systems as they happen. See Webhooks at the end of this page, and add endpoints in Setup → Webhooks.

AI assistants: the same API keys connect an AI assistant to your account over MCP, at https://splashsend.com/api/mcp. Its calls count towards the key's rate limit. See Connecting an AI assistant.

Building with an AI tool: this reference is also written for AI tools to read, at https://splashsend.com/developers/llms.txt, and as an MCP server your editor can search, with no key, at https://splashsend.com/api/mcp/docs.

Errors
StatusCodeMeaning
400 validation_error A field is missing, or has a value the endpoint can't take.
400 invalid_request The body isn't valid JSON, or isn't the shape the endpoint expects.
400 read_only The data set is built in or read from a connected source, so it can't be changed this way.
400 limit_reached A cap was reached, such as 5,000 rows in a data set, 100 data sets, 500 lists in an account or 50 subscriptions for an app.
400 sender_not_verified A campaign can't be sent or scheduled until the account's sender email is verified in Setup.
400 design_not_ready The design can't be sent as it is, such as one without an unsubscribe link. The message says what to fix.
400 send_refused The campaign couldn't be sent, for the reason the message gives.
401 unauthorized The key or token is missing, wrong, expired or revoked, or the app was removed.
403 forbidden The key or token doesn't have the scope this request needs.
403 key_in_web_page A key made under Apps was sent from a web page, where anyone can copy it. Call SplashSend from a server, and make a new key for the app.
403 plan_limit_reached Your plan's people limit is reached, so a new person can't be added, or its sends for the month are used up.
403 not_open_yet The account isn't open for sending yet.
404 not_found The person, list, data set or row isn't in your account.
409 stopped_address The address is stopped because it bounced or was reported as spam, so it can't be subscribed.
409 sent_recently This design went, or is scheduled to go, to the same people within a day, so it wasn't sent again.
409 idempotency_in_progress A request with this Idempotency-Key is still running. Wait for the Retry-After header (5 seconds), then retry.
413 payload_too_large The body is over the size limit.
422 idempotency_key_reused This Idempotency-Key was already used for a different request. Send a new key.
429 rate_limited The key, or the public app on this account, made more than 100 requests in a minute. Wait for the Retry-After header (60 seconds), then retry.
500 internal_error Something went wrong on our side.

Base URL https://splashsend.com · Download the OpenAPI spec to use in Postman, Insomnia or your own client.

Account

Which account a key or token is for, and what it may do.

GET/api/v1/me

Who this key or token is for

The account's name, the app the key or token belongs to, whether that app is in test mode, and what it may do. Any working key or token can ask, whatever it may do. account is the brand's or sender's name, and null for an account with neither.

Responses
  • 200 Who the key or token is for.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.account string or null The account's name, as people see it on its emails.
data.app string or null The app the key or token belongs to.
data.test_mode boolean True while the app is in test mode: nothing it does starts a drip, sends an email or sends a webhook.
data.scopes array of string What it may do.
Example
curl -X GET "https://splashsend.com/api/v1/me" \
  -H "Authorization: Bearer sk_live_…"

People

The people on your list. Their details, such as a birthday, are called properties here, and are named the way they are in SplashSend.

GET/api/v1/contacts

List people

Needs the contacts:read scope.

Newest first.

Parameters
NameType
page integer · query The page to read, from 1. default: 1 minimum: 1
limit integer · query How many to read on a page, up to 100. default: 25 minimum: 1 maximum: 100
status "subscribed" | "unsubscribed" · query Only people with this status.
email string · query Only the person at this address, to look someone up before changing them. format: email
Responses
  • 200 A page of people.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of Person
meta PageMeta Paging details for a list.
Example
curl -X GET "https://splashsend.com/api/v1/contacts" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/contacts

Add or update a person

Needs the contacts:write scope.

Adds a person by email address, subscribed, or updates the person already at that address.

Someone already here who never subscribed, for example someone first seen through an event or an order, is subscribed, and this request is recorded as their consent. Someone who unsubscribed stays unsubscribed unless you send "resubscribe": true, so only send it when they've asked to hear from you again. A stopped address, one that bounced or was reported as spam, is never subscribed.

When this request subscribes someone, their welcome drips start and contact.subscribed is sent, unless you send "startAutomations": false. source is set only for someone new. To add many people at once, use POST /contacts/import.

preferences switches on the email topics you name. For someone new, the topics you don't name are switched off. For someone already here, nothing is switched off.

meta says what happened. warnings explains a kept opt-out, a stopped address and anything that was skipped.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
emailrequired string Their email address. format: email
firstName string Their first name.
lastName string Their last name.
name string Their full name, split into first and last. Ignored when firstName or lastName is sent.
source string Where they came from, such as website. Set only for someone new.
properties PropertyValues Details keyed by name, as they're called in SplashSend (any case).
preferences array of string The names of the email topics they chose.
resubscribe boolean Subscribe someone who unsubscribed. Send it only when they've asked to hear from you again. default: false
startAutomations boolean Start their welcome drips and send contact.subscribed when this request subscribes them, and start drips that watch the details you set. Send false for someone from your records rather than a new sign-up. default: true
Responses
  • 200 They were already here, and are updated.
  • 201 They're new, and have been added.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The key doesn't have contacts:write (forbidden), or adding them would go over your plan's people limit (plan_limit_reached).
  • 409 A request with this Idempotency-Key is still running (idempotency_in_progress). Retry after the Retry-After header's 5 seconds.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200 or 201
FieldType
data Person A person, as every People endpoint returns them.
meta object
meta.created boolean True when they're new.
meta.subscribed boolean True when this request subscribed them.
meta.suppressed boolean True when the address is stopped, so it wasn't subscribed.
meta.keptOptOut boolean True when they had unsubscribed and still are, because resubscribe wasn't true.
warnings array of string Notes for you, such as a kept opt-out or details that were skipped. The field is left out when there are none.
Example
curl -X POST "https://splashsend.com/api/v1/contacts" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "sam@example.com",
    "firstName": "Sam",
    "lastName": "Lee",
    "source": "website",
    "properties": {
      "Birthday": "1990-05-15"
    },
    "preferences": [
      "Newsletter"
    ]
  }'
POST/api/v1/contacts/import

Import people

Needs the contacts:write scope.

Adds or updates up to 500 people in one request, for a migration or a backfill.

Someone new is added subscribed, with consent recorded, unless their row says "subscriptionStatus": "unsubscribed", for example someone who had opted out of your old email service. Someone already here keeps their subscription status and source, so an import never brings back someone who unsubscribed. Their name and details are updated from the row.

Automations are off unless you send "startAutomations": true. Without it nobody starts a welcome drip or a drip that watches a detail or a list, and contact.subscribed isn't sent.

Each row is handled on its own. A row that fails is listed in meta.errors and the rest carry on. If your plan's people limit is reached, the import stops: meta.planLimitReached is true, and meta.stoppedAtIndex is the row it stopped at. That row and the rows after it weren't added. To add everyone to a list, send listId, which needs lists:write as well.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
contactsrequired array of ImportContactInput The people, up to 500. max items: 500
startAutomations boolean Start welcome drips, drips that watch a detail or a list, and contact.subscribed for these people. Leave it out for people from your records. default: false
listId string Add everyone imported to this list. Needs lists:write.
source string The source for someone new whose row has none. Defaults to api_import.
Responses
  • 200 The import ran. Rows that failed are listed in meta.errors.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The key doesn't have contacts:write, or you sent listId and it doesn't have lists:write.
  • 404 The list in listId isn't in your account.
  • 409 A request with this Idempotency-Key is still running (idempotency_in_progress). Retry after the Retry-After header's 5 seconds.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.created integer People added.
data.updated integer People already here, updated.
data.failed integer Rows that weren't saved.
data.addedToList integer People added to the list. The field is there only when you sent listId.
meta object
meta.startAutomations boolean Whether automations ran.
meta.errors array of object Up to 50 rows that failed.
meta.errors[].index integer The row's position in contacts, from 0.
meta.errors[].email string The row's email address, when it had one.
meta.errors[].message string Why it failed.
meta.unknownProperties array of string Detail names that aren't in your account, up to 50.
meta.planLimitReached boolean True when the import stopped at your plan's people limit.
meta.stoppedAtIndex integer The row the import stopped at. It and the rows after it weren't added.
warnings array of string Notes about details that were skipped, or a list that couldn't be updated. The field is left out when there are none.
Example
curl -X POST "https://splashsend.com/api/v1/contacts/import" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      {
        "email": "sam@example.com",
        "firstName": "Sam",
        "lastName": "Lee"
      },
      {
        "email": "alex@example.com",
        "name": "Alex Morgan",
        "subscriptionStatus": "unsubscribed"
      }
    ],
    "source": "old_platform"
  }'
GET/api/v1/contacts/{id}

Get a person

Needs the contacts:read scope.

Includes their details in properties, keyed by name. A detail with no value is left out.

Parameters
NameType
idrequired string · path The person's id.
Responses
  • 200 The person.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data PersonWithProperties A person with their details.
Example
curl -X GET "https://splashsend.com/api/v1/contacts/cm4u2x8yq0003l208f1k9d7vz" \
  -H "Authorization: Bearer sk_live_…"
PATCH/api/v1/contacts/{id}

Update a person

Needs the contacts:write scope.

Changes only the fields you send.

Setting subscriptionStatus to subscribed subscribes them, records their consent and sends contact.subscribed. It doesn't start welcome drips, so use POST /contacts for a new sign-up. A stopped address can't be subscribed and gets a 409 (stopped_address). Setting it to unsubscribed unsubscribes them and sends contact.unsubscribed.

contact.updated is sent only when their name, source or details changed.

Parameters
NameType
idrequired string · path The person's id.
Body
FieldType
firstName string Their first name.
lastName string Their last name.
name string Their full name, split into first and last. Ignored when firstName or lastName is sent. A blank value never clears a stored name.
source string Where they came from.
subscriptionStatus "subscribed" | "unsubscribed"
properties PropertyValues Details keyed by name, as they're called in SplashSend (any case).
Responses
  • 200 The person, with their details.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 409 The address is stopped, so it can't be subscribed (stopped_address).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data PersonWithProperties A person with their details.
warnings array of string Notes about anything that was skipped. The field is left out when there are none.
Example
curl -X PATCH "https://splashsend.com/api/v1/contacts/cm4u2x8yq0003l208f1k9d7vz" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Samantha",
    "properties": {
      "Birthday": "1990-05-15"
    }
  }'
DELETE/api/v1/contacts/{id}

Unsubscribe a person

Needs the contacts:write scope.

Unsubscribes the person and sends contact.unsubscribed. Nothing is deleted: they stay in your account with their history. Someone already unsubscribed is left as they are.

Parameters
NameType
idrequired string · path The person's id.
Responses
  • 200 The person, now unsubscribed.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data Person A person, as every People endpoint returns them.
Example
curl -X DELETE "https://splashsend.com/api/v1/contacts/cm4u2x8yq0003l208f1k9d7vz" \
  -H "Authorization: Bearer sk_live_…"
GET/api/v1/contacts/{id}/properties

List a person's details

Needs the contacts:read scope.

Every detail in your account, with this person's value. A detail they have no value for has "".

Parameters
NameType
idrequired string · path The person's id.
Responses
  • 200 Their details.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of PropertyValue
Example
curl -X GET "https://splashsend.com/api/v1/contacts/cm4u2x8yq0003l208f1k9d7vz/properties" \
  -H "Authorization: Bearer sk_live_…"
PUT/api/v1/contacts/{id}/properties

Set a person's details

Needs the contacts:write scope.

Sets the details you send and leaves the others as they are. A name that isn't a detail in your account is skipped with a warning, and if none of the names are details, the request gets a 400. contact.updated is sent when a value changed, and drips that watch a detail start when its value changes.

Parameters
NameType
idrequired string · path The person's id.
Body
FieldType
propertiesrequired PropertyValues Details keyed by name, as they're called in SplashSend (any case).
Responses
  • 200 Their details.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of PropertyValue
warnings array of string Notes about anything that was skipped. The field is left out when there are none.
Example
curl -X PUT "https://splashsend.com/api/v1/contacts/cm4u2x8yq0003l208f1k9d7vz/properties" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "properties": {
      "Birthday": "1990-05-15"
    }
  }'
GET/api/v1/contacts/{id}/profile

Get a person's profile

Needs the contacts:read scope.

The person with their details, their consent for email and text messages, how their emails did, what happened to them recently, their orders and the data-set rows matched to them, in one request.

Their emails are counted the way Reports counts them: an email sent again after a failed try counts once, and a click made by link-checking software isn't counted.

What happened to them also needs events:read, their orders orders:read, their data-set rows data:read, and the names of lists that stop mail reaching them lists:read. What a key can't see is left out, and meta.left_out names it.

Parameters
NameType
idrequired string · path The person's id.
Responses
  • 200 Their profile.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data Profile A person with everything SplashSend knows about them.
meta object
meta.left_out array of string The parts of the profile, and the events, left out because the key can't see them. The field is left out when there are none.
Example
curl -X GET "https://splashsend.com/api/v1/contacts/cm4u2x8yq0003l208f1k9d7vz/profile" \
  -H "Authorization: Bearer sk_live_…"

Lists

Lists you add people to yourself. A list set to never be emailed (blockSending) keeps everyone on it out of every send, without changing their subscriptions.

GET/api/v1/lists

List your lists

Needs the lists:read scope.

Newest first.

Parameters
NameType
page integer · query The page to read, from 1. default: 1 minimum: 1
limit integer · query How many to read on a page, up to 100. default: 25 minimum: 1 maximum: 100
search string · query Only lists whose name or description contains this.
Responses
  • 200 A page of lists.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of List
meta PageMeta Paging details for a list.
Example
curl -X GET "https://splashsend.com/api/v1/lists" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/lists

Create a list

Needs the lists:write scope.

Creates a list you add people to yourself. Send "blockSending": true to make it a "Never email these people" list from the start. An account can have up to 500 lists.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
namerequired string max length: 120
description string or null Send null to clear it. max length: 500
blockSending boolean Makes it a "Never email these people" list. Take someone off the list and they can be emailed again as before.
Responses
  • 201 Created.
  • 400 The request isn't valid, or the account already has 500 lists (limit_reached).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 409 A request with this Idempotency-Key is still running (idempotency_in_progress). Retry after the Retry-After header's 5 seconds.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 201
FieldType
data List A list you add people to yourself.
Example
curl -X POST "https://splashsend.com/api/v1/lists" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Workshop regulars",
    "description": "People who have been to three or more workshops."
  }'
GET/api/v1/lists/{id}

Get a list

Needs the lists:read scope.

Includes how many people are on it.

Parameters
NameType
idrequired string · path The list's id.
Responses
  • 200 The list.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data List A list you add people to yourself.
Example
curl -X GET "https://splashsend.com/api/v1/lists/cm4u3b1zt0007l2083n6pq2xa" \
  -H "Authorization: Bearer sk_live_…"
PATCH/api/v1/lists/{id}

Update a list

Needs the lists:write scope.

Changes only the fields you send.

Parameters
NameType
idrequired string · path The list's id.
Body
FieldType
name string max length: 120
description string or null Send null to clear it. max length: 500
blockSending boolean Makes it a "Never email these people" list. Take someone off the list and they can be emailed again as before.
Responses
  • 200 The list, updated.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data List A list you add people to yourself.
Example
curl -X PATCH "https://splashsend.com/api/v1/lists/cm4u3b1zt0007l2083n6pq2xa" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Workshop regulars 2026"
  }'
DELETE/api/v1/lists/{id}

Delete a list

Needs the lists:write scope.

Deletes the list. The people on it stay in your account, and their subscriptions don't change.

Parameters
NameType
idrequired string · path The list's id.
Responses
  • 200 Deleted.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.deleted true
Example
curl -X DELETE "https://splashsend.com/api/v1/lists/cm4u3b1zt0007l2083n6pq2xa" \
  -H "Authorization: Bearer sk_live_…"
GET/api/v1/lists/{id}/members

List a list's members

Needs the lists:read scope.

Newest additions first. A member's email and name are included only for a key with contacts:read.

Parameters
NameType
idrequired string · path The list's id.
page integer · query The page to read, from 1. default: 1 minimum: 1
limit integer · query How many to read on a page, up to 100. default: 25 minimum: 1 maximum: 100
Responses
  • 200 A page of members.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of ListMember
meta PageMeta Paging details for a list.
Example
curl -X GET "https://splashsend.com/api/v1/lists/cm4u3b1zt0007l2083n6pq2xa/members" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/lists/{id}/members

Add people to a list

Needs the lists:write scope.

Adds people already in your account, by id or email address, up to 500 in one request. Nobody is created here: to add new people and put them on a list in one go, use POST /contacts/import with listId. Ids and addresses that aren't in your account come back in meta. Drips that watch this list start only if you send "startAutomations": true.

Parameters
NameType
idrequired string · path The list's id.
Body
FieldType
contactIds array of string
emails array of string
startAutomations boolean Start drips that watch this list for the people added. default: false
Responses
  • 200 Added.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.added integer People added to the list.
data.alreadyMembers integer People who were on it already.
meta object
meta.startAutomations boolean Whether drips that watch this list were started.
meta.unknownIds array of string Ids that aren't in your account, up to 50.
meta.unknownEmails array of string Addresses that aren't in your account, up to 50.
Example
curl -X POST "https://splashsend.com/api/v1/lists/cm4u3b1zt0007l2083n6pq2xa/members" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      "sam@example.com"
    ],
    "contactIds": [
      "cm4u2x8yq0004l208t6r3b1nh"
    ]
  }'
DELETE/api/v1/lists/{id}/members

Remove people from a list

Needs the lists:write scope.

Takes people off the list, by id or email address. The people and their subscriptions don't change.

Parameters
NameType
idrequired string · path The list's id.
Body
FieldType
contactIds array of string
emails array of string
Responses
  • 200 Removed.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.removed integer People taken off the list.
meta object
meta.unknownIds array of string Ids that aren't in your account, up to 50.
meta.unknownEmails array of string Addresses that aren't in your account, up to 50.
Example
curl -X DELETE "https://splashsend.com/api/v1/lists/cm4u3b1zt0007l2083n6pq2xa/members" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      "sam@example.com"
    ]
  }'

Events

Things people do, such as a booking or a sign-up, recorded so they can start drips.

GET/api/v1/events

List events

Needs the events:read scope.

Newest first. An event's email is included only for a key with contacts:read.

Parameters
NameType
page integer · query The page to read, from 1. default: 1 minimum: 1
limit integer · query How many to read on a page, up to 100. default: 25 minimum: 1 maximum: 100
name string · query Only events with this name.
email string · query Only this person's events. Needs contacts:read, and without it the request gets a 403. format: email
Responses
  • 200 A page of events.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The key doesn't have events:read, or it filtered by email without contacts:read.
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of Event
meta PageMeta Paging details for a list.
Example
curl -X GET "https://splashsend.com/api/v1/events" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/events

Record an event

Needs the events:write scope.

Records something a person did, such as booking a class, and starts any drip whose trigger is an event with this name.

The event is attached to the person at email. Someone new is added unsubscribed, because an event isn't consent to marketing email, and they count towards your plan's people limit.

Drips start for an event from the last 3 days, or one dated ahead. An older event is recorded without starting any, unless you send "startAutomations": true. Send false to record any event without starting drips.

Send external_id so a retried event is recorded once. A second event with the same external_id records nothing and starts nothing, and the response is the first event with meta.duplicate set to true.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
emailrequired string The person who did it. format: email
namerequired string The event's name, such as class_booked. max length: 120
properties object Anything else about the event, up to 16KB as JSON.
occurred_at string When it happened. Defaults to now. format: date-time
external_id string or number Your id for this event, so a retry is recorded once.
startAutomations boolean true starts drips for an event of any age. false starts none.
Responses
  • 201 Recorded, or found already recorded.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The key doesn't have events:write (forbidden), or adding the person would go over your plan's people limit (plan_limit_reached).
  • 409 A request with this Idempotency-Key is still running (idempotency_in_progress). Retry after the Retry-After header's 5 seconds.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 201
FieldType
data object
data.id string The event's id.
data.name string
data.email string The person it's attached to. For a duplicate that belongs to a different address, it's left out unless the key has contacts:read. format: email
data.occurred_at string format: date-time
data.external_id string or null
meta object
meta.contact_created boolean True when this event added the person.
meta.duplicate boolean True when an event with this external_id was already recorded. Nothing new was stored.
meta.automations_started boolean True when drips with this event as their trigger were started, if there are any.
Example
curl -X POST "https://splashsend.com/api/v1/events" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "sam@example.com",
    "name": "class_booked",
    "properties": {
      "class": "Beginners wheel",
      "seats": 2
    },
    "external_id": "booking-10482"
  }'

Orders

Orders from a shop that isn't connected through Shopify.

GET/api/v1/orders

List orders

Needs the orders:read scope.

Orders sent in with POST /orders, newest order first. email is included only for a key with contacts:read.

Parameters
NameType
page integer · query The page to read, from 1. default: 1 minimum: 1
limit integer · query How many to read on a page, up to 100. default: 25 minimum: 1 maximum: 100
Responses
  • 200 A page of orders.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of Order
meta PageMeta Paging details for a list.
Example
curl -X GET "https://splashsend.com/api/v1/orders" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/orders

Add or update an order

Needs the orders:write scope.

Records an order from your shop by order_id. To update it, send the same order_id again with only what changed.

A buyer new to your account is added as a customer who can't be emailed, unless accepts_marketing is true because they agreed to marketing email at checkout. A buyer already here keeps their subscription status. If your plan's people limit is reached, the order is saved without a person and meta.warning says so.

When this order subscribes a new buyer, their welcome drips start and contact.subscribed is sent if the order is from the last 3 days. Send "startAutomations": true to start them for an older order, or false to never start them.

Drips for an order placed, a first order or a repeat order run once, when a new order from the last 3 days arrives, and order.placed is sent then. "Order shipped" drips run, and order.fulfilled is sent, when the order moves into a shipped status (fulfilled, completed, complete, shipped or delivered) and is from the last 30 days.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
order_idrequired string or number Your order number or id.
email string The buyer's email address. Needed for a new order. format: email
total number The order's total. Needed for a new order. minimum: 0
currency string A three-letter currency code, such as GBP. Defaults to USD.
items array of object What was bought. Each item's name, or title, names it in review emails.
status string The order's status in your shop. fulfilled, completed, complete, shipped and delivered count as shipped.
order_date string When it was placed. Defaults to now. format: date-time
source string Where it came from, such as your shop's name. Defaults to woocommerce.
accepts_marketing boolean True when the buyer agreed to marketing email at checkout. Used only when this order adds them. default: false
startAutomations boolean For a buyer this order subscribes: true starts their welcome drips for an order of any age, and false never starts them.
Responses
  • 201 Saved.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 409 A request with this Idempotency-Key is still running (idempotency_in_progress). Retry after the Retry-After header's 5 seconds.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 201
FieldType
data object
data.id string The order's id in SplashSend.
data.order_id string Your order number or id.
data.email string Sent when the request included it, or the key has contacts:read. format: email
data.total number
data.currency string
data.status string or null
data.order_date string format: date-time
data.contact_id string or null The buyer's person id. Null when the order has no person.
meta object
meta.contact_created boolean True when this order added the buyer.
meta.warning string Present when your plan's people limit kept the order from being linked to a person.
Example
curl -X POST "https://splashsend.com/api/v1/orders" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "1042",
    "email": "sam@example.com",
    "total": 42.5,
    "currency": "GBP",
    "items": [
      {
        "name": "Speckled mug",
        "quantity": 2,
        "price": 18
      }
    ],
    "status": "processing",
    "order_date": "2026-10-09T10:30:00Z"
  }'

Activity

Everything that happened in your account, read in one place: SplashSend's own events and the events your systems send. SplashSend's own use the same names as the webhooks.

The events SplashSend records:

- email.sent (Email sent): An email to a person, counted once however many tries it took. Its status says how it went: delivered, bounced, failed when no try got through, or sent while there's no word back yet. Needs contacts:read. - email.delivered (Email delivered): An email that neither failed nor bounced. The full list shows it as email.sent with the status delivered; ask for email.delivered by name to list just these. Needs contacts:read. - email.failed (Email couldn't be sent): An email no try got through. The full list shows it as email.sent with the status failed; ask for email.failed by name to list just these. Needs contacts:read. - email.bounced (Email bounced): An email the receiving server turned away. The full list shows it as email.sent with the status bounced; ask for email.bounced by name to list just these. Needs contacts:read. - email.opened (Email opened): Each open as recorded, so an email opened twice has two. Reports counts an email as opened once. Needs contacts:read. - email.clicked (Link clicked): Each click a person made. Link-checking software opens every link the moment an email arrives, and those clicks aren't counted. Needs contacts:read. - email.complained (Marked as spam): A spam report from the person's email provider. Needs contacts:read. - contact.subscribed (Person subscribed): Subscribing, or subscribing again, with how they did it. Reports leaves out imports and customers brought in from a shop's history, since those record people arriving in bulk. Needs contacts:read. - contact.unsubscribed (Person unsubscribed): Leaving by any route, from the unsubscribe link to a spam report. Needs contacts:read. - contact.suppressed (Emails stopped): A person's emails stopped by hand or after a bounce. Needs contacts:read. - order.placed (Order placed): An order from a connected Shopify store or one sent through POST /orders. One refunded in full, voided, cancelled or whose payment failed is marked reversed and isn't revenue. Needs orders:read. - checkout.abandoned (Checkout left unfinished): A Shopify checkout with an email address that wasn't finished, and whether they bought later. Needs orders:read. - viewed_product (Viewed a product): Recorded by the website script for someone who came from one of your emails. - added_to_cart (Added to basket): Recorded by the website script for someone who came from one of your emails. - searched (Searched the site): Recorded by the website script. A search that looks like an address or a long number isn't kept. - viewed_collection (Viewed a collection): Recorded by the website script for someone who came from one of your emails. - form.submitted (Form submitted): An answer to one of your forms. Answers marked as spam aren't counted. Webhooks send a Collect answer as submission.created. Needs contacts:read. - drip.enrolled (Started a drip): A person starting a drip. Someone who goes through a drip again is shown at their latest start. Needs contacts:read. - back_in_stock.requested (Asked to hear when it's back): A request made through a form to hear when a product is back in stock. Needs contacts:read.

GET/api/v1/activity

List what happened

Needs the events:read scope.

Everything that happened, newest first, or what happened to one person with email. Each event says whether it's one of SplashSend's own (kind: built_in) or one your systems sent (kind: custom), so an event you named order.placed yourself is never mistaken for an order.

An email is listed once, as email.sent with its status, however many tries it took. Ask for email.delivered, email.bounced or email.failed by name to list the emails with that outcome under that name. A click made by link-checking software isn't listed.

Events about a person (emails, joining and leaving, form answers, drips started and back in stock requests) need contacts:read, and orders and unfinished checkouts need orders:read. What a key can't see is left out, and meta.left_out names it. An event's email is sent only to a key with contacts:read.

To page, send the meta.next_cursor of one page as cursor to get the next. It's null on the last page.

Parameters
NameType
limit integer · query How many to read on a page, up to 100. default: 25 minimum: 1 maximum: 100
cursor string · query Where the last page ended: its meta.next_cursor.
name string · query Only this event: one from GET /activity/catalogue, such as email.clicked, or one your systems send.
kind "built_in" | "custom" · query built_in for SplashSend's own events, custom for the ones your systems send.
email string · query Only this person's events. Needs contacts:read, and without it the request gets a 403. format: email
since string · query From this time: a date such as 2026-10-01 or a time such as 2026-10-01T09:00:00Z.
until string · query Up to this time, not including it.
Responses
  • 200 A page of what happened.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The key doesn't have events:read, or it filtered by email without contacts:read.
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of ActivityEvent
meta object
meta.limit integer How many fit on a page.
meta.next_cursor string or null Send as cursor for the next page. Null on the last page.
meta.left_out array of string Events left out because the key can't see them. The field is left out when there are none.
Example
curl -X GET "https://splashsend.com/api/v1/activity" \
  -H "Authorization: Bearer sk_live_…"
GET/api/v1/activity/count

Count an event

Needs the events:read scope.

How many times an event happened, for the whole account or one person, over a stretch of time, counted the way GET /activity lists it: an email sent again after a failed try counts once, and a click made by link-checking software isn't counted. For a name that is both one of SplashSend's events and one your systems send, kind=custom counts yours.

Parameters
NameType
namerequired string · query The event to count: one from GET /activity/catalogue, or one your systems send.
kind "built_in" | "custom" · query built_in for SplashSend's own events, custom for the ones your systems send.
email string · query Only this person's events. Needs contacts:read, and without it the request gets a 403. format: email
since string · query From this time: a date such as 2026-10-01 or a time such as 2026-10-01T09:00:00Z.
until string · query Up to this time, not including it.
Responses
  • 200 The count.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The key doesn't have events:read, it counted an event about a person or filtered by email without contacts:read, or it counted orders or unfinished checkouts without orders:read.
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.name string
data.kind "built_in" | "custom" Which events were counted.
data.count integer
data.since string or null format: date-time
data.until string or null format: date-time
data.email string The person counted for, when you asked for one. format: email
Example
curl -X GET "https://splashsend.com/api/v1/activity/count" \
  -H "Authorization: Bearer sk_live_…"
GET/api/v1/activity/catalogue

The events SplashSend records

Needs the events:read scope.

Every kind of event SplashSend records: its name, what it carries in properties, how it's counted, and any scope needed beyond events:read to see it.

Responses
  • 200 The catalogue.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of CatalogueEvent
Example
curl -X GET "https://splashsend.com/api/v1/activity/catalogue" \
  -H "Authorization: Bearer sk_live_…"

Data sets

Facts your business keeps that aren't about one person, such as your catalogue, form answers or a timetable. A set you keep yourself takes rows over the API.

GET/api/v1/data

List data sets

Needs the data:read scope.

Every data set that's switched on, with its fields. Use it to find a set's key.

Responses
  • 200 The data sets.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data_sets array of DataSetSummary
Example
curl -X GET "https://splashsend.com/api/v1/data" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/data

Create a data set

Needs the data:write scope.

Creates a data set you keep yourself and send rows to. An account can have up to 100 data sets.

fields lists its columns, each with a label and a type. Each field gets a key made from its label, which is what rows and email tags use. A set sent with no fields gets one, called "Name".

audienceMode says who a row is for. everyone, the default, gives everyone the same row. matched gives each person the row that matches one of their details: send matchOn with the person's detail and the set's field. A matched set made without matchOn gives nobody a row until you choose them in the app. internal keeps the set out of emails, for use in groups. A set that picks people by rules is set up in the app.

joins links the set to up to 4 others. Each join pairs one of this set's fields with a field of another set. When a person has a row of this set, the other set's tags read its row with the same value in that field, so a set of shops joined to the weather on the town gives each person the forecast for their own shop. Values match when they're the same apart from capital letters and spaces at either end.

The response is the new set's key.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
namerequired string The set's name. Its key is made from it. max length: 80
description string max length: 500
category string Where it's filed in the app. Defaults to "Kept by you".
fields array of DataFieldInput Its columns, up to 40. max items: 40
audienceMode "everyone" | "matched" | "internal" Who a row is for. default: "everyone"
matchOn MatchOn What a matched set matches on.
joins array of DataJoin Other sets it joins, up to 4. max items: 4
Responses
  • 201 Created.
  • 400 The request isn't valid (invalid_request), or the account already has 100 data sets (limit_reached).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 409 A request with this Idempotency-Key is still running (idempotency_in_progress). Retry after the Retry-After header's 5 seconds.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 201
FieldType
key string The new set's key.
Example
curl -X POST "https://splashsend.com/api/v1/data" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Workshops",
    "description": "Upcoming workshops and how many places are left.",
    "fields": [
      {
        "label": "Title",
        "type": "words"
      },
      {
        "label": "Date",
        "type": "date"
      },
      {
        "label": "Price",
        "type": "money"
      },
      {
        "label": "Places left",
        "type": "number"
      }
    ]
  }'
GET/api/v1/data/{key}

Get a data set

Needs the data:read scope.

Its fields, and how many rows it has.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.
Responses
  • 200 The data set.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
key string Its identifier, used in the API and in email tags.
name string
description string or null
source "live" | "manual" manual for a set you keep yourself, live for one read from a connected source.
writable boolean True when it takes rows over the API.
row_count integer
updated_at string or null When its rows last changed, when that's known. format: date-time
fields array of DataField
joins array of DataJoin The other sets it joins.
Example
curl -X GET "https://splashsend.com/api/v1/data/workshops" \
  -H "Authorization: Bearer sk_live_…"
PATCH/api/v1/data/{key}

Update a data set

Needs the data:write scope.

Changes the fields you send. name, description, category and matchOn work on any set. fields works only on a set you keep yourself.

fields is the whole new list of columns. A field sent with its key keeps its values, and keeps any label, type or example you leave out. A field you leave out of the list is removed. A field that is removed and later added back starts empty. The field a matched set matches people on, and a field a join uses, can't be removed.

matchOn makes it a matched set and says what it matches on, as on create.

joins works on any set and replaces its joins, as on create. Send an empty list to remove them. A join to a set that has since been switched off can stay, and starts working again when that set is back on.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.
Body
FieldType
name string max length: 80
description string max length: 500
category string
fields array of DataFieldInput The whole new list of columns. max items: 40
matchOn MatchOn What a matched set matches on.
joins array of DataJoin The whole new list of joins. max items: 4
Responses
  • 200 The data set, updated.
  • 400 The request isn't valid (invalid_request), or it changes the fields of a set read from a connected source (read_only).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
key string
name string
description string or null
fields array of DataField
joins array of DataJoin
Example
curl -X PATCH "https://splashsend.com/api/v1/data/workshops" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Workshops and classes",
    "description": "Upcoming workshops and classes, with places left."
  }'
DELETE/api/v1/data/{key}

Delete a data set

Needs the data:write scope.

Deletes a set you keep yourself, or one added through "Connect an app", with all its rows. Ideas it put on your calendar are removed too. A built-in set can't be deleted: switch it off in the app instead.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.
Responses
  • 200 Deleted.
  • 400 The set is built in (read_only).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
deleted true
Example
curl -X DELETE "https://splashsend.com/api/v1/data/workshops" \
  -H "Authorization: Bearer sk_live_…"
GET/api/v1/data/{key}/rows

List a data set's rows

Needs the data:read scope.

Reads the rows of any data set that's switched on. A built-in set is read when you ask, so its rows are current.

Each row's values are keyed by field key. A row in a set you keep yourself also has its id, which you send to update or delete it. A column labelled ID is keyed id_2, so it never hides the row's id.

Some sets leave out rows whose date has passed. Send include_past=true to list those too.

Without contacts:read, columns holding someone's email are left out, and form answers are limited to the ones shown on your site, with first names only.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.
limit integer · query How many rows to read. default: 50 minimum: 1 maximum: 200
offset integer · query How many rows to skip. default: 0 minimum: 0
include_past boolean · query Include rows whose date has passed. default: false
Responses
  • 200 A page of rows.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data_set string The set's key.
fields array of DataField Its fields. Columns holding email addresses are left out without contacts:read.
rows array of DataRow
total integer How many rows there are to read, for paging.
limit integer
offset integer
Example
curl -X GET "https://splashsend.com/api/v1/data/workshops/rows" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/data/{key}/rows

Add rows

Needs the data:write scope.

Adds rows to a set you keep yourself, up to 5,000 in one request (4MB). A set holds up to 5,000 rows.

Each row is an object of values. A key can be a field's key, in any case, or its label. Keys that don't match a field are skipped and listed in warnings. A row where no key matches a field is refused with a 400, and so is a request whose rows are all empty.

"mode": "replace" swaps every row in the set for the rows you send. A row that comes back unchanged keeps its id and doesn't start drips waiting for a new row.

A date column is stored as YYYY-MM-DD. A value such as 14/10/2026 or 14 Oct 2026 is read as a date too, day first unless your account's country is the US or Canada.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
rowsrequired array of RowValues The rows, each an object of values. max items: 5000
mode "append" | "replace" append adds the rows after the ones already there. replace swaps every row in the set for these. default: "append"
Responses
  • 201 Saved.
  • 400 The request isn't valid (invalid_request), the set is read from a connected source (read_only), or it would hold more than 5,000 rows (limit_reached).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 409 A request with this Idempotency-Key is still running (idempotency_in_progress). Retry after the Retry-After header's 5 seconds.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 201
FieldType
added integer How many rows were saved.
total integer How many rows the set has now.
mode "append" | "replace"
warnings array of string Keys that didn't match a field. The field is left out when there are none.
Example
curl -X POST "https://splashsend.com/api/v1/data/workshops/rows" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      {
        "title": "Wreath making",
        "date": "2026-12-05",
        "price": 45,
        "places_left": 8
      },
      {
        "title": "Beginners wheel",
        "date": "2026-12-12",
        "price": 60,
        "places_left": 4
      }
    ]
  }'
PATCH/api/v1/data/{key}/rows

Update rows

Needs the data:write scope.

Changes up to 500 rows in one request, each found by its id. Send only the values that change, and the rest of the row stays as it is. Keys are matched the same way as when adding rows. An id that isn't in the set is skipped, and updated is how many were found.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.
Body
FieldType
updatesrequired array of object Up to 500 changes. max items: 500
updates[].idrequired string The row's id.
updates[].valuesrequired RowValues A row's values, keyed by a field's key in any case, or by its label.
Responses
  • 200 Updated.
  • 400 The request isn't valid, sends more than 500 updates, or the set is read from a connected source (read_only).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
updated integer How many of the ids were found.
warnings array of string Keys that didn't match a field. The field is left out when there are none.
Example
curl -X PATCH "https://splashsend.com/api/v1/data/workshops/rows" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "updates": [
      {
        "id": "cm4u5d0re000bl208h4w1s6kc",
        "values": {
          "places_left": 6
        }
      }
    ]
  }'
DELETE/api/v1/data/{key}/rows

Delete rows

Needs the data:write scope.

Deletes up to 1,000 rows by id. Ids that aren't in the set are ignored. To clear every row, send { "all": true } instead of ids.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.
Body
FieldType
ids array of string The ids of the rows to delete. max items: 1000
all boolean Delete every row in the set. Send it only when you mean to empty the set.
Responses
  • 200 Deleted.
  • 400 The request sends no ids or more than 1,000, or the set is read from a connected source (read_only).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
removed integer How many rows were deleted.
Example
curl -X DELETE "https://splashsend.com/api/v1/data/workshops/rows" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "ids": [
      "cm4u5d0re000bl208h4w1s6kc",
      "cm4u5d0re000cl208x9j2v3mn"
    ]
  }'
PATCH/api/v1/data/{key}/rows/{rowId}

Update a row

Needs the data:write scope.

Changes one row. Send the values that change, inside values or on their own. Keys are matched the same way as when adding rows.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.
rowIdrequired string · path The row's id, from GET /data/{key}/rows.
Body
FieldType
values RowValues A row's values, keyed by a field's key in any case, or by its label.
Responses
  • 200 Updated.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
updated 1
warnings array of string Keys that didn't match a field. The field is left out when there are none.
Example
curl -X PATCH "https://splashsend.com/api/v1/data/workshops/rows/cm4u5d0re000bl208h4w1s6kc" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "values": {
      "places_left": 6
    }
  }'
DELETE/api/v1/data/{key}/rows/{rowId}

Delete a row

Needs the data:write scope.

Deletes one row from a set you keep yourself.

Parameters
NameType
keyrequired string · path The data set's key, as GET /data lists it, such as shopify_products. It's also on the set's Source tab in the app.
rowIdrequired string · path The row's id, from GET /data/{key}/rows.
Responses
  • 200 Deleted.
  • 400 The set is read from a connected source (read_only).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
deleted true
Example
curl -X DELETE "https://splashsend.com/api/v1/data/workshops/rows/cm4u5d0re000bl208h4w1s6kc" \
  -H "Authorization: Bearer sk_live_…"

Products

Your product catalogue, for product blocks in emails.

GET/api/v1/products

List products

Needs the products:read scope.

Products from your Shopify store and products sent with POST /products, newest first.

Parameters
NameType
page integer · query The page to read, from 1. default: 1 minimum: 1
limit integer · query How many to read on a page, up to 100. default: 25 minimum: 1 maximum: 100
Responses
  • 200 A page of products.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of Product
meta PageMeta Paging details for a list.
Example
curl -X GET "https://splashsend.com/api/v1/products" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/products

Add or update products

Needs the products:write scope.

Adds products to your catalogue, or updates them, by product_id, so they can appear in product blocks in emails. Send one product, or { "products": [ … ] } with up to 100.

An update changes only the fields you send. A field sent as null is cleared, apart from title and handle, which can't be. title is needed only for a new product.

status is active, draft or archived, and only active products appear in emails. Other platforms' words are understood: WooCommerce's publish is active, private and pending are drafts, and trash is archived. A status that isn't recognised is an error for that product.

A product that can't be saved is listed in meta.errors, and the others are saved. If none can be saved, the request gets a 400.

Body: One product
FieldType
product_idrequired string or number Your id for the product. Sending it again updates the product.
title string Its name. Needed for a new product.
price number or string Its price, such as 18 or "18.00".
currency string A currency code, such as GBP.
image_url string Its picture, as an http(s) address. format: uri
url string Its page in your shop. Product blocks link here. format: uri
handle string Its slug in your shop's addresses. Defaults to product_id.
description string max length: 300
product_type string Its type, such as Mugs.
vendor string Who makes it.
status string active, draft or archived, in any case. Defaults to active for a new product.
tags string or array of string Its tags, as an array or as one string.
inventory integer How many you have in stock. When a product goes from 0 to more, the people who asked to hear when it was back are told (a drip that starts with "They asked to hear when it’s back"). minimum: 0
in_stock boolean For a shop that only knows whether it is in stock: true or false. Ignored when inventory is sent.
Body: Several products
FieldType
productsrequired array of ProductInput Up to 100 products. max items: 100
Responses
  • 200 Saved.
  • 400 The request isn't valid, or none of the products could be saved. Then meta.errors lists each reason.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.upserted integer How many products were saved.
meta object
meta.errors array of string Products that couldn't be saved, each with the reason.
Examples
One product
curl -X POST "https://splashsend.com/api/v1/products" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "mug-speckled",
    "title": "Speckled mug",
    "price": 18,
    "currency": "GBP",
    "image_url": "https://shop.example.com/images/speckled-mug.jpg",
    "url": "https://shop.example.com/products/speckled-mug",
    "status": "active"
  }'
Several products
curl -X POST "https://splashsend.com/api/v1/products" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      {
        "product_id": "mug-speckled",
        "title": "Speckled mug",
        "price": 18,
        "currency": "GBP"
      },
      {
        "product_id": "bowl-ash",
        "title": "Ash glaze bowl",
        "price": "24.00",
        "currency": "GBP",
        "status": "draft"
      }
    ]
  }'

Groups

Groups pick people by rules and keep themselves up to date. A campaign can go to one.

GET/api/v1/groups

List groups

Needs the contacts:read scope.

Your groups, pinned ones first. The groups of addresses that bounced or reported spam aren't listed, because nobody in them is emailed. built_in marks the two SplashSend keeps for you, Everyone and Subscribed.

Responses
  • 200 The groups.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of object
data[].id string
data[].name string
data[].description string or null
data[].built_in boolean
data[].created_at string format: date-time
data[].updated_at string format: date-time
Example
curl -X GET "https://splashsend.com/api/v1/groups" \
  -H "Authorization: Bearer sk_live_…"

Campaigns

Draft a campaign for someone on your team to finish, or send one of your designs to a group, a list or everyone, now or at a time. A send goes through the same checks as the Send button in SplashSend.

GET/api/v1/designs

List designs

Needs the campaigns:write scope.

The designs in your Designs library, most recently changed first, for choosing one to send. subject is the subject line saved with a design, if it has one.

Parameters
NameType
page integer · query The page to read, from 1. default: 1 minimum: 1
limit integer · query How many to read on a page, up to 100. default: 25 minimum: 1 maximum: 100
Responses
  • 200 A page of designs.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of object
data[].id string
data[].name string
data[].subject string or null
data[].folder string or null
data[].created_at string format: date-time
data[].updated_at string format: date-time
meta PageMeta Paging details for a list.
Example
curl -X GET "https://splashsend.com/api/v1/designs" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/campaigns

Draft or send a campaign

Needs the campaigns:write or campaigns:send scope.

Without send, makes a draft for someone on your team to finish and send in SplashSend. It needs campaigns:write.

With send, sends one of your designs to a group, a list or everyone ("now"), or schedules it (a time). It needs campaigns:send, and design_id, who it goes to and a subject line. It goes through the same checks as the Send button: a verified sender, your plan's sends, the unsubscribe link, and only subscribed people, leaving out stopped addresses, lists set to never be emailed, people who opted out of the campaign's topic and anyone their own email frequency would skip. A send that's refused makes nothing, and the error says why.

The same design can't go to the same group, list or everyone twice within a day, whoever sent it first: that's refused with sent_recently. An app in test mode gets a draft and sends nothing.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
name string What the campaign is called in SplashSend. Defaults to the design's name. max length: 200
subject string The subject line. Defaults to the one saved with the design. max length: 300
preview_text string The line shown after the subject in the inbox. Defaults to the design's own. max length: 150
design_id string A design from GET /designs. Needed to send.
group_id string Send it to a group from GET /groups.
list_id string Send it to a list from GET /lists.
everyone boolean Send it to everyone subscribed.
send string now, or a time to schedule it for, such as 2026-10-24T09:00:00Z. Leave it out for a draft. Needs campaigns:send.
Responses
  • 201 Drafted, sent or scheduled.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 A permission is missing, or your plan's sends for the month are used up (plan_limit_reached).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 409 This design went, or is scheduled to go, to the same people within a day (sent_recently), or the same Idempotency-Key is still being handled.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 201
FieldType
data object
data.id string
data.name string
data.subject string or null
data.status "draft" | "scheduled" | "sending" | "sent"
data.scheduled_at string or null When a scheduled campaign goes. format: date-time
data.edit_url string Where someone on your team opens it. format: uri
meta object
meta.sent boolean True when it's on its way.
meta.scheduled boolean
meta.test_mode boolean Present when the app is in test mode and nothing was sent.
meta.note string
Example
curl -X POST "https://splashsend.com/api/v1/campaigns" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "design_id": "cm4u2xi5l0012l208u7i9o1pa",
    "group_id": "cm4u2xh4k0011l208y6u8i0op",
    "subject": "Three new glazes are in",
    "send": "now"
  }'

Webhook subscriptions

An app's own subscriptions to the account's events, so it hears of them as they happen (REST hooks, as Zapier uses). Messages are the same as Setup → Webhooks sends, signed with the subscription's own secret, which is shown once, when it's made. An app may subscribe to an event it could read through GET /activity, so most events need contacts:read. A 410 Gone from the address ends the subscription, and removing the app ends all of its subscriptions. An app sees and ends only its own, and the account sees each one in Setup → Webhooks, marked as added by an app.

GET/api/v1/webhooks

List this app's subscriptions

The subscriptions this app holds on this account, newest first. active is false while one is paused, after a day of failed deliveries.

Responses
  • 200 The subscriptions.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of Subscription
Example
curl -X GET "https://splashsend.com/api/v1/webhooks" \
  -H "Authorization: Bearer sk_live_…"
POST/api/v1/webhooks

Subscribe to events

Sends each of these events to url as it happens. event_name narrows event.recorded to one of your own events by name, such as Class booked. step_note narrows drip.step to the Notify your systems steps with that note, matched without regard to capital letters. Each narrows only its own event. The answer holds the signing secret, the only time it's shown. An app may hold 50 subscriptions on an account.

Accepts an Idempotency-Key header. Send a new key with each new request and the same key when you retry it: a retry gets the first response back instead of acting twice.

Body
FieldType
urlrequired string An https address. Events can't be sent to internal or private addresses. format: uri
eventsrequired array of "contact.subscribed" | "contact.unsubscribed" | "contact.updated" | "campaign.sent" | "email.delivered" | "email.opened" | "email.clicked" | "email.bounced" | "email.complained" | "order.placed" | "order.fulfilled" | "submission.created" | "drip.enrolled" | "drip.step" | "form.submitted" | "event.recorded" The events to send, as Webhooks lists them.
event_name string Only this one of your own events, with event.recorded.
step_note string Only Notify your systems steps with this note, with drip.step. max length: 500
Responses
  • 201 Subscribed.
  • 400 The request isn't valid. error.code is validation_error or invalid_request, and error.message says what to change.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 409 A request with this Idempotency-Key is still running (idempotency_in_progress). Retry after the Retry-After header's 5 seconds.
  • 413 The body is over the size limit (payload_too_large).
  • 422 This Idempotency-Key was used for a different request (idempotency_key_reused).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 201
FieldType
data Subscription
Example
curl -X POST "https://splashsend.com/api/v1/webhooks" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/splashsend/123",
    "events": [
      "event.recorded"
    ],
    "event_name": "Class booked"
  }'
GET/api/v1/webhooks/{id}

Read a subscription

Parameters
NameType
idrequired string · path The subscription's id.
Responses
  • 200 The subscription.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data Subscription One of an app's webhook subscriptions.
Example
curl -X GET "https://splashsend.com/api/v1/webhooks/ID" \
  -H "Authorization: Bearer sk_live_…"
DELETE/api/v1/webhooks/{id}

End a subscription

Nothing more is sent to it, and its delivery log goes with it.

Parameters
NameType
idrequired string · path The subscription's id.
Responses
  • 200 Ended.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.id string
data.deleted true
Example
curl -X DELETE "https://splashsend.com/api/v1/webhooks/ID" \
  -H "Authorization: Bearer sk_live_…"

Report cards

Cards an app adds to Reports from the events it sends. A card counts one event, or adds up one of its numbers, over the dates a dashboard shows, split by day, by week or by one of the event's details. The figures are worked out when the account looks at them, and the app never sees them.

Reports lists an app's cards under From your apps, and the account chooses which dashboards they go on. Only the app can change its cards, and they go when the app is removed. An app may keep 20 cards on an account.

GET/api/v1/report-cards

List this app's report cards

Needs the events:write scope.

Responses
  • 200 The cards, oldest first.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of ReportCard
Example
curl -X GET "https://splashsend.com/api/v1/report-cards" \
  -H "Authorization: Bearer sk_live_…"
GET/api/v1/report-cards/{key}

Read a report card

Needs the events:write scope.

Parameters
NameType
keyrequired string · path The app's own key for the card.
Responses
  • 200 The card.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data ReportCard One of this app's report cards.
Example
curl -X GET "https://splashsend.com/api/v1/report-cards/classes-by-week" \
  -H "Authorization: Bearer sk_live_…"
PUT/api/v1/report-cards/{key}

Make or replace a report card

Needs the events:write scope.

Makes the card, or replaces this app's card with this key. Sending the same card again changes nothing, so it's safe to send each time the app starts.

event is the name of one of your events. A card counts them, or with sum adds up that number from each one (as money with money). split is none, day, week (the default) or property, which splits by the event detail named in property. chart is number for a card with no split, line for one split by day or week, or bars or table for any split.

Parameters
NameType
keyrequired string · path The app's own key for the card.
Body
FieldType
namerequired string Its title in Reports. max length: 80
description string A line under the title saying what it shows. max length: 200
eventrequired string The name of the event it reads, as you send it to POST /events. max length: 120
split "none" | "day" | "week" | "property" How its figure is split. default: "week"
property string With split: property: the event detail to split by, such as class. Events sent without it are under Not given. max length: 60
sum string Add up this number from each event, such as price, instead of counting them. An event without a number there adds nothing. max length: 60
money boolean With sum: show the total as money, in the account's currency. default: false
chart "number" | "line" | "bars" | "table" How it's drawn. Defaults to number with no split, line by day or week, and bars by a detail.
Responses
  • 200 Replaced.
  • 201 Made.
  • 400 The card isn't valid (invalid_request), or the app already keeps 20 cards on the account (limit_reached).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200 or 201
FieldType
data ReportCard One of this app's report cards.
Example
curl -X PUT "https://splashsend.com/api/v1/report-cards/classes-by-week" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Classes booked, by class",
    "description": "Bookings made through Hearth in these dates.",
    "event": "Class booked",
    "split": "property",
    "property": "class",
    "chart": "bars"
  }'
DELETE/api/v1/report-cards/{key}

Remove a report card

Needs the events:write scope.

It comes off every dashboard it's on.

Parameters
NameType
keyrequired string · path The app's own key for the card.
Responses
  • 200 Removed.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.deleted true
Example
curl -X DELETE "https://splashsend.com/api/v1/report-cards/classes-by-week" \
  -H "Authorization: Bearer sk_live_…"

Drip steps

Steps an app adds to the account's drip builder. Each is listed under From your apps when someone adds a step, with the app's name, and asks for the settings the app wants. When a person reaches it, the app hears a drip.step webhook about them, with the step's key and the settings, through its own subscription (POST /webhooks). Nothing else hears it.

Needs events:read and contacts:read, what hearing drip.step needs. Only the app can change its steps, and they go when the app is removed. An app may keep 20 steps on an account.

GET/api/v1/drip-steps

List this app's drip steps

Needs the events:read and contacts:read scopes.

Responses
  • 200 The steps, oldest first.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of DripStep
Example
curl -X GET "https://splashsend.com/api/v1/drip-steps" \
  -H "Authorization: Bearer sk_live_…"
GET/api/v1/drip-steps/{key}

Read a drip step

Needs the events:read and contacts:read scopes.

Parameters
NameType
keyrequired string · path The app's own key for the step.
Responses
  • 200 The step.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data DripStep One of this app's drip steps.
Example
curl -X GET "https://splashsend.com/api/v1/drip-steps/offer-next-class" \
  -H "Authorization: Bearer sk_live_…"
PUT/api/v1/drip-steps/{key}

Make or replace a drip step

Needs the events:read and contacts:read scopes.

Makes the step, or replaces this app's step with this key. Sending the same step again changes nothing, so it's safe to send each time the app starts.

fields are the settings the account fills in on the step, up to 6, each sent in values when someone reaches it. To hear the step, subscribe to drip.step with POST /webhooks.

Parameters
NameType
keyrequired string · path The app's own key for the step.
Body
FieldType
namerequired string Its name in the drip builder. max length: 60
description string What it does, shown when it's chosen. max length: 200
fields array of StepField max items: 6
Responses
  • 200 Replaced.
  • 201 Made.
  • 400 The step isn't valid (invalid_request), or the app already keeps 20 steps on the account (limit_reached).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200 or 201
FieldType
data DripStep One of this app's drip steps.
Example
curl -X PUT "https://splashsend.com/api/v1/drip-steps/offer-next-class" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Offer the next class",
    "description": "Hearth offers them a place on the next class of the kind you choose.",
    "fields": [
      {
        "key": "class_type",
        "label": "Kind of class",
        "type": "choice",
        "options": [
          "Wheel throwing",
          "Glazing",
          "Hand building"
        ],
        "required": true
      },
      {
        "key": "discount",
        "label": "Discount (%)",
        "type": "number"
      }
    ]
  }'
DELETE/api/v1/drip-steps/{key}

Remove a drip step

Needs the events:read and contacts:read scopes.

It leaves the builder's list. A drip that already uses it sends nothing at that step, and the person carries on.

Parameters
NameType
keyrequired string · path The app's own key for the step.
Responses
  • 200 Removed.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.deleted true
Example
curl -X DELETE "https://splashsend.com/api/v1/drip-steps/offer-next-class" \
  -H "Authorization: Bearer sk_live_…"

Email blocks

Blocks an app adds to the account's email designer, listed under From your apps. A block shows rows from a data set the app made, as cards with a title, a line, a picture and a button, filled in when the email sends: a set for everyone gives its first rows, and a set matched to people gives each person their own row.

Needs data:write. Only the app can change its blocks, and they go when the app is removed. An app may keep 20 blocks on an account.

GET/api/v1/email-blocks

List this app's email blocks

Needs the data:write scope.

Responses
  • 200 The blocks, oldest first.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data array of EmailBlock
Example
curl -X GET "https://splashsend.com/api/v1/email-blocks" \
  -H "Authorization: Bearer sk_live_…"
GET/api/v1/email-blocks/{key}

Read an email block

Needs the data:write scope.

Parameters
NameType
keyrequired string · path The app's own key for the block.
Responses
  • 200 The block.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data EmailBlock One of this app's email blocks.
Example
curl -X GET "https://splashsend.com/api/v1/email-blocks/upcoming-classes" \
  -H "Authorization: Bearer sk_live_…"
PUT/api/v1/email-blocks/{key}

Make or replace an email block

Needs the data:write scope.

Makes the block, or replaces this app's block with this key. data_set is the key of a set this app made (POST /data), switched on and not kept out of emails. title, text, image and link name its fields, by key or label: each card shows the title, a line of text and a picture, and its button opens the link. count is how many cards at most, 1 to 6.

Parameters
NameType
keyrequired string · path The app's own key for the block.
Body
FieldType
namerequired string Its name in the designer. max length: 60
description string max length: 200
data_setrequired string The key of a data set this app made.
titlerequired string The field each card's title shows.
text string The field each card's line of text shows.
image string The field holding each card's picture address.
link string The field holding the address each card's button opens.
button string The words on each card's button. Needs link. Defaults to View. max length: 30
count integer How many cards at most. default: 3 minimum: 1 maximum: 6
Responses
  • 200 Replaced.
  • 201 Made.
  • 400 The block isn't valid (invalid_request), or the app already keeps 20 blocks on the account (limit_reached).
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 413 The body is over the size limit (payload_too_large).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200 or 201
FieldType
data EmailBlock One of this app's email blocks.
Example
curl -X PUT "https://splashsend.com/api/v1/email-blocks/upcoming-classes" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Upcoming classes",
    "description": "The next classes with places left.",
    "data_set": "classes",
    "title": "Class",
    "text": "When",
    "image": "Photo",
    "link": "Booking link",
    "button": "Book a place",
    "count": 3
  }'
DELETE/api/v1/email-blocks/{key}

Remove an email block

Needs the data:write scope.

It leaves the designer's list. A design that already uses it shows nothing there when it sends.

Parameters
NameType
keyrequired string · path The app's own key for the block.
Responses
  • 200 Removed.
  • 401 The API key is missing, wrong or revoked (unauthorized).
  • 403 The API key doesn't have the scope this endpoint needs (forbidden).
  • 404 The person, list, data set or row isn't in your account (not_found).
  • 429 The key made more than 100 requests in a minute (rate_limited).
Response 200
FieldType
data object
data.deleted true
Example
curl -X DELETE "https://splashsend.com/api/v1/email-blocks/upcoming-classes" \
  -H "Authorization: Bearer sk_live_…"

Webhooks

Events SplashSend sends to your own systems as they happen. Add endpoints in Setup → Webhooks, where each one gets its own signing secret and receives only the events you pick.

Message: a POST with a JSON body of { id, event, timestamp, data }. id (evt_…) stays the same through every retry and any later redelivery, so use it to skip a message you've already handled. timestamp is when this copy was sent, so a redelivery has a new one and a check on its age still passes. data holds ids and plain values, and never a secret.

Signature: X-SplashSend-Signature is the hex HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret. Compute it over the body exactly as it arrived, and compare the two in constant time. X-SplashSend-Event names the event.

Delivery: reply with any 2xx status to acknowledge a message. A message is tried up to three times: the second try comes 5 seconds after the first fails, and the third 15 seconds after the second. Each try waits up to 10 seconds for a reply. Redirects aren't followed, so a 3xx reply counts as a failure. Up to four messages are sent to an endpoint at once, so they can arrive out of order.

Pausing: an endpoint is paused when every try has failed for 20 messages in a row and the first of those failures was at least a day ago. The account's owner and admins are emailed when that happens. In the endpoint's Deliveries log you can Redeliver one message, or click Send failed ones again to resend everything from the last 30 days that didn't get through. Delivery logs are kept for 30 days.

Email events: every email.* event carries emailSendId, contactId and email, the address the email went to. campaignId is set for a campaign email, dripStepId for a drip email and scheduledEmailId for a Cruise email, and the other two are null.

Names: every event about a person also carries their firstName and lastName, null when they have none.

Apps: an app subscribes to these events with POST /webhooks rather than through Setup. See Webhook subscriptions.

EVENTcontact.subscribed

Person subscribed

Someone joined your list: through a form, the API, an AI assistant, the app, a new Shopify customer or a checkout opt-in. An import or a store's first sync doesn't send it, unless the import was told to start automations. Some sources also send the person's name. When you need their details, read them with GET /contacts/{id}.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "contact.subscribed",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "email": "sam@example.com",
    "name": "Sam Lee"
  }
}
EVENTcontact.unsubscribed

Person unsubscribed

Someone unsubscribed, from an unsubscribe link, their preferences page, the app, the API or your Shopify store. It's also sent when a spam complaint unsubscribes them.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "contact.unsubscribed",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "email": "sam@example.com"
  }
}
EVENTcontact.updated

A person's details changed

A person's name, email address, source or details changed. changed lists what changed: name, email, source or a detail's name. Nothing is sent when a save changes nothing.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "contact.updated",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "email": "sam@example.com",
    "changed": [
      "name",
      "Birthday"
    ]
  }
}
EVENTcampaign.sent

Campaign sent

A campaign finished sending. totalSent is how many emails it sent.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "campaign.sent",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "campaignId": "cm4t9k2ab0001qz0870m3fh2c",
    "name": "October newsletter",
    "totalSent": 1250
  }
}
EVENTemail.delivered

Email delivered

The receiving mail server accepted an email.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "email.delivered",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "emailSendId": "cm4u6p1sd0009l208e7c5x4wy",
    "campaignId": "cm4t9k2ab0001qz0870m3fh2c",
    "email": "sam@example.com",
    "scheduledEmailId": null,
    "dripStepId": null,
    "contactId": "cm4u2x8yq0003l208f1k9d7vz"
  }
}
EVENTemail.opened

Email opened

Someone opened an email. An open is counted when the email's images load, so some mail apps report opens nobody made and others report none.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "email.opened",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "emailSendId": "cm4u6p1sd0009l208e7c5x4wy",
    "campaignId": "cm4t9k2ab0001qz0870m3fh2c",
    "email": "sam@example.com",
    "scheduledEmailId": null,
    "dripStepId": null,
    "contactId": "cm4u2x8yq0003l208f1k9d7vz"
  }
}
EVENTemail.clicked

Link clicked

Someone clicked a link in an email. url is where the link goes, without a form's personal-link token. linkId is the link's name, when you gave it one. Clicks made by link-checking software as the email arrives aren't sent.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "email.clicked",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "emailSendId": "cm4u6p1sd0009l208e7c5x4wy",
    "campaignId": "cm4t9k2ab0001qz0870m3fh2c",
    "email": "sam@example.com",
    "scheduledEmailId": null,
    "dripStepId": null,
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "url": "https://shop.example.com/sale",
    "linkId": "shop-the-sale"
  }
}
EVENTemail.bounced

Email bounced

An email bounced. bounceType is hard when the address can't receive email, which stops it at once, or soft for a problem that may pass. Three soft bounces stop an address.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "email.bounced",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "emailSendId": "cm4u6p1sd0009l208e7c5x4wy",
    "campaignId": "cm4t9k2ab0001qz0870m3fh2c",
    "email": "sam@example.com",
    "scheduledEmailId": null,
    "dripStepId": null,
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "bounceType": "hard"
  }
}
EVENTemail.complained

Marked as spam

Someone reported an email as spam. They're unsubscribed and their address is stopped. contact.unsubscribed is sent too, if they were subscribed.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "email.complained",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "emailSendId": "cm4u6p1sd0009l208e7c5x4wy",
    "campaignId": "cm4t9k2ab0001qz0870m3fh2c",
    "email": "sam@example.com",
    "scheduledEmailId": null,
    "dripStepId": null,
    "contactId": "cm4u2x8yq0003l208f1k9d7vz"
  }
}
EVENTorder.placed

Order placed

A new order arrived from your Shopify store or POST /orders. It's sent once per order, and only for an order from the last 3 days, so a backfill or a store's first sync doesn't send one for every past order. contactId is null when the order isn't linked to a person.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "order.placed",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "orderId": "cm4u7r3ty000dl208k2p8v6nb",
    "source": "shopify",
    "email": "sam@example.com",
    "total": 42.5,
    "currency": "GBP",
    "orderNumber": "1042",
    "contactId": "cm4u2x8yq0003l208f1k9d7vz"
  }
}
EVENTorder.fulfilled

Order shipped

An order moved into a shipped status: fulfilled in Shopify, or a shipped status sent to POST /orders. It's sent for an order from the last 30 days, including a guest order, which has contactId null.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "order.fulfilled",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "orderId": "cm4u7r3ty000dl208k2p8v6nb",
    "source": "shopify",
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "email": "sam@example.com"
  }
}
EVENTsubmission.created

Form answer / review received

Someone's answers to a form were saved, such as a review or a survey. rating is the score they gave, when the form asks for one. subjectKey names what the answer is about, such as a Shopify product. contactId and email are null for an anonymous answer.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "submission.created",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "submissionId": "cm4u8s4uv000fl208q1d7m5xa",
    "formId": "cm4s1f5gh0002rt09a6b3n8qk",
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "email": "sam@example.com",
    "rating": 5,
    "subjectKey": "gid://shopify/Product/7712345678901"
  }
}
EVENTdrip.enrolled

Someone started a drip

Someone started a drip. trigger is what started it, such as contact_subscribed or custom_event.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "drip.enrolled",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "dripId": "cm4r0d2ef0004wx07c9n1h6ps",
    "dripName": "Welcome series",
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "trigger": "contact_subscribed"
  }
}
EVENTdrip.step

A drip reached a Notify your systems step

A drip reached a "Notify your systems" step for someone. note is the text typed on the step, when there is one. A step an app added (PUT /drip-steps/{key}) carries step, its key and name, and values, the settings the account filled in, and goes only to that app's own subscriptions.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "drip.step",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "dripId": "cm4r0d2ef0004wx07c9n1h6ps",
    "dripName": "Welcome series",
    "stepId": "cm4r0d2ef0006wx07z3k8j1tg",
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "email": "sam@example.com",
    "note": "Add to the CRM"
  }
}
EVENTform.submitted

Form submitted

Someone submitted a form that has a "Send it to another tool" action. It's sent once per submission. answers holds only the form's own questions, keyed by each question's id.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "form.submitted",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "formId": "cm4s1f5gh0002rt09a6b3n8qk",
    "formName": "Feedback",
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "email": "sam@example.com",
    "name": "Sam Lee",
    "rating": 5,
    "subjectKey": null,
    "answers": {
      "rating": 5,
      "review": "Loved the class"
    }
  }
}
EVENTevent.recorded

An event your systems sent

One of your own events was recorded through POST /events or by an app, and it happened in the last three days: older history sent in later isn't sent. name is the event's name and properties what came with it. Your website's page views and form answers aren't sent here, since they have events of their own.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "event.recorded",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "eventId": "cm4u9t5vw000gl208r2e8n6yb",
    "name": "Class booked",
    "contactId": "cm4u2x8yq0003l208f1k9d7vz",
    "email": "sam@example.com",
    "properties": {
      "class": "Wheel throwing for beginners",
      "date": "2026-10-24"
    },
    "occurredAt": "2026-10-18T13:20:00.000Z",
    "externalId": "hearth-1040",
    "firstName": "Sam",
    "lastName": "Lee"
  }
}
EVENTwebhook.test

Test message

Sent by the Test button on an endpoint in Setup → Webhooks, whether or not the endpoint is switched on. It isn't an event you subscribe to.

{
  "id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
  "event": "webhook.test",
  "timestamp": "2026-10-09T10:00:00.000Z",
  "data": {
    "test": true,
    "message": "This is a test event from SplashSend."
  }
}

Objects

These are the objects the endpoints and webhooks send and receive. A type shown as a link in any table is described here.

OBJECTError

The body of every error response.

Fields
FieldType
error object
error.code "validation_error" | "invalid_request" | "read_only" | "limit_reached" | "sender_not_verified" | "design_not_ready" | "send_refused" | "unauthorized" | "forbidden" | "key_in_web_page" | "plan_limit_reached" | "not_open_yet" | "not_found" | "stopped_address" | "sent_recently" | "idempotency_in_progress" | "payload_too_large" | "idempotency_key_reused" | "rate_limited" | "internal_error" A fixed code your code can act on.
error.message string What went wrong, in words you can show a person.
OBJECTStepField

A setting the account fills in on an app's drip step.

Fields
FieldType
keyrequired string Its key in values.
labelrequired string The words the account sees. max length: 60
type "text" | "number" | "choice" default: "text"
options array of string With type: choice: 2 to 20 options.
required boolean The drip can't go live with it empty. default: false
OBJECTDripStepInput
Fields
FieldType
namerequired string Its name in the drip builder. max length: 60
description string What it does, shown when it's chosen. max length: 200
fields array of StepField max items: 6
OBJECTDripStep

One of this app's drip steps.

Fields
FieldType
key string
name string
description string or null
fields array of StepField
created_at string format: date-time
updated_at string format: date-time
OBJECTEmailBlockInput
Fields
FieldType
namerequired string Its name in the designer. max length: 60
description string max length: 200
data_setrequired string The key of a data set this app made.
titlerequired string The field each card's title shows.
text string The field each card's line of text shows.
image string The field holding each card's picture address.
link string The field holding the address each card's button opens.
button string The words on each card's button. Needs link. Defaults to View. max length: 30
count integer How many cards at most. default: 3 minimum: 1 maximum: 6
OBJECTEmailBlock

One of this app's email blocks.

Fields
FieldType
key string
name string
description string or null
data_set string
title string
text string or null
image string or null
link string or null
button string or null
count integer
created_at string format: date-time
updated_at string format: date-time
OBJECTReportCardInput

A card for Reports, made from one of your events.

Fields
FieldType
namerequired string Its title in Reports. max length: 80
description string A line under the title saying what it shows. max length: 200
eventrequired string The name of the event it reads, as you send it to POST /events. max length: 120
split "none" | "day" | "week" | "property" How its figure is split. default: "week"
property string With split: property: the event detail to split by, such as class. Events sent without it are under Not given. max length: 60
sum string Add up this number from each event, such as price, instead of counting them. An event without a number there adds nothing. max length: 60
money boolean With sum: show the total as money, in the account's currency. default: false
chart "number" | "line" | "bars" | "table" How it's drawn. Defaults to number with no split, line by day or week, and bars by a detail.
OBJECTReportCard

One of this app's report cards.

Fields
FieldType
key string
name string
description string or null
event string
split "none" | "day" | "week" | "property"
property string or null
sum string or null
money boolean
chart "number" | "line" | "bars" | "table"
created_at string format: date-time
updated_at string format: date-time
OBJECTSubscription

One of an app's webhook subscriptions.

Fields
FieldType
id string
url string format: uri
events array of string
event_name string or null The one of your own events it's narrowed to, with event.recorded.
step_note string or null The step note it's narrowed to, with drip.step.
active boolean False while it's paused after a day of failed deliveries, or switched off in Setup.
paused_at string or null format: date-time
created_at string format: date-time
OBJECTPageMeta

Paging details for a list.

Fields
FieldType
page integer This page's number, from 1.
limit integer How many fit on a page.
total integer How many there are in all.
totalPages integer How many pages there are.
OBJECTPerson

A person, as every People endpoint returns them.

Fields
FieldType
id string The person's id.
email string format: email
name string Their full name, or an empty string when it isn't known.
firstName string or null
lastName string or null
source string Where they first came from.
subscriptionStatus "subscribed" | "unsubscribed"
suppressed boolean True when the address is stopped because it bounced or was reported as spam. A stopped address is never emailed, whatever subscriptionStatus says.
createdAt string format: date-time
updatedAt string format: date-time
OBJECTPersonWithProperties

A person with their details.

Fields
FieldType
id string The person's id.
email string format: email
name string Their full name, or an empty string when it isn't known.
firstName string or null
lastName string or null
source string Where they first came from.
subscriptionStatus "subscribed" | "unsubscribed"
suppressed boolean True when the address is stopped because it bounced or was reported as spam. A stopped address is never emailed, whatever subscriptionStatus says.
createdAt string format: date-time
updatedAt string format: date-time
properties map of string Their details, keyed by name. Only details with a value are included.
OBJECTPersonResult

What POST /contacts returns.

Fields
FieldType
data Person A person, as every People endpoint returns them.
meta object
meta.created boolean True when they're new.
meta.subscribed boolean True when this request subscribed them.
meta.suppressed boolean True when the address is stopped, so it wasn't subscribed.
meta.keptOptOut boolean True when they had unsubscribed and still are, because resubscribe wasn't true.
warnings array of string Notes for you, such as a kept opt-out or details that were skipped. The field is left out when there are none.
OBJECTPropertyValues

Details keyed by name, as they're called in SplashSend (any case). A value can be text, a number, or true or false. Send an array for a List or Multi-select detail, and null to clear a value. A date detail is stored as YYYY-MM-DD, and 15/01/1990 (day first) is read as 1990-01-15. The first 200 details are read, and each value is cut at 2,000 characters. A name that isn't a detail in your account is skipped with a warning. Shopify Tags, address details and details SplashSend works out for itself can't be set here, and are skipped with a warning too.

Type: map of string, number, boolean, array or null

OBJECTPropertyValue

One of your details, with a person's value.

Fields
FieldType
id string The detail's id.
name string The detail's name.
type string What kind of value it holds, such as text, date or list.
value string Their value, as text. Empty when they have none.
OBJECTProfile

A person with everything SplashSend knows about them.

Fields
FieldType
id string The person's id.
email string format: email
name string Their full name, or an empty string when it isn't known.
firstName string or null
lastName string or null
source string Where they first came from.
subscriptionStatus "subscribed" | "unsubscribed"
suppressed boolean True when the address is stopped because it bounced or was reported as spam. A stopped address is never emailed, whatever subscriptionStatus says.
createdAt string format: date-time
updatedAt string format: date-time
consent object
consent.email object
consent.email.status "subscribed" | "unsubscribed"
consent.email.mailable boolean True when a send would reach them. A send doesn't reach someone who unsubscribed, whose address is stopped or who is on a list marked never to be emailed.
consent.email.consentedAt string or null When they agreed to hear from you. format: date-time
consent.email.consentMethod string or null How they agreed, such as signup_form, import, shopify_sync or api.
consent.email.unsubscribedAt string or null format: date-time
consent.email.unsubscribeReason string or null The reason they gave for leaving, when they gave one.
consent.email.suppressedAt string or null When their address was stopped. format: date-time
consent.email.suppressionReason string or null Why it was stopped. One of hard_bounce, soft_bounce_limit and complaint.
consent.email.blockedByLists array of object The lists marked never to be emailed that they're on. Sent only to a key with lists:read.
consent.email.blockedByLists[].id string
consent.email.blockedByLists[].name string
consent.sms object
consent.sms.phone string or null Their mobile number in international format, such as +447700900123.
consent.sms.status "not_subscribed" | "subscribed" | "unsubscribed"
consent.sms.consentedAt string or null When they agreed to texts. format: date-time
consent.sms.consentMethod string or null How they agreed, such as signup_form, shopify_sync or api.
consent.sms.unsubscribedAt string or null format: date-time
consent.sms.suppressedAt string or null When texts to their number were stopped. format: date-time
consent.sms.suppressionReason string or null Why they were stopped. One of invalid_number, carrier_rejected and stop.
consent.topics array of object The topics on your preferences page that they've chosen to be in or out of.
consent.topics[].name string
consent.topics[].subscribed boolean
details array of object Their details that have a value.
details[].name string
details[].type string What kind of value it holds, such as text, date or list.
details[].value string
details[].source "manual" | "auto" | "guessed" How the value got there, in the words their page in SplashSend uses. manual is a value you entered, auto one kept up to date automatically, and guessed one filled in as they arrived.
emails object The emails sent to them, counted the way Reports counts them.
emails.sent integer Emails sent to them. An email sent again after a failed try counts once.
emails.delivered integer
emails.bounced integer
emails.opened integer Emails they opened.
emails.clicked integer Emails they clicked a link in. A click made by link-checking software isn't counted.
emails.lastSentAt string or null format: date-time
recentActivity array of ActivityEvent The 25 most recent things that happened to them, newest first. GET /activity with their email lists the rest. Sent only to a key with events:read.
orders object Their orders, from a Shopify store or sent with POST /orders. Sent only to a key with orders:read.
orders.count integer Their sales. An order refunded in full, voided, cancelled or failed isn't one.
orders.revenue number What their sales came to.
orders.currency string or null The currency of their latest sale.
orders.recent array of object Their 10 most recent orders, newest first, refunded and cancelled ones included.
orders.recent[].id string The order's id in SplashSend.
orders.recent[].order_id string or null The shop's order number.
orders.recent[].total number
orders.recent[].currency string
orders.recent[].status string or null The order's status, as the shop sent it.
orders.recent[].order_date string format: date-time
orders.recent[].source "shopify" | "api" shopify for a Shopify store's order, api for one sent with POST /orders.
orders.recent[].is_sale boolean False when it was refunded in full, voided, cancelled or failed.
orders.recent[].credited_to_email boolean True when it's credited to an email they were sent.
data array of object The data-set rows matched to them: in each data set matched on a detail, the row whose column equals theirs. Then the rows those join to, such as the class a booking is for, where an email would read the same row. Sent only to a key with data:read.
data[].set string The data set's key.
data[].name string The data set's name.
data[].values map of string The row's values keyed by column name, written the way an email prints them.
data[].through string For a joined row, the key of the set whose row joins to it. Left out for their own rows.
OBJECTImportContactInput

One person in an import.

Fields
FieldType
emailrequired string Their email address. format: email
firstName string Their first name.
lastName string Their last name.
name string Their full name, split into first and last. Ignored when firstName or lastName is sent.
source string Where they came from. Used only for someone new.
subscriptionStatus "subscribed" | "unsubscribed" How someone new arrives. Defaults to subscribed. Someone already here keeps their status.
properties PropertyValues Details keyed by name, as they're called in SplashSend (any case).
OBJECTImportResult

What POST /contacts/import returns.

Fields
FieldType
data object
data.created integer People added.
data.updated integer People already here, updated.
data.failed integer Rows that weren't saved.
data.addedToList integer People added to the list. The field is there only when you sent listId.
meta object
meta.startAutomations boolean Whether automations ran.
meta.errors array of object Up to 50 rows that failed.
meta.errors[].index integer The row's position in contacts, from 0.
meta.errors[].email string The row's email address, when it had one.
meta.errors[].message string Why it failed.
meta.unknownProperties array of string Detail names that aren't in your account, up to 50.
meta.planLimitReached boolean True when the import stopped at your plan's people limit.
meta.stoppedAtIndex integer The row the import stopped at. It and the rows after it weren't added.
warnings array of string Notes about details that were skipped, or a list that couldn't be updated. The field is left out when there are none.
OBJECTEvent

A recorded event.

Fields
FieldType
id string The event's id.
name string
email string The person's email address, sent only to a key with contacts:read. format: email
properties object or null What was sent with it.
occurred_at string format: date-time
external_id string or null Your id for it, when you sent one.
OBJECTActivityEvent

Something that happened.

Fields
FieldType
id string The id of what it records: the email, the order, the answer and so on.
name string The event, such as email.opened or one your systems send.
kind "built_in" | "custom" built_in for SplashSend's own events, custom for the ones your systems send.
occurred_at string format: date-time
email string or null Whose it was, sent only to a key with contacts:read. Null for an answer given without an email address. format: email
first_name string or null Their first name, when SplashSend has it. Sent only to a key with contacts:read.
last_name string or null Their last name, when SplashSend has it. Sent only to a key with contacts:read.
properties object What it carries. GET /activity/catalogue lists each built-in event's; a custom event's are what your systems sent with it.
OBJECTCatalogueEvent

A kind of event SplashSend records.

Fields
FieldType
name string Its name. Webhooks use the same one.
label string Its name in plain words.
group string Email, People, Shop, Website, Forms or Drips.
properties array of object What it carries in properties.
properties[].key string
properties[].label string
properties[].type string
counting string How it's counted.
webhook boolean Webhooks send it under this name.
needs string or null The scope needed beyond events:read to see it: contacts:read or orders:read.
OBJECTOrder

An order sent in with POST /orders.

Fields
FieldType
id string The order's id in SplashSend.
order_id string Your order number or id.
email string The buyer's email address, sent only to a key with contacts:read. format: email
total number
currency string
status string or null
order_date string format: date-time
contact_id string or null The buyer's person id. Null when the order has no person.
source string
OBJECTList

A list you add people to yourself.

Fields
FieldType
id string
name string
description string or null
blockSending boolean When true, nobody on the list is sent a campaign, a drip email or a Cruise email, even when another group they're in is picked. Their subscriptions don't change.
memberCount integer How many people are on it.
createdAt string format: date-time
updatedAt string format: date-time
OBJECTListEnvelope

A list, as the single-list endpoints return it.

Fields
FieldType
data List A list you add people to yourself.
OBJECTListInput

The fields of a list you can set.

Fields
FieldType
name string max length: 120
description string or null Send null to clear it. max length: 500
blockSending boolean Makes it a "Never email these people" list. Take someone off the list and they can be emailed again as before.
OBJECTListMember

A person on a list.

Fields
FieldType
id string The person's id.
email string Their email address, sent only to a key with contacts:read. format: email
name string Their name, sent only to a key with contacts:read.
subscriptionStatus "subscribed" | "unsubscribed"
addedAt string When they were added to the list. format: date-time
OBJECTMembersInput

People already in your account, by id, by email address or both. Up to 500 in one request.

Fields
FieldType
contactIds array of string
emails array of string
OBJECTProductInput

A product to add or update.

Fields
FieldType
product_idrequired string or number Your id for the product. Sending it again updates the product.
title string Its name. Needed for a new product.
price number or string Its price, such as 18 or "18.00".
currency string A currency code, such as GBP.
image_url string Its picture, as an http(s) address. format: uri
url string Its page in your shop. Product blocks link here. format: uri
handle string Its slug in your shop's addresses. Defaults to product_id.
description string max length: 300
product_type string Its type, such as Mugs.
vendor string Who makes it.
status string active, draft or archived, in any case. Defaults to active for a new product.
tags string or array of string Its tags, as an array or as one string.
inventory integer How many you have in stock. When a product goes from 0 to more, the people who asked to hear when it was back are told (a drip that starts with "They asked to hear when it’s back"). minimum: 0
in_stock boolean For a shop that only knows whether it is in stock: true or false. Ignored when inventory is sent.
OBJECTProduct

A product in your catalogue.

Fields
FieldType
product_id string The id you sent, or Shopify's id for a Shopify product.
source "api" | "shopify" api for a product sent with POST /products.
title string
handle string
price string or null Its price, as text.
currency string or null
image_url string or null
url string or null Its page in your shop, when known.
product_type string or null
vendor string or null
status "ACTIVE" | "DRAFT" | "ARCHIVED" Only ACTIVE products appear in emails.
updated_at string format: date-time
OBJECTDataSetSummary

A data set, as GET /data lists it.

Fields
FieldType
key string Its identifier, used in the API and in email tags.
name string
description string or null
source "live" | "manual" manual for a set you keep yourself, live for one read from a connected source.
writable boolean True when it takes rows over the API.
row_count integer
updated_at string or null When its rows last changed, when that's known. format: date-time
fields array of DataField
joins array of DataJoin The other sets it joins.
OBJECTDataField

A column of a data set.

Fields
FieldType
key string What rows and email tags call it.
label string Its name in the app.
type "words" | "number" | "money" | "date" | "boolean" | "link" | "image"
OBJECTDataFieldInput

A column of a data set you keep yourself.

Fields
FieldType
label string Its name. Needed for a new field. max length: 60
type "words" | "number" | "money" | "date" | "boolean" | "link" | "image" What it holds. default: "words"
key string Its key, made from the label when left out. On an update, send a field's key to keep that field.
example string A sample value, shown in the app.
OBJECTDataJoin

A join from this set to another one.

Fields
FieldType
fieldrequired string One of this set's fields. A request can name it by key or label.
torequired string The other set's key.
to_fieldrequired string The other set's field with the matching values. A request can name it by key or label.
OBJECTMatchOn

What a matched set matches on.

Fields
FieldType
detailrequired string The person's side: email, name, source or the name of one of your details.
fieldrequired string The set's side: one of its fields, by key or label.
OBJECTRowValues

A row's values, keyed by a field's key in any case, or by its label.

Type: map of string, number, boolean or null

OBJECTDataRows

A page of a data set's rows.

Fields
FieldType
data_set string The set's key.
fields array of DataField Its fields. Columns holding email addresses are left out without contacts:read.
rows array of DataRow
total integer How many rows there are to read, for paging.
limit integer
offset integer
OBJECTDataRow

One row: its values keyed by field key, and its id in a set you keep yourself.

Fields
FieldType
id string The row's id, in a set you keep yourself.
OBJECTWebhookEnvelope

The body of every webhook message.

Fields
FieldType
id string The message's id, such as evt_…. The same on every retry and redelivery.
event string The event's name, such as order.placed.
timestamp string When this copy was sent. A redelivery has a new one. format: date-time
data object What happened, as ids and plain values. It never holds a secret.