Developer resources
Two ways in: the drop-in widget, or the same REST API the widget uses. Everything below is served today at https://chatbot.trooply.ai.
1. What you receive on approval
- A widget public key (
store_pub_…). It identifies your store and is safe to put in a web page: it can start an anonymous session and nothing else. - A dashboard sign-in for your first operator at /dashboard: the email address from your application and a one-time password, which you replace with your own the first time you sign in. If that address already signs in to another store, you sign in with your store key and a username instead.
- Your website, as named in your application, admitted as a browser origin — automatically, within a minute of approval.
- A Setup page in your dashboard: connect a Shopify store by its address, check how its products are read, bring in your catalogue, add your policies, try your assistant on a private preview page, then put it on your website.
2. The widget
Add two lines to any page. The element mounts a chat panel in the corner; the module is served from this domain and is updated by us.
<commerce-ai-chat
store-public-key="store_pub_YOUR_KEY"
api-base-url="https://chatbot.trooply.ai"
locale="en-IN"></commerce-ai-chat>
<script type="module" src="https://chatbot.trooply.ai/widget/index.js"></script>
The widget renders product cards, citations, confirmation previews and hand-off notices from the answer contract below; it holds no secret.
3. The REST API
JSON over HTTPS. Every response carries a correlation_id; errors are problem details with a stable code such as RATE_LIMITED or VALIDATION_FAILED, and rate-limited responses say when to retry.
Start a session
POST /v1/sessions/anonymous
{ "store_public_key": "store_pub_YOUR_KEY", "locale": "en-IN" }
→ 200 { "session_token": "…", "expires_in": 3600, "role": "anonymous" }
Send the token as Authorization: Bearer … on every call that follows. Minting is limited per client address. Signed-in customer sessions (/v1/sessions/exchange, for order and return questions) are set up with you at onboarding, because they need an assertion your backend signs.
Open a conversation
POST /v1/chat/sessions
{ "locale": "en-IN" }
→ 201 { "thread_id": "thr_…" }
Send a message
POST /v1/chat/messages
{ "thread_id": "thr_…", "text": "do you have black running shoes under 4000" }
→ 200 {
"outcome": "answer",
"answer": {
"answer_status": "answered",
"response_markdown": "We found Men's Black Running Shoes for you. …",
"citations": [ { "handle": "ev2", "concept_id": "…", "concept_version": 3, "title": "…" } ],
"products": [ { "product_id": "prd_…", "title": "…", "price_state": "live", "price": { … } } ],
"clarification_question": null,
"conflicts": [], "limitations": [], "next_actions": [],
"handoff": null, "live_facts": [], "freshness": { … }, "versions": { … }
},
"events": [ … ]
}
| outcome | Meaning | Show the customer |
|---|---|---|
answer | Grounded reply; may carry citations and product cards | response_markdown, citations, products |
clarification | The request was ambiguous | clarification_question |
confirmation_required | An action is proposed and waits for consent | The preview, with Confirm / Cancel |
handoff | Evidence ran out or two policies disagree; a person takes over | response_markdown and your support contact |
denied | The session may not do that | response_markdown |
temporary_failure | A dependency was unavailable; retry later | A retry notice |
Prices and stock in products are the platform's live reads, never the model's text. price_state is live, unavailable or not_requested; render the number only when it is live.
Stream instead
POST /v1/chat/messages/stream (same body; response is text/event-stream)
Events arrive in sequence as the turn progresses and end with message.completed, whose payload is the same answer object. GET /v1/chat/sessions/{thread_id} returns the transcript and GET /v1/chat/sessions/{thread_id}/events replays events for reconnects.
Confirm or cancel an action
POST /v1/chat/actions/confirm { "thread_id": "thr_…", "intent_id": "int_…" }
POST /v1/chat/actions/cancel { "thread_id": "thr_…", "intent_id": "int_…" }
A confirmation is single-use, bound to the session, and carries the arguments that were previewed. It expires; a stale one is refused with CONFIRMATION_INVALID and the customer is shown a fresh preview.
Search the catalogue directly
GET /v1/catalog/search?q=running+shoes&color=black&price_max_minor=400000&limit=10
→ 200 {
"products": [ { "product_id": "prd_…", "title": "…",
"price_state": "live", "price": { "amount_minor": 359900, "currency": "INR", … },
"availability_state": "live", "availability": { "state": "in_stock", … } } ],
"price_filter": { "min_minor": null, "max_minor": 400000, "currency": "INR",
"checked": 12, "excluded_outside_range": 3, "excluded_without_live_price": 1 },
"warnings": []
}
Same session token. The filters category_id, color and size may repeat, and sku matches one SKU. Results come from your published catalogue only. Price and availability are read live for the first ten products. warnings carries price_and_availability_not_verified_live when any returned product was not read live.
price_min_minor and price_max_minor are in minor units (₹4,000 is 400000). They are judged only on prices read live, in your store's currency. A product whose price could not be read is left out and counted; it is never let through on an indexed price. price_filter is present only when a limit was given, and says how many close matches were checked and why any were left out.
End a session
POST /v1/sessions/revoke { "reason": "logout" } → 204
Connect a Shopify store
POST /v1/admin/connections/storefront
{ "store_url": "yourstore.com", "private_token": "…", "reason": "connecting our store" }
POST /v1/admin/connections/storefront/token
{ "connection_id": "conn_…", "private_token": "…", "reason": "large catalogue" }
The dashboard's Setup page makes these calls. They take a dashboard session, not the widget's, and the connector:write:store permission (operations connection.create and connection.credential).
private_token is optional when connecting. It is a Storefront API private token you create in Shopify's Headless channel, recommended for large catalogues. The second call switches a store that is already connected to reading with a token. Either way, the token is checked against your store before anything is kept, stored encrypted, and never echoed in a response or recorded in the audit trail.
4. Limits
Your plan sets turns per minute and concurrent turns per tenant; every session is also bounded on its own. A refused request returns 429 with RATE_LIMITED, the window it hit and a retry time. Bursts are refused, never queued.
5. Test your integration
Before you have a key of your own, the demonstration store answers with store_pub_tenant_a_local:
curl -s https://chatbot.trooply.ai/v1/sessions/anonymous \
-H 'content-type: application/json' \
-d '{"store_public_key":"store_pub_tenant_a_local"}'
Then open a conversation and ask “what is your return policy”. You should get outcome: answer with citations. That is exactly what your own store will do once its policies are published in your dashboard.