Authentication
Every ReachBell API request is authenticated. There are two modes, and which one you use depends on who's making the call.
- JWT — for browser sessions in the dashboard. Short-lived, user-scoped, organization-aware.
- API key — for server-to-server traffic from your backend, marketing automation, or the SDK. Long-lived, project-scoped.
Pick the mode that matches the caller. Most production integrations use API keys.
Choosing a mode
| Caller | Mode | Header |
|---|---|---|
| Dashboard user (browser) | JWT | Authorization: Bearer <token> + x-org-id |
| Your backend or worker | API key | x-api-key: rb_live_... |
| SDK on a customer site | API key | x-api-key: rb_live_... (handled for you) |
| Sandbox / staging tests | API key (test) | x-api-key: rb_test_... |
Pick API keys for anything that runs without a human in the loop. JWTs expire and need refresh handling — fine for the dashboard, painful everywhere else.
JWT authentication
JWTs are issued by POST /auth/login. The dashboard uses them for every request a signed-in user makes.
Logging in
curl -X POST https://api.reachbell.com/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"password": "your-password"
}'
A successful response looks like this:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "usr_01HX0...",
"email": "you@example.com",
"name": "Ada Lovelace",
"organizations": [
{ "id": "org_01HX1...", "name": "Acme", "role": "OWNER" }
]
}
}
Store the access_token and send it on every subsequent request as the Authorization header.
Sending the token
curl https://api.reachbell.com/projects \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "x-org-id: org_01HX1..."
The x-org-id header tells the API which organization context to use. Most routes that read or write organization-scoped resources require it. If you omit it on a route that needs it, you'll get a 403.
Token lifetime
Tokens expire after seven days by default. The exact window is controlled by the JWT_EXPIRES_IN env var on the API. When the token expires, calls return 401 Unauthorized — the dashboard redirects to the login screen; your own integrations should re-call /auth/login.
JWTs are bearer tokens. Treat them like passwords — never log them, never check them into source control, never expose them to a customer's browser unless that customer owns them.
API key authentication
API keys live on a single project. They authenticate every server-side call and the SDK on customer sites.
Key format
rb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
rb_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
rb_live_keys hit your real subscribers and bill against your plan.rb_test_keys hit the sandbox — pushes are accepted but never actually delivered. Use these in CI.
Sending a key
curl https://api.reachbell.com/subscribers/project/proj_01HX2... \
-H "x-api-key: rb_live_yourkey"
The key implicitly identifies the project, so most routes don't need an x-org-id header when called this way.
Rotating keys
API keys never expire on their own. You rotate them from the dashboard:
- Go to Settings → API keys.
- Click Generate new key — both keys are live simultaneously so you can roll your integration over with no downtime.
- Update your environment.
- Click Revoke on the old key.
Rotate any key you suspect has leaked. Rotate every key at least once a year regardless. Old keys live forever until revoked, and forever is a long time.
Rate limits
ReachBell rate-limits per IP address per route group.
| Route group | Limit |
|---|---|
| Default | 600 requests / minute |
Ingest (/subscribers, /transactional/*) | 120 requests / minute |
When you exceed a limit you get a 429 Too Many Requests response with a Retry-After header (seconds). Back off and retry.
If your traffic is bursty and legitimate, contact support — we'll raise the cap on a per-project basis.
OpenAPI playground
Every endpoint described in these docs is also live in the interactive OpenAPI playground:
https://api.reachbell.com/docs
You can paste a JWT or API key into the "Authorize" panel and run requests against the live API straight from the browser.
Common authentication errors
A few responses you'll see while wiring things up for the first time:
{
"statusCode": 401,
"message": "Invalid or expired token",
"requestId": "req_01HX4..."
}
Your JWT is expired or malformed. Re-call POST /auth/login.
{
"statusCode": 401,
"message": "Invalid API key",
"requestId": "req_01HX4..."
}
The key doesn't exist or has been revoked. Double-check the value, and verify against the dashboard's Settings → API keys page.
{
"statusCode": 403,
"message": "Missing x-org-id header",
"requestId": "req_01HX4..."
}
You authenticated with a JWT but didn't send the x-org-id header. Add it.
{
"statusCode": 403,
"message": "Resource belongs to a different project",
"requestId": "req_01HX4..."
}
Your API key is scoped to one project; the resource you asked for lives on another. Use the right key.
Every error response includes a
requestId. Always log it — it's the single most useful thing to quote to support when something doesn't work.
What's next?
- Try the Subscribers API to register and tag devices.
- Send your first message with the Campaigns API.
- Read about error codes so your integration handles failures cleanly.