---
title: "Invites"
description: "Invite users into your ThunderPhone organization by email, choose the role each invite carries, revoke pending invites, and accept or decline the ones you receive."
---

Invites are the mechanism for bringing new members into an organization.
The admin-side endpoints create/list/revoke invites scoped to your org.
The token-side endpoints let an invitee look up, accept, or decline an
invite by its secret token — those endpoints don't require you to be a
member of the target org (the token itself identifies it).

## Endpoints

| Method | Path | Required role | Description |
|--------|------|---------------|-------------|
| `GET` | `/v1/invites` | `admin+` | List pending invites |
| `POST` | `/v1/invites` | `admin+` | Send an invite |
| `DELETE` | `/v1/invites/{invite_id}` | `admin+` | Revoke a pending invite |
| `GET` | `/v1/invites/{token}` | authenticated | Look up an invite by token |
| `POST` | `/v1/invites/{token}/accept` | authenticated | Accept an invite |
| `POST` | `/v1/invites/{token}/decline` | authenticated | Decline an invite |
| `POST` | `/v1/invites/{token}/request-access` | authenticated | Ask the inviter to re-issue the invite to your email |

<Warning>
  Invites decide who joins your organization, so every endpoint on this
  page except the token lookup refuses `sk_live_` API keys with `403`.
  Admin-side calls need a signed-in session (`Authorization: Token
  <session-token>` plus `X-ThunderPhone-Org`); the token-side calls act
  for the signed-in invitee and need only their session token. See
  [What API keys can't do](/api-reference/introduction#what-api-keys-cant-do).
</Warning>

## Invite object (admin list/create)

The list and create endpoints return this object. For the by-token
lookup see [the token lookup response](#look-up-by-token).

```json
{
  "id": 18,
  "email": "new-teammate@example.com",
  "role": "member",
  "status": "pending",
  "invited_by_email": "owner@example.com",
  "created_at": "2026-04-20T18:24:10.113Z",
  "expires_at": "2026-04-27T18:24:10.113Z",
  "token": "a1b2c3d4-..."
}
```

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Internal invite id (used by the revoke endpoint) |
| `email` | string | Invitee email |
| `role` | string | Role granted on acceptance: `admin` or `member` |
| `status` | string | `pending`, `accepted`, or `declined` |
| `invited_by_email` | string | Email of the admin/owner who sent the invite |
| `created_at` | timestamp | ISO 8601 UTC |
| `expires_at` | timestamp | 7 days after `created_at` by default |
| `token` | UUID | The invite's secret join token, so an admin can copy the invite link and hand it over directly. Treat it like the invite email itself |

---

## List pending invites

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

```python Python
invites = requests.get(
    "https://api.thunderphone.com/v1/invites",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN", "X-ThunderPhone-Org": "42"},
).json()
```
</CodeGroup>

Returns a JSON array of [Invite objects](#invite-object-admin-listcreate)
in `pending` state, sorted by `created_at` descending.

---

## Send an invite

<CodeGroup>
```bash cURL
curl -X POST https://api.thunderphone.com/v1/invites \
  -H "Authorization: Token YOUR_SESSION_TOKEN" \
  -H "X-ThunderPhone-Org: 42" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "new-teammate@example.com",
    "role": "member"
  }'
```

```python Python
invite = requests.post(
    "https://api.thunderphone.com/v1/invites",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN", "X-ThunderPhone-Org": "42"},
    json={"email": "new-teammate@example.com", "role": "member"},
).json()
```
</CodeGroup>

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | string | yes | Invitee email |
| `role` | string | yes | `admin` or `member` |

Returns `201 Created` with the [Invite object](#invite-object-admin-listcreate).
The invitee receives an email containing a link of the form
`https://app.thunderphone.com/invite/{token}`.

| Status | Condition |
|--------|-----------|
| `400` | Email already has a pending invite for this org, or is already a member |
| `403` | Caller is not an admin/owner |

---

## Revoke an invite

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

```python Python
requests.delete(
    f"https://api.thunderphone.com/v1/invites/{invite_id}",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN", "X-ThunderPhone-Org": "42"},
)
```
</CodeGroup>

Returns `204 No Content`. Only invites with status `pending` can be
revoked.

---

## Look up by token

Public lookup used by the dashboard's invite-landing page. **This
endpoint is unauthenticated** — the token itself is the credential.
Used to render the "You've been invited to join *Acme Ops*" UI.

<CodeGroup>
```bash cURL
curl https://api.thunderphone.com/v1/invites/a1b2c3d4-...
```

```python Python
invite = requests.get(
    f"https://api.thunderphone.com/v1/invites/{token}",
).json()
```
</CodeGroup>

Response:

```json
{
  "id": 18,
  "org_name": "Acme Ops",
  "email": "new-teammate@example.com",
  "role": "member",
  "status": "pending",
  "invited_by_email": "owner@example.com",
  "created_at": "2026-04-20T18:24:10.113Z",
  "expires_at": "2026-04-27T18:24:10.113Z"
}
```

| Status | Condition |
|--------|-----------|
| `200` | Pending, not expired — response includes `org_name` for UI display |
| `404` | Token not found |
| `410` | Token exists but is already accepted / declined, or has expired |

---

## Accept an invite

The caller must be authenticated; their user is added to the target org
with the role specified on the invite.

<CodeGroup>
```bash cURL
curl -X POST https://api.thunderphone.com/v1/invites/a1b2c3d4-.../accept \
  -H "Authorization: Token YOUR_SESSION_TOKEN"
```

```python Python
result = requests.post(
    f"https://api.thunderphone.com/v1/invites/{token}/accept",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN"},
).json()
```
</CodeGroup>

Returns `200 OK`:

```json
{
  "detail": "Invite accepted.",
  "org_id": 42
}
```

| Status | Condition |
|--------|-----------|
| `200` | Invite accepted; caller is now a member |
| `403` | Caller is signed in as a user whose email does not match the invite, or the request used an API key |
| `404` | Token not found |
| `410` | Token exists but is already accepted / declined, or has expired |

---

## Decline an invite

<CodeGroup>
```bash cURL
curl -X POST https://api.thunderphone.com/v1/invites/a1b2c3d4-.../decline \
  -H "Authorization: Token YOUR_SESSION_TOKEN"
```

```python Python
requests.post(
    f"https://api.thunderphone.com/v1/invites/{token}/decline",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN"},
)
```
</CodeGroup>

Returns `204 No Content`.

---

## Request access to an invite

For a signed-in user who received an invite link addressed to a
**different** email (e.g. a forwarded invite): emails the original
inviter asking them to grant access to the caller's account instead.
No membership is created — the inviter decides.

<CodeGroup>
```bash cURL
curl -X POST https://api.thunderphone.com/v1/invites/a1b2c3d4-.../request-access \
  -H "Authorization: Token YOUR_SESSION_TOKEN"
```
</CodeGroup>

No request body. Returns `204 No Content` when the request email was
sent.

| Status | Condition |
|--------|-----------|
| `204` | Access request sent to the inviter |
| `400` | The invite is already addressed to your email (just accept it), or it has no sender to approve access |
| `403` | The request used an API key |
| `404` | Token not found |
| `410` | Invite already accepted / declined, or expired |
| `429` | Rate limited — max 3 access requests per hour per user |
| `503` | The request email could not be sent |

---

## Related

<CardGroup cols={2}>
  <Card title="Members" icon="users" href="/api-reference/members">
    Manage members and roles after they join.
  </Card>
  <Card title="Organizations" icon="building" href="/api-reference/organizations">
    Manage the org resource itself.
  </Card>
</CardGroup>
