> ## 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.

# Use the core-scope SDK escape hatch

> Reach every REST route the typed helper does not wrap, in one of the six core-scope SDKs — Python, Go, Ruby, PHP, Java, .NET — and know when to upgrade to the Node SDK for full parity.

# Use the core-scope SDK escape hatch

Six Orbit SDKs are **core-scope**: [Python](/sdks/python), [Go](/sdks/go), [Ruby](/sdks/ruby), [PHP](/sdks/php), [Java](/sdks/java), and [.NET](/sdks/csharp). Each wraps the platform's core resources as typed helpers — messaging (SMS, WhatsApp, email), voice calls, contacts, campaigns, verify (OTP), numbers, and webhooks — and ships one low-level `request*` method so every REST route stays reachable anyway. This page covers that escape hatch in all six languages: what the typed helpers cover versus what falls to the hatch, one worked call against the same route in each syntax, and where the [Node SDK](/sdks/node) (full parity) becomes the better tool.

The escape method is a first-class client method, not a raw HTTP workaround. It inherits what the typed helpers inherit: the key you initialized with, retry on 429/5xx with exponential backoff, an auto-generated `Idempotency-Key` on every non-GET, and the shared `OrbitError`/`OrbitApiError` tree on failure. The shared contract lives on the [SDKs index](/sdks).

## 1. What is typed, and what falls to the hatch

Typed helpers cover the core loop. Deeper surfaces — conferences, IVR, recordings and transcripts, dialer, SIP trunks, analytics, audit-log export — fall to the hatch. Check the per-language page for the exact scope list:

| Language               | Typed helpers cover                                                    | Escape hatch covers                                         |
| ---------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------- |
| [Python](/sdks/python) | Messaging, voice calls, contacts, campaigns, verify, numbers, webhooks | Conferences, IVR, recordings, dialer, SIP trunks, analytics |
| [Go](/sdks/go)         | Messaging, voice calls, contacts, campaigns, verify, webhooks          | Same gap                                                    |
| [Ruby](/sdks/ruby)     | Same as Go                                                             | Same gap                                                    |
| [PHP](/sdks/php)       | Same as Go                                                             | Same gap, plus SIP trunks and recordings                    |
| [Java](/sdks/java)     | Same as Go                                                             | Same gap, plus WhatsApp template components                 |
| [.NET](/sdks/csharp)   | Same as Go                                                             | Same gap                                                    |

A route no SDK page mentions stays reachable regardless — the hatch accepts any path under the `https://api.orbit.devotel.io/api/v1` base URL with any JSON body.

## 2. Six languages, one call — create a voice conference

`POST /api/v1/voice/conferences` with the same body (`name`, `participants`, `from`, `record`, `maxParticipants`) so you can diff syntax across languages. The route is out of typed scope in all six; the curl form is the reference. The hatch wants a leading-slash path; the conference id lands under `data`.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/voice/conferences \
    -H "X-API-Key: dv_live_sk_..." -H "Content-Type: application/json" \
    -d '{"name":"Q3 compliance review","participants":["+14155552671"],"from":"+18005551234","record":true,"maxParticipants":8}'
  ```

  ```python Python theme={null}
  result = client.request(
      "POST",
      "/api/v1/voice/conferences",
      json_body={"name":"Q3 compliance review","participants":["+14155552671"],"from":"+18005551234","record":True,"maxParticipants":8},
  )
  print(result["data"]["id"])
  ```

  ```go Go theme={null}
  var conf struct {
      Data struct{ ID string `json:"id"` } `json:"data"`
  }
  body := map[string]interface{}{"name":"Q3 compliance review","participants":[]interface{}{"+14155552671"},"from":"+18005551234","record":true,"maxParticipants":8}
  err = client.Request(ctx, "POST", "/api/v1/voice/conferences", nil, body, &conf, "")
  ```

  ```ruby Ruby theme={null}
  conf = client.request(
    "POST",
    "/api/v1/voice/conferences",
    json_body: {"name"=>"Q3 compliance review","participants"=>["+14155552671"],"from"=>"+18005551234","record"=>true,"maxParticipants"=>8},
  )
  puts conf.dig("data", "id")
  ```

  ```php PHP theme={null}
  $conf = $client->request(
      'POST',
      '/api/v1/voice/conferences',
      null,
      ['name'=>'Q3 compliance review','participants'=>['+14155552671'],'from'=>'+18005551234','record'=>true,'maxParticipants'=>8],
  );
  printf("conf: %s\n", $conf['data']['id'] ?? '?');
  ```

  ```java Java theme={null}
  Map<String, Object> conf = client.request(
      "POST",
      "/api/v1/voice/conferences",
      null,
      Map.of("name","Q3 compliance review","participants",List.of("+14155552671"),"from","+18005551234","record",true,"maxParticipants",8),
      null
  );
  System.out.println("conf: " + conf.get("data"));
  ```

  ```csharp .NET theme={null}
  var conf = await client.RequestAsync(
      "POST",
      "/api/v1/voice/conferences",
      jsonBody: new Dictionary<string, object?> { ["name"]="Q3 compliance review", ["participants"]=new object?[]{"+14155552671"}, ["from"]="+18005551234", ["record"]=true, ["maxParticipants"]=8 }
  );
  Console.WriteLine($"conf: {conf.GetProperty("data").GetProperty("id").GetString()}");
  ```
</CodeGroup>

Two shapes to know about the return value:

* **Decoded envelope.** Python, Ruby, PHP, and Java return the parsed JSON, so index `["data"]["id"]` (or `conf.dig("data", "id")`, `$conf['data']['id']`, `conf.get("data")`) holds the resource. Go unmarshalls into an `out` struct you declare; .NET returns a `JsonElement` walked with `GetProperty`.
* **GET when you poll.** Poll status with a plain GET through the same method: `client.request("GET", "/api/v1/voice/conferences/" + id)`. Non-GETs carry an auto idempotency key; GETs do not need one.

## 3. Guardrails, and when to upgrade to Node

1. **Prefer the typed helper when one exists.** The hatch type-checks at runtime only; a bad field through the hatch fails at the API with a `422`, on a typed call it fails in your test or IDE. Use the hatch only where the scope table says so.
2. **Pin the body against the API reference, not a guess.** The hatch is one hop from the raw REST call, so the field names you send are the contract in the [API reference](/api-reference). Verify a new body in the [sandbox](/guides/sandbox-test-mode) before it touches live data.
3. **Keep the error shape the same.** The hatch raises the same `OrbitError` subclasses the helpers do; handle it the same way and do not unwrap a transport exception around it.
4. **Upgrade to [Node SDK](/sdks/node) at the first whole surface.** If a feature area lives entirely behind the hatch — conference participants, IVR with DTMF guards, dialer across queues — maintain that surface in Node instead of collecting six hatch snippets. Node is feature-complete and typed at compile time. Stay on the hatch when gaps are occasional one-offs, where a typed surface you do not need is just trade without incentive.

## See also

* [SDKs index](/sdks) — the scope-by-language table this guide shortens.
* [SDK catalog walkthrough](/guides/developer-sdks-catalog) — picking a language and reading live status.
* [Per-language recipes](/guides/per-language-recipes) — the core loop past first send.
* [API reference](/api-reference) — every route the hatch can address.
