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

Organizations

Client Portals

Create and administer branded client portals over the ThunderPhone API, including agent scope, viewers, logos, custom domains, and DNS verification.

Client portals give external viewers a branded, read-only call-history experience for a selected set of agents. The management API is organization scoped and requires an admin or owner authenticated with an sk_live_ key.

Viewer login, sessions, call browsing, transcripts, and audio use the hosted portal's separate viewer authentication. They are not sk_live_ integration endpoints.

Endpoints

MethodPathDescription
GET/v1/client-portalsList portals.
POST/v1/client-portalsCreate a portal.
GET/v1/client-portals/{portal_id}Retrieve a portal.
PATCH/v1/client-portals/{portal_id}Update details, active state, branding color, or agents.
DELETE/v1/client-portals/{portal_id}Permanently delete a portal.
POST/v1/client-portals/{portal_id}/logoUpload or replace a logo.
DELETE/v1/client-portals/{portal_id}/logoRemove the logo.
GET / POST/v1/client-portals/{portal_id}/viewersList or add viewers.
PATCH / DELETE/v1/client-portals/{portal_id}/viewers/{viewer_id}Activate/deactivate or delete a viewer.
GET / POST/v1/client-portals/{portal_id}/domainsList or add custom domains.
DELETE/v1/client-portals/{portal_id}/domains/{domain_id}Remove a custom domain.
POST/v1/client-portals/{portal_id}/domains/{domain_id}/verifyCheck the ownership TXT record.

Portal object

{
  "id": 18,
  "name": "Acme Dental",
  "slug": "acme-dental",
  "accent_color": "#2e8fff",
  "is_active": true,
  "logo_url": "https://…",
  "agent_ids": [12, 19],
  "agent_count": 2,
  "viewer_count": 3,
  "calls_last_30d": 147,
  "created_at": "2026-07-16T18:24:10.113Z",
  "updated_at": "2026-08-05T09:40:12.551Z"
}
FieldTypeDescription
idintegerPortal id used by management endpoints.
namestringClient-facing name, maximum 255 characters.
slugstringGlobally unique hosted URL slug, maximum 100 characters.
accent_colorstringEmpty for the default, or a full six-digit hex color.
is_activebooleanInactive portals cannot be opened by viewers.
logo_urlstring | nullTemporary or hosted URL for the current logo.
agent_idsarray of integerAgents whose calls are in the portal scope.
agent_count, viewer_countintegerCurrent related-resource counts.
calls_last_30dintegerCalls from assigned agents in the preceding 30 days.
created_at, updated_attimestampISO 8601 timestamps.

List and create portals

curl https://api.thunderphone.com/v1/client-portals \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"

The list is sorted by name, then id.

curl -X POST https://api.thunderphone.com/v1/client-portals \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme Dental","slug":"acme-dental"}'
FieldRequiredValidation
nameyesNon-empty after trimming; maximum 255 characters.
slugnoMaximum 100 characters. Lowercase letters, numbers, and single hyphens only; must be unique and not reserved. A unique slug is generated from name when omitted.

Returns 201 Created with the portal object.

Retrieve, update, and delete

PATCH /v1/client-portals/{portal_id} accepts any subset of:

FieldTypeBehavior
namestringReplaces the client-facing name.
slugstringReplaces the hosted URL slug after uniqueness validation.
is_activebooleanEnables or disables all viewer access.
accent_colorstringSix-digit hex such as #2e8fff, or "" for the default.
agent_idsarray of integerReplaces the complete assigned-agent set. Duplicates are removed; every id must belong to the organization. Use [] for no visible calls.
curl -X PATCH https://api.thunderphone.com/v1/client-portals/18 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_ids":[12,19],"accent_color":"#2e8fff"}'

DELETE returns 204 No Content and permanently removes the portal, its viewers, domain records, and logo reference. It does not delete agents or calls.

Upload multipart form field file. The accepted formats are PNG, JPEG, and WebP, with a maximum size of 2 MB. The file contents must match the declared MIME type.

curl -X POST https://api.thunderphone.com/v1/client-portals/18/logo \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -F "file=@acme-logo.png"

The response is {"logo_url":"https://…"}. Uploading a new logo replaces the old one. DELETE removes it and returns 204.

Viewers

The viewer object is:

{
  "id": 31,
  "email": "client@example.com",
  "is_active": true,
  "has_password": false,
  "created_at": "2026-08-05T09:40:12.551Z",
  "updated_at": "2026-08-05T09:40:12.551Z"
}

Add a unique email address:

curl -X POST https://api.thunderphone.com/v1/client-portals/18/viewers \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"client@example.com"}'

Returns 201. PATCH accepts only is_active; setting it to false blocks login without deleting the viewer. DELETE returns 204.

Custom domains

Create a domain with its hostname only:

curl -X POST https://api.thunderphone.com/v1/client-portals/18/domains \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"hostname":"calls.client.com"}'

Returns 201:

{
  "id": 7,
  "hostname": "calls.client.com",
  "verification_token": "tp_verify_…",
  "status": "pending",
  "verified_at": null,
  "cname_record": {
    "name": "calls.client.com",
    "value": "CNAME_TARGET_FROM_RESPONSE"
  }
}

The hostname is normalized, can be at most 253 characters, and must be unique across portals. Add the returned CNAME, then add this TXT record:

TypeNameValue
TXT_tp-portal-verify.calls.client.comThe returned verification_token.

Check it with:

curl -X POST https://api.thunderphone.com/v1/client-portals/18/domains/7/verify \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"

On success, the domain object has status: "active" and verified_at. While the TXT record is not visible, the response remains pending and repeats the expected TXT and CNAME records. DNS propagation can take several minutes.

DELETE /domains/{domain_id} returns 204 and releases the hostname for reuse.

Errors and authorization

StatusMeaning
400Invalid slug, color, agent list, upload, email, or hostname.
401Missing or invalid API key.
403The authenticated user or key is not authorized for admin management.
404Portal or nested resource is outside the current organization or absent.
204Successful deletion.

For the dashboard workflow and viewer experience, see Client portals.