---
title: "Organizations"
description: "List, create, update, and delete the organizations that own your agents, phone numbers, and billing, including the legacy single-URL webhook configuration."
---

Organizations are the top-level container for every ThunderPhone
resource — agents, phone numbers, calls, billing, and keys. An API key is
bound to exactly one organization; the endpoints on this page are the
handful that let you enumerate or manage the org resource itself.

The dashboard lets a single user belong to multiple organizations and
switch between them. The API treats your key's bound org as implicit on
every other page in this reference — you do **not** need an org id in
URLs for resource operations. See [Authentication](/api-reference/introduction)
for the full story.

## Endpoints

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/v1/orgs/` | List organizations the caller belongs to |
| `POST` | `/v1/orgs/` | Create an organization |
| `GET` | `/v1/orgs/{id}/` | Retrieve a single organization |
| `PATCH` | `/v1/orgs/{id}/` | Update the organization's name or reporting settings |
| `DELETE` | `/v1/orgs/{id}/` | Delete the organization |
| `GET` | `/v1/webhook` | Get the single-URL webhook (see note below) |
| `PUT` / `PATCH` | `/v1/webhook` | Set the single-URL webhook |

## Organization object

```json
{
  "id": 42,
  "name": "Acme Operations",
  "role": "owner",
  "timezone": "America/Los_Angeles",
  "business_hours": {
    "mon": [{"start": "09:00", "end": "17:00"}]
  },
  "excluded_numbers": ["+14155550199"],
  "max_concurrent_calls": 20,
  "min_seconds_between_calls_to_same_number": 10,
  "new_agent_max_call_duration_seconds": 3600,
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:24:10.113Z"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Server-assigned organization id |
| `name` | string | Display name |
| `role` | string \| null | On list responses, the caller's role in this org (`owner`, `admin`, `member`) |
| `timezone` | string | IANA time zone (up to 64 characters) for call-report business hours. `""` means UTC |
| `business_hours` | object | Weekly schedule keyed by `mon` … `sun`; each day holds at most four `{start, end}` windows in 24-hour `HH:MM`, end later than start. `{}` means no after-hours split |
| `excluded_numbers` | array | The organization's own test numbers (up to 50, E.164). Calls from or to them are left out of report counts and Client Portals |
| `max_concurrent_calls` | integer | Read-only. Most calls this organization can have in progress at once, across inbound, outbound, web and test calls. Default 20. See [Call limits](/api-reference/introduction#call-limits) |
| `min_seconds_between_calls_to_same_number` | integer | Read-only. Minimum seconds between outbound calls from this organization to the same number. Default 10; `0` means no spacing. See [Call limits](/api-reference/introduction#call-limits) |
| `new_agent_max_call_duration_seconds` | integer \| null | Read-only. The `max_call_duration_seconds` a new agent starts with when you create it without one: 3600 (one hour) unless ThunderPhone set another value for your organization; `null` means new agents have no per-agent limit |
| `created_at` | timestamp | ISO 8601 in UTC |
| `updated_at` | timestamp | ISO 8601 in UTC |

---

## List organizations

Returns every organization the caller is a member of.

<CodeGroup>
```bash cURL
curl https://api.thunderphone.com/v1/orgs/ \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
```

```python Python
resp = requests.get(
    "https://api.thunderphone.com/v1/orgs/",
    headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
)
orgs = resp.json()
```

```javascript Node.js
const orgs = await fetch("https://api.thunderphone.com/v1/orgs/", {
  headers: { Authorization: `Bearer ${process.env.THUNDERPHONE_API_KEY}` },
}).then((r) => r.json());
```
</CodeGroup>

Response is a JSON array of [Organization objects](#organization-object).

<Note>
  An API key is bound to exactly one organization, so this list almost
  always has a single entry when authenticated with `sk_live_`. The
  dashboard flow uses personal tokens and can return multiple orgs.
</Note>

---

## Create organization

<CodeGroup>
```bash cURL
curl -X POST https://api.thunderphone.com/v1/orgs/ \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Operations"}'
```

```python Python
resp = requests.post(
    "https://api.thunderphone.com/v1/orgs/",
    headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
    json={"name": "Acme Operations"},
)
org = resp.json()
```
</CodeGroup>

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | yes | 1–120 characters |

Returns `201 Created` with the new [Organization object](#organization-object).
The creator becomes the `owner` member automatically.

---

## Get organization

<CodeGroup>
```bash cURL
curl https://api.thunderphone.com/v1/orgs/42/ \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
```

```python Python
resp = requests.get(
    f"https://api.thunderphone.com/v1/orgs/{org_id}/",
    headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
)
```
</CodeGroup>

Returns `200 OK` with an [Organization object](#organization-object), or `404` if
the caller is not a member of the requested org.

---

## Update organization

<CodeGroup>
```bash cURL
curl -X PATCH https://api.thunderphone.com/v1/orgs/42/ \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Ops"}'
```

```python Python
resp = requests.patch(
    f"https://api.thunderphone.com/v1/orgs/{org_id}/",
    headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
    json={"name": "Acme Ops"},
)
```
</CodeGroup>

Returns `200 OK` with the updated [Organization object](#organization-object).
Owners and admins can send `name`, `timezone`, `business_hours` and
`excluded_numbers`; see [Organization settings](/guides/organization-settings#configure-reporting-through-the-api)
for an example.

---

## Delete organization

<Warning>
  Destructive and irreversible. Only allowed for the `owner`, from a
  signed-in session: an `sk_live_` API key gets `403` here (see
  [What API keys can't do](/api-reference/introduction#what-api-keys-cant-do)).
  You must remove all other members first. A user cannot delete their
  only organization.
</Warning>

<CodeGroup>
```bash cURL
curl -X DELETE https://api.thunderphone.com/v1/orgs/42/ \
  -H "Authorization: Token YOUR_SESSION_TOKEN"
```

```python Python
resp = requests.delete(
    f"https://api.thunderphone.com/v1/orgs/{org_id}/",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN"},
)
```
</CodeGroup>

Returns `204 No Content`.

| Status | Condition |
|--------|-----------|
| `204` | Deleted |
| `400` | Other members still present, or this is the caller's only org |
| `403` | Caller is not the owner, or the request used an API key |

---

## Legacy single-URL webhook

<Info>
  This is the **original** webhook config: one URL receives every event
  type (`call.incoming`, `call.complete`, …). It is still supported for
  existing integrations.   New integrations should use the
  [Webhook Endpoints](/webhooks/endpoints) system (multiple URLs,
  per-endpoint secrets, event filtering) — see
  [Webhooks overview](/webhooks/overview).
</Info>

### Get webhook config

<CodeGroup>
```bash cURL
curl https://api.thunderphone.com/v1/webhook \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
```

```python Python
resp = requests.get(
    "https://api.thunderphone.com/v1/webhook",
    headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
)
```
</CodeGroup>

Returns `200 OK` with the webhook row, or `404` if the org has not
configured a webhook yet.

```json
{
  "url": "https://example.com/thunderphone-webhook",
  "secret": "whsec_abc123...",
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:24:10.113Z"
}
```

### Set webhook URL

<CodeGroup>
```bash cURL
curl -X PUT https://api.thunderphone.com/v1/webhook \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/thunderphone-webhook"}'
```

```python Python
resp = requests.put(
    "https://api.thunderphone.com/v1/webhook",
    headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
    json={"url": "https://example.com/thunderphone-webhook"},
)
```
</CodeGroup>

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `url` | string | yes | HTTPS URL; `http://` is allowed only for `localhost` during local dev |

The `secret` is generated server-side and rotated only when you
explicitly call `PUT` again. Store it in your secret manager and use it
to verify the `X-ThunderPhone-Signature-V2` signature on inbound requests — see
[Webhooks overview](/webhooks/overview#signature-verification).

---

## Related

<CardGroup cols={2}>
  <Card title="Members" icon="users" href="/api-reference/members">
    Manage roles and memberships within an organization.
  </Card>
  <Card title="Invites" icon="envelope" href="/api-reference/invites">
    Invite users to join your organization.
  </Card>
  <Card title="Webhooks" icon="bolt" href="/webhooks/overview">
    Subscribe to real-time events delivered as HTTP POST requests.
  </Card>
  <Card title="API Keys" icon="key" href="/api-reference/developer-api-keys">
    Rotate the `sk_live_` keys your integrations use.
  </Card>
</CardGroup>
