On this page
Response Formats#
Conversation API responses can be shaped to fit the channel you're delivering them on. Configure the format once per API key in the Console — the same chat agent runs, only the response envelope changes.
markdown and continue to receive the same content/products/actions shape they always have. The only additive change is a new top-level format field on every non-error response — strict schema validators should add it to their parser.Available Formats#
| Format | Use when | Response shape adds |
|---|---|---|
markdown | Web chat UIs, existing integrations | Nothing — today's shape (now with format) |
plain_text | SMS bridges, voice agents, no-UI flows | content is plain text; links flattened to text (url); images dropped |
facebook_messenger | Middleware bridging FB Messenger → your bot → Gydr | elements (≤10), quick_replies (≤13), attachments |
instagram_messaging | Instagram Direct middleware | elements (≤10), quick_replies (≤13), attachments — web_url/postback buttons only, ≤1000-char text |
whatsapp_business | WhatsApp Cloud API middleware | buttons (≤3) OR list (≤10), media_items (≤5), native locations, attachments |
Configuring the Format#
- Go to Console → Settings → Developer
- Create a new API key or edit an existing one — the Response Content Format dropdown unlocks once the
conversations:chatscope is enabled - Pick the format and save
- Changes take effect immediately — the next call to
POST /widgets/{widgetId}/conversations/{sessionId}/messagesuses the new format
Response Examples#
Same agent turn, four shapes. All examples assume the agent returned a couple of product suggestions and a handoff quick reply.
{
"data": {
"messageId": "msg_01HX...",
"format": "markdown",
"content": "Here are two pizzas you might like:\n\n- **Margherita** — RM28\n- **Pepperoni** — RM32\n\nLet me bring in a member of our team to help you with this.",
"products": [
{ "id": "p1", "name": "Margherita", "price": 28, "currency": "MYR", "image": "https://cdn/..." },
{ "id": "p2", "name": "Pepperoni", "price": 32, "currency": "MYR", "image": "https://cdn/..." }
],
"actions": [
{ "label": "Talk to staff", "value": "handoff:int_support" }
],
"handoff": true,
"sessionEnded": false,
"credits": { "chat": 12, "retrieval": 2, "orchestration": 1, "embedding": 0, "rerank": 1, "plugin": 2, "pluginRerank": 0, "crawler": 0 },
"usage": { "inputTokens": 482, "outputTokens": 73 }
}
}The credits Field#
Every conversation response carries a credits object alongside usage. It has seven numeric keys — chat, retrieval, orchestration, embedding, rerank, plugin, and crawler — and all seven are always present, so you can read any key without a presence check. A key that did not apply to the turn is 0. What each key meters is documented in How Credits Work.
Breaking change (1.31.0)
The credits.api field was renamed to credits.plugin, and three new buckets were added — retrieval, orchestration, and crawler. If your integration reads credits.api, update it to read credits.plugin.
Mixed Knowledge Base Example (Facebook Messenger)#
When the agent surfaces both products and locations in the same turn, they share the 10-element cap. Products come first, then locations. FAQs and document chunks stay inline in content — they don't produce elements.
{
"data": {
"messageId": "msg_01HX...",
"format": "facebook_messenger",
"content": "Our delivery policy is free over RM50 (FAQ). Two outlets near you are open now.",
"elements": [
{
"title": "Margherita",
"subtitle": "RM28",
"image_url": "https://cdn/margherita.jpg",
"buttons": [{ "type": "web_url", "title": "Order", "url": "https://shop/margherita" }]
},
{
"title": "KL Sentral Outlet",
"subtitle": "Level 2, Nu Sentral, 50470 KL",
"image_url": "https://cdn/outlets/kl-sentral.jpg",
"buttons": [
{ "type": "web_url", "title": "View on map", "url": "https://maps.google.com/?q=3.1349,101.6869" },
{ "type": "phone_number", "title": "Call", "payload": "+60312345678" },
{ "type": "web_url", "title": "Website", "url": "https://example.com/kl-sentral" }
]
},
{
"title": "Bangsar Outlet",
"subtitle": "Jln Maarof, Bangsar, 59100 KL",
"image_url": "https://cdn/outlets/bangsar.jpg",
"buttons": [
{ "type": "web_url", "title": "View on map", "url": "https://maps.google.com/?q=3.1262,101.6724" },
{ "type": "phone_number", "title": "Call", "payload": "+60312345679" }
]
}
],
"products": [ /* ... */ ],
"locations": [ /* still present with full lat/lng */ ],
"actions": [],
"handoff": false,
"sessionEnded": false,
"credits": { /* ... */ },
"usage": { /* ... */ }
}
}Action Protocol (handoff & end-session)#
Quick reply / button payloads round-trip the raw action.value from the agent. Two prefixes are signals your middleware reads; everything else is plain text:
handoff:<integrationId>— a human team is ready for live chat. The id is the team's integration id in your account (for examplehandoff:int_support). It is a signal: the Conversation API never starts a live handoff itself, so your code begins the live chat on your side. It appears only after Gydr confirmed the team can be reached, and only when a single team is reachable.handoff:prompt— several teams are reachable and the visitor must choose. Channels that render actions use it as the cue to ask "which team?". On the Conversation API you will instead receive the team pick as the response'sform(withhumanAgentStep: "team_pick"andhandoff: false); see the section below.end_session:<reason>— an offer to end the session (e.g.end_session:manual). Ending is your call: it is a button for your UI, not something Gydr does on its own. To end, call the end endpoint.- Any other value — sent as a plain user message
Your code owns these values. Never post one back as a message: a live handoff starts on your side, handoff steps are answered through the form-submissions endpoint below, and a session ends through the end endpoint. Sending handoff:..., end_session:... or a handoff form's option value (#HANDOFF#..., #HUMANAGENT#...) as the message content returns 400 protocol_value_not_a_message on the content field. No credit is used.
humanAgentStep and server-built forms#
When a visitor asks for a person, shows frustration, or the bot fails repeatedly, the response carries a form that Gydr built itself (not the model) as its single form, and repeats its step at the top level as humanAgentStep so you can branch without reading the form: offer, team_pick or ticket_form. A single reachable Live desk is not a form: it arrives as handoff: true with a handoff:<id> action. A form has the same shape as every other form and is answered the same way, through POST .../form-submissions; the answer is decided by Gydr with no model turn. Example, a team pick:
{
"data": {
"format": "markdown",
"content": "Which team would you like to reach?",
"handoff": false,
"humanAgentStep": "team_pick",
"form": {
"formId": "df_3f9a1c0b7d2e4a65b8c01",
"origin": "human_agent_choice",
"humanAgentStep": "team_pick",
"cancellable": true,
"questions": [
{
"key": "team_pick",
"kind": "choice",
"prompt": "Which team would you like to reach?",
"required": true,
"multiSelect": false,
"options": [
{ "value": "#HANDOFF#int_billing", "label": "Billing" },
{ "value": "#HANDOFF#int_sales", "label": "Sales" }
]
}
]
}
}
}Submit the chosen option's value under the question's key. The step table, the live-chat signal, tickets and their credit cost are documented under Sessions & Messages. The same fields appear in every output format; the channel-specific envelopes (quick replies, buttons) carry only the actions.
WhatsApp 24-Hour Customer Service Window#
Channel Mapping Reference#
| Gydr field | FB Messenger | WhatsApp Cloud API |
|---|---|---|
| content | Text message body (≤2000 chars, chunked as string[] if longer) | Text body in WA inline syntax (≤4096 chars, chunked similarly) |
| elements | Generic Template carousel elements array (≤10; ≤3 buttons each) | Not directly mapped — use media_items instead |
| quick_replies | quick_replies array (≤13) | Not present — use buttons or list |
| buttons | Not present — use quick_replies or per-element buttons | Interactive reply-buttons (1–3) |
| list | Not present — promote to multi-element or multiple replies | Interactive list message (≤10 rows; auto-promoted when actions > 3) |
| media_items | Map to per-card image + buttons | Send each as image + caption + CTA URL button (≤5) |
| locations | Render as a card with map link + phone button | Native location message type |
| attachments | Image attachment messages | Media messages (image / video / document) |
Instagram Messaging uses the same elements / quick_replies shape as Facebook, with two differences: buttons are web_url/postback only (no phone/Call button — a location's phone is folded into the element subtitle), and the text body is capped at 1000 characters.
