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

# Organization

## Worked organization samples

The endpoint list below documents each operation's parameters; this overlay
walks the org profile the way a dashboard shell or account-integration
actually uses it: **read the org → branch on plan and wallet balance →
update name and settings → handle the auth failures**. Every request uses
your API key (`X-API-Key`) against `https://api.orbit.devotel.io`. The
base plan field, budget, and settings keys you see here are the public
contract — examples below tab cURL and Node.js (TypeScript).

Every response follows the same envelope — `data` plus a `meta` block
carrying `request_id` and `timestamp`. Quote `meta.request_id` when you
report a failure; support can trace the request end-to-end from it.

### 1. Read the org profile

`GET /api/v1/organization` returns the authenticated organization's record:
name, slug, plan, wallet balance, team-member and per-second rate limits,
logo, branding, general settings, and whether it is a subaccount. This is
the call the dashboard shell makes on every page load, so it is also the
right "ping" for a server-side client — a 200 here confirms the key is
valid, scoped to a live org, and outside its rate limit.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.orbit.devotel.io/api/v1/organization" \
    -H "X-API-Key: dv_live_sk_your_key_here"
  ```

  ```typescript Node.js theme={null}
  const res = await fetch("https://api.orbit.devotel.io/api/v1/organization", {
    headers: {
      "X-API-Key": process.env.ORBIT_API_KEY!,
    },
  });
  console.log(await res.json());
  ```
</CodeGroup>

```json 200 theme={null}
{
  "data": {
    "id": "org_01J9Z8ABCDEF",
    "name": "Acme Inc",
    "slug": "acme",
    "plan": "growth",
    "tenantId": "11111111-2222-3333-4444-555555555555",
    "balance": 250000,
    "maxTeamMembers": 25,
    "rateLimitPerSecond": 50,
    "logoUrl": "https://cdn.example.com/acme/logo.svg",
    "branding": {
      "logo_url": "https://cdn.example.com/acme/logo.svg",
      "primary_color": "#4f46e5",
      "accent_color": "#22d3ee",
      "dashboard_name": "Acme Comms",
      "support_email": "support@acme.example"
    },
    "settings": {
      "timezone": "America/New_York",
      "locale": "en-US",
      "dateFormat": "MM/dd/yyyy"
    },
    "billing_currency": "USD",
    "isSubaccount": false,
    "monthlyBudgetCents": 1000000,
    "createdAt": "2026-01-02T09:00:00.000Z",
    "updatedAt": "2026-08-13T12:00:00.000Z"
  },
  "meta": {
    "request_id": "req_01JA2B3CDE",
    "timestamp": "2026-09-09T12:00:00.000Z"
  }
}
```

Field notes a reader will hit on first integration:

* `balance` is the wallet balance in USD cents — `250000` is \$2,500.00. It
  is `null` on the brief degraded shell (see below), so branch on that
  before rendering a dollar figure.
* `billing_currency` is resolved from `settings.wallet_currency` and falls
  back to `USD`; format every cost figure with it so displayed amounts
  match the currency billing charges against.
* `monthlyBudgetCents` is the org's monthly budget ceiling — the
  denominator spend-percent billing alerts divide by. `null` means no
  budget is set and spend-percent alerts never fire.
* `isSubaccount` is derived server-side from the org's parent link. A
  `true` value gates reseller-only affordances — a subaccount cannot
  create nested subaccounts.
* A transient database blip returns a degraded shell — same 200, `id` +
  `plan` + `tenantId` filled from the auth context, the remaining
  DB-sourced fields `null`. Handle the nulls rather than erroring; the
  next poll self-corrects.

### 2. Branch on plan and balance

A typical shell reads the org once, then decides what to render: a
low-balance banner, a plan-gated feature, a budget progress bar. Both
edge cases matter — the balance can be `null` (degraded shell) and the
budget can be `null` (never configured).

```typescript Node.js theme={null}
type OrganizationResponse = {
  data: {
    plan: string | null;
    balance: number | null; // USD cents; null on the degraded shell
    monthlyBudgetCents: number | null;
    isSubaccount: boolean;
    billing_currency: string;
  };
};

const res = await fetch("https://api.orbit.devotel.io/api/v1/organization", {
  headers: { "X-API-Key": process.env.ORBIT_API_KEY! },
});
if (!res.ok) {
  throw new Error(`Org read failed: ${res.status}`);
}
const { data: org } = (await res.json()) as OrganizationResponse;

// Balance banner — skip when the read is degraded (null), never treat a
// null balance as $0.00.
const LOW_BALANCE_CENTS = 5_000; // $50.00
if (org.balance !== null && org.balance < LOW_BALANCE_CENTS) {
  showLowBalanceBanner(org.balance / 100, org.billing_currency);
}

// Budget progress — only when a budget is configured.
if (org.monthlyBudgetCents !== null) {
  renderBudgetMeter(org.monthlyBudgetCents);
}

// Plan gate.
const canUseFlows = ['growth', 'scale', 'enterprise'].includes(org.plan ?? '');
if (canUseFlows) renderFlowsNav();
```

<Note>
  Wallet semantics per endpoint family (what fires a running balance
  down, what tops it up) live in the [billing
  reference](/api-reference/billing); this page documents the org record
  itself.
</Note>

### 3. Update name and general settings

`PUT /api/v1/organization` updates the org's name, general settings, and
monthly budget. It shares its handler with `PUT /api/v1/settings/general`,
so the same write-boundary guards apply (unsubscribe-redirect URL checks,
escalation-recipient allowlist, IP-allowlist lockout protection) and the
settings cache invalidates immediately. Requires the **owner or admin**
role — a member or developer key gets 403 (see [Errors](#4-errors)).

The settings bag is free-form JSONB; a few keys are load-bearing and are
listed below. Regional display prefs (`timezone`, `locale`, `dateFormat`)
can be sent either as top-level fields or inside `settings` — the
controller folds top-level values into the bag before writing, and lifts
them back to the top level on read.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT "https://api.orbit.devotel.io/api/v1/organization" \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: org-update-2026-09-09-0001" \
    -d '{
      "name": "Acme Incorporated",
      "timezone": "America/New_York",
      "monthly_budget_cents": 1500000,
      "settings": {
        "wallet_currency": "USD",
        "dnc_sync_enabled": true
      }
    }'
  ```

  ```typescript Node.js theme={null}
  const res = await fetch("https://api.orbit.devotel.io/api/v1/organization", {
    method: "PUT",
    headers: {
      "X-API-Key": process.env.ORBIT_API_KEY!,
      "Content-Type": "application/json",
      "Idempotency-Key": "org-update-2026-09-09-0001",
    },
    body: JSON.stringify({
      name: "Acme Incorporated",
      timezone: "America/New_York",
      monthly_budget_cents: 1_500_000,
      settings: {
        wallet_currency: "USD",
        dnc_sync_enabled: true,
      },
    }),
  });
  console.log(await res.json());
  ```
</CodeGroup>

```json 200 theme={null}
{
  "data": {
    "id": "org_01J9Z8ABCDEF",
    "name": "Acme Incorporated",
    "settings": {
      "timezone": "America/New_York",
      "locale": "en-US",
      "dateFormat": "MM/dd/yyyy",
      "wallet_currency": "USD",
      "dnc_sync_enabled": true
    },
    "monthly_budget_cents": 1500000
  },
  "meta": {
    "request_id": "req_01JA2B3CDF",
    "timestamp": "2026-09-09T12:00:01.000Z"
  }
}
```

Semantics to code against:

* **`monthly_budget_cents`** — integer budget ceiling in cents; `0` is
  rejected (a zero divisor would make every spend-percent alert fire).
  Send `null` to clear the budget; omit the field to leave it unchanged.
* **`name`** — 1–200 characters; the rename echoes to the org record
  provider-side, so the dashboard header updates without a separate call.
* **`settings.dnc_sync_enabled`** — must be a literal boolean. A string
  `"true"` or numeric `1` is rejected at validation — and even if it
  slipped through, the DNC pre-flight endpoint gates on the literal
  boolean and would keep returning 403.
* **`Idempotency-Key`** — pass a stable client-generated value to dedupe
  retries on transient timeouts; the same key replays the original
  response for 24 h on 2xx. `409` means the same key is still in flight.

**Sandbox.** The update is a control-plane write — it changes only your
org's own record, triggers no provider delivery, and deducts no credits,
so it is safe to exercise from any environment. The `X-Test-Mode` header
matters for *session*-authenticated calls (browser clients): set
`X-Test-Mode: true` to route the call through the test-mode pipeline and
see `meta.test_mode: true` on the response. For server-to-server keys it
is ignored — use a `dv_test_sk_*` test-prefixed key, which unconditionally
enables sandbox behaviour, instead of toggling the header on a
`dv_live_sk_*` key. The read and the update both honour the header for
session clients, so a full read→write round trip in test mode touches no
live state.

### 4. Errors

Errors follow the `{ error, meta }` envelope. The two failures every
integration must handle:

**401 — the API key is invalid or has been revoked.** This fires for a
malformed key, a key that was rotated or deleted under **Developer → API
keys**, and a key whose owning org was removed. Do not retry a 401 —
rotate from a known-good credential before re-sending anything.

```json 401 theme={null}
{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The provided API key is invalid or has been revoked.",
    "status": 401
  },
  "meta": {
    "request_id": "req_01JA2B3CDG",
    "timestamp": "2026-09-09T12:00:02.000Z"
  }
}
```

**403 — role too low for the write.** `PUT /api/v1/organization` requires
the owner or admin role. A member or developer-role key (and a session
for a user without the role) cannot update the org — reads still work
with any role:

```json 403 theme={null}
{
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "This action requires one of: owner, admin",
    "status": 403
  },
  "meta": {
    "request_id": "req_01JA2B3CDH",
    "timestamp": "2026-09-09T12:00:03.000Z"
  }
}
```

A validation failure (a `0` budget, a non-boolean `dnc_sync_enabled`, an
unsafe unsubscribe or sandbox webhook URL) returns `422 VALIDATION_ERROR`
with a `details` object pointing at the rejected field, the same shape the
rest of this page's writes follow.
