API reference
Base URL https://api.sendslice.com. JSON in, JSON out.
Quickstart
POST /signupwith your email. You get a starter key.- Click the confirmation link. This ends the starter key.
- Sign in with the same address and issue a new API key.
- Design the email in the builder and save it. You get a code like
bld_x7Qp2mZc. POST /sendwith 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.
# 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.
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 -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" }
}'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 -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 -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.
- Confirm your account address. Registering a domain before that returns
email_not_confirmed. POST /domainswith the domain. The response lists the records.- Publish the ownership TXT record: name
_sendslice.<domain>, valuesendslice-verify=<token>. The token is in the response. - Publish the DKIM CNAME records from the same response.
- Poll
GET /domainsuntil the status isverified. 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.
_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.
{
"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..."
}
}| Code | Status | Meaning |
|---|---|---|
| invalid_request | 400 | The body did not match the schema. details names the field. |
| unauthorized | 401 | Missing or unrecognised API key. |
| forbidden details.code: send_cap | 403 | The month’s allowance is spent. It resets on the 1st. |
| forbidden details.code: unverified_recipient | 403 | This account can only send to confirmed addresses, and that one is not confirmed. |
| forbidden details.code: recipient_cap | 403 | At the limit of confirmed addresses. |
| forbidden details.code: build_limit_reached | 403 | The account already has 250 saved builds. |
| email_not_confirmed | 403 | Confirm your own address before registering a domain. |
| domain_not_verified | 403 | Your sending domain is no longer verified. Nothing is sent until it is. |
| recipient_suppressed | 403 | That address bounced or complained on this account. GET /suppressions lists it. |
| recipient_suppressed_platform | 403 | That address bounced or complained on the shared address. Send from your own verified domain to reach it. |
| not_found | 404 | No such route or resource, including a deleted build code. |
| conflict | 409 | That resource already exists, or something else changed it first. |
| domain_taken | 409 | Another account has registered that domain. |
| conflict details.code: idempotency_key_in_progress | 409 | A request with that Idempotency-Key is still running. Retry with the same key after it finishes. |
| rate_limited | 429 | Too many requests. Back off and retry. |
| internal_error | 500 | Failure on our side. Quote the request_id. |
| upstream_failure | 502 | A service we depend on failed. Safe to retry. |
| service_unavailable | 503 | A 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
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.
| Field | Type | Description |
|---|---|---|
email | string | Required. The account address. A confirmation link is sent to it. |
name | string | Optional. Shown as the sender name once you send from your own domain. |
brand_kit | object | Optional. Same fields as POST /brand-kit. |
curl -X POST https://api.sendslice.com/signup \
-H 'content-type: application/json' \
-d '{ "email": "you@example.com", "name": "Acme" }'{
"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
}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.
{
"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
}Send a new confirmation link to the account address. No body. Use the starter key from signup. Rate limited.
curl -X POST https://api.sendslice.com/confirm/resend \ -H 'authorization: Bearer ss_live_...'
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 -X POST https://api.sendslice.com/confirm/recipient \
-H 'authorization: Bearer ss_live_...' \
-H 'content-type: application/json' \
-d '{ "email": "colleague@yourcompany.com" }'Replace your API key. Authenticate with the key being replaced; it stops working as soon as the new one is issued. No body.
curl -X POST https://api.sendslice.com/keys/rotate \ -H 'authorization: Bearer ss_live_...'
Sending
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.
| Field | Type | Description |
|---|---|---|
to | string | Required. One recipient address. |
subject | string | Required. One line, up to 998 characters. |
code | string | A saved build code, bld_.... Uses that build’s template and look. |
template_id | string | A template id, styled with your brand kit: welcome password-reset alert generic-message. |
html | string | Your own HTML. No template, no brand kit. |
data | object | Template fields. Only with code or template_id. |
reply_to | string | Optional 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 -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" }
}'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(){
"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"
}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 -X GET https://api.sendslice.com/sends?limit=50 \ -H 'authorization: Bearer ss_live_...'
Builds
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 -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"
}'{
"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"
}
}Every saved build on the account, newest first.
curl -X GET https://api.sendslice.com/builds \ -H 'authorization: Bearer ss_live_...'
One saved build.
curl -X GET https://api.sendslice.com/builds/bld_x7Qp2mZc \ -H 'authorization: Bearer ss_live_...'
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 -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 a build. Returns 204. Sends using its code fail with 404 from then on; a deleted code is never reused.
curl -X DELETE https://api.sendslice.com/builds/bld_x7Qp2mZc \ -H 'authorization: Bearer ss_live_...'
Brand kit
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 -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" }'The saved brand kit, or the defaults if none was set.
Domains
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 -X POST https://api.sendslice.com/domains \
-H 'authorization: Bearer ss_live_...' \
-H 'content-type: application/json' \
-d '{ "domain": "mail.your.app" }'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(){
"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. ..."
}Your domains, each re-checked on read. A domain is verified once the DKIM records resolve and the ownership TXT carries its token.
curl -X GET https://api.sendslice.com/domains \ -H 'authorization: Bearer ss_live_...'
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 -X DELETE https://api.sendslice.com/domains/mail.your.app \ -H 'authorization: Bearer ss_live_...'
Suppressions
Addresses that hard-bounced or marked your mail as spam. Sends to them are refused with recipient_suppressed.
Reference data
The templates and the fields each takes: welcome password-reset alert generic-message.
Every value a brand kit accepts: flavours, corner styles, border styles, font pairings.
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.
| Plan | Per month | Sends | No verified domain | Verified domain |
|---|---|---|---|---|
| Whiff | Free | 50 | From the shared address, to confirmed addresses only (max 5), test banner | From your domain, to confirmed addresses only (max 5), test banner |
| Taste | $9 | 5,000 | From the shared address, to confirmed addresses only (max 5), test banner | From your domain, to anyone, no banner |
| Slice | $19 | 25,000 | From the shared address, to confirmed addresses only (max 5), test banner | From your domain, to anyone, no banner |
| Whole pie | $99 | 250,000 | From the shared address, to confirmed addresses only (max 5), test banner | From your domain, to anyone, no banner |