Skip to content

Operators

Operators are the human team that answers a tenant’s conversations. The backend surface (/api/v1/relay/*, signed with HMAC#1) lets your backend provision operators and mint tokens for them, without your users ever holding a relay password.

This is the “merchant chat” model: a merchant from your marketplace becomes an operator scoped to their own queues, and authenticates through your identity system — the relay trusts your HMAC#1 signature.

A reminder of the model (see Overview): when a visitor session is created, the mode field picks the queue.

  • bot (default) — messages go to the RAG assistant until an escalation, which hands them off to the human queue.
  • human — the first message directly creates a pending assignment and the bot never steps in.

Operators serve the human lane. The routing_keys list restricts them to certain queues (for example a merchant’s store ids); null or an empty list means “tenant-wide operator”, who sees every conversation.

POST /relay/provision/operator creates-or-reuses an operator for one of your own users. The operation is idempotent on (email, calling tenant): re-provisioning refreshes the membership’s routing_keys (a merchant who gains a second store) instead of failing. The password is random and never returned — the token exchange below is the only authentication path.

  1. Your backend authenticates the merchant with your system (session, application JWT, etc.).
  2. Your backend calls /relay/provision/operator signed with HMAC#1, with the merchant’s email, display name and routing_keys.
  3. The relay creates the operator (or refreshes its membership) and returns its operator_id.
from bloonio_chat_relay_client import get_chat_client
chat = get_chat_client() # signs HMAC#1 automatically
# The exact SDK method wraps POST /relay/provision/operator.

Response — 201 on creation, 200 when the operator already existed (created: false):

{
"status_code": 201,
"data": {
"operator_id": "019e4af0-...",
"email": "merchant@acme.com",
"display_name": "Acme Boutique",
"tenant_id": "019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"routing_keys": ["store_42", "store_77"],
"created": true
},
"message": "Operator provisioned"
}

Body fields: email (stable tenant-side key, required), display_name (required), avatar_url (optional), routing_keys (optional, up to 50 entries; null = tenant-wide).

POST /relay/fetch/operator-token exchanges the tenant’s HMAC#1 trust for an operator JWT. Keep this call behind your own user authentication: the identity on the relay side then rests on your system.

Fenêtre de terminal
curl $BASE_URL/api/v1/relay/fetch/operator-token \
-X POST \
-H "X-Bloonio-Tenant-Id: 019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f" \
-H "X-Bloonio-Timestamp: 1718960000000" \
-H "X-Bloonio-Signature: <signature>" \
-H "Content-Type: application/json" \
-d '{"email": "merchant@acme.com"}'
{
"status_code": 200,
"data": {
"operator_id": "019e4af0-...",
"display_name": "Acme Boutique",
"operator_token": "<jwt>",
"expires_at": 1719564800,
"tenant_id": "019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"routing_keys": ["store_42", "store_77"]
},
"message": "Operator token minted"
}

The token is valid for ~7 days (expires_at in unix seconds). The operator uses it to open their WebSocket and serve their queue.

The minted JWT carries only the calling tenant’s membership in its tids claim ({tenant_id: role}). An operator can belong to several tenants (the “Slack workspace” model), but:

Issuance conditions: the operator must exist and be active (otherwise 404), and have an active membership in the calling tenant (otherwise 403).