API conventions
The rules that hold across every ReachBell REST endpoint: where the API lives, how you authenticate, what rate limits apply, how lists paginate, and the shape of an error. Read this once and the per-resource pages (Subscribers, Transactional, Events, and so on) will read the same way.
Base URL
All requests go to:
https://api.reachbell.com
Endpoints are mounted at the root — there is no /v1 or /api version prefix. A path is exactly what each reference page lists, e.g. POST /transactional/send, POST /events, GET /subscribers/.... Requests must be HTTPS.
Authentication
Server-to-server calls authenticate with a project API key in the x-api-key header:
curl https://api.reachbell.com/projects/context \
-H "x-api-key: rb_live_yourkey"The key is project-scoped, so most endpoints don't need any organization header when called this way. Keys look like rb_live_… and must stay server-side — get one from Settings → API keys in the dashboard (see API keys & your first server send).
Dashboard/browser sessions use a JWT (Authorization: Bearer … plus x-org-id) instead. Both modes, key format, and rotation are covered in full on the Authentication page. For backend integrations, use an API key.
A missing or unrecognized key returns 401:
{
"statusCode": 401,
"message": "Invalid API key",
"requestId": "req_01HX4..."
}Request tracing
Every response carries an x-request-id response header. If you send your own x-request-id on the request, the API echoes it back; otherwise it generates one. Error bodies also include the same value as requestId (see Error envelope below).
Log this value. When something doesn't behave, quoting the request ID to support is the single fastest way to trace exactly what happened.
Rate limits
The API is rate-limited per IP address, on a 60-second rolling window, across two buckets:
| Bucket | Limit | Applies to |
|---|---|---|
| Default | 600 requests / minute | dashboard and general traffic |
| Ingest | 120 requests / minute | the high-volume write surface — /subscribers, /events, /transactional/*, and the messaging send routes |
Exceed a bucket and you get a 429 Too Many Requests. The framework returns the standard rate-limit response headers alongside it — Retry-After (seconds to wait), plus X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Honour Retry-After and back off; a little jitter on retries avoids a thundering herd.
Build retry-with-backoff into any client that sends in bursts. If your legitimate traffic outgrows these caps, contact support — limits can be raised per project.
The default caps can be tuned per deployment via the RATE_LIMIT_RPM and RATE_LIMIT_INGEST_RPM environment variables; the numbers above are the defaults on the hosted API.
Idempotency
ReachBell does not currently support client-supplied idempotency keys. There is no Idempotency-Key header — sending one has no effect, and a retried request is processed as a new request.
In practice this matters less than it sounds, because the operations you'll retry are forgiving:
- Transactional sends return
200with adeliveredflag rather than failing when there's no active subscription, so a retry doesn't error — though a retried successful send can deliver the notification twice. For anything you must not double-send, dedupe on your side before calling. identifyandtagare naturally idempotent: re-running them converges to the same subscriber state.- Webhooks are deduplicated server-side on the provider's message ID, so the same provider event won't be processed twice.
If first-class idempotency keys would help your integration, let support know — it's on the roadmap, and the section above will document the header when it ships.
Pagination
List endpoints are page-based. The common query parameters are:
| Param | Default | Notes |
|---|---|---|
page | 1 | 1-indexed |
limit | 50 | capped at 100 |
A paginated list response wraps its rows alongside the page metadata, for example:
{
"items": [ /* … */ ],
"total": 1280,
"page": 1,
"limit": 50,
"pages": 26
}Iterate by requesting page=1, page=2, … until page reaches pages (or a page comes back with fewer than limit rows).
Pagination isn't perfectly uniform across the API yet — a few list endpoints name the rows after the resource (e.g.
subscribersrather thanitems) or acceptoffsetinstead ofpage. Each reference page documents its exact parameters and envelope; treat the table above as the default to expect.
Error envelope
Errors come back as JSON with a consistent shape: an HTTP statusCode, a human-readable message, and the requestId for that call.
{
"statusCode": 400,
"message": "Validation failed",
"error": "Bad Request",
"requestId": "req_01HX4..."
}statusCode— mirrors the HTTP status.message— a string, or an array of strings for validation errors (one entry per failing field).error— a short status label (e.g.Bad Request,Unauthorized), present on most client errors.requestId— the same value as thex-request-idresponse header.
Server errors never leak internals — a 500 is always exactly:
{
"statusCode": 500,
"message": "Internal server error",
"requestId": "req_01HX4..."
}For the meaning of each status and how to handle it, see Error codes.
Successful responses are returned directly — there is no envelope wrapping success payloads in a
datafield. Each endpoint's reference page shows its exact response shape.
What's next?
- Get a key and send your first request: API keys & your first server send.
- The full auth picture (API keys and dashboard JWTs): Authentication.
- Decode failures cleanly: Error codes.
- Skip the raw HTTP for Node backends with the Server SDK.