Documentation Index
Fetch the complete documentation index at: https://orbit-docs.devotel.io/llms.txt
Use this file to discover all available pages before exploring further.
API Integration
Orbit is API-first. Every feature you can click in the dashboard is backed by a REST endpoint you can call from your own code. This page is the map to the rest of the API reference — where to start, what to test against, and what to watch for in production.
Base URLs
| Environment | Base URL | Use for |
|---|
| Production | https://orbit-api.devotel.io/api | Live traffic, billed |
| Sandbox | https://sandbox-api.devotel.io/api | Integration testing, free, test credits granted automatically |
The sandbox mirrors production endpoints 1:1. Requests don’t move real money, don’t send real messages (SMS/WhatsApp/RCS/email/voice are captured, not dispatched), and don’t touch your production tenant. Authentication works the same way — you get a separate API key for sandbox in Dashboard → Settings → API Keys → Sandbox.
Sandbox responses include a x-orbit-env: sandbox header so your integration can assert it’s talking to the right environment.
Authentication
Every request needs an API key in the Authorization header:
curl https://orbit-api.devotel.io/api/messages \
-H "Authorization: Bearer sk_live_abc123..." \
-H "Content-Type: application/json"
- Keys are per-tenant. Subaccounts get their own scoped keys.
- Keys have prefix:
sk_live_ (production) or sk_test_ (sandbox).
- Rotate keys anytime at Settings → API Keys — rotation is instant, old keys invalidate.
- Leaked a key? Revoke immediately; Orbit does not charge you for traffic after the revoke timestamp.
Full detail: Authentication.
Channels covered by the API
Everything in the dashboard is available via API. Same payloads, same behavior.
| Channel | Endpoint root | Reference |
|---|
| SMS (including 10DLC, short-code, toll-free) | /messages with channel=sms | Messaging API |
| WhatsApp Business | /messages with channel=whatsapp | Messaging API |
| RCS Business Messaging | /messages with channel=rcs | Messaging API |
| Viber Business | /messages with channel=viber | Messaging API |
| Email (SMTP + transactional) | /messages with channel=email | Messaging API |
| Voice (inbound, outbound, AI agent, IVR) | /voice/calls | Voice API |
| Verify (OTP codes, Silent Auth) | /verify | Verify API |
| Numbers (search, purchase, release, port) | /numbers | Numbers API |
| Contacts & lists | /contacts | Contacts API |
| Campaigns | /campaigns | Campaigns API |
| AI Agents | /agents | Agents API |
| Flows | /flows | Flows API |
| Billing & usage | /billing | Billing API |
The /messages endpoint is channel-polymorphic: you specify channel, and the payload shape adapts. The same JSON shape gets you SMS delivery, WhatsApp template sends, RCS rich cards, and email — the only difference is what’s inside the content block.
Your first API call
The fastest way to confirm your key works:
curl https://orbit-api.devotel.io/api/messages \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "sms",
"from": "+16572262362",
"to": "+15555551234",
"content": { "text": "Hello from Orbit" }
}'
Response:
{
"id": "msg_01HXYZ...",
"status": "queued",
"channel": "sms",
"cost_estimate": { "currency": "USD", "amount": 0.0075 },
"created_at": "2026-04-17T10:23:00Z"
}
Check status at GET /messages/:id or subscribe to delivery webhooks (recommended).
Webhooks
Webhooks are how Orbit tells your backend about events: a message was delivered, a call connected, an agent escalated a conversation, a number was ported in. You register a URL and we POST events to it.
Setup
- Dashboard → Settings → Webhooks → Add endpoint
- Enter your HTTPS URL (HTTP rejected in production)
- Pick the event types you want (or subscribe to
* for everything)
- Copy the signing secret (
whsec_...) — you need it to verify signatures
Every Orbit webhook carries an Orbit-Signature header. Verify it before trusting the payload.
// Node example
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(body: string, header: string, secret: string) {
const [t, v1] = header.split(",").map(p => p.split("=")[1]);
const expected = createHmac("sha256", secret)
.update(`${t}.${body}`)
.digest("hex");
return timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
Replay protection: reject any signature whose t (timestamp) is older than 5 minutes.
Retries
Orbit retries failed webhooks with exponential backoff: 1s → 5s → 25s → 2m → 10m → 1h → 6h. After 6h we stop retrying and flag the endpoint. You can replay from Dashboard → Webhooks → Event log.
A webhook is “successful” if your server returns any 2xx within 10 seconds. Anything else (timeout, 5xx, 4xx except 410 Gone) triggers retry. Return 410 Gone to permanently disable a single event (e.g., you’ve deprecated handling for that type).
Dead endpoints
If we see 20 consecutive failures, we auto-disable the endpoint and email the org admin. Re-enable it from the dashboard once you’ve fixed it.
Full detail: Webhooks overview, Event catalog, Security.
Rate limits
Default limits apply per API key. Higher limits available on request.
| Endpoint family | Default rate | Notes |
|---|
/messages (send) | 600 / min across all channels | Per-key. Carriers / Meta / Apple have their own throttling on top. |
/messages (read) | 3,000 / min | |
/voice/calls (outbound) | 60 / min | Per-number concurrency limits also apply from your provider. |
/contacts, /campaigns, /agents, /flows | 300 / min | |
/numbers/search | 120 / min | |
/billing | 120 / min | |
| Auth / signup / password reset | 20 / min per IP | |
Rate-limit headers are on every response:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 583
X-RateLimit-Reset: 1713357600
When you exceed the limit you get HTTP 429 with:
{ "error": { "code": "rate_limited", "retry_after": 12 } }
Use retry_after (seconds), not fixed backoff. Full detail: Rate Limits.
Error codes
All errors follow the same shape:
{
"error": {
"code": "insufficient_balance",
"message": "Your wallet balance of $0.12 is below the $0.50 minimum to send this WhatsApp conversation. Add funds or lower send volume.",
"request_id": "req_01HXYZ...",
"documentation_url": "https://docs.devotel.io/reference/error-codes#insufficient_balance"
}
}
Always log request_id — support can trace it end-to-end.
Common codes you’ll hit during integration:
| Code | HTTP | Meaning | Fix |
|---|
invalid_api_key | 401 | Key revoked, wrong env, or typo | Check Settings → API Keys |
tenant_suspended | 403 | Usually billing or compliance | Check dashboard banner |
rate_limited | 429 | Too many requests | Respect retry_after |
insufficient_balance | 402 | Wallet empty or below channel min | Top up wallet |
invalid_number | 400 | Not E.164 or not reachable | Validate upstream |
number_not_sms_capable | 400 | Toll-free without 10DLC reg, or inbound-only number | Register or swap number |
waba_template_not_approved | 400 | Template still pending | Check status in /templates |
channel_not_enabled | 403 | Tenant hasn’t finished channel onboarding | Go to Settings → Channels |
duplicate_idempotency_key | 409 | Same Idempotency-Key reused with different body | Use unique keys per send |
validation_error | 400 | Payload shape wrong — error.fields lists each issue | Read error.fields |
Full list: Error Codes.
Idempotency
Every POST that creates a resource (message, call, contact, campaign) accepts an Idempotency-Key header. Use it.
curl https://orbit-api.devotel.io/api/messages \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: order-conf-98421" \
-H "Content-Type: application/json" \
-d '{...}'
Orbit stores the response for 24 hours. Replay the exact same key + body → you get the cached response, not a duplicate send. Replay with a different body → 409 duplicate_idempotency_key.
This is the single most important pattern for production reliability. Use it on every creation endpoint.
List endpoints return cursor-paginated results:
{
"data": [...],
"has_more": true,
"next_cursor": "eyJpZCI6Im1zZ18wMUhYWVovLi4uIn0="
}
Pass ?cursor=<next_cursor>&limit=100 for the next page. No offset — cursor is stable against concurrent inserts.
Full detail: Pagination.
Postman collections
Pre-built collections per channel — import one and you’re testing in 30 seconds.
| Collection | Import link |
|---|
| Orbit — Core (auth, messages, contacts, webhooks) | postman/orbit-core.json |
| Orbit — Voice & IVR | postman/orbit-voice.json |
| Orbit — Campaigns & Flows | postman/orbit-campaigns.json |
| Orbit — Agents (AI) | postman/orbit-agents.json |
| Orbit — Numbers & Verify | postman/orbit-numbers-verify.json |
| Orbit — Billing & Usage | postman/orbit-billing.json |
Each collection has variables for {{base_url}} and {{api_key}} pre-wired. Default environment is sandbox.
Also available: OpenAPI 3.1 spec at https://orbit-api.devotel.io/api/openapi.json — import into any codegen (openapi-generator, oats, orval, kiota).
SDKs
If you’d rather not hand-roll HTTP, we maintain official SDKs. They handle auth, retries, pagination, webhook signature verification, and typed models.
Browser: use the Orbit JS SDK for client-side widgets (chat embed, voice browser calling). Never ship a server key to the browser — use a short-lived session token from /auth/browser-token.
Recommended integration flow:
- Develop against sandbox with
sk_test_* keys
- Stage against sandbox with your staging webhook URL (not production)
- Prod swap keys + webhook URL; everything else identical
Sandbox retains data for 30 days then purges. Do not rely on it for long-term storage.
Support
Always include request_id from the failing response in any support ticket — it’s our fastest route to root cause.