REST API recipes
This is the cookbook the SDK index points at: since no SDK is published to a package registry yet, every recipe here is REST-first. The first ten recipes pair curl with the Node SDK (source-only, unpublished); the eight added below (voice, numbers, WhatsApp templates, AI agents, CDP segments, flows, knowledge bases, custom fields) pair curl with raw Python — the SDK modules (client.voice, client.numbers) where one exists, requests against the REST path where scope has not caught up. Between the two languages you can lean on whichever you read faster. Each task is self-contained; run it against the sandbox with a dv_test_sk_… key, then swap in your live key.
Authenticate every request with X-API-Key. Base URL is https://api.orbit.devotel.io/api/v1 — sandbox is the same URL with a test key, not a separate host. Full primer: API integration.
Task index
1. Send a message, poll status, receive the webhook
Send one SMS, learn its id, then track delivery two ways: a direct status poll and a delivery webhook. Webhooks are the production answer — poll once to confirm the send works, then move to events. Send:cURL
Node SDK
202 Accepted returns the persisted send in the standard envelope. data.id (msg_…) is your handle; data.status starts as sent (or test_sent in sandbox) and moves as the carrier reports.
Poll status once:
cURL
Node SDK
message.delivered and message.failed (or * for everything). Orbit POSTs each status change to you; verify the X-Orbit-Signature header before trusting the body — verification is one function call per API integration → Webhooks. Polling is for the first integration smoke test; production consumers subscribe.
Canonical pages: Messaging API, Webhook events.
2. Handle a 429 with retry_after
When your send rate passes the per-key limit, the API answers 429 with a body that names the wait inside the envelope (error.retry_after) and again as a Retry-After header. Read whichever your HTTP client exposes; honor it before any fallback backoff — doubling on your own can retry inside the window and re-429.
cURL
Node SDK
3. Verify an OTP, end to end
Two requests: send the code, then check what the user typed. The platform generates, delivers, and expires the code; your backend never stores or compares it. Send — capturedata.verification_id from the response:
cURL
Node SDK
cURL
Node SDK
status: "approved" is the only pass. failed means the code didn’t match; re-check with the same verification_id until attempts_remaining hits 0, then send a fresh one. expired means the send’s TTL lapsed — send again. In sandbox, a test key simulates delivery; find the expected code in the verification’s dashboard row.
Canonical page: Verify API. A longer REST-only walkthrough (the same two calls plus webhook fallback): Verify integration without our SDK.
4. List and cursor-paginate
List endpoints return stable cursors — no offsets, so concurrent inserts can’t shift your page. Request the first page without a cursor, then passmeta.pagination.cursor until meta.pagination.has_more is false.
cURL
Node SDK
INVALID_CURSOR — restart the iteration without a cursor; never reuse cursors across requests changed in filters or across retries older than the same-minute window. Full table of cursor-vs-offset endpoints: Pagination.
5. Batch-import contacts, poll the import job
For anything past a few hundred rows, skip per-rowPOST /contacts calls and enqueue one async import. The endpoint accepts the parsed CSV as a rows array, returns 202 with a job_id, and runs the insert in the background. Poll the job id for progress.
cURL
Node SDK
status is no longer pending/running:
cURL
Node SDK
merge_strategy: "skip" (the default) leaves existing contacts untouched on duplicate keys; "merge" updates them with your new values. A completed row besides the failed ones reports per-row outcomes; rows that failed validation are listed as skipped, not imported as empty records. A 503 IMPORT_QUEUE_UNAVAILABLE means the background queue is briefly down — retry after a few seconds; don’t fall back to hundreds of single creates under load.
Canonical pages: Contacts API, Import contacts guide.
6. Score a destination before you send
POST /risk/score fuses the platform’s fraud detectors into one 0–100 verdict you can query before committing an SMS send or verify. It’s read-only and advisory — it never dispatches, so it’s safe to burn before every high-cost send.
cURL
Node SDK
recommendation: allow — proceed to the send; review — queue for a human only if the send is high-value; block — suppress the send and log the decision. band is the same verdict bucketed; score (0–100) is the worst-signal composite — the per-channel breakdown names which detector fired. This is a gate on your send decision, not a platform block: enforcement stays in your code, and outbound SMS still exits only through the platform.
Canonical page: Risk API. The pumping-attack context this protects against: SMS pumping protection.
7. Run a loyalty earn → preview → redeem round trip
Points come from events you already send to the CDP — loyalty computes a member’s balance from your event history when you check it, so there is no separate loyalty ledger to sync. The round trip: dry-run the program with preview, read a member’s balance, then burn points. Preview — evaluate your active program (or an override in the request body) against sample events. Nothing writes to the database, so burn this freely while you tune earn rules:cURL
Node SDK
order-completed and loyalty.points_redeemed in the ledger: loyalty.points_adjusted (an operator’s manual credit or debit) and loyalty.program_configured (a revision under a config sentinel contact that members never write):
cURL
Node SDK
balance is the spendable remainder; use GET /loyalty/members (paginated) to list every member or a specific id for one. The traits block also exposes loyalty_points_balance, loyalty_tier, and friends exactly as segments and journeys see them.
Redeem — burn points atomically. Concurrent redemptions against the same contact serialize, so a double-click can’t overspend:
cURL
Node SDK
201 Created with the redemption event id and the post-burn balance. An overspend returns 409 INSUFFICIENT_POINTS with the available remainder — burden the retry on a smaller amount or abort. Manual operator adjustments (POST /loyalty/members/:contactId/adjust) share the same overspend-safe path.
Canonical page: Loyalty API. The program-design walkthrough this loop plugs into: Loyalty program setup.
8. Open an OAuth connection and pull synced records
The integrations loop is connect → status → data: start the OAuth flow, confirm the connection formed, then read the synced records. Connect and sync trigger are owner/admin-gated; status and data are reads any scoped key can run. Start the connect flow — get theauth_url you redirect the operator to:
cURL
Node SDK
404; an integration server misconfigured answers 503. Either is terminal — fix in Settings → Integrations, not in your retry loop.
Confirm status — the OAuth popup closing is not proof the connection formed; ask before you read data:
cURL
Node SDK
connected: false resolves cleanly (with an empty sync list) rather than an error, so branch on it without a try/catch. Once connected, the same payload carries the connection’s metadata and per-sync status.
Pull synced records — name the model (contacts, deals, …) in the query:
cURL
Node SDK
POST /integrations/:id/sync { "sync_name": "contacts" } instead of waiting for the scheduled cadence; a 502 on data reads means the upstream fetch failed, retry after a beat.
Canonical page: Integrations API. The full CRM loop (webhooks, writeback, debugging): Connect HubSpot & Salesforce end to end.
9. Coordinate the team in a team-chat channel
Team chat is the internal surface — channels, DMs, and huddles for the operators running your workspace, membership-gated so nothing ever reaches a customer. Useful when a back-office bot or dashboard posts handoff notes: list your channels, post to one, read it back. List your channels — every read is membership-scoped, so you see only channels your API key’s principal belongs to:cURL
Node SDK
cURL
Node SDK
403 here is a membership gate, not a key problem — the same principal must be on the channel from the same identity; have the owner add you with POST /team-chat/channels/:id/members. Reactions, threads, DMs, and huddles follow the same shape off the message id.
Canonical page: TeamChat API. The operator workflows behind it: Team Chat guide.
10. Errors as recipes
Errors are recipes too — the failure classes in API error handling by example map one-to-one onto the tasks above. Branch onerror.code, never on message text.
Anything not in this table: read
meta.docs_url in the error envelope — it links to the code’s remedy in the Error Code Reference. Branch on the retriable-vs-terminal table in API error handling by example.
11. Place an outbound voice call
Originate a call, then track it to completion either by polling or — in production — by subscribing tocall.completed in your webhook endpoint. The to/from are enough for a ring-only call; pass answer_url (HTTPS, on your server) returned-verb say/gather/dial instructions when the call answers. The second snippet uses the Python SDK’s typed voice module.
Place:
cURL
Python SDK
201 Created returns the created call in the standard envelope — data.id (call_…) is your handle. Full-body reference (record/amd/metadata, the answer_url vs inline-verb tradeoff): Voice API.
Poll once:
cURL
Python SDK
call.completed (and call.failed) in Settings → Webhooks; verify X-Orbit-Signature before trusting the body, same contract as message DLRs. The full event list: Webhook events.
Canonical page: Voice API.
12. Rent a number and start a port-in
Search the inventory for a contiguous block, purchase one into your tenant, then — when the digits you actually want live at another carrier — pre-check portability and open a port-in request. Search available numbers:cURL
Python SDK
cURL
Python SDK
cURL
Python SDK
GET /numbers/porting (list of your open requests) — the carrier completes it asynchronously; don’t poll the check endpoint. Full catalogue — rent/release/webhook binding and port lifecycle: Numbers API.
13. Publish a WhatsApp template and send it
Outside Meta’s 24-hour customer-service window, every WhatsApp send has to ride an approved template. The loop: list your templates to find the approved name, then send it with positional variables. Find the approved template (thedata array carries each template’s Meta status — APPROVED/PENDING/REJECTED — so you never copy a name that hasn’t cleared review):
cURL
Python
template_params filling positional {{1}}…{{N}} placeholders (or Meta-native components):
cURL
Python
language.code must be the exact locale the template was approved under in WhatsApp Manager, or the send is rejected with 422 WHATSAPP_TEMPLATE_NOT_FOUND (Meta 132001). Poll GET /messages/:id for the DLR. Creating new templates flows through the same /templates surface — full parameter reference: Messaging API.
14. Converse through the agent copilot
When a conversation reaches the unified inbox, an operator can ask the AI copilot for a suggested reply instead of drafting one. The loop: a live conversation id (from the inbox feed or a webhook), then one request per suggestion. Ask the copilot on a conversation:cURL
Python
GET /conversations (the unified-inbox feed) or a conversation.created webhook. Supporting knowledge for the copilot’s answers lives in the Knowledge bases API; the conversation family: Conversations API.
15. Build a CDP segment and read its members
Segments are the CDP’s saved audiences — an AND/OR filter over traits, events, and scores that auto-refreshes as events land. Create one, then page its membership when you need the actual contacts behind it. Create a segment:cURL
Python
201 Created returns the created segment already materialised — data.contact_count is live immediately, not a scheduler estimate.
Read members:
cURL
Python
auto_refresh (default true) keeps membership current as new CDP events update the fields the rules read. Programmatic POST /contacts/segments is an alternative to the dashboard builder; the full filter shape and exports: Segments API.
16. Run a Flow and inspect the execution
Flows are the visual automation graph a tenant builds in the dashboard — a trigger into a directed set of steps (delays, branches, channel sends). Create or fetch a flow id, trigger a run, then read the execution back step-by-step. Create a flow (the shape lives in your dashboard —POST /flows saves the graph; subsequent POST /flows/:id/publish versions it live):
cURL
Python
trigger_data payload optional, available to nodes as {{…}} template references:
cURL
Python
cURL
Python
GET /flows, GET /flows/executions/summary) give the same history tail the dashboard renders. Create/trigger/history detail: Flows API.
17. Upload a document and run a semantic KB search
A knowledge base is the retrieval store an AI agent grounds its answers on. The loop: create the base, push a document into it (multipart for binary files, JSON for text), poll the document list until indexing finishes, then run the same semantic search the agent uses — so you can preview exactly what would ground its answer. Create a knowledge base —201 Created returns the created base; data.id (kb_…) is the handle every subsequent call needs:
cURL
Python
knowledge:write scope; reads (list/poll/search) answer to knowledge:read. A 403 here is a scope/role gap on the key, not a malformed body — a 422 carries a field problem.
Upload a document — send binary files (PDF/DOCX, images for OCR) as multipart:
cURL
cURL
Python
data.status: "processing" — the upload is accepted but the chunks still have to embed. Wait on GET /knowledge-bases/:id/documents until the row flips:
cURL
Python
ready means retrievable; failed means extraction broke — re-queue just that doc with POST /knowledge-bases/:id/documents/:docId/retry (a 202, then poll again). partial means some chunks embedded; treat it as searchable but incomplete. Polling the base GET /knowledge-bases/:id instead of the documents list gives you only counts, not per-doc status.
Search — run the same semantic retrieval an agent call would get. This read needs only knowledge:read, and the per-base search config (recency boost, source weights, min score) applies here too:
cURL
Python
data.results is the expected response; limit caps at 20. Attach the base to an agent and its calls resolve through this same search.
Canonical page: Knowledge Bases API. The agent this grounds: Agent copilot.
18. Define a custom field and set it on a contact
Custom fields extend the contact record with tenant-defined keys — a tier, a region, a support PIN — that segments and exports then filter on. The loop: define the field once (key + type are immutable), write a value onto a contact, then read the contact back and see the value inline. Define the field — forselect / multiselect the options list is required; pick one of text, number, date, boolean, url, select, multiselect. 201 Created returns the definition; the key (customer_tier here) is the handle every value writes against:
cURL
Python
PATCH /custom-fields/:id later can rename or re-order but never re-type the key — choose the type the first time; migrations between types are a new field plus a re-write.
Set a value on a contact — PUT /custom-fields/values validates the value against the definition (type, regex, enum, length) before it lands, so a bad value answers 422 instead of dirtying the record:
cURL
Python
custom_attributes object, returned inline by GET /contacts/:id right after the write (the write invalidates the contact cache):
cURL
Python
custom_attributes.customer_tier and shows up in exports. List the definitions back with GET /custom-fields to drive a settings table or a field picker.
Canonical pages: Custom Fields API, Contacts API.
See also
- API integration — base URLs, sandbox, rate limits, idempotency
- Error handling by example — the error envelope and retry classes
- Pagination — cursor vs offset semantics
- Starter examples — runnable repos these recipes appear in
- Loyalty program setup — earn rules, tiers, and the redemption flow behind Task 7
- Connect HubSpot & Salesforce end to end — the full CRM loop behind Task 8
- Team Chat — operator workflows behind Task 9
- Voice API — full create-body reference for Task 11
- Numbers API — rent, release, and the porting lifecycle behind Task 12
- Messaging API — template + WhatsApp send parameters for Task 13
- Conversations API — copilot + inbox reads for Task 14
- Segments API — filter shapes and exports for Task 15
- Flows API — trigger + execution detail for Task 16
- Knowledge Bases API — upload → index → semantic search for Task 17
- Custom Fields API — definition + value lifecycle for Task 18
- SD runs, no registry yet — when the SDKs publish, every curl here translates one-to-one