ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Organizations

Invites

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

MethodPathRequired roleDescription
GET/v1/invitesadmin+List pending invites
POST/v1/invitesadmin+Send an invite
DELETE/v1/invites/{invite_id}admin+Revoke a pending invite
GET/v1/invites/{token}authenticatedLook up an invite by token
POST/v1/invites/{token}/acceptauthenticatedAccept an invite
POST/v1/invites/{token}/declineauthenticatedDecline an invite
POST/v1/invites/{token}/request-accessauthenticatedAsk the inviter to re-issue the invite to your email

Invite object (admin list/create)

The list and create endpoints return this object. For the by-token lookup see the token lookup response.

{
  "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-..."
}
FieldTypeDescription
idintegerInternal invite id (used by the revoke endpoint)
emailstringInvitee email
rolestringRole granted on acceptance: admin or member
statusstringpending, accepted, or declined
invited_by_emailstringEmail of the admin/owner who sent the invite
created_attimestampISO 8601 UTC
expires_attimestamp7 days after created_at by default
tokenUUIDThe 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

cURL
curl https://api.thunderphone.com/v1/invites \
  -H "Authorization: Token YOUR_SESSION_TOKEN" \
  -H "X-ThunderPhone-Org: 42"
Python
invites = requests.get(
    "https://api.thunderphone.com/v1/invites",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN", "X-ThunderPhone-Org": "42"},
).json()

Returns a JSON array of Invite objects in pending state, sorted by created_at descending.


Send an invite

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
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()
FieldTypeRequiredDescription
emailstringyesInvitee email
rolestringyesadmin or member

Returns 201 Created with the Invite object. The invitee receives an email containing a link of the form https://app.thunderphone.com/invite/{token}.

StatusCondition
400Email already has a pending invite for this org, or is already a member
403Caller is not an admin/owner

Revoke an invite

cURL
curl -X DELETE https://api.thunderphone.com/v1/invites/18 \
  -H "Authorization: Token YOUR_SESSION_TOKEN" \
  -H "X-ThunderPhone-Org: 42"
Python
requests.delete(
    f"https://api.thunderphone.com/v1/invites/{invite_id}",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN", "X-ThunderPhone-Org": "42"},
)

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.

cURL
curl https://api.thunderphone.com/v1/invites/a1b2c3d4-...
Python
invite = requests.get(
    f"https://api.thunderphone.com/v1/invites/{token}",
).json()

Response:

{
  "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"
}
StatusCondition
200Pending, not expired — response includes org_name for UI display
404Token not found
410Token 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.

cURL
curl -X POST https://api.thunderphone.com/v1/invites/a1b2c3d4-.../accept \
  -H "Authorization: Token YOUR_SESSION_TOKEN"
Python
result = requests.post(
    f"https://api.thunderphone.com/v1/invites/{token}/accept",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN"},
).json()

Returns 200 OK:

{
  "detail": "Invite accepted.",
  "org_id": 42
}
StatusCondition
200Invite accepted; caller is now a member
403Caller is signed in as a user whose email does not match the invite, or the request used an API key
404Token not found
410Token exists but is already accepted / declined, or has expired

Decline an invite

cURL
curl -X POST https://api.thunderphone.com/v1/invites/a1b2c3d4-.../decline \
  -H "Authorization: Token YOUR_SESSION_TOKEN"
Python
requests.post(
    f"https://api.thunderphone.com/v1/invites/{token}/decline",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN"},
)

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.

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

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

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