Skip to content

Tenant management

The admin endpoints live under /api/v1 and are protected by the X-Admin-Key header (platform operator). They handle the entire lifecycle of a tenant after provisioning.

A single rule to remember:

  • provision/tenant takes a JSON body (no tenant_id, it is generated);
  • all other operations take ?tenant_id= as a query parameter, and a JSON body only when there is something to write (update/tenant).
MethodPathParameterEffect
POST/provision/tenantbodyCreates the tenant; returns tenant_id + tenant_secret + widget_public_key (once)
POST/update/tenant?tenant_id= + bodyPartial update (exclude_unset)
POST/rotate/tenant-secret?tenant_id=New secret; the old one is invalid immediately
POST/rotate/widget-key?tenant_id=New widget key; the old one stops working immediately
POST/suspend/tenant?tenant_id=HMAC#1 calls return 403 until reactivation
POST/reactivate/tenant?tenant_id=Lifts the suspension
GET/fetch/tenant?tenant_id=Returns the tenant (without secret)
GET/fetch/tenants?limit=&offset=Paginated list (without secrets)

update/tenant writes only the fields present in the body. Use it for the rate limit, the callback URL, branding, origins, feature flags, quotas, the plan_tier and the stripe_customer_id.

Fenêtre de terminal
curl "$BASE_URL/api/v1/update/tenant?tenant_id=019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-X POST \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"plan_tier": "pro",
"monthly_msg_quota": 50000,
"agent_seats": 25,
"branding_primary_color": "#0055FF"
}'

Secret rotation and status changes do not go through update/tenant — they have their own endpoints, below.

rotate/tenant-secret generates a new tenant_secret. The old one is invalid immediately: every backend that signs with HMAC#1 must switch to the new value at the same instant.

Fenêtre de terminal
curl "$BASE_URL/api/v1/rotate/tenant-secret?tenant_id=019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-X POST \
-H "X-Admin-Key: YOUR_ADMIN_KEY"
{
"status_code": 200,
"data": {
"tenant_id": "019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"tenant_secret": "sk_...",
"rotated_at": "2026-06-21T11:00:00Z"
},
"message": "Secret rotated. Save the new tenant_secret — old secret is now invalid."
}

rotate/widget-key generates a new widget_public_key. This is a chat-specific operation (the Auth Relay has no equivalent). The old key stops working immediately: update every <script data-public-key=...> tag and every SDK configuration.

Fenêtre de terminal
curl "$BASE_URL/api/v1/rotate/widget-key?tenant_id=019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-X POST \
-H "X-Admin-Key: YOUR_ADMIN_KEY"
{
"status_code": 200,
"data": {
"tenant_id": "019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"widget_public_key": "pk_...",
"rotated_at": "2026-06-21T11:05:00Z"
},
"message": "Widget public key rotated. Update <script data-public-key=...> on every tenant page; old key stops working immediately."
}

Suspending a tenant makes all of its HMAC#1 calls return 403 until reactivation — useful in case of abuse or non-payment.

  1. SuspendPOST /suspend/tenant?tenant_id=.... Signed backend calls are refused (403); the configuration stays intact.
  2. ReactivatePOST /reactivate/tenant?tenant_id=.... Calls resume immediately.
Fenêtre de terminal
# Suspend
curl "$BASE_URL/api/v1/suspend/tenant?tenant_id=019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-X POST -H "X-Admin-Key: YOUR_ADMIN_KEY"
# Reactivate
curl "$BASE_URL/api/v1/reactivate/tenant?tenant_id=019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-X POST -H "X-Admin-Key: YOUR_ADMIN_KEY"

fetch/tenant returns a full tenant without its tenant_secret (the widget_public_key is present, though). fetch/tenants paginates the list via limit (1–500, default 100) and offset.

Fenêtre de terminal
curl "$BASE_URL/api/v1/fetch/tenants?limit=50&offset=0" \
-H "X-Admin-Key: YOUR_ADMIN_KEY"