Skip to main content

OpenAPI recipes cookbook

The sample-writing policy guarantees a two-language floor on every sample, and the language-policy page spells out which endpoints carry the full six-language group. Both ship single-call snippets. This cookbook is the next level: each recipe is a complete, runnable sequence — call, keep the id, call again — with the envelope rules written down once instead of discovered one 422 at a time. Every recipe runs in cURL, Node.js, and Python, per the two-language floor plus the Python policy. Use a test key (dv_test_sk_…) so each chain runs against the sandbox; the only places that use a different key shape are the public-key endpoints, and the recipe says so inline. Field and parameter definitions live on the linked API-reference pages — this page is the sequence, not the schema.

Task index

Authenticate every request with X-API-Key. Base URL https://api.orbit.devotel.io/api/v1 — the sandbox is the same URL with a dv_test_sk_… key, not a separate host. (Recipe 3 opens with the public-key endpoint under /sdk, which deliberately sits outside /api/v1 — see the SDK API page.)

1. Send an SMS, then poll its status

The send returns the message id synchronously and the delivery state asynchronously. A webhook is the durable way to consume the status change (wire one up); until then, poll GET /messages/{id} until data.status leaves queued — the lifecycle values are on Message status lifecycle.
Replace the poll with a webhook receiver when volume grows — events include message.delivered and message.failed; the signed-body verification loop per language is in Verify webhook signatures.

2. Create a contact idempotently, then upsert custom fields

Two discipline points in one recipe: Idempotency-Key on the create so a safe retry can’t duplicate the row, and custom-field values written through the values endpoint so the write validates against the field definition. At least one of phone / email is required on the create; field keys and types are defined first under /api/v1/custom-fields.
Values come back inline on the contact as custom_fields — re-read GET /contacts/{id} to see the upsert. The full idempotency contract (replay headers, TTL, 409 on conflicting bodies) is in Idempotent requests.

3. Build a segment over CDP events

The event-ingest endpoint is public-key — it is safe to call from browser or mobile code. Segment building is secret-key — it is a back-office read over your whole contact base. This split is the entire reason the two calls take different keys:
  1. Ingest with the tenant public key (dv_test_pk_… in test mode) in X-API-Key, on the /sdk/track path outside /api/v1.
  2. Segment with the secret key (dv_test_sk_…) in X-API-Key, on /api/v1/segments/export.csv with a filter body referencing the ingested event.
The CSV response bypasses the { data, meta } envelope — the body is the file. Event ingestion is versioned separately from the REST API (SDK API); the export’s filter grammar is on the Segments API. In a browser, use the @devotel-orbit/web SDK rather than hand-rolling the identify
  • track calls.

4. Upload a file and attach it to a send

Uploaded files return a file id and a signed URL; channel sends accept the media URL on the payload. This recipe uploads an image and rides the URL into an MMS. The 25 MB upload cap and the signed-URL TTL are on the Files API page.
The same file_id flow drives WhatsApp template sends — pass the URL in the template’s media component. Oversized or HEIC/TIFF source images need to be re-encoded first with POST /messages/media/convert; the recipe surfaces that branch inside the MMS page rather than blocking the upload.

5. Bootstrap a reseller subaccount

The five calls a CSPaaS reseller runs to take a child from row to priced-and-capped. Each is idempotent-safe to rerun with the same body — the PUTs are full-set replaces.
Two one-shot shortcuts exist on this surface: POST /subaccounts/provision walks the create + pricing + funding + branding chain atomically, and the dashboard subaccount wizard drives the same steps. The five discrete calls stay canonical for resellers scripting the lifecycle from their own tooling. Every endpoint here requires an owner or admin role on the parent organization; scope and shape details per endpoint are on the Subaccounts reference.

6. Read a subaccount’s live usage counters

Budgets are enforced send-side against a near-real-time counter; the statement settles the authoritative spend afterward. GET usage/live bridges the two — the dashboard’s live-usage widget reads it, and your tooling can poll it directly for a last-moment pre-warn.
Returned rows carry spent_cents (reconciled statement spend), live_counter_cents (the send-time gate counter the budget enforces against, per the response shape the Subaccounts page documents), and breached (true when the counter passes the channel budget’s threshold_cents, when a budget is set). The route serves no-cache responses — poll it; do not memoize.

7. Stream the live request log to a terminal

GET /logs/tail is a SSE stream, not a buffered fetch — the generic request() helper never returns until the stream closes, so consume it with raw HTTP and an incremental frame parser. The event catalog is event: log rows; the API page for the endpoint is Live request-log tail.
The stream excludes health/readiness probes, provider webhook ingress, and the tail endpoint itself. Each frame carries method, path_pattern, status_code, and duration_ms — plenty to grep against a stuck integration without shipping the whole request body anywhere.

8. The envelope rule every recipe inherits

Every response — success or failure — is the same shape, so your code can branch once instead of per-endpoint. Success (2xx). The payload is under data; meta.request_id and meta.timestamp always accompany it. List endpoints add meta.pagination. Quote the request_id when you open a support issue — it is keyed into the request log recipe 7 streams.
200 success
Validation failure (422). The SDK turns this into a typed ValidationError; raw clients should read data.errors for the per-field issues the schema rejected. The envelope still closes in meta.
422 validation failure
Everything else. Auth failures return 401 (UNAUTHORIZED / INVALID_API_KEY); scope failures 403 (INSUFFICIENT_PERMISSIONS); throttles return 429 with a Retry-After header and a retry_after field on the body. The canonical code table is on Error codes; the error-handling examples walk the retry decision tree per language. All six languages follow the same envelope — the per-language unwrap idioms are on How request samples work.

See also