Reference

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_key embedded 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

POST /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

FieldTypeRequiredDescription
reseller_idstringReseller scope. Caller must own this reseller or be admin.
merchant_idstringMerchant scope within the reseller.
template_idstring (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_originsstring[]Exact-match list of Origin / Referer values. Empty list = deny all.
max_sessions_per_ip_hourintegerDefault 60. Sessions per IP per hour.
max_messages_per_ip_hourintegerDefault 600. Chat messages per IP per hour.
max_concurrent_per_ipintegerDefault 4. Concurrent open sessions per IP.
max_voice_sessions_per_ip_hourintegerDefault 10. Voice connects per IP per hour.
activebooleanDefault 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

The key is minted server-side using 32 bytes of `secrets.token_urlsafe` (~256 bits, ~43 chars). It is never accepted on Create or Update. To rotate a key, delete the config and create a new one (or request a dedicated rotate endpoint when that feature lands).

List widget configs

GET /agent/voice/breeze-buddy/widget-config/list

Returns a paginated list of widget_config rows visible to the caller.

Query parameters

ParameterTypeDescription
reseller_idstringFilter by reseller. Non-admins are restricted to their own resellers.
merchant_idstringFilter by merchant.
include_inactivebooleanDefault false. Include rows with active: false.
pageintegerDefault 1.
limitintegerDefault 50.

Response

{
  "widget_configs": [ /* WidgetConfigResponse[] */ ],
  "total": 12,
  "page": 1,
  "limit": 50
}

Get a widget config

GET /agent/voice/breeze-buddy/widget-config/{id}

Returns a single widget_config row by its id.


Update a widget config

PUT /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)

FieldTypeDescription
template_idstring (UUID)Swap to a different template. Same reseller/merchant scoping rules as create.
allowed_originsstring[]Replace the entire origins list.
max_sessions_per_ip_hourintegerNew rate limit.
max_messages_per_ip_hourintegerNew rate limit.
max_concurrent_per_ipintegerNew rate limit.
max_voice_sessions_per_ip_hourintegerNew rate limit.
activebooleanFlip active/inactive without deleting the row.

Delete a widget config

DELETE /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 roleAccessible rows
adminAll rows in any reseller / merchant scope
resellerOnly rows whose reseller_id is in the caller’s reseller_ids claim
merchantOnly 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.

MethodPathDescription
POST/agent/voice/breeze-buddy/widget/sessionCreate a new session. Auth: public_widget_key + Origin header. Returns session_id + widget_token.
POST/agent/voice/breeze-buddy/widget/session/{id}/messageSend a user message. Returns SSE stream. Auth: widget_token.
POST/agent/voice/breeze-buddy/widget/session/{id}/cancelCancel 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/connectOpen a voice attachment. Returns room_url + daily_token. Auth: widget_token.
POST/agent/voice/breeze-buddy/widget/session/{id}/voice/endClose the voice attachment. Auth: widget_token.
POST/agent/voice/breeze-buddy/widget/session/{id}/endEnd 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).

Was this helpful?