Sendslay

Developers

The Sendslay API.

18 endpoints, one header, and a key that can read and write your list but can never send a campaign. Everything below works today — nothing here is planned.

Quick start

Three minutes to your first call.

One base URL, one header. There is no SDK to install and no OAuth dance.

1. Make a key

In your dashboard: Settings → API keys. Tick only the scopes that integration needs. The key is shown once — it is stored hashed, so nobody, including us, can read it back to you.

2. Send it as a Bearer token

Every request goes to https://sendslay.com/api/v1 over HTTPS. Keys start with sk_live_.

3. Your plan has to include API access

API access is on Growth and Scale. On Free and Starter, key creation is refused — and an existing key stops working if the workspace moves down to one of those plans, which is checked on every single request, not just when the key was made. See pricing.

Your first call
curl -H 'Authorization: Bearer sk_live_…' \
  'https://sendslay.com/api/v1/subscribers?pageSize=5'
Adding someone
curl -X POST 'https://sendslay.com/api/v1/subscribers' \
  -H 'Authorization: Bearer sk_live_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "jane@example.com",
    "firstName": "Jane",
    "consentSource": "api"
  }'

Keys belong on your server. A key in browser or mobile code is public the moment it ships — and it carries every scope you ticked.

Scopes

Seven scopes. A key has exactly what you ticked.

A key is not a user — it has no role and no seat. Its scopes are its whole authority, so anything not on this list cannot be done with a key at all.

ScopeWhat it lets a key do
subscribers:readRead your subscribers, including their email addresses
subscribers:writeAdd, update and delete subscribers
groups:readRead your groups
groups:writeCreate and change groups
campaigns:readRead campaigns and their results
suppression:readRead your suppression list
suppression:writeAdd addresses to your suppression list

What keys cannot do — on purpose

This list is short because it is meant to be. Each of these is missing for a reason, not because we have not got to it yet.

Sending a campaign
There is no campaigns:write scope. Sending is the one thing in this product that cannot be undone: a wrong loop, a leaked key, and half-written email reaches your whole list. That decision stays with a person who has seen the pre-send checks.
Creating or revoking API keys
A key can never make another key. Otherwise one leaked key keeps itself alive forever — you revoke the old one, not knowing it already made a new one.
Billing, domains, senders and team
These are the workspace owner's decisions and need a signed-in human.
Deleting someone's data under GDPR
It runs on one person's request and has no undo. Not work for a script.
Un-suppressing an address, and deleting groups
Both are quiet ways to start mailing people who should not be mailed. Do them in the dashboard, where you can see what you are doing.

Reference

All 18 endpoints.

Every path below is relative to the base URL, and every one of them needs the Authorization header. Anything not listed here refuses API keys outright, even with a perfectly valid key.

Base URL
https://sendslay.com/api/v1

Subscribers

Seven endpoints. This is where nearly every integration lives — your app gains a customer, and one call later they are on the list with a consent record attached.

GET/subscriberssubscribers:read

List subscribers, newest first.

Query

search
Matches email, first name or last name.
status
ACTIVE · UNSUBSCRIBED · BOUNCED · JUNK · UNCONFIRMED
groupId
Only members of this group.
page
Default 1.
pageSize
Default 25, maximum 100.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  'https://sendslay.com/api/v1/subscribers?status=ACTIVE&pageSize=50'
Response
{
  "data": [ … ],
  "total": 1284,
  "page": 1,
  "pageSize": 25
}

pageSize must be between 1 and 100 — a larger number is a 400, not a silent cap. Read total and walk the pages.

GET/subscribers/:idsubscribers:read

One subscriber, with the groups they are in.

Request
curl -H 'Authorization: Bearer sk_live_…' \
  https://sendslay.com/api/v1/subscribers/9f1c...
Response
{
  … every field above, plus:
  "memberships": [
    { "groupId": "4b2a...", "group": { "name": "Newsletter" } }
  ]
}

The list endpoint does not include memberships — only this one does. If you need groups for many subscribers, ask per group (GET /groups/:id/subscribers) rather than calling this in a loop.

POST/subscriberssubscribers:write

Add a subscriber.

Body

email *
Must be a valid address.
firstName · lastName
Up to 100 characters each.
status
Defaults to ACTIVE.
fields
Any JSON object — your own custom fields.
timezone
IANA name, e.g. "Asia/Karachi". A wrong name is rejected with 400, not quietly turned into UTC.
consentSource
Where permission came from — "api", "signup-form", your own label.
requireDoubleOptIn
true sends a confirmation email and creates the subscriber as UNCONFIRMED. No campaign reaches them until they click.
groupIds
Up to 50 group ids to join on creation.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  -X POST https://sendslay.com/api/v1/subscribers \
  -H 'Content-Type: application/json' \
  -d '{"email":"jane@example.com","firstName":"Jane","consentSource":"api","requireDoubleOptIn":true}'
Response
{
  "id": "9f1c...",
  "tenantId": "3a7e...",
  "email": "jane@example.com",
  "firstName": "Jane",
  "lastName": "Doe",
  "status": "ACTIVE",
  "externalCustomerId": null,
  "totalOrders": 0,
  "totalSpent": 0,
  "lastOrderAt": null,
  "fields": { "city": "Karachi" },
  "timezone": "Asia/Karachi",
  "consentSource": "api",
  "consentAt": "2026-09-20T09:14:02.881Z",
  "consentIp": "203.0.113.7",
  "subscribedAt": "2026-09-20T09:14:02.881Z",
  "unsubscribedAt": null,
  "createdAt": "2026-09-20T09:14:02.881Z",
  "updatedAt": "2026-09-20T09:14:02.881Z"
}

The IP the call comes from is recorded as consentIp. That record is the only evidence you have if someone files an abuse report, so send consentSource on every call.

PATCH/subscribers/:idsubscribers:write

Change a subscriber. Send only the fields you are changing.

Body

Any field from POST
All optional here.
timezone: null
Clears it — the workspace timezone takes over again.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  -X PATCH https://sendslay.com/api/v1/subscribers/9f1c... \
  -H 'Content-Type: application/json' \
  -d '{"lastName":"Khan","fields":{"plan":"pro"}}'

If the address is on your suppression list, status is forced to UNSUBSCRIBED no matter what you send — and unsubscribing always puts the address on that list. So writing status: "ACTIVE" cannot quietly put someone back on the list who asked to be off it.

DELETE/subscribers/:idsubscribers:write

Delete a subscriber.

Request
curl -H 'Authorization: Bearer sk_live_…' \
  -X DELETE https://sendslay.com/api/v1/subscribers/9f1c...
Response
{ "id": "9f1c..." }

This removes the row, not the suppression entry. If they unsubscribed or bounced, that address stays suppressed — which is what keeps a re-import from mailing them again.

POST/subscribers/:id/groupssubscribers:write

Add a subscriber to one or more groups.

Body

groupId
A single group.
groupIds
Up to 50. Send either shape.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  -X POST https://sendslay.com/api/v1/subscribers/9f1c.../groups \
  -H 'Content-Type: application/json' \
  -d '{"groupIds":["4b2a...","7c9d..."]}'
DELETE/subscribers/:id/groups/:groupIdsubscribers:write

Remove a subscriber from one group.

Request
curl -H 'Authorization: Bearer sk_live_…' \
  -X DELETE https://sendslay.com/api/v1/subscribers/9f1c.../groups/4b2a...

Groups

Five endpoints. A group is a list you put people on by hand or by integration — as opposed to a segment, which is a rule.

GET/groupsgroups:read

List groups with their member counts.

Query

search
Matches the name.
page · pageSize
Default 1 and 25, max 100.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  https://sendslay.com/api/v1/groups
Response
{
  "data": [
    { "id": "4b2a...", "name": "Newsletter", "description": null,
      "memberCount": 812,
      "createdAt": "2026-08-02T11:20:00.000Z",
      "updatedAt": "2026-09-19T06:02:11.118Z" }
  ],
  "total": 6, "page": 1, "pageSize": 25
}
GET/groups/:idgroups:read

One group, with memberCount.

Request
curl -H 'Authorization: Bearer sk_live_…' \
  https://sendslay.com/api/v1/groups/4b2a...
GET/groups/:id/subscriberssubscribers:read

The members of a group.

Query

Same as GET /subscribers
search, status, page, pageSize.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  'https://sendslay.com/api/v1/groups/4b2a.../subscribers?status=ACTIVE'
Response
{
  "data": [ … ],
  "total": 1284,
  "page": 1,
  "pageSize": 25
}

Note the scope: this returns email addresses, so it needs subscribers:read — not groups:read.

POST/groupsgroups:write

Create a group.

Body

name *
1–120 characters, unique in your workspace.
description
Up to 500 characters.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  -X POST https://sendslay.com/api/v1/groups \
  -H 'Content-Type: application/json' \
  -d '{"name":"Newsletter"}'
PATCH/groups/:idgroups:write

Rename a group or change its description.

Request
curl -H 'Authorization: Bearer sk_live_…' \
  -X PATCH https://sendslay.com/api/v1/groups/4b2a... \
  -H 'Content-Type: application/json' \
  -d '{"name":"Weekly newsletter"}'

Deleting a group is not available to keys — a group is attached to campaigns and forms, and that is a decision for a person looking at the screen.

Segments

Two endpoints, both read-only. A segment is a saved rule ("no opens in 30 days"), so its membership is computed, never stored.

GET/segmentsgroups:read

All segments, most recently changed first.

Request
curl -H 'Authorization: Bearer sk_live_…' \
  https://sendslay.com/api/v1/segments
Response
[
  { "id": "1d4f...", "name": "Quiet customers", "description": null,
    "match": "ALL", "rules": [ … ],
    "createdAt": "…", "updatedAt": "…" }
]

This one returns a plain array, not the paginated { data, total } shape — there are rarely many segments.

GET/segments/:idgroups:read

One segment and its rules.

Request
curl -H 'Authorization: Bearer sk_live_…' \
  https://sendslay.com/api/v1/segments/1d4f...

Segments use the groups scopes on purpose — both answer the same question, "who is on this list". Creating and changing segments is not available to keys.

Campaigns

Two endpoints, both read-only — this is how you pull results into your own dashboard.

GET/campaignscampaigns:read

List campaigns with their counters.

Query

search
Matches name or subject.
status
DRAFT · SCHEDULED · SENDING · SENT · FAILED
page · pageSize
Default 1 and 25, max 100.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  'https://sendslay.com/api/v1/campaigns?status=SENT'
Response
{
  "data": [
    { "id": "8e3b...", "name": "September update", "subject": "What shipped",
      "status": "SENT", "sentAt": "2026-09-14T04:00:07.412Z",
      "recipientCount": 1204, "deliveredCount": 1189,
      "openedCount": 431, "clickedCount": 96,
      "bouncedCount": 15, "unsubscribedCount": 3,
      "sender": { "id": "…", "name": "Sendslay", "email": "hello@yourdomain.com" },
      "group": { "id": "4b2a...", "name": "Newsletter", "memberCount": 812 } }
  ],
  "total": 18, "page": 1, "pageSize": 25
}

openedCount is a soft number everywhere in email, not only here: Apple Mail Privacy Protection opens messages on the reader's behalf. Clicks are the number a human actually produced.

GET/campaigns/:idcampaigns:read

One campaign, including its HTML body and A/B variants.

Request
curl -H 'Authorization: Bearer sk_live_…' \
  https://sendslay.com/api/v1/campaigns/8e3b...

Creating, editing, scheduling and sending a campaign are all unavailable to keys. See "What keys cannot do" below — this is the one decision we will not let a script make.

Suppression

Two endpoints. The suppression list is the set of addresses that are never mailed again, whatever any group or import says.

GET/suppressionsuppression:read

List suppressed addresses.

Query

search
Matches the address.
reason
BOUNCE · COMPLAINT · UNSUBSCRIBE · MANUAL
page · pageSize
Default 1 and 25, max 100.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  'https://sendslay.com/api/v1/suppression?reason=COMPLAINT'
Response
{
  "data": [
    { "id": "2c8a...", "email": "bounced@example.com",
      "reason": "BOUNCE", "detail": "smtp; 550 5.1.1 user unknown",
      "createdAt": "2026-09-11T22:04:51.007Z" }
  ],
  "total": 47, "page": 1, "pageSize": 25
}
POST/suppressionsuppression:write

Suppress an address by hand.

Body

email *
The address to stop mailing.
reason
MANUAL or UNSUBSCRIBE only. BOUNCE and COMPLAINT belong to Amazon's feedback, and they never come off — the API will not let you write one.
detail
Your own note, up to 500 characters.
Request
curl -H 'Authorization: Bearer sk_live_…' \
  -X POST https://sendslay.com/api/v1/suppression \
  -H 'Content-Type: application/json' \
  -d '{"email":"angry@example.com","reason":"MANUAL","detail":"asked us by phone"}'

Removing an address from the suppression list is not available to keys. One careless script undoing a complaint is how a sending reputation dies, and it takes months to rebuild.

How it behaves

Things that are true on every endpoint.

Read this once and you will not have to guess again.

Paging is { data, total, page, pageSize }

Every list endpoint returns that shape, except GET /segments, which returns a plain array. pageSize must be 1–100. Asking for 5,000 is a 400, not a silent 100 — so read total and walk the pages. There is no endpoint that hands you the whole list at once, on purpose: that one call is what takes an API down.

An unknown field is a 400, not a shrug

Send frstName and the whole request is rejected. We would rather fail loudly than accept a typo and drop the value — which is how half a list ends up with no first name and nobody notices for a month.

Times are UTC, ISO 8601

Both in and out. There is no local-time mode. timezone on a subscriber is a separate thing — it decides when they receive mail, not how dates are written.

Your workspace never has to be named

There is no tenant or workspace id in any path, body or query. The key already decides which workspace you are in, and it cannot reach another one.

60 requests per minute, per key

Every response carries X-RateLimit-Remaining, so a well-behaved script never has to be surprised. Over the limit you get a 429 with Retry-After in seconds. The limit is per key — a separate key per integration gets its own allowance.

Errors say what happened

401  Invalid or revoked API key
     …or your plan no longer includes API access.
     All three read the same on purpose.

403  This API key does not have the
     "subscribers:write" scope.

404  Subscriber 9f1c… not found
     Also what you get for another
     workspace's id — never "forbidden".

409  jane@example.com already exists

429  Too many requests. Retry-After: 37

A validation failure is a 400 whose message is an array — one line per field that was wrong.

Make a key and try it.

Settings → API keys, tick two scopes, and your first call works a minute later. API access comes with Growth.

Stuck on something the page does not answer? support@sendslay.com — a person reads it.