The API
A unified REST API for lifecycle email and SMS, audience management, real-time data validation, and the retention signals that tell you which accounts are about to churn — built for high-volume senders.
Introduction
The API is organized around predictable, resource-oriented URLs, returns JSON-encoded responses, and uses standard HTTP verbs, status codes, and authentication. It is designed for production traffic: every endpoint is idempotent where it should be, paginated where it returns lists, and instrumented with a request ID you can trace.
Everything you can do in the dashboard you can do through the API: send and schedule messages, sync contacts, validate data before you send, stream delivery events, and read churn-risk signals to drive automated win-back.
Core principles
- HTTPS only. Requests over plain HTTP are refused.
- JSON in, JSON out. Send
Content-Type: application/json. - Stable versions. Pinned per request; breaking changes never reach a pinned version.
- Safe retries. Mutating calls accept an idempotency key.
https://api.hestiasignals.com/v1
curl https://api.hestiasignals.com/v1/messages/email \
-H "Authorization: Bearer sk_live_•••" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a1b2c3" \
-d '{ "to": "ava@acme.com", ... }'
Quickstart
Send your first message in under a minute. Grab a test key from Settings → API keys, then make the call on the right. Test keys never deliver to real inboxes, so you can run this safely.
1 · Send a message
Authenticate with your key, set a recipient, and send. The response returns a message id you can use to retrieve status or correlate webhook events.
2 · Listen for events
Point a webhook at your server to receive delivered, opened, and bounced events as they happen.
curl https://api.hestiasignals.com/v1/messages/email \
-H "Authorization: Bearer sk_test_•••" \
-d '{ "to":"ava@acme.com",
"from":"team@news.brand.com",
"subject":"Welcome aboard",
"template_id":"tmpl_welcome" }'
Authentication
Authenticate with your secret key as a bearer token in the Authorization header. Keys are environment-scoped: sk_live_… sends real traffic, sk_test_… runs in a sandbox. Create, roll, and scope keys in the dashboard.
Key permissions
Restricted keys can be limited to specific resources — for example a key that may send messages but not read contacts — so a leaked key never exposes more than its scope.
Authorization: Bearer sk_live_4eC39HqLyjWDarjtT1zdp7dc
{
"error": {
"type": "authentication_error",
"message": "No valid API key provided."
}
}
Requests & idempotency
All POST requests accept an Idempotency-Key header. If a request is interrupted — a dropped connection, a timeout, a retry — replaying it with the same key returns the original result instead of performing the action twice. Keys are stored for 24 hours.
Generate a unique key per logical operation (a UUID works well). Reusing a key with a different body returns a 409, which protects you from accidental double-sends at scale.
Metadata
Attach a metadata object of up to 50 key–value pairs to most resources. It is never used by the system and is returned verbatim — ideal for storing your own IDs.
curl https://api.hestiasignals.com/v1/messages/email \
-H "Authorization: Bearer sk_live_•••" \
-H "Idempotency-Key: 9f1c-44a2-8b30" \
-d '{ "to": "ava@acme.com", ... }'
# Replaying with the same key returns
# the same msg_… instead of sending twice.
Versioning
The API is versioned by date. Your account is pinned to the version that was current when you integrated, and that behavior never changes underneath you. Upgrade deliberately by setting the Version header, test against it, then make it your default in the dashboard.
Additive changes — new endpoints, new optional fields, new event types — ship without a version bump, so write parsers that ignore unknown fields.
-H "Version: 2026-06-01"
Pagination
List endpoints are cursor-paginated. Pass limit (1–100, default 20) and walk forward with starting_after, using the last object's id from the previous page. The envelope's has_more tells you when to stop — cursors stay stable even as new records are created.
Parameters
20.{
"object": "list",
"url": "/v1/contacts",
"has_more": true,
"data": [ { "id": "con_5kT1", … } ]
}
curl "https://api.hestiasignals.com/v1/contacts?limit=20&starting_after=con_5kT1"
Rate limits
Limits apply per key and are returned on every response so you can throttle before you are throttled. A burst allowance absorbs short spikes; sustained traffic above your steady rate returns 429 with a Retry-After. Wait for the time it indicates, then retry with exponential backoff.
429.# requests / second burst
Starter 10 20
Growth 50 100
Scale 200 400
Enterprise custom custom
Errors
The API uses conventional HTTP status codes. 2xx means success, 4xx indicates a request problem you can fix, and 5xx indicates a rare error on our side. Every error body is typed, carries a stable code, names the offending param, and includes a request_id for support.
Error types
{
"error": {
"type": "validation",
"code": "invalid_email",
"message": "'to' is not a valid email.",
"param": "to",
"doc_url": "https://docs.../invalid_email",
"request_id": "req_8sK2c91Lp0"
}
}
Test mode
Every endpoint works identically with a test key, but nothing is delivered and nothing is billed. Use the magic recipients below to force outcomes deterministically, so your integration tests can assert on bounces and complaints without touching a real inbox.
bounced event.complained event.delivered then opened.curl https://api.hestiasignals.com/v1/messages/email \
-H "Authorization: Bearer sk_test_•••" \
-d '{ "to": "bounce@test.hestiasignals.com", ... }'
Send an email
Sends a transactional or lifecycle email to one recipient. Suppressed addresses are skipped and returned with a suppressed status — they never count against deliverability.
Body parameters
{{first_name}}.html or template_id.curl https://api.hestiasignals.com/v1/messages/email \
-H "Authorization: Bearer sk_live_•••" \
-H "Content-Type: application/json" \
-d '{
"to": "ava@acme.com",
"from": "team@news.brand.com",
"subject": "We saved your seat, {{first_name}}",
"template_id": "tmpl_winback_02",
"variables": { "first_name": "Ava" },
"tags": ["win-back"]
}'
{
"id": "msg_3aZ91kP2",
"object": "email",
"status": "queued",
"to": "ava@acme.com",
"created_at": "2026-06-12T09:24:11Z"
}
Send an SMS
Sends an SMS to a single E.164 number. Long messages are split into segments automatically; the response reports the segment count so you can reconcile usage.
Body parameters
+35799123456.curl https://api.hestiasignals.com/v1/messages/sms \
-H "Authorization: Bearer sk_live_•••" \
-d '{
"to": "+35799123456",
"from": "BRAND",
"body": "Your cart is still here, Ava → https://r.tv/x"
}'
{
"id": "sms_7Bq02Xm9",
"status": "queued",
"segments": 1
}
Batch send
Queue up to 1,000 messages in a single call. The batch is accepted atomically and processed asynchronously; you receive a batch_id immediately and per-message events stream to your webhook as they send. Ideal for a scheduled win-back blast without hammering the single-send endpoint.
curl https://api.hestiasignals.com/v1/messages/batch \
-H "Authorization: Bearer sk_live_•••" \
-d '{
"template_id": "tmpl_winback_02",
"messages": [
{ "to": "ava@acme.com", "variables": {"first_name":"Ava"} },
{ "to": "ben@globex.com", "variables": {"first_name":"Ben"} }
]
}'
{ "batch_id": "batch_K2p9", "queued": 2 }
Retrieve a message
Returns the current state of a message, including its delivery timeline. Status transitions are queued → sent → delivered, with bounced, complained, or suppressed as terminal outcomes.
{
"id": "msg_3aZ91kP2",
"status": "delivered",
"events": [
{ "type":"sent", "at":"…:11Z" },
{ "type":"delivered", "at":"…:14Z" }
]
}
Create a contact
Creates or updates a contact, keyed on email — calling it again upserts, so it is safe to run inside a sync loop. Consent is first-class: record per-channel opt-in here, and the system enforces it on every send.
Body parameters
{ "email": true, "sms": false }.curl https://api.hestiasignals.com/v1/contacts \
-H "Authorization: Bearer sk_live_•••" \
-d '{
"email": "ava@acme.com",
"first_name": "Ava",
"consent": { "email": true, "sms": false },
"attributes": { "plan": "growth", "mrr": 799 }
}'
List contacts
Returns a cursor-paginated list, filterable by attribute, consent, and creation date. Combine filters to build any segment server-side — for example all growth-plan contacts who opted into email and were created this quarter.
curl -G https://api.hestiasignals.com/v1/contacts \
-H "Authorization: Bearer sk_live_•••" \
--data-urlencode "attributes[plan]=growth" \
--data-urlencode "consent[email]=true" \
--data-urlencode "limit=50"
Delete a contact
Permanently erases a contact and its message history — a hard delete that satisfies a GDPR erasure request. The address is added to your suppression list so it cannot be re-imported by accident. This action cannot be undone.
{ "id": "con_5kT1", "deleted": true }
Validate an address
Runs real-time syntax, domain, mailbox, and risk checks on an email or phone number, flagging spam traps and catch-all domains before you ever send. Wire this into your signup form to keep junk out of your list at the source.
Response fields
deliverable, risky, or undeliverable.{
"email": "ava@acme.com",
"result": "deliverable",
"score": 97,
"is_spam_trap": false,
"is_catch_all": false
}
Bulk validation job
Validate an entire list asynchronously. Submit up to one million records, poll the job (or wait for the job.completed webhook), then download a results file. The job reports live progress so you can show a meter in your own UI.
{
"id": "job_Vd83",
"status": "processing",
"total": 240000,
"processed": 18250
}
Retention signals
This is the intelligence layer. The model scores every account for churn risk and exposes the factors behind each score, so you can trigger the right intervention while the account is still savable — not read about it in a quarterly report. Filter by risk band and feed the result straight into a journey.
low, medium, or high.{
"object": "list",
"data": [ {
"contact_id": "con_5kT1",
"risk": "high",
"score": 88,
"factors": [
"no_login_30d",
"support_tickets_up",
"usage_down_60pct"
]
} ]
}
Webhook endpoints
Register an HTTPS URL and we'll POST a signed JSON payload whenever a subscribed event fires — delivery, opens, bounces, complaints, opt-outs, and churn-risk crossings. Endpoints retry with exponential backoff for up to 24 hours until your server returns 2xx.
Verifying signatures
Every payload is signed. Compute an HMAC-SHA256 of the raw request body using your endpoint's signing secret and compare it, in constant time, to the Signature header. Reject anything that doesn't match — this is how you know the event truly came from us.
import crypto from "crypto";
function verify(rawBody, header, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(header),
Buffer.from(expected)
);
}
import hmac, hashlib
def verify(raw_body, header, secret):
expected = hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(header, expected)
{
"id": "evt_9Lm2",
"type": "email.opened",
"created_at": "2026-06-12T09:31:02Z",
"data": {
"message_id": "msg_3aZ91kP2",
"contact": "ava@acme.com"
}
}
Event types
Subscribe to exactly the events you need. The catalog below is stable; new types are added over time, so handle unknown types gracefully.
curl https://api.hestiasignals.com/v1/webhook_endpoints \
-H "Authorization: Bearer sk_live_•••" \
-d '{
"url": "https://hooks.yoursite.com/rtv",
"events": ["email.bounced","signal.churn_risk"]
}'
Suppressions
The suppression list holds addresses that must never be contacted — unsubscribes, hard bounces, and complaints are added automatically. Any send to a suppressed address is skipped at the platform level and cannot be overridden by an API call.
curl https://api.hestiasignals.com/v1/suppressions \
-H "Authorization: Bearer sk_live_•••" \
-d '{ "email": "ava@acme.com", "reason": "manual" }'