Widget config API
Admin API for creating, updating, listing, and deleting per-merchant widget configurations that power the Buddy Assist embed.
A widget_config row is the server-side record that:
- Generates and stores the opaque
public_widget_keyembedded in merchant storefronts. - Binds a template to a reseller + merchant pair.
- Enforces per-merchant rate limits and origin allowlists.
All endpoints require a standard RBAC JWT (not a widget_token). The widget never calls these endpoints directly — they are admin/reseller API calls.
Create a widget config
/agent/voice/breeze-buddy/widget-config Creates a new widget_config row. One row per reseller + merchant pair is enforced — a 409 is returned if a row already exists.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reseller_id | string | ✓ | Reseller scope. Caller must own this reseller or be admin. |
merchant_id | string | ✓ | Merchant scope within the reseller. |
template_id | string (UUID) | ✓ | UUID of the template to run. Must belong to the same reseller; if the template has a merchant_id it must also match. |
allowed_origins | string[] | — | Exact-match list of Origin / Referer values. Empty list = deny all. |
max_sessions_per_ip_hour | integer | — | Default 60. Sessions per IP per hour. |
max_messages_per_ip_hour | integer | — | Default 600. Chat messages per IP per hour. |
max_concurrent_per_ip | integer | — | Default 4. Concurrent open sessions per IP. |
max_voice_sessions_per_ip_hour | integer | — | Default 10. Voice connects per IP per hour. |
active | boolean | — | Default true. Inactive configs behave like 404 to the widget. |
Response
{
"id": "wc_01JXYZ…",
"reseller_id": "acme-reseller",
"merchant_id": "acme-store",
"public_widget_key": "BPlS8aL3Xk7tRvNqZ…",
"template_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"allowed_origins": ["https://acme-store.myshopify.com"],
"max_sessions_per_ip_hour": 60,
"max_messages_per_ip_hour": 600,
"max_concurrent_per_ip": 4,
"max_voice_sessions_per_ip_hour": 10,
"active": true,
"created_at": "2026-05-26T10:00:00Z",
"updated_at": "2026-05-26T10:00:00Z"
} public_widget_key is server-generated
List widget configs
/agent/voice/breeze-buddy/widget-config/list Returns a paginated list of widget_config rows visible to the caller.
Query parameters
| Parameter | Type | Description |
|---|---|---|
reseller_id | string | Filter by reseller. Non-admins are restricted to their own resellers. |
merchant_id | string | Filter by merchant. |
include_inactive | boolean | Default false. Include rows with active: false. |
page | integer | Default 1. |
limit | integer | Default 50. |
Response
{
"widget_configs": [ /* WidgetConfigResponse[] */ ],
"total": 12,
"page": 1,
"limit": 50
} Get a widget config
/agent/voice/breeze-buddy/widget-config/{id} Returns a single widget_config row by its id.
Update a widget config
/agent/voice/breeze-buddy/widget-config/{id} Partial update — only the fields you supply are changed. public_widget_key cannot be changed via this endpoint.
Request body (all fields optional)
| Field | Type | Description |
|---|---|---|
template_id | string (UUID) | Swap to a different template. Same reseller/merchant scoping rules as create. |
allowed_origins | string[] | Replace the entire origins list. |
max_sessions_per_ip_hour | integer | New rate limit. |
max_messages_per_ip_hour | integer | New rate limit. |
max_concurrent_per_ip | integer | New rate limit. |
max_voice_sessions_per_ip_hour | integer | New rate limit. |
active | boolean | Flip active/inactive without deleting the row. |
Delete a widget config
/agent/voice/breeze-buddy/widget-config/{id} Deletes the row. Any embed still using the old public_widget_key will receive 404 on its next session-create attempt.
Response
{
"status": "success",
"message": "widget_config wc_01JXYZ… deleted",
"deleted_id": "wc_01JXYZ…"
} RBAC rules
| Caller role | Accessible rows |
|---|---|
admin | All rows in any reseller / merchant scope |
reseller | Only rows whose reseller_id is in the caller’s reseller_ids claim |
merchant | Only rows whose merchant_id is in the caller’s merchant_ids claim |
A non-admin calling with a reseller_id or merchant_id they don’t own returns 403.
Widget-public session endpoints
These endpoints are called by the embed (no RBAC JWT needed — widget_token only) and are documented here for completeness.
| Method | Path | Description |
|---|---|---|
POST | /agent/voice/breeze-buddy/widget/session | Create a new session. Auth: public_widget_key + Origin header. Returns session_id + widget_token. |
POST | /agent/voice/breeze-buddy/widget/session/{id}/message | Send a user message. Returns SSE stream. Auth: widget_token. |
POST | /agent/voice/breeze-buddy/widget/session/{id}/cancel | Cancel the in-flight SSE turn (Stop button). Best-effort and idempotent — always returns 202. If no turn is in flight, this is a no-op. Auth: widget_token. |
POST | /agent/voice/breeze-buddy/widget/session/{id}/voice/connect | Open a voice attachment. Returns room_url + daily_token. Auth: widget_token. |
POST | /agent/voice/breeze-buddy/widget/session/{id}/voice/end | Close the voice attachment. Auth: widget_token. |
POST | /agent/voice/breeze-buddy/widget/session/{id}/end | End the whole conversation. Auth: widget_token. |
GET | /agent/voice/breeze-buddy/widget/session/{id} | Get session state for resume. Auth: widget_token. |
All widget-public routes return permissive CORS headers (Origin enforcement happens at the application layer inside the handlers, not via global CORSMiddleware).