{"openapi":"3.1.0","info":{"title":"Ha Tang chat gateway API","version":"1","description":"Public endpoints used by the chat widget and the native SDKs. Every /widget/v1 route checks the request Origin against the site's domain and allowed origins, unless the caller presents a site app key (x-hatang-app-key) or a visitor token minted through one; authenticated calls carry the visitor token from bootstrap. Realtime traffic uses the websocket at /ws?site=…(&app=…&install=… for apps), protocol v1; close codes: 4001 auth_timeout, 4003 auth_failed, 4004 protocol_outdated, 4005 session_replaced. See docs/chat-protocol.md and docs/chat-native-sdk.md."},"servers":[{"url":"https://chat.hatang.dev"}],"tags":[{"name":"Widget"},{"name":"Files"},{"name":"Agent"},{"name":"Hosted"},{"name":"Assets"}],"paths":{"/widget/v1/sites/{siteId}/bootstrap":{"post":{"tags":["Widget"],"summary":"Create or resume a visitor session (browsers: allowed Origin; native SDKs: app key + `app.install_id`)","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-hatang-app-key","in":"header","required":false,"description":"Site app key (hak_…) for native SDKs; replaces the browser Origin check. Create one in the dashboard under Support → Channels → your website → Apps.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BootstrapRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bootstrap"}}}},"401":{"description":"Unknown app key"},"403":{"description":"Origin not allowed, app key revoked, or site disabled"}},"operationId":"postWidgetV1SitesBySiteIdBootstrap"}},"/widget/v1/sites/{siteId}/conversations":{"post":{"tags":["Widget"],"summary":"Start a conversation (HTTP fallback)","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}}},"operationId":"postWidgetV1SitesBySiteIdConversations"},"get":{"tags":["Widget"],"summary":"The visitor's own conversations, newest activity first, resolved ones included (an app's inbox)","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"all (default) | open | resolved","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, max 50","schema":{"type":"string"}},{"name":"after","in":"query","required":false,"description":"Cursor: last conversation id of the previous page","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversations":{"type":"array","items":{"$ref":"#/components/schemas/VisitorConversation"}},"has_more":{"type":"boolean"}}}}}}},"operationId":"getWidgetV1SitesBySiteIdConversations"}},"/widget/v1/sites/{siteId}/conversations/{id}/messages":{"get":{"tags":["Widget"],"summary":"Timeline items of one of the visitor's conversations: after `after_seq` (sync) or before `before_seq` (earlier history); `hasMore` is kept one release for old bundles","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}},{"name":"after_seq","in":"query","required":false,"description":"Return items with seq greater than this","schema":{"type":"string"}},{"name":"before_seq","in":"query","required":false,"description":"Return items with seq lower than this (wins over after_seq)","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, max 200","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"has_more":{"type":"boolean"},"hasMore":{"type":"boolean","deprecated":true}}}}}}},"operationId":"getWidgetV1SitesBySiteIdConversationsByIdMessages"},"post":{"tags":["Widget"],"summary":"Send a message (HTTP fallback); content may be empty when attachments are given","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string"},"content":{"type":"string"},"attachments":{"type":"array","items":{"type":"string"}}},"required":["client_id","content"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"429":{"description":"Rate limited"}},"operationId":"postWidgetV1SitesBySiteIdConversationsByIdMessages"}},"/widget/v1/sites/{siteId}/leads":{"post":{"tags":["Widget"],"summary":"Capture a lead from any form: creates/identifies the visitor, opens a conversation, routes it and counts a conversion (no visitor token needed; 10/min per IP)","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadRequest"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"conversation_id":{"type":"string"},"visitor_id":{"type":"string"},"customer_id":{"type":["string","null"]}}}}}},"429":{"description":"Rate limited"}},"operationId":"postWidgetV1SitesBySiteIdLeads"}},"/widget/v1/sites/{siteId}/offline-message":{"post":{"tags":["Widget"],"summary":"Pre-chat form: name, email and first message while nobody is online","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfflineMessageRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}}},"operationId":"postWidgetV1SitesBySiteIdOfflineMessage"}},"/widget/v1/sites/{siteId}/uploads":{"post":{"tags":["Widget"],"summary":"Upload an attachment (multipart, field `file`, 10 MB; images incl. HEIC, PDF, office files, zip, mp4, m4a)","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attachment"}}}},"413":{"description":"Too large"},"422":{"description":"Unsupported type"}},"operationId":"postWidgetV1SitesBySiteIdUploads"}},"/widget/v1/sites/{siteId}/purchases":{"post":{"tags":["Widget"],"summary":"Report a finished App Store / Google Play purchase from the app (SDK reportPurchase). With `signed_transaction` (StoreKit 2 JWS) or `purchase_token` (Play) it is checked with the store and recorded for this visitor (Play purchases are acknowledged); a store that says no is a 422. The team also gets an internal note in the open conversation","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","enum":["ios","android"]},"store_product_id":{"type":"string"},"transaction_id":{"type":["string","null"]},"signed_transaction":{"type":["string","null"],"description":"iOS: Transaction.jwsRepresentation"},"purchase_token":{"type":["string","null"],"description":"Android: Purchase.purchaseToken"},"product_type":{"type":["string","null"],"enum":["subs","inapp",null]},"kind":{"type":["string","null"],"enum":["consumable","non_consumable",null],"description":"Android one-time products"},"conversation_id":{"type":["string","null"]},"amount":{"type":["number","null"]},"currency":{"type":["string","null"]}},"required":["platform","store_product_id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"recorded":{"type":"boolean","description":"A note was written"},"verified":{"type":"boolean"},"pending":{"type":"boolean","description":"Play: the payment is still pending"},"product_id":{"type":["string","null"]},"subscription":{"type":["object","null"]},"purchase":{"type":["object","null"]}}}}}},"422":{"description":"Not a genuine purchase for this app, or the app is not connected to the store"}},"operationId":"postWidgetV1SitesBySiteIdPurchases"}},"/widget/v1/sites/{siteId}/devices":{"put":{"tags":["Widget"],"summary":"Native SDKs: register this phone's push token for the visitor (FCM v1 with the site's Firebase credentials); re-registering moves the token to the current visitor","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterVisitorDevice"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"device":{"$ref":"#/components/schemas/VisitorDevice"}}}}}},"429":{"description":"Rate limited"}},"operationId":"putWidgetV1SitesBySiteIdDevices"},"delete":{"tags":["Widget"],"summary":"Forget this phone's push token","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}},"operationId":"deleteWidgetV1SitesBySiteIdDevices"}},"/widget/v1/sites/{siteId}/reset":{"post":{"tags":["Widget"],"summary":"Forget this visitor: the old token stops working and a fresh visitor is issued","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-visitor-token","in":"header","required":true,"description":"Visitor token from bootstrap","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"install_id":{"type":"string","description":"Native SDKs: the install id, kept on the new visitor"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"visitor_token":{"type":"string"},"visitor_id":{"type":"string"}}}}}}},"operationId":"postWidgetV1SitesBySiteIdReset"}},"/widget/v1/sites/{siteId}/sessions/restore":{"post":{"tags":["Widget"],"summary":"Exchange a magic-link token from a chat email for a visitor token (with an app key, the token is minted for that app)","parameters":[{"name":"siteId","in":"path","required":true,"description":"Public site id (site_…)","schema":{"type":"string"}},{"name":"x-hatang-app-key","in":"header","required":false,"description":"Site app key (hak_…) for native SDKs; replaces the browser Origin check. Create one in the dashboard under Support → Channels → your website → Apps.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"visitor_token":{"type":"string"},"visitor_id":{"type":"string"},"conversation_id":{"type":"string"},"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}},"404":{"description":"Unknown or expired link"}},"operationId":"postWidgetV1SitesBySiteIdSessionsRestore"}},"/files/{id}":{"get":{"tags":["Files"],"summary":"Download an attachment with a signed URL","parameters":[{"name":"id","in":"path","required":true,"description":"File id","schema":{"type":"string"}},{"name":"exp","in":"query","required":true,"description":"Expiry (unix seconds)","schema":{"type":"string"}},{"name":"sig","in":"query","required":true,"description":"HMAC signature","schema":{"type":"string"}}],"responses":{"200":{"description":"File bytes (Content-Disposition: attachment)"},"403":{"description":"Bad or expired signature"}},"operationId":"getFilesById"}},"/app/version":{"get":{"tags":["Agent"],"summary":"Support app update check (public): `update` is none, available (below APP_<PLATFORM>_LATEST) or required (below APP_<PLATFORM>_MIN). Apps send `X-Hatang-App: <platform>/<version>` on /agent/* requests; a build below the minimum gets 426 upgrade_required","parameters":[{"name":"platform","in":"query","required":true,"description":"ios | android | macos","schema":{"type":"string"}},{"name":"version","in":"query","required":false,"description":"Installed version, e.g. 1.2.0","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string"},"version":{"type":"string","nullable":true},"latest":{"type":"string","nullable":true},"min":{"type":"string","nullable":true},"update":{"type":"string","enum":["none","available","required"]},"store_url":{"type":"string"}}}}}},"422":{"description":"Unknown platform"}},"operationId":"getAppVersion"}},"/agent/uploads":{"post":{"tags":["Files"],"summary":"Upload an attachment as an agent (Bearer agent token, multipart `file` + `site_id`; 10 MB, videos 25 MB, a .mov is stored as MP4 when the host has ffmpeg)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attachment"}}}}},"operationId":"postAgentUploads"}},"/agent/devices":{"put":{"tags":["Agent"],"summary":"Register the support app on this phone or Mac (Bearer mobile agent token); upserts by device_id. Phones send `expo_push_token`; the Mac app (platform macos) sends `apns_token` and `apns_env`","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDevice"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"device":{"$ref":"#/components/schemas/Device"}}}}}}},"operationId":"putAgentDevices"}},"/agent/devices/{deviceId}":{"delete":{"tags":["Agent"],"summary":"Forget this phone (sign-out)","parameters":[{"name":"deviceId","in":"path","required":true,"description":"Device id minted by the app","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}},"operationId":"deleteAgentDevicesByDeviceId"}},"/agent/devices/{deviceId}/prefs":{"get":{"tags":["Agent"],"summary":"This phone's notification preferences","parameters":[{"name":"deviceId","in":"path","required":true,"description":"Device id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"device":{"$ref":"#/components/schemas/Device"}}}}}}},"operationId":"getAgentDevicesByDeviceIdPrefs"},"patch":{"tags":["Agent"],"summary":"Change mute, event kinds, quiet hours or time zone for this phone","parameters":[{"name":"deviceId","in":"path","required":true,"description":"Device id","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevicePrefs"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"device":{"$ref":"#/components/schemas/Device"}}}}}}},"operationId":"patchAgentDevicesByDeviceIdPrefs"}},"/agent/canned":{"get":{"tags":["Agent"],"summary":"Canned responses: the agent's own (private) ones first, then the team's","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"canned":{"type":"array","items":{"$ref":"#/components/schemas/CannedResponse"}}}}}}}},"operationId":"getAgentCanned"},"post":{"tags":["Agent"],"summary":"Add a canned response: the agent's own, or the team's with `shared` (chat:manage). An empty shortcut is made from the title","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CannedInput"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"canned":{"$ref":"#/components/schemas/CannedResponse"}}}}}},"403":{"description":"Needs chat:reply, or chat:manage to share"},"409":{"description":"Shortcut taken"}},"operationId":"postAgentCanned"}},"/agent/conversations/{id}/scheduled":{"get":{"tags":["Agent"],"summary":"The conversation's scheduled messages (waiting or failed), soonest first","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"scheduled":{"type":"array","items":{"$ref":"#/components/schemas/ScheduledMessage"}}}}}}}},"operationId":"getAgentConversationsByIdScheduled"},"post":{"tags":["Agent"],"summary":"Schedule a reply: sent at `send_at` as this agent's message. At least a minute ahead, within 30 days and before the channel's reply window closes (chat:reply)","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","maxLength":5000},"send_at":{"type":"string","format":"date-time"}},"required":["content","send_at"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"scheduled":{"$ref":"#/components/schemas/ScheduledMessage"}}}}}},"403":{"description":"Needs chat:reply"},"422":{"description":"Time out of range, or the conversation is closed"}},"operationId":"postAgentConversationsByIdScheduled"}},"/agent/scheduled/{id}":{"patch":{"tags":["Agent"],"summary":"Change the text or the time of a scheduled (or failed) message","parameters":[{"name":"id","in":"path","required":true,"description":"Scheduled message id","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","maxLength":5000},"send_at":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"scheduled":{"$ref":"#/components/schemas/ScheduledMessage"}}}}}}},"operationId":"patchAgentScheduledById"},"delete":{"tags":["Agent"],"summary":"Cancel a scheduled message","parameters":[{"name":"id","in":"path","required":true,"description":"Scheduled message id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"scheduled":{"$ref":"#/components/schemas/ScheduledMessage"}}}}}}},"operationId":"deleteAgentScheduledById"}},"/agent/scheduled/{id}/send":{"post":{"tags":["Agent"],"summary":"Send a scheduled message now","parameters":[{"name":"id","in":"path","required":true,"description":"Scheduled message id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"scheduled":{"$ref":"#/components/schemas/ScheduledMessage"}}}}}}},"operationId":"postAgentScheduledByIdSend"}},"/agent/canned/{id}":{"patch":{"tags":["Agent"],"summary":"Edit one of the agent's own responses (the team's need chat:manage); fields are optional","parameters":[{"name":"id","in":"path","required":true,"description":"Canned response id","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CannedInput"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"canned":{"$ref":"#/components/schemas/CannedResponse"}}}}}},"403":{"description":"A team response without chat:manage"},"404":{"description":"Not found, or someone else's private response"}},"operationId":"patchAgentCannedById"},"delete":{"tags":["Agent"],"summary":"Delete one of the agent's own responses (the team's need chat:manage)","parameters":[{"name":"id","in":"path","required":true,"description":"Canned response id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"403":{"description":"A team response without chat:manage"},"404":{"description":"Not found, or someone else's private response"}},"operationId":"deleteAgentCannedById"}},"/agent/profile-links":{"get":{"tags":["Agent"],"summary":"The business's profile links: URL templates (https, {{user_id}} = verified identify() id else the claimed one marked unverified, {{email}}, {{phone}}, {{name}}, {{customer_id}}, {{conversation_id}}, {{site_id}}, {{field.<key>}}, {{data.<key>}}) the client fills per conversation; links with a missing value are hidden","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProfileLink"}}}}}}}},"operationId":"getAgentProfileLinks"}},"/agent/home":{"get":{"tags":["Agent"],"summary":"Home summary for the signed-in employee: chat, orders, tickets, payments and employees sections by role and switched-on module, plus an attention list","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HomeSummary"}}}}},"operationId":"getAgentHome"}},"/agent/reminders":{"get":{"tags":["Agent"],"summary":"The signed-in agent's open reminders (create and complete them over the socket)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"reminders":{"type":"array","items":{"$ref":"#/components/schemas/Reminder"}}}}}}}},"operationId":"getAgentReminders"}},"/agent/agents":{"get":{"tags":["Agent"],"summary":"Employees who can pick up conversations (assign picker)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}}}}}}}},"operationId":"getAgentAgents"}},"/agent/conversations":{"get":{"tags":["Agent"],"summary":"Inbox snapshot: `status` open (default; includes pending) | pending | resolved | closed | all, `scope` mine | unassigned | all, `q`, `after` cursor, `unanswered=1`, `flag`, `limit` ≤ 100","parameters":[{"name":"status","in":"query","required":false,"description":"Status filter","schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"description":"mine | unassigned | all","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Search","schema":{"type":"string"}},{"name":"after","in":"query","required":false,"description":"Cursor: last conversation id of the previous page","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"has_more":{"type":"boolean"}}}}}}},"operationId":"getAgentConversations"}},"/agent/conversations/{id}":{"get":{"tags":["Agent"],"summary":"One conversation with the latest 100 timeline items (messages carry signed attachment links)","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"has_more":{"type":"boolean"}}}}}}},"operationId":"getAgentConversationsById"}},"/agent/conversations/{id}/email":{"get":{"tags":["Agent"],"summary":"Email side of a conversation: subject, CC participants, first-answer deadline and how each emailed reply landed (null when the conversation never used email)","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"object","nullable":true,"properties":{"subject":{"type":"string","nullable":true},"to":{"type":"string","nullable":true},"mailbox":{"type":"string","nullable":true},"participants":{"type":"array","items":{"type":"string"}},"first_response_due_at":{"type":"string","format":"date-time","nullable":true},"statuses":{"type":"object","description":"Chat message id → sent | delivered | opened | clicked | bounced | complained | failed","additionalProperties":{"type":"string"}}}}}}}}}},"operationId":"getAgentConversationsByIdEmail"}},"/agent/conversations/{id}/rating-request":{"post":{"tags":["Agent"],"summary":"Ask the customer for a rating now (whether or not automatic CSAT is on); result \"rated\" when they already did","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"result":{"type":"string","enum":["sent","rated"]}}}}}}},"operationId":"postAgentConversationsByIdRatingRequest"}},"/agent/conversations/{id}/block":{"post":{"tags":["Agent"],"summary":"Block the conversation's customer: this and their later conversations go to Spam","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}}}}}},"operationId":"postAgentConversationsByIdBlock"}},"/agent/conversations/{id}/mute":{"post":{"tags":["Agent"],"summary":"Mute (body { muted: true }) or unmute the conversation for the signed-in agent","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}},"operationId":"postAgentConversationsByIdMute"}},"/agent/conversations/{id}/ai":{"post":{"tags":["Agent"],"summary":"Pause (body { paused: true }) or resume the AI assistant in the conversation","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}},"operationId":"postAgentConversationsByIdAi"}},"/agent/conversations/{id}/learn":{"post":{"tags":["Agent"],"summary":"Teach the AI from one of our public replies: the AI reads the exchange, the closest knowledge and the AI instructions and drafts a Q/A and/or one instruction line (nothing is saved). Needs chat:reply; 429 ai_budget_exhausted, 503 ai_not_configured / ai_unavailable","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"message_id":{"type":"string","description":"The agent's or assistant's public reply"}},"required":["message_id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearningDraft"}}}}},"operationId":"postAgentConversationsByIdLearn"}},"/agent/conversations/{id}/learn/save":{"post":{"tags":["Agent"],"summary":"Save the confirmed draft: the Q/A joins the \"Câu hỏi từ khách\" FAQ (skipped when the same question is there; a matching open knowledge gap is answered), the instruction is appended as \"- …\" (409 instructions_full past 4000 characters). The reply is then re-sent as a message frame with metadata.learned","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearningSaveRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearningSaveResult"}}}}},"operationId":"postAgentConversationsByIdLearnSave"}},"/agent/messages/{id}/recall":{"post":{"tags":["Agent"],"summary":"Recall a message we sent (web chat; personal Zalo on Zalo too); the customer keeps a recalled marker, the team a private note","parameters":[{"name":"id","in":"path","required":true,"description":"Message id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"object"}}}}}}},"operationId":"postAgentMessagesByIdRecall"}},"/agent/customers/{id}":{"get":{"tags":["Agent"],"summary":"The customer card: record, latest notes and the business's custom fields (CRM on and readable)","parameters":[{"name":"id","in":"path","required":true,"description":"Customer id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"customer":{"type":"object"},"notes":{"type":"array","items":{"type":"object"}},"fields":{"type":"array","items":{"type":"object"}}}}}}}},"operationId":"getAgentCustomersById"},"patch":{"tags":["Agent"],"summary":"Edit any of name, email, phone, owner_id, stage, labels, fields","parameters":[{"name":"id","in":"path","required":true,"description":"Customer id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"customer":{"type":"object"}}}}}}},"operationId":"patchAgentCustomersById"}},"/agent/customers/{id}/notes":{"post":{"tags":["Agent"],"summary":"Add a note about the customer (body { body })","parameters":[{"name":"id","in":"path","required":true,"description":"Customer id","schema":{"type":"string"}}],"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"object"}}}}}}},"operationId":"postAgentCustomersByIdNotes"}},"/agent/customers/{id}/notes/{noteId}":{"delete":{"tags":["Agent"],"summary":"Delete one of your own notes","parameters":[{"name":"id","in":"path","required":true,"description":"Customer id","schema":{"type":"string"}},{"name":"noteId","in":"path","required":true,"description":"Note id","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}},"operationId":"deleteAgentCustomersByIdNotesByNoteId"}},"/agent/ai/drafts/{id}":{"post":{"tags":["Agent"],"summary":"Insert or discard the AI assistant's suggested reply","parameters":[{"name":"id","in":"path","required":true,"description":"Draft id","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["inserted","discarded"]}},"required":["status"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}},"operationId":"postAgentAiDraftsById"}},"/c/{token}":{"get":{"tags":["Hosted"],"summary":"Hosted continuation page opened from a chat email; loads the widget full-page and restores the session","parameters":[{"name":"token","in":"path","required":true,"description":"Link token","schema":{"type":"string"}}],"responses":{"200":{"description":"HTML"},"404":{"description":"Expired link"}},"operationId":"getCByToken"}},"/widget.js":{"get":{"tags":["Assets"],"summary":"Widget loader (mutable, 5 min cache)","responses":{"200":{"description":"JavaScript"}},"operationId":"getWidgetJs"}},"/w/{file}":{"get":{"tags":["Assets"],"summary":"Hashed widget bundle (immutable)","parameters":[{"name":"file","in":"path","required":true,"description":"Bundle file name","schema":{"type":"string"}}],"responses":{"200":{"description":"JavaScript"}},"operationId":"getWByFile"}},"/rate/{id}":{"get":{"tags":["Hosted"],"summary":"One-click CSAT from an email: records the score (s=1..5, signed link) and offers a comment form","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"HTML"},"404":{"description":"Invalid link"}},"operationId":"getRateById"},"post":{"tags":["Hosted"],"summary":"Comment for an email rating (form post from the rating page)","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation id","schema":{"type":"string"}}],"responses":{"200":{"description":"HTML"},"404":{"description":"Invalid link"}},"operationId":"postRateById"}},"/preview":{"get":{"tags":["Assets"],"summary":"Live widget preview for the dashboard's site settings: the real bundle with a stub boot and no network, configured by postMessage from the dashboard (the only page allowed to frame it)","responses":{"200":{"description":"HTML"}},"operationId":"getPreview"}}},"components":{"schemas":{"BootstrapRequest":{"type":"object","properties":{"token":{"type":"string","maxLength":2048},"page":{"type":"object","properties":{"url":{"type":"string","maxLength":2048},"title":{"type":"string","maxLength":300},"referrer":{"type":"string","maxLength":2048}},"required":["url"]},"app":{"type":"object","properties":{"install_id":{"type":"string","minLength":8,"maxLength":128},"version":{"type":"string","maxLength":40},"sdk":{"type":"string","maxLength":60},"os":{"type":"string","maxLength":20},"os_version":{"type":"string","maxLength":40},"device_model":{"type":"string","maxLength":80},"locale":{"type":"string","maxLength":10}},"required":["install_id"]},"screen":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"params":{"type":"object","propertyNames":{"type":"string","maxLength":60},"additionalProperties":{"type":"string","maxLength":500}}},"required":["name"]},"campaign":{"type":"object","propertyNames":{"type":"string","maxLength":20,"pattern":"^[a-z_]+$"},"additionalProperties":{"type":"string","maxLength":200}},"locale":{"type":"string","maxLength":10},"analytics":{"type":"object","properties":{"ga_client_id":{"type":"string","maxLength":128},"fbp":{"type":"string","maxLength":128},"fbc":{"type":"string","maxLength":512}}}}},"RegisterDevice":{"oneOf":[{"type":"object","properties":{"device_id":{"type":"string","minLength":8,"maxLength":128},"app_version":{"type":"string","maxLength":40},"device_name":{"type":"string","maxLength":120},"tz":{"type":"string","maxLength":64},"locale":{"type":"string","maxLength":10},"platform":{"type":"string","enum":["ios","android"]},"expo_push_token":{"type":"string","pattern":"^Expo(nent)?PushToken\\[[^\\]\\s]+\\]$"}},"required":["device_id","platform","expo_push_token"]},{"type":"object","properties":{"device_id":{"type":"string","minLength":8,"maxLength":128},"app_version":{"type":"string","maxLength":40},"device_name":{"type":"string","maxLength":120},"tz":{"type":"string","maxLength":64},"locale":{"type":"string","maxLength":10},"platform":{"type":"string","const":"macos"},"apns_token":{"type":"string","pattern":"^[0-9a-fA-F]{64,200}$"},"apns_env":{"default":"production","type":"string","enum":["sandbox","production"]}},"required":["device_id","platform","apns_token"]}]},"RegisterVisitorDevice":{"type":"object","properties":{"token":{"type":"string","minLength":20,"maxLength":4096},"provider":{"default":"fcm","type":"string","enum":["fcm","apns"]},"platform":{"type":"string","enum":["ios","android","web","macos","windows","linux"]},"install_id":{"type":"string","minLength":8,"maxLength":128},"locale":{"type":"string","maxLength":10},"app_version":{"type":"string","maxLength":40},"os_version":{"type":"string","maxLength":40},"device_model":{"type":"string","maxLength":80}},"required":["token","platform"]},"VisitorDevice":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["fcm","apns"]},"platform":{"type":"string"},"token_suffix":{"type":"string"},"locale":{"type":["string","null"]},"app_version":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"last_seen_at":{"type":["string","null"],"format":"date-time"}}},"DevicePrefs":{"type":"object","properties":{"muted":{"type":"boolean"},"muted_until":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}]},"events":{"type":"object","properties":{"visitor_message":{"type":"boolean"},"assigned":{"type":"boolean"},"reminder":{"type":"boolean"},"sla":{"type":"boolean"}}},"quiet_hours":{"anyOf":[{"type":"object","properties":{"start":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"end":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"}},"required":["start","end"]},{"type":"null"}]},"tz":{"type":"string","maxLength":64}}},"Device":{"type":"object","properties":{"id":{"type":"string"},"device_id":{"type":"string"},"platform":{"type":"string","enum":["ios","android","macos"]},"provider":{"type":"string","enum":["expo","apns"],"description":"expo = phones through Expo; apns = the Mac app, straight to Apple"},"apns_env":{"type":["string","null"],"enum":["sandbox","production",null]},"app_version":{"type":["string","null"]},"device_name":{"type":["string","null"]},"tz":{"type":"string"},"locale":{"type":["string","null"]},"muted":{"type":"boolean"},"muted_until":{"type":["string","null"],"format":"date-time"},"events":{"type":"object","properties":{"visitor_message":{"type":"boolean"},"assigned":{"type":"boolean"},"reminder":{"type":"boolean"},"sla":{"type":"boolean"}}},"quiet_hours":{"type":["object","null"],"properties":{"start":{"type":"string"},"end":{"type":"string"}}},"created_at":{"type":"string","format":"date-time"},"last_seen_at":{"type":["string","null"],"format":"date-time"}}},"ProfileLink":{"type":"object","required":["id","label","url"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"url":{"type":"string","description":"https URL template with {{variables}} after the host"},"site_ids":{"type":"array","items":{"type":"string"},"description":"Only for conversations of these sites; absent = every site"}}},"CannedResponse":{"type":"object","properties":{"id":{"type":"string"},"shortcut":{"type":"string"},"title":{"type":"string"},"content":{"type":"string"},"subject":{"type":["string","null"]},"is_private":{"type":"boolean"},"mine":{"type":"boolean","description":"The agent's own response"},"editable":{"type":"boolean","description":"This agent may edit or delete it"},"locale":{"type":["string","null"]}}},"ScheduledMessage":{"type":"object","description":"Also sent live to every agent of the business as the `scheduled` frame","properties":{"id":{"type":"string"},"conversation_id":{"type":"string"},"content":{"type":"string"},"send_at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["scheduled","sent","canceled","failed"]},"error":{"type":["string","null"]},"message_id":{"type":["string","null"],"description":"The message it became, once sent"},"created_by":{"type":["string","null"]},"created_by_name":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CannedInput":{"type":"object","properties":{"title":{"type":"string","maxLength":100},"content":{"type":"string","maxLength":5000},"shortcut":{"type":"string","pattern":"^[a-z0-9-]{1,63}$"},"shared":{"type":"boolean","description":"The team's response (chat:manage) instead of the agent's own"}},"required":["title","content"]},"Agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"role":{"type":"string"}}},"HomeSummary":{"type":"object","properties":{"as_of":{"type":"string","format":"date-time"},"modules":{"type":"array","items":{"type":"string"},"description":"Modules the business has switched on; sections of a switched-off module are left out"},"chat":{"type":"object"},"orders":{"type":"object"},"payments":{"type":"object"},"tickets":{"type":"object"},"employees":{"type":"object"},"attention":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"href":{"type":"string"},"since":{"type":"string","format":"date-time"}}}}}},"Reminder":{"type":"object","properties":{"id":{"type":"string"},"conversation_id":{"type":"string"},"user_id":{"type":"string"},"due_at":{"type":"string","format":"date-time"},"note":{"type":["string","null"]},"done_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"OfflineMessageRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"message":{"type":"string","minLength":1,"maxLength":4000},"website":{"type":"string","maxLength":0}},"required":["message"]},"LeadRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"phone":{"type":"string","maxLength":40},"email":{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"message":{"type":"string","maxLength":2000},"source":{"type":"string","maxLength":100},"fields":{"type":"object","propertyNames":{"type":"string","maxLength":60},"additionalProperties":{"type":"string","maxLength":500}},"page":{"type":"object","properties":{"url":{"type":"string","maxLength":2048},"title":{"type":"string","maxLength":300},"referrer":{"type":"string","maxLength":2048}},"required":["url"]},"website":{"type":"string","maxLength":200}}},"Bootstrap":{"type":"object","properties":{"visitor_token":{"type":"string"},"visitor":{"type":"object"},"config":{"type":"object"},"availability":{"type":"object","properties":{"online":{"type":"boolean"},"within_hours":{"type":"boolean"},"next_open_at":{"type":["string","null"]},"agents_online":{"type":"integer"}}},"conversation":{"type":["object","null"]},"timeline":{"type":"array","items":{"type":"object"}},"timeline_has_more":{"type":"boolean","description":"Older items exist before the first timeline item; fetch them with GET …/messages?before_seq"},"bundle":{"type":"string"},"ws_url":{"type":"string","description":"Connect here; for apps it already carries app and install parameters"},"protocol_version":{"type":"integer"},"app":{"type":["object","null"],"properties":{"id":{"type":"string"},"platform":{"type":"string"}}}}},"Conversation":{"type":"object","properties":{"id":{"type":"string"},"site_id":{"type":"string"},"status":{"type":"string","enum":["open","pending","resolved","closed"]},"seq":{"type":"integer"}}},"VisitorConversation":{"type":"object","properties":{"id":{"type":"string"},"site_id":{"type":"string"},"status":{"type":"string","enum":["open","pending","resolved","closed"]},"seq":{"type":"integer"},"visitor_unread":{"type":"integer"},"last_message_at":{"type":["string","null"],"format":"date-time"},"last_message_preview":{"type":["string","null"]},"last_message_sender_type":{"type":["string","null"]},"first_response_at":{"type":["string","null"]},"resolved_at":{"type":["string","null"]},"subject":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"agent":{"type":["object","null"],"properties":{"name":{"type":"string"},"avatar_url":{"type":["string","null"]}}}}},"Message":{"type":"object","properties":{"id":{"type":"string"},"conversation_id":{"type":"string"},"seq":{"type":"integer"},"sender_type":{"type":"string","enum":["visitor","agent","bot","system"]},"content":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"Attachment":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"type":{"type":"string"}}},"LearningFaq":{"type":"object","required":["question","answer"],"properties":{"question":{"type":"string","maxLength":500},"answer":{"type":"string","maxLength":4000}}},"LearningDraft":{"type":"object","required":["status","reason","faq","instruction","existing","run_id"],"properties":{"status":{"type":"string","enum":["new","already_known","nothing_to_learn"]},"reason":{"type":"string","description":"One short sentence for the agent, in the conversation's language"},"faq":{"oneOf":[{"$ref":"#/components/schemas/LearningFaq"},{"type":"null"}]},"instruction":{"type":["string","null"],"maxLength":500},"existing":{"type":"array","maxItems":3,"items":{"type":"object","properties":{"source_title":{"type":"string"},"excerpt":{"type":"string"}}}},"run_id":{"type":["string","null"],"description":"null when no model call was needed"}}},"LearningSaveRequest":{"type":"object","required":["message_id"],"properties":{"message_id":{"type":"string"},"faq":{"oneOf":[{"$ref":"#/components/schemas/LearningFaq"},{"type":"null"}]},"instruction":{"type":["string","null"],"maxLength":500}}},"LearningSaveResult":{"type":"object","required":["faq_added","instruction_added"],"properties":{"faq_added":{"type":"boolean"},"instruction_added":{"type":"boolean"},"skipped_reason":{"type":"string","enum":["faq_exists","instruction_exists","already_known"]}}}}}}