Skip to content

Assistant

Prefix: /v1/assistant on the Public API.

The assistant is a streaming chat. Clients must render blocks from the stream (and from saved messages). Do not invent prices, stock, or product names.

Auth

ClientHow identity is sent
Signed-in customerAuthorization: Bearer <accessToken>
GuestOnly if GET /v1/initstore.assistant.allowGuests is true

Guests need a stable 32-character hex key: generate once, persist, and send X-Assistant-Guest: <32-hex> on every assistant call. After OTP login, guest threads merge onto the customer; you can drop the header.

If Settings → Assistant has enabled: false, all assistant routes fail. If guests are disabled, unsigned callers get AUTHENTICATION_REQUIRED.

Rate limits: list conversations 60/min, send message 20/min.

Endpoints

MethodPathAuthResponsePurpose
GET/v1/assistant/conversationsCustomer or guestJSON envelopePaginated conversation list
GET/v1/assistant/conversations/:conversationIdOwnerJSON envelopeConversation + messages with hydrated blocks
POST/v1/assistant/messagesCustomer or guestSSE (text/event-stream)Send a turn
POST/v1/assistant/actions/:actionId/confirmOwnerJSON envelopeApply a proposed cart / delivery mutation
POST/v1/assistant/conversations/:conversationId/handoffOwnerJSON envelopeCreate a support ticket from the thread
POST/v1/assistant/messages/:messageId/feedbackOwnerJSON envelope{ "feedback": "up" | "down" | null }

Conversation status: active | handed_off | closed. A thread closes when it hits the message/token cap; the next send starts a new conversation.

Send a message (SSE)

http
POST /v1/assistant/messages
Content-Type: application/json
Accept: text/event-stream
Authorization: Bearer <accessToken>
X-Assistant-Guest: <32-hex>   # guests on mobile

{
  "conversationId": "665f0c0c0c0c0c0c0c0c0c0f",
  "message": "Add two bottles of milk"
}
Body fieldRequiredNotes
messageyes1–2000 characters
conversationIdnoOmit on the first message; the stream returns a new id

This endpoint is not the JSON envelope. Each SSE frame looks like:

event: <name>
data: { ...json... }

Stream events (in order)

eventdataClient should
message_start{ "conversationId": "<id>" }Persist the conversation id
user_message{ "conversationId", "message" }Show the user turn (message has _id, role: "user", content, blocks, feedback)
text_delta{ "delta": "..." }Append to the live assistant text
tool_start{ "name", "callId" }Optional: show a “looking up…” state
tool_end{ "name", "callId", "ok" }Clear the tool spinner
block{ "block": { "kind": "...", ... } }Append a UI card (see kinds below)
error{ "code", "message" }Show the error; code is a statusMessage or HTTP-like code
message_end{ "conversationId", "message" }Replace the live turn with the full assistant message (hydrated blocks)

Typical sequence:

  1. message_start
  2. user_message
  3. zero or more text_delta / tool_start / tool_end / block
  4. message_end or error

Keep the connection open until message_end or a terminal error. Then close.

Message object

Returned on GET .../conversations/:id, on user_message, and on message_end:

FieldMeaning
_idMessage id (use for feedback)
conversationIdParent thread
roleuser | assistant | system
contentPlain text of the turn
blocksOrdered UI cards (see kind)
feedbackup | down | null
createdAt, updatedAtISO timestamps

Block kind values

Every block is an object with a kind string. Switch on kind and render the matching fields. Ignore unknown kinds so new cards do not crash old apps.

kindFieldsRender
texttextMarkdown/plain assistant prose
productsproducts[]Product cards (localized name, prices in fils, images)
product_detailproductSingle product card
cart_actionactionId, items[], status, optional estimatedTotalProposed cart change — Confirm bound to actionId while status is pending
cart_summarycartCurrent cart (same shape as GET /v1/cart)
orderorderOrder summary (_id, orderNumber, status, total, itemCount, thumbnails)
order_statusorderSame summary, shown as a status update
offersoffers[], optional couponCodePromotion cards
reciperecipe, servings, ingredientCountRecipe card
faqitems[]FAQ Q&A from store content
categoriescategories[]Aisle cards
brandsbrands[]Brand cards
delivery_slotsdays[]Slot picker (Kuwait YYYY-MM-DD)
delivery_infooptional areaName, zoneName, fee, etaMinutesCurrent delivery context (fee in fils)
locationsitems[] of { label, address?, phone?, lat, lng }Map / branch pins
handoffticketId, ticketNumberLink to the created support ticket
actionssuggestions[] of { label, prompt }Chip buttons: sending prompt as the next user message
errorcode, optional messageInline error card

cart_action status

statusMeaning
pendingShow Confirm. Cart is unchanged until confirm succeeds
confirmedAlready applied
cancelledAbandoned
expiredConfirm window passed

items[] on cart_action: { productId, variantId, quantity, product? }.

Do not call cart mutation endpoints yourself for assistant proposals. Always POST /v1/assistant/actions/:actionId/confirm.

Confirm a proposed action

http
POST /v1/assistant/actions/{actionId}/confirm
Authorization: Bearer <accessToken>

results:

FieldMeaning
messageLocalized acknowledgement
blocksUpdated cards (typically cart_summary and a cart_action with status: "confirmed")

Until this call succeeds, the cart is unchanged. Confirm is owner-only (same customer or guest key).

Pending action types the model may propose (payload is server-side; the client only confirms):

add_to_cart, update_cart_item, remove_cart_item, apply_coupon, apply_loyalty, add_recipe_to_cart, reorder, select_delivery_area, select_address, clear_cart, clear_coupon, clear_loyalty, set_express.

Handoff to support

http
POST /v1/assistant/conversations/{conversationId}/handoff
Content-Type: application/json

{ "subject": "Missing item", "category": "order", "subcategory": "missing_items" }

All body fields are optional. results includes ticketId, ticketNumber, and message. Conversation status becomes handed_off. A handoff block may also appear in the thread.

Feedback

http
POST /v1/assistant/messages/{messageId}/feedback
Content-Type: application/json

{ "feedback": "up" }

feedback is up, down, or null (clear).

Client checklist

  1. Call GET /v1/init and hide chat if store.assistant.enabled is false.
  2. Parse SSE by event name; JSON-parse each data line.
  3. Switch UI cards on block.kind.
  4. For kind: "cart_action" with status: "pending", confirm via the actions endpoint — never mutate /v1/cart for that proposal.
  5. After message_end, prefer the hydrated message.blocks over the live deltas if they disagree.
  6. Do not invent catalog data; only render what the API sent.