Skip to main content

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 with the Node SDK (source-only, unpublished) as the second snippet. Ten tasks cover the integrations most teams build first — loyalty, OAuth integrations, and internal team chat round out the messaging, verification, contacts, and risk loops below. Each one is self-contained — pick the task you need, 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
A 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
Receive the webhook instead. Register one endpoint in Settings → Webhooks subscribed to 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
The full retry-wrapper pattern (capped exponential fallback when the server didn’t name a wait) lives in API error handling by example; per-channel rates are in API integration → Rate limits.

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 — capture data.verification_id from the response:
cURL
Node SDK
Check — the user’s answer plus the id from send:
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 pass meta.pagination.cursor until meta.pagination.has_more is false.
cURL
Node SDK
A wrong or expired cursor is a 422 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-row POST /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
Poll the job until 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
Branch on 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
Read a member’s balance — points accrue from CDP events your app already emits (track on the CDP API); the read endpoint projects those events into the member’s standing on demand. Two compensation events join 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
Success is 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 the auth_url you redirect the operator to:
cURL
Node SDK
A provider with no OAuth credentials registered fails 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
An empty array is a valid answer — it means the connection is live but the first sync hasn’t landed records for that model yet. An owner/admin kicks an immediate run with 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
Post to a channel:
cURL
Node SDK
A 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 on error.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.

See also