> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Orbit SDK status: Node, Web, Go, .NET, Java, Python, PHP, Ruby

> Compare Orbit by Devotel SDKs. Node and Web are feature-complete, Go, .NET, Java, Python, PHP, and Ruby cover the 8 core resources.

# SDKs

Orbit has two SDKs that are feature-complete in workspace source today
(Node.js, Web). Six more, **Go**, **.NET**, **Java**, **Python**, **PHP**,
and **Ruby**, each wrap the platform's 8 core resources (messaging, voice,
contacts, campaigns, verify, webhooks) but are not yet at parity with Node —
see
[Go: core-scope, not full parity](#go-core-scope-not-full-parity),
[.NET: core-scope, not full parity](#net-core-scope-not-full-parity),
[Java: core-scope, not full parity](#java-core-scope-not-full-parity),
[Python: core-scope, not full parity](#python-core-scope-not-full-parity),
[PHP: core-scope, not full parity](#php-core-scope-not-full-parity),
and
[Ruby: core-scope, not full parity](#ruby-core-scope-not-full-parity)
below. **None of the eight has been published to a package registry
yet.** When
they ship, every SDK will share the same auth model (`X-API-Key`), retry
policy (3 attempts on 429/5xx with exponential backoff), idempotency
contract (auto-generated `Idempotency-Key` on every non-GET), and webhook
verification format (`t=<unix_ts>,v1=<hex_hmac>` HMAC-SHA256, 5-minute
replay window).

<Note>
  **Pre-launch — no SDK is currently published to a public registry.**
  Until first publish, the Node + Web SDK source lives in the monorepo
  under `packages/sdk-node/` and `packages/sdk-web/`. Install commands
  here describe the *future* registry shapes — not the *current* reach
  (every public registry probe today returns 404, Node and Web included).
  Go, .NET, Java, Python, PHP, and Ruby now wrap 8 core resources in
  workspace source (see below) but none is full parity. Build against the
  REST API directly until first publish — the
  [REST API recipes](/guides/api-recipes) cookbook walks the core
  send/verify/contacts/risk flows task by task, with the Node SDK as the
  second snippet on each. Anything the core-scope clients
  don't wrap is still reachable: each of those six SDKs ships an escape
  hatch (`client.Request*` / `client.request`) — every per-language page
  shows a worked example hitting a real uncovered route
  ([Go](/sdks/go#covered-route-missing-use-the-escape-hatch),
  [.NET](/sdks/csharp#covered-route-missing-use-the-escape-hatch),
  [Java](/sdks/java#covered-route-missing-use-the-escape-hatch),
  [Python](/sdks/python#covered-route-missing-use-the-escape-hatch),
  [PHP](/sdks/php#covered-route-missing-use-the-escape-hatch),
  [Ruby](/sdks/ruby#covered-route-missing-use-the-escape-hatch)).
</Note>

<Note>
  Where a table below marks a language **GA**, that describes the *API
  surface* — the methods exist and are implemented in the workspace
  source. It does **not** mean the package is installable; see the
  registry table for publish status.
</Note>

## In active development (workspace source)

<CardGroup cols={2}>
  <Card title="Node.js / TypeScript" icon="js" href="/sdks/node">
    Source: `packages/sdk-node/` — future package name `@devotel-orbit/node`.
    Hand-written, fully typed. Node 18+.
    Status: **feature-complete in workspace, NOT published**.
  </Card>

  <Card title="Web (browser)" icon="globe" href="/sdks/web">
    Source: `packages/sdk-web/` — future package name `@devotel-orbit/web`.
    Embeddable chat widget + inbound APIs.
    Status: **feature-complete in workspace, NOT published**.
  </Card>
</CardGroup>

## Core-scope quickstarts (pre-registry)

<CardGroup cols={2}>
  <Card title="Python" icon="code" href="/sdks/python">
    Messaging, voice, contacts, campaigns, verify. Source-only, NOT
    published.
  </Card>

  <Card title="Go" icon="code" href="/sdks/go">
    Messaging, voice, contacts, campaigns, verify. Source-only, NOT
    published.
  </Card>

  <Card title="Ruby" icon="code" href="/sdks/ruby">
    Messaging, voice, contacts, campaigns, verify. Source-only, NOT
    published.
  </Card>

  <Card title="PHP" icon="code" href="/sdks/php">
    Messaging, voice, contacts, campaigns, verify. Source-only, NOT
    published.
  </Card>

  <Card title="Java" icon="code" href="/sdks/java">
    Messaging, voice, contacts, campaigns, verify. Source-only, NOT
    published.
  </Card>

  <Card title=".NET / C#" icon="code" href="/sdks/csharp">
    Messaging, voice, contacts, campaigns, verify. Source-only, NOT
    published.
  </Card>
</CardGroup>

## Go: core-scope, not full parity

Source: `packages/sdk-go/` (module `github.com/devotel/orbit-go`) now wraps
8 core resources with typed methods and its own test suite: messaging
(SMS/WhatsApp/email via `client.Messages()`), voice (`client.Voice()`),
contacts (`client.Contacts()`), campaigns (`client.Campaigns()`), verify/OTP
(`client.Verify()`), and webhook signature verification
(`orbit.VerifyWebhook`). This is no longer an empty placeholder.

It is **not** feature-complete against the Node SDK, and it is
**unpublished / pre-GA** like every non-Node/Web language today — do not
advertise it to customers as install-ready. Sub-resources the Node SDK
exposes are intentionally out of scope for now: voice
conferences/IVR/dialer/recordings, contact lists/segments/scoring, and the
verify voice-biometrics endpoints. For those, use the Node SDK or call the
REST API directly (the Go client also exposes a low-level
`client.Request(ctx, method, path, ...)` escape hatch). See
`packages/sdk-go/README.md`'s **Scope** section for the authoritative,
up-to-date list of what is and isn't wrapped.

## .NET: core-scope, not full parity

Source: `packages/sdk-csharp/` (package `Orbit.Sdk`, targets .NET 8.0) also
wraps the same 8 core resources with typed methods and its own test suite:
messaging (SMS/WhatsApp/email via `client.Messages`), voice
(`client.Voice`), contacts (`client.Contacts`), campaigns
(`client.Campaigns`), verify/OTP (`client.Verify`), and webhook signature
verification (`Webhooks.Verify`). Like Go, this is no longer an empty
placeholder.

It is **not** feature-complete against the Node SDK, and it is
**unpublished / pre-GA** like every non-Node/Web language today — do not
advertise it to customers as install-ready. Sub-resources the Node SDK
exposes are intentionally out of scope for now: RCS, agents, conversations,
billing, and voice recordings/transcripts/conferences/IVR/dialer. For
those, use the Node SDK or call the REST API directly (the .NET client
also exposes a low-level `client.RequestAsync(method, path, ...)` escape
hatch). See `packages/sdk-csharp/README.md`'s **Scope today** section for
the authoritative, up-to-date list of what is and isn't wrapped.

## Java: core-scope, not full parity

Source: `packages/sdk-java/` (JDK 11+, no live Maven coordinates yet —
the registry moves off the retired legacy name before publish) also
wraps the same 8 core resources with typed methods
and its own test suite: messaging (SMS/WhatsApp/email via
`client.messages`), voice (`client.voice`), contacts (`client.contacts`),
campaigns (`client.campaigns`), verify/OTP (`client.verify`), and webhook
signature verification (`Webhooks.verify`). Like Go and .NET, this is no
longer an empty placeholder.

It is **not** feature-complete against the Node SDK, and it is
**unpublished / pre-GA** like every non-Node/Web language today — do not
advertise it to customers as install-ready. Sub-resources the Node SDK
exposes are intentionally out of scope for now: WhatsApp templates,
message streaming, contact segments/lists/opt-outs, campaign
journeys/A-B tests, voice recordings/transcripts/conferences/IVR/dialer,
verify voice-biometrics, plus RCS, agents, conversations, and billing.
For those, use the Node SDK or call the REST API directly (the Java
client also exposes a low-level `client.request(method, path, ...)`
escape hatch). See `packages/sdk-java/README.md`'s **Scope** section for
the authoritative, up-to-date list of what is and isn't wrapped.

## Python: core-scope, not full parity

Source: `packages/sdk-python/` (the module imports as `orbit_sdk`; no PyPI
package is live yet — the registry moves off the retired legacy name
before publish) also wraps the same 8 core resources, fully
hand-written against the Python standard library (`urllib.request`, zero
runtime dependencies), with its own pytest suite: messaging (SMS/WhatsApp/
email via `client.messages`), voice (`client.voice`), numbers + HLR lookup
(`client.numbers`, `client.lookup`), contacts (`client.contacts`),
campaigns (`client.campaigns`), verify/OTP (`client.verify`), and webhook
signature verification (`verify_webhook`). Like Go, .NET, and Java, this is
no longer an empty placeholder.

It is **not** feature-complete against the Node SDK, and it is
**unpublished / pre-GA** like every non-Node/Web language today — do not
advertise it to customers as install-ready. Sub-resources the Node SDK
exposes are intentionally out of scope for now: RCS, agents, conversations,
billing, contact lists/segments/scoring, campaign journeys/A-B tests, and
voice recordings/transcripts/conferences/IVR/dialer. For those, use the
Node SDK or call the REST API directly (the Python client also exposes a
low-level `client.request(method, path, ...)` escape hatch). See
`packages/sdk-python/README.md`'s **Scope** section for the authoritative,
up-to-date list of what is and isn't wrapped.

## PHP: core-scope, not full parity

Source: `packages/sdk-php/` (PHP 8.1+, no Packagist package live yet —
the registry moves off the retired legacy name before publish) also
wraps the same 8 core resources with typed methods
and its own PHPUnit suite: messaging (SMS/WhatsApp/email via
`$client->messages`), voice (`$client->voice`), contacts
(`$client->contacts`), campaigns (`$client->campaigns`), verify/OTP
(`$client->verify`), and webhook signature verification
(`Webhooks::verify`). Like Go, .NET, Java, and Python, this is no longer an
empty placeholder.

It is **not** feature-complete against the Node SDK, and it is
**unpublished / pre-GA** like every non-Node/Web language today — do not
advertise it to customers as install-ready. Sub-resources the Node SDK
exposes are intentionally out of scope for now: voice conferences, IVR
flows/intents, the outbound dialer, SIP trunks, voice clones, VAQI
analytics, live transcript streaming, contact lists/segments, and
voice-biometric caller authentication. For those, use the Node SDK or call
the REST API directly (the PHP client also exposes a low-level
`OrbitClient::request(method: ..., path: ..., ...)` escape hatch). See
`packages/sdk-php/README.md`'s **Scope** section for the authoritative,
up-to-date list of what is and isn't wrapped.

## Ruby: core-scope, not full parity

Source: `packages/sdk-ruby/` (future gem name `orbit_sdk`, targets Ruby
2.7+) also wraps the same 8 core resources with typed methods and its own
RSpec suite: messaging (SMS/WhatsApp/email via `client.messages`), voice
(`client.voice`), contacts (`client.contacts`), campaigns
(`client.campaigns`), verify/OTP (`client.verify`), and webhook signature
verification (`OrbitSdk::Webhooks.verify`). Like Go, .NET, Java, Python,
and PHP, this is no longer an empty placeholder.

It is **not** feature-complete against the Node SDK, and it is
**unpublished / pre-GA** like every non-Node/Web language today — do not
advertise it to customers as install-ready. Sub-resources the Node SDK
exposes are intentionally out of scope for now: RCS, agents, conversations,
billing, voice recordings/transcripts/conferences/IVR/dialer, contact
lists/segments/scoring, campaign journeys/A-B tests, and verify
voice-biometrics. For those, use the Node SDK or call the REST API directly
(the Ruby client also exposes a low-level
`client.request(method, path, **opts)` escape hatch). See
`packages/sdk-ruby/README.md`'s **Scope** section for the authoritative,
up-to-date list of what is and isn't wrapped.

## Client / mobile SDKs (different scope, by design)

Besides the two server-capable SDKs above, the workspace also ships
**client-embed SDKs** for native apps — each with its own quickstart page:
[Swift / iOS](/sdks/swift) (source: `packages/sdk-swift/`, shipped as Swift
Package Manager libraries),
[Android (Kotlin)](/sdks/android) (source: `packages/sdk-android/`,
Gradle/Maven),
[Flutter](/sdks/flutter) (source: `packages/sdk-flutter/`, Dart), and
[React Native](/sdks/react-native) (source: `packages/sdk-react-native/`,
npm). These are
**not** thin ports of the Node SDK and are not meant to grow into one. Each
is scoped to the same end-user-facing surfaces the Web SDK's widget mode
covers — conversations/inbox chat, owned in-app messages/content cards,
video rooms, and (on Swift) VoIP push registration for inbound-call delivery —
and authenticates only with a publishable key (`dv_live_pk_…`), because they
ship inside a distributed app binary.

They deliberately do **not** expose the backend/management resource clients
(`messaging`, `whatsapp`, `email`, `contacts`, `campaigns`, `verify`, billing,
etc.) — those require a secret key and must only ever be called from a
backend, via the Node SDK or the REST API directly. If a client/mobile SDK's
file count looks small next to Node's, that is this intentional split, not a
gap to fill.

## Client / mobile SDK sources

For the sensor-exemption contract each client/mobile SDK's README must honor:

* Source: `packages/sdk-web/` — Web (browser, TypeScript, npm). README
  documents the deliberate scope as a **"Scope — this is a client SDK, not a
  full API client"** heading.
* Source: `packages/sdk-swift/` — iOS (Swift, SPM). README documents the
  deliberate scope as a **"Scope — this is a client SDK, not a full API
  client"** heading.
* Source: `packages/sdk-android/` — Android (Kotlin/Gradle). Same deliberate
  scope heading in its README.
* Source: `packages/sdk-flutter/` — Flutter (Dart, pub). Same deliberate scope
  heading in its README.
* Source: `packages/sdk-react-native/` — React Native (TypeScript). Same
  deliberate scope heading in its README.

## Future registry coordinates

No public registry entry exists for ANY language today — including Node
and Web. Do not attempt to install; the package names below describe the
*future* coordinates and may change before first publish.

The publishable npm scope is `@devotel-orbit` (the `@devotel` npm org is
not ours), so the two npm SDKs ship as `@devotel-orbit/node` and
`@devotel-orbit/web`. Non-npm coordinates are unaffected.

| Language      | Future package name           | Future registry                 | Status                     |
| ------------- | ----------------------------- | ------------------------------- | -------------------------- |
| Node.js / TS  | `@devotel-orbit/node`         | npm                             | Unpublished (active)       |
| Web (browser) | `@devotel-orbit/web`          | npm                             | Unpublished (active)       |
| Python        | `orbit_sdk` (module)          | PyPI — coordinates TBD          | Unpublished (core-scope)   |
| Go            | `github.com/devotel/orbit-go` | pkg.go.dev                      | Unpublished (core-scope)   |
| PHP           | `packages/sdk-php/` (source)  | Packagist — coordinates TBD     | Unpublished (core-scope)   |
| Ruby          | `orbit_sdk`                   | RubyGems                        | Unpublished (core-scope)   |
| Java          | `packages/sdk-java/` (source) | Maven Central — coordinates TBD | Unpublished (core-scope)   |
| .NET / C#     | `Orbit.Sdk`                   | NuGet                           | Unpublished (core-scope)   |
| Swift (iOS)   | `orbit-swift-sdk` (SPM)       | Swift Package Index             | Unpublished (client-scope) |
| Android       | `io.devotel:orbit-chat-sdk`   | Maven Central                   | Unpublished (client-scope) |
| Flutter       | `orbit_flutter`               | pub.dev                         | Unpublished (client-scope) |
| React Native  | `@devotel-orbit/react-native` | npm                             | Unpublished (client-scope) |

Each SDK already has a quickstart page wired into the docs nav — the
core-scope [Python](/sdks/python), [Go](/sdks/go), [Ruby](/sdks/ruby),
[PHP](/sdks/php), [Java](/sdks/java), and [.NET](/sdks/csharp) pages, the
client/mobile [Swift](/sdks/swift), [Android](/sdks/android),
[Flutter](/sdks/flutter), and [React Native](/sdks/react-native) pages,
and the two feature-complete pages [/sdks/node](/sdks/node) and
[/sdks/web](/sdks/web). Each per-language page carries a first-send
quickstart, an OTP verify round trip, and one paginated list example
against a cursor-based endpoint — the pattern explained prose-side in the
[Pagination guide](/guides/pagination) — plus a cross-link to the runnable
repositories on [Starter examples](/guides/starter-examples). Once each
SDK lands on its registry, its page
carries the production install command, a version pin, and the
per-language quickstart. The codegen pipeline that drives this lives at
`tools/sdk-codegen/README.md`.

## Common patterns across languages

### Authentication

Every SDK authenticates with an `X-API-Key`. The GA Node SDK exposes a
single constructor — `new Orbit({ apiKey })` (alias `new Devotel({ apiKey })`)
— and you pass your own env var (e.g. `process.env.ORBIT_API_KEY`); the Web
surfaces take a `publicKey` instead. There is **no** `fromApiKey()` static
or `from_env()` helper on the GA SDKs today — those are roadmap-language for
the codegen'd clients, so the **From env** column below is marked *Roadmap*
on the still-roadmap languages that list one (core-scope languages that
already implement it are marked with their own status instead).

| Language | Direct                                      | From env (Roadmap)                   | Status                   |
| -------- | ------------------------------------------- | ------------------------------------ | ------------------------ |
| Node     | `new Orbit({ apiKey: 'dv_live_sk_...'})`    | — (use `process.env.ORBIT_API_KEY`)  | GA                       |
| Web      | `OrbitVoiceAgent.init({ publicKey: '...'})` | — (use bundler-injected env)         | GA                       |
| Python   | `OrbitClient.from_api_key('...')`           | `OrbitClient.from_env()`             | Core-scope (unpublished) |
| Go       | `orbit.NewClient('...')`                    | — (use `os.Getenv("ORBIT_API_KEY")`) | Core-scope (unpublished) |
| PHP      | `OrbitClient::fromApiKey('...')`            | `OrbitClient::fromEnv()`             | Core-scope (unpublished) |
| Ruby     | `OrbitSdk::Client.from_api_key('...')`      | `OrbitSdk::Client.from_env`          | Core-scope (unpublished) |
| Java     | `OrbitClient.fromApiKey("...")`             | `OrbitClient.fromEnv()`              | Core-scope (unpublished) |
| C#       | `OrbitClient.FromApiKey("...")`             | `OrbitClient.FromEnv()`              | Core-scope (unpublished) |

The GA Node and Web rows have **no** `fromApiKey`/env-reading helper — only
the constructor shown in the **Direct** column.

### Webhook verification

Webhook verification is server-side only — the Web SDK does not expose a
verifier (browser surfaces never receive inbound webhooks). Each server-side
SDK exposes a one-call verifier that returns the decoded event on success or
throws `OrbitWebhookSignatureError` on any failure (malformed header, expired
timestamp, signature mismatch, invalid JSON):

* **Node** *(GA)*: `Orbit.webhooks.constructEvent(body, signature, secret)`
* **Python** *(core-scope, unpublished)*: `verify_webhook(payload=..., signature=..., secret=...)`
* **Go** *(core-scope, unpublished)*: `orbit.VerifyWebhook(body, header, secret, 0, time.Time{})`
* **PHP** *(core-scope, unpublished)*: `Webhooks::verify(payload: ..., signature: ..., secret: ...)`
* **Ruby** *(core-scope, unpublished)*: `OrbitSdk::Webhooks.verify(payload:, signature:, secret:)`
* **Java** *(core-scope, unpublished)*: `Webhooks.verify(body, signature, secret)`
* **C#** *(core-scope, unpublished)*: `Webhooks.Verify(payload, signature, secret)`

## Worked example

The same end-to-end flow — connect, send one SMS, verify an inbound webhook —
in the exact constructor and method signatures above. Keep it under ten lines
per step; per-language detail lives on each SDK's own page.

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { Orbit, OrbitRateLimitError, OrbitValidationError } from '@devotel-orbit/node';

    const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY });
    ```

    ```typescript theme={null}
    const message = await orbit.messages.send({
      channel: 'sms',
      to: '+14155552671',
      body: 'Hello from Orbit!',
    });
    ```

    ```typescript theme={null}
    const event = Orbit.webhooks.constructEvent(rawBody, signatureHeader, webhookSecret);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from orbit_sdk import OrbitClient

    client = OrbitClient.from_env()  # reads ORBIT_API_KEY
    ```

    ```python theme={null}
    msg = client.messages.send_sms(to="+14155552671", body="Hello from Orbit!")
    ```

    ```python theme={null}
    from orbit_sdk import verify_webhook

    event = verify_webhook(payload=payload_bytes, signature=header, secret=webhook_secret)
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    client, err := orbit.NewClient(os.Getenv("ORBIT_API_KEY"))
    ```

    ```go theme={null}
    msg, err := client.Messages().SendSMS(ctx, orbit.SendSMSInput{
        To:   "+14155552671",
        Body: "Hello from Orbit!",
    })
    ```

    ```go theme={null}
    event, err := orbit.VerifyWebhook(body, signatureHeader, webhookSecret, 0, time.Time{})
    ```
  </Tab>
</Tabs>

For error handling, branch on the subclasses the [error hierarchy](#error-hierarchy)
names. In Node, distinguish a retryable rate limit from an unretryable
validation failure:

```typescript theme={null}
import { OrbitRateLimitError, OrbitValidationError } from '@devotel-orbit/node';

try {
  await orbit.messages.send({ channel: 'sms', to, body });
} catch (e) {
  if (e instanceof OrbitRateLimitError) {
    // 429 — respect the Retry-After hint (surfaced in `details`) before retrying.
  } else if (e instanceof OrbitValidationError) {
    // 400/422 — the request body failed validation; fix it, don't retry.
  } else {
    throw e;
  }
}
```

### Error hierarchy

Every SDK exposes the same error tree:

* `OrbitApiError` (base) → catch this to handle any non-2xx API response.
  Carries `code`, `statusCode` (aliased as `status`), `details`, `docsUrl`,
  and `requestId`, plus `isRateLimited` / `isClientError` / `isServerError`
  getters.
* `OrbitAuthError` — 401/403 (bad API key or missing scope).
* `OrbitNotFoundError` — 404 (resource doesn't exist or isn't visible).
* `OrbitRateLimitError` — 429. Check the `Retry-After` header (also surfaced
  in `details`) to decide when to retry.
* `OrbitValidationError` — 400/422 (request body failed server-side validation).
* `OrbitServerError` — 5xx after retries exhausted, or persistent network failure.
* `OrbitWebhookSignatureError` — webhook verification failed. Carries a
  `code` of `'bad_signature' | 'expired' | 'malformed'`. Extends `Error`,
  **not** `OrbitApiError`, so a broad `catch (OrbitApiError)` block can't
  accidentally swallow + retry forgeries.

## Operator: publishing checklist

See `tools/sdk-codegen/README.md` for the per-registry publish commands.
Pre-launch, the SDKs are committed source-only; first publish to each
registry is an operator action.
