API reference · v1
Reach12 API reference
Send SMS and email through Reach12 with a key of your own. Base URL:
https://api.reach12.com/v1
Version 1 covers SMS (Israeli mobiles, through InforU, under your own sender name) and email (one recipient per request, from an address on a domain we verified for you). There is no voice or call endpoint in v1, and no WhatsApp; GET /policy/contact-window answers the hours question for a call you place yourself.
Nothing is open by default. We create your account, set what it may send, and mint your keys. There is no self-service signup yet: ask for early access.
Keys
| Key | Does |
|---|---|
sd_live_... | Sends for real. |
sd_test_... | Runs the whole path (validation, caps, opt-outs, hours) and records the message, but never reaches a carrier. Test messages, opt-outs and caps live in a sandbox of their own, apart from live. |
A key is shown once, when it is minted. We keep only its hash, so a lost key cannot be recovered: tell us, and we revoke it and mint a new one.
Send it as a header, either spelling:
X-Api-Key: sd_live_...
Authorization: Bearer sd_live_...
Every answer carries X-SendOS-Mode: live|test, so a test key pasted into production is noticed at the first response.
Conventions
- JSON in, JSON out. Errors are always
{ "error": { "code", "message", "requestId", ... } }. X-Request-Id: send your own (letters, digits,_ . : -, up to 64) and we echo it; otherwise we mint one. Quote it when you contact us.- Rate limit: 60 requests a minute per key. Every answer carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset(a unix time); a 429 carriesRetry-After. - Categories:
transactional(a code, a receipt, something the person asked for right now),service(the default: reminders, updates) andmarketing. Opt-outs holdserviceandmarketing;transactionalstill goes. - Idempotency: send
Idempotency-Key: <1-100 of A-Z a-z 0-9 _ . : ->(oridempotencyKeyin the body) on a send, and a retry with the same key and the same body gets the first answer back (markedIdempotent-Replayed: true) for 24 hours, without sending again. The same key with a different body is refused. A refusal that may pass later (a cap, the hours, the service being unavailable) is not kept, so the same key can be retried. A vendor failure IS kept: a timeout may still have delivered, and a retry must not send twice; use a new key when you mean to send again.
Send an SMS
POST /sms
| Field | |
|---|---|
to | An Israeli mobile: +9725XXXXXXXX or 05XXXXXXXX. |
text | Up to 670 characters (ten Hebrew segments). |
category | transactional, service (default) or marketing. |
kind | Optional label for your own log, e.g. appointment.reminder. |
idempotencyKey | Optional (or the header). |
A marketing SMS must open with the word פרסומת (Israeli Communications Law, section 30A), and goes out only 09:00 to 20:00 Israel time, Friday and a holiday eve until 14:00, never on Shabbat or a holiday. Outside those hours it is refused with the next allowed moment.
curl https://api.reach12.com/v1/sms \
-H "X-Api-Key: $REACH12_KEY" \
-H "Idempotency-Key: reminder-4812" \
-H "Content-Type: application/json" \
-d '{"to":"0501234567","text":"תזכורת: התור שלך מחר ב-10:00","kind":"appointment.reminder"}'
{ "id": "cm1...", "channel": "sms", "status": "sent", "to": "+972501234567", "mode": "live" }
Send an email
POST /email
| Field | |
|---|---|
to | One address. |
subject | One line, up to 200 characters. |
html, text | At least one. |
replyTo | Optional. |
category, kind, idempotencyKey | As for SMS. |
The sender is always the address we set up for your account; attachments are not supported in v1.
curl https://api.reach12.com/v1/email \
-H "X-Api-Key: $REACH12_KEY" \
-H "Content-Type: application/json" \
-d '{"to":"dana@example.co.il","subject":"הקבלה שלך","text":"תודה!","category":"transactional"}'
Your message log
GET /messages?channel=sms|email&before=<ISO date>&limit=1..100
Newest first, only your own (a test key sees only test messages). Page with nextBefore.
curl "https://api.reach12.com/v1/messages?channel=sms&limit=20" -H "X-Api-Key: $REACH12_KEY"
{ "data": [ { "id": "cm1...", "channel": "sms", "to": "+972501234567", "text": "...", "status": "sent", "error": null, "createdAt": "..." } ], "hasMore": false, "nextBefore": null }
status is sent, failed (then error is vendor_failed) or blocked.
Your account
GET /account
Who the key belongs to, what the account may send, its daily caps, and how much of today's cap is used. today counts exactly what the caps count: an SMS the carrier accepted or refused (a blocked one never reached a carrier) and every email attempt, in Israel's day. A test key sees its own sandbox's day.
curl https://api.reach12.com/v1/account -H "X-Api-Key: $REACH12_KEY"
{
"id": "acme-clinic",
"name": "Acme Clinic",
"status": "active",
"mode": "live",
"key": { "name": "Production server", "prefix": "sd_live_7Hq2" },
"channels": {
"sms": { "enabled": true, "sender": "AcmeClinic" },
"email": { "enabled": true, "from": "Acme Clinic <hello@acmeclinic.com>" }
},
"caps": { "sms": 500, "email": 1000 },
"today": {
"date": "2026-11-10",
"resetsAt": "2026-11-10T22:00:00.000Z",
"sms": { "used": 42, "cap": 500, "remaining": 458 },
"email": { "used": 7, "cap": 1000, "remaining": 993 }
},
"pricing": { "currency": "ILS", "monthlyFee": 25000, "perSms": 8, "perEmail": 1, "perCallMinute": 30 }
}
pricing is null until we set your prices with you. Every amount is in minor units: agorot for ILS, cents for USD.
Usage and what you owe
GET /usage?month=YYYY-MM
One month, by channel and status, with a series by day. month defaults to the current month; months are Israel time, so a message sent at 00:30 on the 1st in Israel belongs to that month. A month that has not begun is refused with invalid_month. For the current month, complete is false and daily stops at today.
When the account is priced, amountDue is the monthly fee plus every message the carrier accepted (sent). A failed or blocked message is never billed, and neither is anything sent with a test key: for a test key billable is false and amountDue is null.
curl "https://api.reach12.com/v1/usage?month=2026-11" -H "X-Api-Key: $REACH12_KEY"
{
"month": "2026-11",
"mode": "live",
"timezone": "Asia/Jerusalem",
"from": "2026-10-31T22:00:00.000Z",
"to": "2026-11-30T22:00:00.000Z",
"complete": false,
"sms": { "sent": 1200, "failed": 30, "blocked": 9 },
"email": { "sent": 450, "failed": 4 },
"daily": [ { "date": "2026-11-01", "sms": { "sent": 40, "failed": 1, "blocked": 0 }, "email": { "sent": 12, "failed": 0 } } ],
"billable": true,
"amountDue": {
"currency": "ILS",
"lines": [
{ "item": "monthly_fee", "quantity": 1, "unitAmount": 25000, "amount": 25000 },
{ "item": "sms", "quantity": 1200, "unitAmount": 8, "amount": 9600 },
{ "item": "email", "quantity": 450, "unitAmount": 1, "amount": 450 },
{ "item": "call_minutes", "quantity": 0, "unitAmount": 30, "amount": 0 }
],
"total": 35050
}
}
The lines always add up to total. The amount for the current month is what it comes to so far.
Your dashboard
Everything above is also at reach12.com/dashboard: paste a key to see the account, the month's usage and amount, the message log and the opt-out list. The key stays in that browser tab only and is sent to nothing but this API.
Opt-outs
One list per account, per channel (all, sms, email, call, whatsapp; all holds every channel). An address is a phone number or an email.
# who asked not to be contacted
curl "https://api.reach12.com/v1/opt-outs?channel=sms" -H "X-Api-Key: $REACH12_KEY"
# someone replied STOP
curl https://api.reach12.com/v1/opt-outs -H "X-Api-Key: $REACH12_KEY" -H "Content-Type: application/json" \
-d '{"address":"0501234567","channel":"sms","reason":"replied STOP"}'
# they asked to be let back in ("all" lifts every channel)
curl -X DELETE https://api.reach12.com/v1/opt-outs -H "X-Api-Key: $REACH12_KEY" -H "Content-Type: application/json" \
-d '{"address":"0501234567","channel":"sms"}'
May I contact someone now?
GET /policy/contact-window?kind=call|message
{ "kind": "call", "allowed": false, "nextWindow": "2026-11-15T08:00:00.000Z" }
Calls: 10:00 to 19:30 (Friday and a holiday eve until 13:00). Messages: 09:00 to 20:00 (until 14:00). Never on Shabbat or a holiday. Israel time.
Refusals
| HTTP | code | Means |
|---|---|---|
| 400 | invalid_request, invalid_phone, missing_text, text_too_long, invalid_category, invalid_kind, marketing_label_required, invalid_email, missing_subject, invalid_subject, missing_body, body_too_large, invalid_reply_to, attachments_not_supported, invalid_address, invalid_idempotency_key, invalid_month | The request itself. |
| 401 | unauthorized | No valid key. The same answer for every reason, on purpose. |
| 403 | channel_not_enabled | This account may not send on that channel yet. |
| 404 | not_found | No such endpoint. |
| 409 | opted_out | The recipient asked not to be contacted on this channel. |
| 409 | idempotency_in_progress | A request with this key is still being processed. |
| 422 | outside_hours | A marketing message outside the hours; nextWindow says when. |
| 422 | idempotency_mismatch | This key was used with a different request. |
| 429 | rate_limited | Too many requests (per key), or too many failed keys from your address. |
| 429 | daily_cap_reached | The account's daily cap for the channel; resetsAt is midnight in Israel. |
| 502 | vendor_failed | The carrier refused the message; it is in your log with status failed. |
| 503 | unavailable | The channel or the API is not available right now. |
Ready for a key?
We onboard early-access accounts one by one and set up your sender name and domain with you.