Skip to main content

The SDK surface model

Orbit ships twelve language SDKs, and they are not twelve parallel attempts at the same thing. They share one architectural model: a fixed split into three tiers, one request contract every client honors, a shared error hierarchy, and a deliberate split between what a secret key can do and what a publishable key can do. This page explains that model. For installation and a first call in a specific language, go straight to the SDK index and the per-language pages linked at the end.

The three tiers

Every SDK occupies exactly one tier, chosen by where it will run and what credential it can hold: The split is a design decision, not a backlog. A client-scope SDK that exposed campaigns or billing would be embedding a server credential in an installable binary; a core-scope SDK wrapping every niche sub-resource would multiply the maintenance surface without adding capability, because the escape hatch (below) already covers those routes.

The shared contract every SDK saturates

Whether the client is hand-written (Node) or codegen-wrapped (Python, Go, Ruby, PHP, Java, .NET), these behaviors are identical:
  • Auth header: every request carries X-API-Key: <key>. Server SDKs expect a secret key; client-scope SDKs use the publishable key and call only public, key-tolerant surfaces.
  • Retry policy: non-2xx responses classified as retryable — a 429 or any 5xx — are retried up to 3 attempts per request by default, with exponential backoff starting at 250 ms and doubling each round, honoring the server’s Retry-After header and clamped at a 60-second ceiling. 4xx responses other than 429 are surfaced immediately.
  • Idempotency: every mutating request (POST, PUT, PATCH, DELETE) carries an Idempotency-Key header. The SDK generates a random UUID once per logical call and reuses it across the retry chain, so a retried send collapses to one charge on the server. You can pin your own key — in Node, pass idempotency on the send input.
  • Webhook verification: server-side SDKs expose a one-call verifier over the Orbit-Signature header (t=<unix_ts>,v1=<hex_hmac>, HMAC-SHA256 with your webhook secret, 5-minute replay window).
Webhook verification is server-side only. Client-scope SDKs never receive inbound webhooks, so none of them ships a verifier.

How the codegen pipeline maps OpenAPI onto typed clients

The single source of truth is apps/api/openapi.yaml, and the pipeline at tools/sdk-codegen turns it into the six non-Node server SDKs (Python, Go, Ruby, PHP, Java, .NET). The shape we ship is two layers:
  1. Generated layer: packages/sdk-<lang>/generated/ holds raw openapi-generator output — verbose, auto-named, wiped and re-emitted from the spec on every pnpm sdk:codegen run. Customers never import from it.
  2. Handcrafted wrapper: packages/sdk-<lang>/src/ (or lib/) holds the OrbitClient constructor, the auth helper, the error hierarchy, and the typed resource methods customers actually call. The wrapper imports types and low-level operations from the generated layer.
The wrapper is where an OpenAPI operationId becomes an idiomatic method. Regeneration is idempotent, and a spec-drift gate (pnpm sdk:check-spec-drift <lang>) fails a release when the published spec’s hash no longer matches the language’s pinned hash — so a route rename can’t slip through as a customer 404.

When the escape hatch is the right tool

Each core-scope client also exposes a low-level, untyped request helper — client.request in Node/Python/Java/Ruby, client.Request* in Go/.NET — that applies the full contract (auth header, retries, idempotency, error parsing) to any path. Use it for exactly two cases:
  • a covered route the typed resources don’t wrap yet (RCS, agents, billing, voice IVR/dialer): the helper keeps the contract without pretending the route has a typed shape;
  • a versioned preview or admin route you don’t want to hand-build headers for.
Anything stable belongs in a typed method; the escape hatch exists so an unwrapped route never forces you to drop down to raw fetch.

Worked example: POST /messages/sms

The same endpoint at each layer, showing what the codegen-to-wrapper mapping produces end to end. 1. The OpenAPI operation (apps/api/openapi.yaml):
2. The typed Node method — the codegen pipeline’s operationId ends as an idiomatic wrapper method:
3. The request the client actually sends — the raw fetch shape after the wrapper applies the shared contract:
Any failure along that chain surfaces as a subclass of OrbitApiError (below) carrying statusCode, code, requestId, and on 429 the Retry-After hint in details.

Publishable key vs. secret key

Orbit issues two key envelopes, and which one a client-scope SDK is allowed to hold is the whole point of the envelope split:
  • Secret key (dv_live_sk_…) — full API access. Server-side only: it must never ship inside a mobile app, a browser bundle, a public repository, or a client-side environment variable. Every server SDK (GA Node, core-scope languages) authenticates with this.
  • Publishable key (dv_live_pk_…) — public ingest + client-scope surfaces only: identified/anonymous event ingest (/sdk/identify, /sdk/track), conversations/inbox chat, in-app content cards, video rooms, and the voice-agent widget. Safe to embed in a distributed binary.
A client-scope SDK is deliberately unable to call any backend or management resource — messaging, whatsapp, email, contacts, campaigns, verify, billing, and every admin route — because those require the secret key. If you need them from a backend, use the Node SDK or call the REST API directly. A leaked publishable key exposes only anonymous ingest, which is rate-limited per-key; a leaked secret key exposes your whole tenant, so the envelope split exists.

The error hierarchy

Every SDK exposes the same tree, so a catch pattern you write in Node ports to Python unchanged:
  • OrbitApiError (base) — catches any non-2xx API response. Carries code, statusCode (aliased as status), details, docsUrl, and requestId, plus isRateLimited / isClientError / isServerError getters.
  • OrbitAuthError — 401/403: bad key or missing scope.
  • OrbitNotFoundError — 404: resource absent or not visible.
  • OrbitRateLimitError — 429: retryable; check Retry-After in details before retrying.
  • OrbitValidationError — 400/422: the request body failed validation; fix it, don’t retry.
  • OrbitServerError — 5xx after retries exhausted, or persistent network failure.
  • OrbitWebhookSignatureError — webhook verification failed, with a code of bad_signature | expired | malformed. It extends Error, not OrbitApiError, so a broad catch (OrbitApiError) block can’t accidentally retry a forged webhook.

Where this page ends and the quickstarts begin

Use the model above to choose a tier, then pick a language: Each per-language page carries the install command, the first-send quickstart, an OTP verify round trip, and a paginated list example. The SDK index has registry status per language, and Per-language recipes repeats the core recipes as tabs across cURL and all eight server SDKs.