Tenant management
The tenant management endpoints are protected by the X-Admin-Key header —
the operator key that guards the tenant registry. They are never reachable
with a tenant HMAC signature, and the admin key must never be shipped to a
browser or a device.
For the cross-cutting conceptual model (states, secret shown once, rotation), see also Tenant lifecycle.
Parameter convention: body or query
Section titled “Parameter convention: body or query”| Method | Path | Tenant via | Body |
|---|---|---|---|
| POST | /provision/tenant | body | full |
| POST | /update/tenant | ?tenant_id= | partial |
| POST | /rotate/tenant-secret | ?tenant_id= | — |
| POST | /suspend/tenant | ?tenant_id= | — |
| POST | /reactivate/tenant | ?tenant_id= | — |
| GET | /fetch/tenant | ?tenant_id= | — |
| GET | /fetch/tenants | — (pagination) | — |
Provision a tenant
Section titled “Provision a tenant”Creates a tenant and returns its tenant_id and tenant_secret. Only
tenant_name is required; the configuration fields can be set later.
curl $BASE_URL/api/v1/provision/tenant \ -X POST \ -H "X-Admin-Key: YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "tenant_name": "example_backend", "rate_limit_per_min": 120, "qr_login_allowed_origins": ["https://example.com"], "callback_url_base": "https://api.example.com", "branding_display_name": "Example", "branding_logo_url": "https://example.com/logo.png", "webauthn_rp_id": "example.com", "webauthn_origins": ["https://example.com"], "passkeys_enabled": true }'The configuration fields and what they drive:
| Field | Used by |
|---|---|
rate_limit_per_min | Rate cap on relay calls (default 60, max 10,000). |
qr_login_allowed_origins | QR login: allowed web origins. Empty list = QR login disabled. |
callback_url_base | Relay → tenant callbacks. The relay appends /relay-callbacks/<flow>. |
branding_display_name | Mobile consent screen: “Sign in to …?”. Falls back to tenant_name. |
branding_logo_url | Logo (square, HTTPS) on the mobile consent screen. |
webauthn_rp_id | WebAuthn Relying Party ID (eTLD+1). Required for passkeys. |
webauthn_origins | Origin allowlist for passkey ceremonies. Empty list = passkeys disabled. |
passkeys_enabled | Passkey progressive rollout flag (null/true/false). |
The response — tenant_secret shown only once:
{ "success": true, "status_code": 201, "message": "Tenant provisioned. Save tenant_secret now — it will not be shown again.", "data": { "tenant_id": "tnt_...", "tenant_secret": "sk_...", "tenant_name": "example_backend", "rate_limit_per_min": 120, "created_at": "2026-06-21T..." }}Update the configuration
Section titled “Update the configuration”Partial update: only the fields present in the body are written. The secret is never changed here (use rotation) and state changes go through suspend / reactivate.
curl "$BASE_URL/api/v1/update/tenant?tenant_id=tnt_..." \ -X POST \ -H "X-Admin-Key: YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "qr_login_allowed_origins": ["https://example.com", "https://preview.example.com"], "branding_display_name": "Example (Preview)" }'{ "success": true, "status_code": 200, "message": "Tenant updated", "data": { "tenant_id": "tnt_...", "tenant_name": "example_backend", "status": "active", "rate_limit_per_min": 120, "qr_login_allowed_origins": ["https://example.com", "https://preview.example.com"], "branding_display_name": "Example (Preview)", "abuse_strikes": 0, "created_at": "2026-06-21T...", "updated_at": "2026-06-21T..." }}Secret rotation
Section titled “Secret rotation”Generates a new tenant_secret. The old secret becomes invalid
immediately — deploy the new one before any subsequent signed call. The new
secret is shown only once.
curl "$BASE_URL/api/v1/rotate/tenant-secret?tenant_id=tnt_..." \ -X POST \ -H "X-Admin-Key: YOUR_ADMIN_KEY"{ "success": true, "status_code": 200, "message": "Secret rotated. Save the new tenant_secret — old secret is now invalid.", "data": { "tenant_id": "tnt_...", "tenant_secret": "sk_...", "rotated_at": "2026-06-21T..." }}Suspend and reactivate
Section titled “Suspend and reactivate”Suspending blocks all of the tenant’s relay calls: a /relay/* request signed
by a suspended tenant is rejected with 403. Reactivation undoes the
suspension.
curl "$BASE_URL/api/v1/suspend/tenant?tenant_id=tnt_..." \ -X POST \ -H "X-Admin-Key: YOUR_ADMIN_KEY"{ "success": true, "status_code": 200, "message": "Tenant suspended", "data": null }curl "$BASE_URL/api/v1/reactivate/tenant?tenant_id=tnt_..." \ -X POST \ -H "X-Admin-Key: YOUR_ADMIN_KEY"{ "success": true, "status_code": 200, "message": "Tenant reactivated", "data": null }Read tenants
Section titled “Read tenants”GET /fetch/tenant returns a tenant’s configuration and state (never the
secret). GET /fetch/tenants returns a paginated list.
curl "$BASE_URL/api/v1/fetch/tenant?tenant_id=tnt_..." \ -H "X-Admin-Key: YOUR_ADMIN_KEY"{ "success": true, "status_code": 200, "message": "OK", "data": { "tenant_id": "tnt_...", "tenant_name": "example_backend", "status": "active", "rate_limit_per_min": 120, "abuse_strikes": 0, "created_at": "2026-06-21T...", "updated_at": null }}curl "$BASE_URL/api/v1/fetch/tenants?limit=100&offset=0" \ -H "X-Admin-Key: YOUR_ADMIN_KEY"limit ranges from 1 to 500 (default 100) and offset is ≥ 0 (default 0).
The response is an array of data objects in the same shape as
/fetch/tenant.