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

# OpenAPI recipes cookbook: multi-call sequences over the shipped endpoints

> Worked, runnable request chains — send and poll an SMS, create a contact idempotently and upsert its custom fields, build a segment from CDP events, attach an uploaded file to a channel send, bootstrap a reseller subaccount with pricing and budgets, read its live usage counters, and tail the request log over SSE — each in cURL, Node.js, and Python, with the full response envelope and the 422 error shape documented for every call.

# OpenAPI recipes cookbook

The [sample-writing policy](/api-reference/sdk-sample-writing) guarantees a
two-language floor on every sample, and the
[language-policy page](/api-reference/sdk-language-policy) 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

| # | Recipe | Endpoints used |
| - | - | - |
| 1 | [Send an SMS, then poll its status](#1-send-an-sms-then-poll-its-status) | `POST /messages/sms`, `GET /messages/{id}` |
| 2 | [Create a contact idempotently, then upsert custom fields](#2-create-a-contact-idempotently-then-upsert-custom-fields) | `POST /contacts`, `PUT /custom-fields/values` |
| 3 | [Build a segment over CDP events](#3-build-a-segment-over-cdp-events) | `POST /sdk/track` (public key), `POST /segments/export.csv` (secret key) |
| 4 | [Upload a file and attach it to a send](#4-upload-a-file-and-attach-it-to-a-send) | `POST /files/upload`, read the returned URL, `POST /messages/sms` |
| 5 | [Bootstrap a reseller subaccount](#5-bootstrap-a-reseller-subaccount) | `POST /subaccounts`, `PUT /subaccounts/{id}/pricing`, `PUT /subaccounts/rate-cards`, `PUT /subaccounts/{id}/assigned-rate-card`, `PUT /subaccounts/{id}/channel-budgets` |
| 6 | [Read a subaccount's live usage counters](#6-read-a-subaccounts-live-usage-counters) | `GET /subaccounts/{id}/usage/live?channel=sms` |
| 7 | [Stream the live request log to a terminal](#7-stream-the-live-request-log-to-a-terminal) | `GET /logs/tail` (SSE) |
| 8 | [The envelope rule every recipe inherits](#8-the-envelope-rule-every-recipe-inherits) | `data` + `meta.request_id`, and the `{ error, meta }` failure shape |

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](/api-reference/sdk).)

## 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](/guides/first-webhook-quickstart)); until then, poll
`GET /messages/{id}` until `data.status` leaves `queued` — the lifecycle
values are on [Message status lifecycle](/api-reference/messages-status-lifecycle).

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Send — keep the id. The SDKs add an Idempotency-Key automatically;
  # with raw curl, send one yourself on retries.
  SEND=$(curl -s -X POST https://api.orbit.devotel.io/api/v1/messages/sms \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: sms-poll-demo-001" \
    -d '{ "to": "+14155552671", "body": "Your order 98421 shipped." }')

  ID=$(echo "$SEND" | python3 -c 'import sys, json; print(json.load(sys.stdin)["data"]["id"])')

  # 2. Poll until status is delivered / failed.
  curl -s https://api.orbit.devotel.io/api/v1/messages/$ID \
    -H "X-API-Key: dv_test_sk_YOUR_KEY"
  ```

  ```typescript Node.js theme={null}
  import { Orbit } from "@devotel-orbit/node";

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

  const sent = await orbit.messages.sendSms({
    to: "+14155552671",
    body: "Your order 98421 shipped.",
  });

  // Poll with generic request() — get-by-id kept out of the typed surface.
  for (let attempt = 0; attempt < 10; attempt++) {
    const { data } = await orbit.request<unknown>("GET", `/messages/${sent.data.id}`);
    const status = (data as { status: string }).status;
    if (status !== "queued") {
      console.log(status); // delivered | failed | …
      break;
    }
    await new Promise((r) => setTimeout(r, 3000));
  }
  ```

  ```python Python theme={null}
  import requests, os, time

  headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
  sent = requests.post(
      "https://api.orbit.devotel.io/api/v1/messages/sms",
      headers={**headers, "Idempotency-Key": "sms-poll-demo-001"},
      json={"to": "+14155552671", "body": "Your order 98421 shipped."},
  ).json()["data"]

  for _ in range(10):
      row = requests.get(
          f"https://api.orbit.devotel.io/api/v1/messages/{sent['id']}",
          headers=headers,
      ).json()["data"]
      if row["status"] not in ("queued", "sending"):
          print(row["status"], row.get("error_code"))
          break
      time.sleep(3)
  ```
</CodeGroup>

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](/guides/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`](/api-reference/custom-fields).

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Create the contact. Replay returns the original response with an
  # Idempotency-Replay: true header instead of a second row.
  curl -X POST https://api.orbit.devotel.io/api/v1/contacts/ \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: signup-5f2d8a1c" \
    -d '{ "email": "jane@example.com", "phone": "+14155550123" }'

  # 2. Upsert a custom-field value — update semantics on the same key.
  curl -X PUT https://api.orbit.devotel.io/api/v1/custom-fields/values \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "contact_id": "cnt_9f8a7b6c", "key": "customer_tier", "value": "gold" }'
  ```

  ```typescript Node.js theme={null}
  const contact = await orbit.request("POST", "/contacts/", {
    body: { email: "jane@example.com", phone: "+14155550123" },
    headers: { "Idempotency-Key": "signup-5f2d8a1c" },
  });

  // Upsert — re-running with a new value on the same key replaces it.
  await orbit.request("PUT", "/custom-fields/values", {
    body: {
      contact_id: (contact.data as { id: string }).id,
      key: "customer_tier",
      value: "gold",
    },
  });
  ```

  ```python Python theme={null}
  import requests, os

  headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
  base = "https://api.orbit.devotel.io/api/v1"

  contact = requests.post(
      f"{base}/contacts/",
      headers={**headers, "Idempotency-Key": "signup-5f2d8a1c"},
      json={"email": "jane@example.com", "phone": "+14155550123"},
  ).json()["data"]

  requests.put(
      f"{base}/custom-fields/values",
      headers=headers,
      json={
          "contact_id": contact["id"],
          "key": "customer_tier",
          "value": "gold",
      },
  ).raise_for_status()
  ```
</CodeGroup>

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](/guides/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.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Ingest events with the PUBLIC key — door opens from the browser.
  curl -X POST https://api.orbit.devotel.io/sdk/track \
    -H "X-API-Key: dv_test_pk_YOUR_PUBLIC_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "jane@example.com",
      "event": "plan_upgraded",
      "properties": { "plan": "gold" }
    }'

  # 2. Export the cohort with the SECRET key — filter on the event.
  curl -X POST https://api.orbit.devotel.io/api/v1/segments/export.csv \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -o plan-upgrades.csv \
    -d '{
      "filters": {
        "op": "AND",
        "conditions": [
          { "field": "event:plan_upgraded", "op": "occurred", "value": null }
        ]
      },
      "filename": "plan-upgrades"
    }'
  ```

  ```typescript Node.js theme={null}
  // Server side — both calls run server-to-server; only the key differs.
  const PUBLIC = process.env.ORBIT_PUBLIC_KEY!; // dv_test_pk_…

  await fetch("https://api.orbit.devotel.io/sdk/track", {
    method: "POST",
    headers: {
      "X-API-Key": PUBLIC,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      email: "jane@example.com",
      event: "plan_upgraded",
      properties: { plan: "gold" },
    }),
  });

  const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY! });
  await orbit.request("POST", "/segments/export.csv", {
    body: {
      filters: {
        op: "AND",
        conditions: [{ field: "event:plan_upgraded", op: "occurred", value: null }],
      },
      filename: "plan-upgrades",
    },
  });
  ```

  ```python Python theme={null}
  import requests, os

  # 1. Public key — the same call the browser SDK makes.
  requests.post(
      "https://api.orbit.devotel.io/sdk/track",
      headers={"X-API-Key": os.environ["ORBIT_PUBLIC_KEY"]},
      json={
          "email": "jane@example.com",
          "event": "plan_upgraded",
          "properties": {"plan": "gold"},
      },
  ).raise_for_status()

  # 2. Secret key — the export endpoint returns CSV, not the JSON envelope.
  resp = requests.post(
      "https://api.orbit.devotel.io/api/v1/segments/export.csv",
      headers={"X-API-Key": os.environ["ORBIT_API_KEY"]},
      json={
          "filters": {
              "op": "AND",
              "conditions": [
                  {"field": "event:plan_upgraded", "op": "occurred", "value": None}
              ],
          },
          "filename": "plan-upgrades",
      },
  )
  with open("plan-upgrades.csv", "wb") as fh:
      fh.write(resp.content)
  ```
</CodeGroup>

The CSV response bypasses the `{ data, meta }` envelope — the body is the
file. Event ingestion is versioned separately from the REST API
([SDK API](/api-reference/sdk)); the export's filter grammar is on the
[Segments API](/api-reference/segments). In a browser, use the
[`@devotel-orbit/web`](/sdks/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](/api-reference/files) page.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Upload once → keep data.id and the signed URL.
  UPLOAD=$(curl -s -X POST https://api.orbit.devotel.io/api/v1/files/upload \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -F "file=@./promo.jpg" \
    -F "purpose=mms_attachment")

  URL=$(echo "$UPLOAD" | python3 -c 'import sys, json; print(json.load(sys.stdin)["data"]["url"])')

  # 2. Send the MMS referencing the uploaded media URL.
  curl -s -X POST https://api.orbit.devotel.io/api/v1/messages/sms \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d "{
      \"to\": \"+14155552671\",
      \"channel\": \"mms\",
      \"body\": \"This week's promo\",
      \"media_urls\": [\"$URL\"]
    }"
  ```

  ```typescript Node.js theme={null}
  import FormData from "form-data";
  import fs from "node:fs";

  const form = new FormData();
  form.append("file", fs.createReadStream("./promo.jpg"));
  form.append("purpose", "mms_attachment");

  const upload = await orbit.request("POST", "/files/upload", {
    body: form.getBuffer(),
    headers: form.getHeaders(),
  });
  const url = (upload.data as { url: string }).url;

  await orbit.messages.sendSms({
    to: "+14155552671",
    channel: "mms",
    body: "This week's promo",
    media_urls: [url],
  });
  ```

  ```python Python theme={null}
  import requests, os

  headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
  base = "https://api.orbit.devotel.io/api/v1"

  with open("./promo.jpg", "rb") as fh:
      upload = requests.post(
          f"{base}/files/upload",
          headers=headers,
          files={"file": fh},
          data={"purpose": "mms_attachment"},
      ).json()["data"]

  requests.post(
      f"{base}/messages/sms",
      headers=headers,
      json={
          "to": "+14155552671",
          "channel": "mms",
          "body": "This week's promo",
          "media_urls": [upload["url"]],
      },
  ).raise_for_status()
  ```
</CodeGroup>

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 `PUT`s are full-set replaces.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Create the child organization.
  CREATE=$(curl -s -X POST https://api.orbit.devotel.io/api/v1/subaccounts \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "name": "Rahal Industries", "plan": "growth" }')
  ID=$(echo "$CREATE" | python3 -c 'import sys, json; print(json.load(sys.stdin)["data"]["id"])')

  # 2. Flat reseller margin + aggregate monthly spend cap.
  curl -X PUT "https://api.orbit.devotel.io/api/v1/subaccounts/$ID/pricing" \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "reseller_margin_pct": 25, "monthly_spend_cap_cents": 5000000 }'

  # 3. Draft a named rate card in the parent library (full-set replace).
  curl -X PUT https://api.orbit.devotel.io/api/v1/subaccounts/rate-cards \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "cards": [
        { "id": "rc_us_voice_25", "name": "US voice 20% + SMS US 25%",
          "lane_cells": ["voice:US:20"], "deck_cells": ["sms:US:25"] }
      ]
    }'

  # 4. Bind the card to this child.
  curl -X PUT "https://api.orbit.devotel.io/api/v1/subaccounts/$ID/assigned-rate-card" \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "assigned_rate_card_id": "rc_us_voice_25" }'

  # 5. Per-channel budgets so the child can cap SMS hard while voice alerts.
  curl -X PUT "https://api.orbit.devotel.io/api/v1/subaccounts/$ID/channel-budgets" \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "budgets": [
        { "channel": "sms", "threshold_cents": 100000, "period": "monthly", "action": "block" },
        { "channel": "voice", "threshold_cents": 60000, "period": "monthly", "action": "alert" }
      ]
    }'
  ```

  ```typescript Node.js theme={null}
  const created = await orbit.request("POST", "/subaccounts", {
    body: { name: "Rahal Industries", plan: "growth" },
  });
  const id = (created.data as { id: string }).id;

  await orbit.request("PUT", `/subaccounts/${id}/pricing`, {
    body: { reseller_margin_pct: 25, monthly_spend_cap_cents: 5000000 },
  });

  await orbit.request("PUT", "/subaccounts/rate-cards", {
    body: {
      cards: [
        {
          id: "rc_us_voice_25",
          name: "US voice 20% + SMS US 25%",
          lane_cells: ["voice:US:20"],
          deck_cells: ["sms:US:25"],
        },
      ],
    },
  });

  await orbit.request("PUT", `/subaccounts/${id}/assigned-rate-card`, {
    body: { assigned_rate_card_id: "rc_us_voice_25" },
  });

  await orbit.request("PUT", `/subaccounts/${id}/channel-budgets`, {
    body: {
      budgets: [
        { channel: "sms", threshold_cents: 100000, period: "monthly", action: "block" },
        { channel: "voice", threshold_cents: 60000, period: "monthly", action: "alert" },
      ],
    },
  });
  ```

  ```python Python theme={null}
  import requests, os

  headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
  base = "https://api.orbit.devotel.io/api/v1"

  child = requests.post(
      f"{base}/subaccounts",
      headers=headers,
      json={"name": "Rahal Industries", "plan": "growth"},
  ).json()["data"]
  sub_id = child["id"]

  requests.put(
      f"{base}/subaccounts/{sub_id}/pricing",
      headers=headers,
      json={"reseller_margin_pct": 25, "monthly_spend_cap_cents": 5000000},
  )

  requests.put(
      f"{base}/subaccounts/rate-cards",
      headers=headers,
      json={
          "cards": [
              {
                  "id": "rc_us_voice_25",
                  "name": "US voice 20% + SMS US 25%",
                  "lane_cells": ["voice:US:20"],
                  "deck_cells": ["sms:US:25"],
              }
          ]
      },
  )

  requests.put(
      f"{base}/subaccounts/{sub_id}/assigned-rate-card",
      headers=headers,
      json={"assigned_rate_card_id": "rc_us_voice_25"},
  )

  requests.put(
      f"{base}/subaccounts/{sub_id}/channel-budgets",
      headers=headers,
      json={
          "budgets": [
              {"channel": "sms", "threshold_cents": 100000, "period": "monthly", "action": "block"},
              {"channel": "voice", "threshold_cents": 60000, "period": "monthly", "action": "alert"},
          ]
      },
  ).raise_for_status()
  ```
</CodeGroup>

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](/api-reference/endpoints/subaccounts).

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

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.orbit.devotel.io/api/v1/subaccounts/org_sub_rahal_00x9/usage/live?channel=sms" \
    -H "X-API-Key: dv_test_sk_YOUR_KEY"
  ```

  ```typescript Node.js theme={null}
  const res = await orbit.request(
    "GET",
    "/subaccounts/org_sub_rahal_00x9/usage/live",
    { query: { channel: "sms" } },
  );
  const sms = (res.data as {
    channels: {
      channel: string;
      spent_cents: number;
      live_counter_cents: number;
      breached: boolean;
    }[];
  }).channels.find((c) => c.channel === "sms");

  console.log(
    SMS spend so far this period:
      ${sms!.live_counter_cents / 100} USD — breached: ${sms!.breached},
  );
  ```

  ```python Python theme={null}
  import requests, os

  res = requests.get(
      "https://api.orbit.devotel.io/api/v1/subaccounts/org_sub_rahal_00x9/usage/live",
      headers={"X-API-Key": os.environ["ORBIT_API_KEY"]},
      params={"channel": "sms"},  # optional; omit for one row per channel
  ).json()["data"]

  (c,) = [c for c in res["channels"] if c["channel"] == "sms"]
  print(
      f"SMS spend this period: {c['live_counter_cents'] / 100:.2f} {res['currency']} "
      f"(statement: {c['spent_cents'] / 100:.2f}) — breached: {c['breached']}"
  )
  ```
</CodeGroup>

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](/api-reference/log-tail).

<CodeGroup>
  ```bash cURL theme={null}
  # Server-side you can send the API key as a header; the ?token= form
  # exists for EventSource, which can't set headers.
  curl -N https://api.orbit.devotel.io/api/v1/logs/tail \
    -H "X-API-Key: dv_test_sk_YOUR_KEY"
  ```

  ```typescript Node.js theme={null}
  const resp = await fetch("https://api.orbit.devotel.io/api/v1/logs/tail", {
    headers: { "X-API-Key": process.env.ORBIT_API_KEY! },
  });
  const reader = resp.body!.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    // SSE frames are delimited by a blank line.
    let idx;
    while ((idx = buffer.indexOf("\n\n")) !== -1) {
      const frame = buffer.slice(0, idx);
      buffer = buffer.slice(idx + 2);
      const dataLine = frame
        .split("\n")
        .find((l) => l.startsWith("data: "));
      if (!dataLine) continue;
      const row = JSON.parse(dataLine.slice(6));
      console.log(row.method, row.path_pattern, row.status_code, row.duration_ms);
    }
  }
  ```

  ```python Python theme={null}
  import requests, os

  with requests.get(
      "https://api.orbit.devotel.io/api/v1/logs/tail",
      headers={"X-API-Key": os.environ["ORBIT_API_KEY"]},
      stream=True,
  ) as resp:
      buffer = ""
      for chunk in resp.iter_content(chunk_size=None):
          buffer += chunk.decode()
          while "\n\n" in buffer:
              frame, buffer = buffer.split("\n\n", 1)
              for line in frame.splitlines():
                  if line.startswith("data: "):
                      print(line[6:], flush=True)
  ```
</CodeGroup>

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.

```json 200 success theme={null}
{
  "data": { "id": "msg_9x2k" },
  "meta": { "request_id": "req_4c9d1e", "timestamp": "2026-10-05T14:03:12Z" }
}
```

**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`.

```json 422 validation failure theme={null}
{
  "data": {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "The request body failed schema validation."
    },
    "errors": [
      { "field": "to", "issue": "not a valid E.164 phone number" },
      { "field": "body", "issue": "required" }
    ]
  },
  "meta": { "request_id": "req_7bd012", "timestamp": "2026-10-05T14:03:12Z" }
}
```

**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](/reference/error-codes);
the [error-handling examples](/guides/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](/api-reference/sdk-sample-writing).

## See also

* [API Overview](/api-reference/overview) — base URL, auth, pagination,
  and the envelope this cookbook builds on
* [REST API recipes](/guides/api-recipes) — the task-by-task cookbook with
  all six languages on every flow
* [Per-language recipes](/guides/per-language-recipes) — the same
  four-way loop in Python / Go / Ruby / PHP / Java / C#
* [Idempotent requests](/guides/idempotent-requests) — the full
  Idempotency-Key contract recipe 2 leans on
* [Error handling examples](/guides/error-handling-examples) — typed error
  handling per language


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.