~/sendslice

API reference

Base URL https://api.sendslice.com. JSON in, JSON out.

Quickstart

  1. POST /signup with your email. You get a starter key.
  2. Click the confirmation link. This ends the starter key.
  3. Sign in with the same address and issue a new API key.
  4. Design the email in the builder and save it. You get a code like bld_x7Qp2mZc.
  5. POST /send with that code.

New accounts send from the shared address, to confirmed addresses only (up to 5), with a test banner, 50 a month. That is why the example sends to you. See Plans.

shell
# 1. create an account (returns a starter key)
curl -X POST https://api.sendslice.com/signup \
  -H 'content-type: application/json' \
  -d '{ "email": "you@example.com" }'

# 2. click the link in the confirmation email
#    (this ends the starter key)

# 3. sign in with the same address, issue a new key

# 4. save a design in the builder, copy its code

# 5. send
curl -X POST https://api.sendslice.com/send \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{ "to": "you@example.com", "subject": "Hello",
        "code": "bld_x7Qp2mZc", "data": { "first_name": "Sam" } }'

Authentication

Send your API key on every authenticated call, in either header.

Only a hash of the key is stored, so it cannot be shown again. If you lose it, sign in and issue a new one. If it leaks, call POST /keys/rotate.

headers
authorization: Bearer ss_live_...
or
x-api-key: ss_live_...

Sending

POST /send takes the message body in one of three ways. Give exactly one.

1. code: a saved build

Design the email in the builder and save it, or call POST /builds. Each build has a code like bld_x7Qp2mZc that fixes the template and the look. Pass the code and the template fields in data. Editing the build changes every later send that uses its code.

curl
curl -X POST https://api.sendslice.com/send \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{
    "to": "sam@example.com",
    "subject": "Welcome to Acme",
    "code": "bld_x7Qp2mZc",
    "data": { "first_name": "Sam" }
  }'
fetch
const res = await fetch('https://api.sendslice.com/send', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.SENDSLICE_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    "to": "sam@example.com",
    "subject": "Welcome to Acme",
    "code": "bld_x7Qp2mZc",
    "data": { "first_name": "Sam" }
  }),
})
const result = await res.json()

2. template_id: a template with your brand kit

Name a template (welcome password-reset alert generic-message) and it is styled with the account brand kit from POST /brand-kit. Defaults apply if no brand kit is set.

curl
curl -X POST https://api.sendslice.com/send \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{
    "to": "sam@example.com",
    "subject": "Reset your password",
    "template_id": "password-reset",
    "data": { "reset_url": "https://your.app/r/abc" }
  }'

3. html: your own markup

Sent as given, with a plain-text part generated from it. No template and no brand kit. data is not allowed. Accounts that carry the test banner (see Plans) get it on html sends too.

curl
curl -X POST https://api.sendslice.com/send \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{
    "to": "sam@example.com",
    "subject": "Receipt #1042",
    "html": "<h1>Thanks</h1><p>Your order shipped.</p>"
  }'

Sending domains

Until a domain verifies, mail goes from the shared address.

  1. Confirm your account address. Registering a domain before that returns email_not_confirmed.
  2. POST /domains with the domain. The response lists the records.
  3. Publish the ownership TXT record: name _sendslice.<domain>, value sendslice-verify=<token>. The token is in the response.
  4. Publish the DKIM CNAME records from the same response.
  5. Poll GET /domains until the status is verified. Both the TXT and the CNAMEs must resolve.

On a paid plan, a verified domain is what lets you send to anyone and removes the test banner. Without one, a paid plan has its full monthly allowance but the shared-address rules: confirmed addresses only, up to 5, with the banner. On the free plan, a verified domain changes the From address only.

Up to 3 domains per account. If a verified domain stops verifying, sends fail with domain_not_verified rather than falling back to the shared address.

dns
_sendslice.mail.your.app   TXT    "sendslice-verify=3f9c0e..."
abc123._domainkey.mail.your.app  CNAME  abc123.dkim.amazonses.com
def456._domainkey.mail.your.app  CNAME  def456.dkim.amazonses.com
ghi789._domainkey.mail.your.app  CNAME  ghi789.dkim.amazonses.com

Errors

Every error has the same shape: a stable code and a request_id to quote to support.

Quota refusals share the top-level forbidden code; branch on details.code for those. Suppression refusals have their own top-level codes.

response403
{
  "error": {
    "code": "forbidden",
    "message": "Sandbox accounts can send 50 emails a month, and the allowance resets on the 1st. ...",
    "details": { "code": "send_cap", "plan": "sandbox", "sends_allowed": 50, "window": "month" },
    "request_id": "req_8c1f..."
  }
}
CodeStatusMeaning
invalid_request400The body did not match the schema. details names the field.
unauthorized401Missing or unrecognised API key.
forbidden
details.code: send_cap
403The month’s allowance is spent. It resets on the 1st.
forbidden
details.code: unverified_recipient
403This account can only send to confirmed addresses, and that one is not confirmed.
forbidden
details.code: recipient_cap
403At the limit of confirmed addresses.
forbidden
details.code: build_limit_reached
403The account already has 250 saved builds.
email_not_confirmed403Confirm your own address before registering a domain.
domain_not_verified403Your sending domain is no longer verified. Nothing is sent until it is.
recipient_suppressed403That address bounced or complained on this account. GET /suppressions lists it.
recipient_suppressed_platform403That address bounced or complained on the shared address. Send from your own verified domain to reach it.
not_found404No such route or resource, including a deleted build code.
conflict409That resource already exists, or something else changed it first.
domain_taken409Another account has registered that domain.
conflict
details.code: idempotency_key_in_progress
409A request with that Idempotency-Key is still running. Retry with the same key after it finishes.
rate_limited429Too many requests. Back off and retry.
internal_error500Failure on our side. Quote the request_id.
upstream_failure502A service we depend on failed. Safe to retry.
service_unavailable503A service we depend on is unavailable, or sending is turned off. Right now this is what every POST /send returns while billing is being set up. Building, saving and previewing are unaffected. Do not retry in a loop; this lasts until billing opens.

Endpoints

Account

POST /signup no key

Create an account. The response carries a starter API key, shown once. Only a hash of it is stored.

The starter key stops working when you click the confirmation link. After confirming, sign in with the same address and issue a new key.

FieldTypeDescription
emailstringRequired. The account address. A confirmation link is sent to it.
namestringOptional. Shown as the sender name once you send from your own domain.
brand_kitobjectOptional. Same fields as POST /brand-kit.
curl
curl -X POST https://api.sendslice.com/signup \
  -H 'content-type: application/json' \
  -d '{ "email": "you@example.com", "name": "Acme" }'
response200
{
  "customer_id": "cus_9f2c...",
  "email": "you@example.com",
  "plan": "sandbox",
  "api_key": "ss_live_...",
  "api_key_prefix": "ss_live_...a1b2",
  "sandbox": {
    "monthly_sends": 50,
    "verified_recipients": [],
    "note": "Check you@example.com and click the confirmation link. ..."
  },
  "confirmation_email_sent": true
}
GET /confirm/:customer_id/:token no key

The link in the confirmation email. The token is single-use. Opened in a browser it renders a page; requested with an Accept header that does not rank HTML first, it returns JSON.

Confirming ends every API key and sign-in issued before it, including the starter key from POST /signup. The response says so with credentials_reset: true. Sign in with the same address and issue a new key.

response200
{
  "confirmed": "you@example.com",
  "customer_id": "cus_9f2c...",
  "verified_recipients": ["you@example.com"],
  "note": "You can now send to you@example.com. Every API key and sign-in issued before this confirmation has stopped working: sign in with you@example.com and issue a new key. ...",
  "credentials_reset": true
}
POST /confirm/resend key

Send a new confirmation link to the account address. No body. Use the starter key from signup. Rate limited.

curl
curl -X POST https://api.sendslice.com/confirm/resend \
  -H 'authorization: Bearer ss_live_...'
POST /confirm/recipient key

Send a confirmation link to another address. Free accounts, and paid accounts without a verified domain, may only mail confirmed addresses, up to 5 of them. Confirm your own address first. Three requests an hour.

curl
curl -X POST https://api.sendslice.com/confirm/recipient \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{ "email": "colleague@yourcompany.com" }'
POST /keys/rotate key

Replace your API key. Authenticate with the key being replaced; it stops working as soon as the new one is issued. No body.

curl
curl -X POST https://api.sendslice.com/keys/rotate \
  -H 'authorization: Bearer ss_live_...'

Sending

POST /send key

Send one email. Give exactly one of code, template_id or html. More than one, or none, is a 400. See Sending for how the three differ.

FieldTypeDescription
tostringRequired. One recipient address.
subjectstringRequired. One line, up to 998 characters.
codestringA saved build code, bld_.... Uses that build’s template and look.
template_idstringA template id, styled with your brand kit: welcome password-reset alert generic-message.
htmlstringYour own HTML. No template, no brand kit.
dataobjectTemplate fields. Only with code or template_id.
reply_tostringOptional reply-to address.

Optional header: Idempotency-Key, 1-128 characters of letters, digits, ., _ or -. A retry with the same key within 24 hours returns the original result instead of sending again. A second call with the same key while the first is still running gets a 409.

curl
curl -X POST https://api.sendslice.com/send \
  -H 'authorization: Bearer ss_live_...' \
  -H 'idempotency-key: welcome-sam-1' \
  -H 'content-type: application/json' \
  -d '{
    "to": "sam@example.com",
    "subject": "Welcome to Acme",
    "code": "bld_x7Qp2mZc",
    "data": { "first_name": "Sam" }
  }'
fetch
const res = await fetch('https://api.sendslice.com/send', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.SENDSLICE_API_KEY}`,
    'idempotency-key': 'welcome-sam-1',
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    "to": "sam@example.com",
    "subject": "Welcome to Acme",
    "code": "bld_x7Qp2mZc",
    "data": { "first_name": "Sam" }
  }),
})
const result = await res.json()
response202
{
  "send_id": "snd_4a81...",
  "message_id": "0100018f...",
  "status": "sent",
  "to": "sam@example.com",
  "subject": "Welcome to Acme",
  "template_used": "bld_x7Qp2mZc",
  "sent_at": "2026-10-12T14:03:22.418Z"
}
GET /sends key

Your send log, newest first: recipient, subject, template or code, time, status and any error. Refused sends are listed as failed with the refusal code. Message bodies are not stored.

Query: limit (1-200, default 50), since (an ISO time), cursor (the next_cursor from the previous page).

curl
curl -X GET https://api.sendslice.com/sends?limit=50 \
  -H 'authorization: Bearer ss_live_...'

Builds

POST /builds key

Save a design and get its code. The builder calls this when you press save; you can call it directly. template_id is required; the look fields are the same as POST /brand-kit.

Up to 250 builds per account.

curl
curl -X POST https://api.sendslice.com/builds \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{
    "name": "Welcome, pecan",
    "template_id": "welcome",
    "flavor": "pecan",
    "logo_url": "https://your.app/logo.png"
  }'
response201
{
  "build": {
    "code": "bld_x7Qp2mZc",
    "name": "Welcome, pecan",
    "template_id": "welcome",
    "brand_kit": { "flavor": "pecan", "logo_url": "https://your.app/logo.png" },
    "created_at": "2026-10-12T14:00:00.000Z",
    "updated_at": "2026-10-12T14:00:00.000Z"
  }
}
GET /builds key

Every saved build on the account, newest first.

curl
curl -X GET https://api.sendslice.com/builds \
  -H 'authorization: Bearer ss_live_...'
GET /builds/:code key

One saved build.

curl
curl -X GET https://api.sendslice.com/builds/bld_x7Qp2mZc \
  -H 'authorization: Bearer ss_live_...'
PUT /builds/:code key

Replace a build. The code stays the same, so sends using it pick up the new look straight away. Every field is replaced; nothing is carried over.

curl
curl -X PUT https://api.sendslice.com/builds/bld_x7Qp2mZc \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{ "template_id": "welcome", "flavor": "cherry" }'
DELETE /builds/:code key

Delete a build. Returns 204. Sends using its code fail with 404 from then on; a deleted code is never reused.

curl
curl -X DELETE https://api.sendslice.com/builds/bld_x7Qp2mZc \
  -H 'authorization: Bearer ss_live_...'

Brand kit

POST /brand-kit key

Set the account’s brand kit, used by sends with template_id. Sends with code use the build’s own look instead.

A flavour sets every field at once; any field you also pass overrides it. Flavours: blueberry apple cherry pecan key-lime lemon-meringue pumpkin blackberry grasshopper coconut-cream rhubarb chocolate-cream. Or try them in the builder.

curl
curl -X POST https://api.sendslice.com/brand-kit \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{ "flavor": "pecan", "logo_url": "https://your.app/logo.png" }'
GET /brand-kit key

The saved brand kit, or the defaults if none was set.

Domains

POST /domains key

Register a sending domain and get the DNS records to publish. Your own address must be confirmed first (email_not_confirmed otherwise). 201 for a new registration, 200 when the domain is already on your account. Up to 3 domains per account.

The first record is the ownership TXT; the rest are DKIM CNAMEs. Publish all of them, then poll GET /domains. See Sending domains.

curl
curl -X POST https://api.sendslice.com/domains \
  -H 'authorization: Bearer ss_live_...' \
  -H 'content-type: application/json' \
  -d '{ "domain": "mail.your.app" }'
fetch
const res = await fetch('https://api.sendslice.com/domains', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.SENDSLICE_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({ domain: 'mail.your.app' }),
})
const result = await res.json()
response201
{
  "domain": {
    "domain": "mail.your.app",
    "status": "pending",
    "sends_from": "no-reply@mail.your.app",
    "created_at": "2026-10-12T14:00:00.000Z",
    "dns_records": [
      { "type": "TXT",
        "name": "_sendslice.mail.your.app",
        "value": "sendslice-verify=3f9c0e..." },
      { "type": "CNAME",
        "name": "abc123._domainkey.mail.your.app",
        "value": "abc123.dkim.amazonses.com" },
      ...two more CNAMEs
    ]
  },
  "next": "Publish these records (the TXT and the CNAMEs), then poll GET /domains. ..."
}
GET /domains key

Your domains, each re-checked on read. A domain is verified once the DKIM records resolve and the ownership TXT carries its token.

curl
curl -X GET https://api.sendslice.com/domains \
  -H 'authorization: Bearer ss_live_...'
DELETE /domains/:domain key

Release a domain and free its slot. Returns 204; a repeat call returns 404. If it was your sending domain, sends go from the shared address again.

curl
curl -X DELETE https://api.sendslice.com/domains/mail.your.app \
  -H 'authorization: Bearer ss_live_...'

Suppressions

GET /suppressions key

Addresses that hard-bounced or marked your mail as spam. Sends to them are refused with recipient_suppressed.

Reference data

GET /templates no key

The templates and the fields each takes: welcome password-reset alert generic-message.

GET /brand-options no key

Every value a brand kit accepts: flavours, corner styles, border styles, font pairings.

GET /health no key

Liveness and the deployed revision. /ready also checks storage.

Plans

Every plan is metered by calendar month and resets on the 1st. A send past the allowance is refused, not billed.

PlanPer monthSendsNo verified domainVerified domain
WhiffFree50From the shared address, to confirmed addresses only (max 5), test bannerFrom your domain, to confirmed addresses only (max 5), test banner
Taste$95,000From the shared address, to confirmed addresses only (max 5), test bannerFrom your domain, to anyone, no banner
Slice$1925,000From the shared address, to confirmed addresses only (max 5), test bannerFrom your domain, to anyone, no banner
Whole pie$99250,000From the shared address, to confirmed addresses only (max 5), test bannerFrom your domain, to anyone, no banner