1. Overview
Assist mode is FAQ + find products + change the cart. Checkout, address, and payment stay in the native Bag. Website widget and WhatsApp are separate channels.
What the shopper can do in chat:
- Ask store questions (hours, shipping, returns)
- Find products by text or photo
- Pick variants, see stock, add or remove lines
- Keep chat and the app Bag on one Odoo draft quotation
- Open native checkout when they are ready to pay
- List and track past orders
Base path: /api/v1/mobile/chat. Do not call Platform REST tools from the shopper app.
2. Connect the app
Two tokens
- MCP API key — workspace secret. Only your app backend uses it to create a session. Never ship it in the mobile binary.
- Customer JWT — returned by bootstrap. The client sends it on every chat request.
After login
Your backend already knows the shopper’s Odoo partner_id. Optionally pass the current draft sale order so chat and Bag stay on one quotation (odoo_cart_order_id).
curl -X POST https://YOUR_HOST/api/v1/mobile/chat/session \
-H "Authorization: Bearer YOUR_MCP_KEY" \
-H "Content-Type: application/json" \
-d '{
"partner_id": 42,
"phone": "+919876543210",
"odoo_cart_order_id": 156,
"assist_mode": true
}'Response includes token, conversation_id, partner_id, odoo_cart_order_id, and current cart. Persist the order id in the app cart session and reload Bag if lines came back.
To resume the same thread later, send conversation_id on bootstrap. Keep assist_mode: true (default).
3. Send messages & widgets
From the app, send text or a tap. Every reply is the same envelope: choose a screen from primary_type and render each item in assistant.widgets.
curl -X POST https://YOUR_HOST/api/v1/mobile/chat/messages \
-H "Authorization: Bearer CUSTOMER_JWT" \
-H "Content-Type: application/json" \
-d '{"type":"text","text":"show me office chairs"}'Button taps:
{"type":"action","action_id":"add_catalog:1","action_label":"Add to cart"}Always apply session after a reply:
cart/cart_count— refresh the in-chat cart badgeodoo_cart_order_id— write it back so Bag reloads the same draftnavigate— ifopen_native_bag, open native checkoutstep—shopping|review|payment(coarse hint)
Widget types you will see:
product_carousel— product cardsoption_list— variants, addresses, generic rowscategory_list— category chipscart_summary— lines + subtotalquick_actions— chipsorder_history/order_detailsnavigate— open the native Bag
Show assistant.text as the bubble. Do not repeat it as a widget header. Product images may be /api/v1/mobile/chat/product-image?product_id=… — load them with the same host (no extra auth on GET).
4. Store FAQ
Shoppers ask policy questions in natural language: opening hours, delivery areas, return window, payment methods, “do you have COD?”. There is no FAQ-specific endpoint.
- Send
{ "type": "text", "text": "what is your return policy?" }. - Render
assistant.text. Usually there are no widgets. - If they switch to shopping (“show me chairs”), continue on the same JWT and conversation.
Answers come from merchant-trained store knowledge (dashboard → eCommerce Agent setup → Store knowledge) plus the Odoo catalog. If the model is unsure, it will say so — do not invent a second FAQ UI.
5. Find products
Shoppers describe what they want. Chat searches the Odoo catalog and returns cards.
- Send text, e.g.
office chairs under 200orshow me sofas. - When
primary_typeisproduct_carousel, render a horizontal list fromwidgets[].items. - Each card: name,
price(payable), optional strikethroughlist_price, image, stock badge, and theactionsthe server sent. Do not invent extra buttons. - On tap, POST
type: actionwith that action’sidandlabel.
Typical add button: add_catalog:{selection} (selection is the card index in that reply, not the Odoo product id). After add, expect cart_summary plus an updated session.cart.
{"type":"text","text":"show me office chairs"}6. Photo search
Same pipeline as text find: vision → catalog search → product_carousel. Requires image search enabled on the server (JPEG, PNG, WebP, GIF, max 10 MB).
JSON + base64 (typical native client)
curl -X POST https://YOUR_HOST/api/v1/mobile/chat/messages/image/json \
-H "Authorization: Bearer CUSTOMER_JWT" \
-H "Content-Type: application/json" \
-d '{
"image_base64": "<base64 bytes>",
"mime_type": "image/jpeg",
"caption": "something like this"
}'Multipart
curl -X POST https://YOUR_HOST/api/v1/mobile/chat/messages/image \ -H "Authorization: Bearer CUSTOMER_JWT" \ -F "file=@chair.jpg" \ -F "caption=looking for this chair"
- Show a camera / gallery control in the composer.
- Upload, then render the reply exactly like a text search (carousel + add to cart).
- Optional
captionnarrows the search (“blue”, “kids size”).
7. Variants & stock
Each carousel item may include:
qty_available,in_stock,allow_out_of_stock_ordercan_order—in_stock || allow_out_of_stock_order
- If
can_orderis false, show sold out and do not send add. The server already swaps the button to an out-of-stock label when needed. - Multi-variant templates use
variant_pick:{template_id}(“Choose options”). - The next reply is
option_listwithpurpose: "product_variants". Each row title is the attribute values; price is on the trailing edge. Tapvariant_select:{product_id}. - Do not use the SKU as the primary label when attributes exist.
8. Cart & Bag sync
Chat writes the same draft sale.order the app uses for Bag when cart sync is on. If Odoo has sale.order.is_app, that app draft is preferred.
- At bootstrap, send
odoo_cart_order_idif the shopper already has a Bag. - After add / remove / clear, persist
session.odoo_cart_order_idand reload Bag from Odoo. Chat lines and Bag lines stay one quotation. - Show cart in chat from
cart_summaryorsession.cart.
Shoppers can also type or tap:
- View: text
cartor actionview_cart - Remove:
remove_cart:{product_id}or “remove the chair” - Clear: action
clear_cartor “empty my cart”
If bootstrap omitted the order id, still write back whatever odoo_cart_order_id the first add creates so later Bag opens are not empty.
9. Checkout in the Bag
With assist_mode: true, chat never places the order or collects payment. Checkout language (“checkout”, “place the order”, “buy this”, “open bag”) and the checkout_cart action all return the same signal.
- Look for
session.navigate === "open_native_bag"and/or anavigatewidget withaction: "open_native_bag". - Open the native Bag / checkout screen. Address and pay stay in the app.
- You may also receive a quick-action chip
open_native_bag— treat it the same (local navigation, not another chat call unless you want the copy again).
Ignore order_review / payment_link for assist-mode shoppers. Those widgets are for WhatsApp and website chat, not the native Bag path.
10. Past orders
Shoppers can ask “where is my order?”, “my orders”, or tap history chips.
- List: text or action
my_orders→ widgetorder_history. - Detail: tap
track_order:{order_name}(e.g.track_order:S00063) →order_details. - Render one card: order name + date, status pills (
tone: success / warning / danger / neutral), lines, totals, delivery block. Refresh uses the sametrack_order:id. - Assist mode still does not take payment in chat. If
payment_urlis present on a leftover unpaid order, opening it is optional; prefer the native order screen.
11. Browse categories
“Show categories” or action browse_categories returns category_list. browse_products returns a general catalog carousel.
- Render chips from the list widget.
- On tap, send the action id the server provided (category select ids are opaque — do not parse them).
- The next reply is a product carousel for that category.
12. Action IDs
Always POST the id from the widget. This table is the contract so you can wire navigation and disabled states.
| Action | When | What you do |
|---|---|---|
add_catalog:N | Simple product card | POST action; refresh Bag from session |
variant_pick:T | Multi-variant card | POST action; show variant option_list |
variant_select:P | A specific variant | POST action; adds that product id |
view_cart / clear_cart / remove_cart:P | Cart chips | POST action; sync Bag |
checkout_cart / open_native_bag | Ready to pay | Open native Bag; do not charge in chat |
my_orders / track_order:NAME | History | POST action; render history or details |
browse_products / browse_categories | Shop more | POST action; render carousel or chips |
Probe GET /api/v1/mobile/chat/config for image_search_enabled and odoo_cart_sync if you need to hide the camera control or Bag badge.