Skip to main content

Call any route from every SDK

Every Orbit API route is reachable from every SDK, including routes the SDK’s typed modules do not wrap yet. Each client ships one untyped request method that carries the same authentication, the same automatic 429/5xx retry with backoff, and the same typed errors as the typed helpers. This page shows that call side by side with cURL and walks one real flow end to end: build a segment, create a contact that matches it, launch a campaign against it, and send. The Sandbox API cookbook is the recipe sheet for sandbox routes; this page is the equivalent for the escape hatch itself, on any route.

1. Why the escape hatch exists

Typed coverage is core scope. The typed helpers each SDK ships wrap the routes most integrations touch, and the SDK packages are the coverage source of truth: this page promises nothing the packages do not ship. Routes go live as public API endpoints first, so a route below the typed modules is still a full citizen of the API: same X-API-Key auth, same rate limits, same { "data", "meta" } envelope, same error codes. The untyped request method is how you reach those routes without dropping to raw HTTP. Compared with a hand-rolled HTTP call it gives you:
  • The same auth — the client attaches your X-API-Key and User-Agent; you never assemble the header by hand.
  • The same retry — 429 and 5xx responses are retried with exponential backoff that honours Retry-After, and every mutating request carries an Idempotency-Key, so a retried POST is not a double send.
  • The same typed errors — a 429 that exhausts the retry budget surfaces as the SDK’s rate-limit error type (and a 400/422 as the validation error type), not a raw status code you have to switch on.
Per the SDK sample coverage policy, cURL and Node.js (TypeScript) are the floor on every sample in these docs; Python, Go, Ruby, and PHP are published SDKs; Java and C# are pre-publish and vendor from source. The worked flow below carries the six published tabs on every step, with the Java and C# forms following in their own section.

2. The unified call matrix

One row per language, same call. The one difference to memorise is the path base: cURL and the Node client take the full path including /api/v1; every other client takes paths relative to /api/v1.
Passing /api/v1/contacts to the Python, Go, Ruby, PHP, Java, or C# client produces /api/v1/api/v1/contacts and a 404. Drop the prefix there; keep it on cURL and Node.js.
Clients for the worked flow, one per language:

3. Worked flow: segment to campaign

The flow: create a segment from a filter, create a contact the filter matches (segment membership is materialised from the filter, so adding a contact to a segment means creating or updating a contact whose attributes match), create a campaign targeted at the segment, and send it. Four calls, shown in every language.

Step 1 — Create the segment

POST /contacts/segments returns 201 with the created, already-materialised segment.

Step 2 — Create a contact the segment matches

POST /contacts with attributes that satisfy the filter. The segment’s auto_refresh flag keeps membership current; a manual POST /contacts/segments/{id}/refresh re-materialises immediately.

Step 3 — Create the campaign on the segment

POST /campaigns with audience_type: "segment" and the segment id. The campaign is created dormant; nothing sends until step 4.

Step 4 — Send it

POST /campaigns/{id}/send takes no body. Pre-launch gates (suppression lists, frequency caps, quiet hours) run on this call exactly as they do for a dashboard send.

The same flow in Java and C# (pre-publish)

Both pre-publish clients expose the identical escape hatch; vendor them from source per the Java and C# pages. The four steps collapse to the same three shapes: a POST with a body, a POST that reads an id back, and a bodyless POST.

4. The envelope: unwrap data, keep meta.request_id

Every response, raw or through an SDK, is the same envelope:
All seven forms above return the whole envelope; none of the clients unwrap it for you. Destructure data for the resource, and log meta.request_id next to the call in your own logs: when you open a support ticket about a specific call, the request id is what ties your trace to the server side. List reads additionally carry meta.pagination with the next_cursor to pass back as ?cursor=.

5. Errors: the typed errors follow you down here

The escape hatch raises the same error types the typed helpers raise. A 429 is retried up to the client’s retry budget; when the budget runs out you get the language’s rate-limit type, and every error carries the server’s error.code plus meta.request_id:

Honoring a 429 without an SDK

A raw caller owns its own retry. Honour Retry-After, cap the attempts, and send a stable Idempotency-Key on mutating calls so the retry cannot double-send:

6. Sandbox keys vs. X-Test-Mode

The escape hatch behaves identically in sandbox: point ORBIT_API_KEY at a sandbox secret key and every sample on this page runs against your sandbox org with nothing delivered and no live recipient touched. Two distinct signals, not interchangeable:
  • A sandbox key (dv_test_sk_…) — the normal one for scripts and CI. The key prefix itself scopes the session to sandbox; the same request shapes work unchanged.
  • The X-Test-Mode: true header — a dashboard-session signal. The console’s Test/Live toggle sets it for you; it is honoured only on dashboard sessions, and a live key (dv_live_sk_…) cannot flip into sandbox with it.
Keep live keys out of test harnesses entirely: a harness that retries a 429 against a live key is a harness that retries against production data. The sandbox overview walks all three activation signals; the Sandbox API cookbook pairs every sandbox route with its escape-hatch line.

7. Where to go next