Answer a widget

October 12, 2026

Table of contents

  1. Request Headers
  2. Request Body
  3. Responses
  4. Model
  5. Examples
  6. Try It

The Muse agent sometimes puts an interactive card, a widget, into its message, for example a few quick-reply choices or a chart. The stream sends each one as a widget event, see GET /chats/chatId/stream, and GET /chats/chatId lists them in messages[].widgets.

kind Fields How to show it
option options Quick-reply choices. Show one button per option, and answer with POST /chats/chatId/widgets/widgetId.
html html A card the agent wrote, such as a chart or a table. It may contain scripts. Show it only inside a sandboxed iframe, sandbox="allow-scripts", never with allow-same-origin. Show fallbackText when you don’t render it.
any other data Muse’s widget data as it is. Other kinds may appear: show fallbackText when you can’t render one.

An html card uses these CSS variables for its colors, so define them in the iframe’s page to match your design: --hatch-widget-text, --hatch-widget-muted, --hatch-widget-accent, --hatch-widget-surface, --hatch-widget-surface-muted, --hatch-widget-border. For example, pass the card as srcdoc:

<iframe sandbox="allow-scripts" srcdoc="<style>:root { --hatch-widget-text: #1f2328; --hatch-widget-muted: #656d76; --hatch-widget-accent: #0969da; --hatch-widget-surface: #ffffff; --hatch-widget-surface-muted: #f6f8fa; --hatch-widget-border: #d0d7de }</style> …the widget's html…"></iframe>

A widget can also arrive on its own, for example an html card the agent presents by itself: its messageId carries no text (in GET /chats/chatId, an agent message with empty text and that widget), and its widgetId is not of the widget-… form. Only option widgets inside a message can be answered through the API.

Answering sends the chosen option’s text as your next message, the same as tapping it in the muse.ai app. The reply streams as usual, like for any message, see POST /chats/chatId/messages.

An option widget event looks like this:

{
  "event": "widget",
  "messageId": "assistant-msg-2ed3bb21-13fe-4759-a8a6-2a8361247e61",
  "widgetId": "widget-f8a4ee04-d3bb-4cc3-b9d5-108a39ba58c4",
  "kind": "option",
  "text": "Set up GitHub repo sync / I'll just attach files in chat",
  "fallbackText": "- Set up GitHub repo sync\n- I'll just attach files in chat",
  "options": [
    "Set up GitHub repo sync",
    "I'll just attach files in chat"
  ]
}

An html widget event looks like this (the html is shortened):

{
  "event": "widget",
  "messageId": "assistant-msg-b54a2c1e-7d3f-4e8a-9b6c-0f1e2d3c4b5a",
  "widgetId": "widget-c29f94b9-647b-45c0-8183-c3bf1c83f502",
  "kind": "html",
  "text": null,
  "fallbackText": "Bar chart of today's top news topics by coverage intensity",
  "html": "<div style=\"padding:16px;color:var(--hatch-widget-text)\">…</div>"
}

https://api.useapi.net/v1/muse/chats/chatId/widgets/widgetId

  • chatId is URL-encoded in the path (it contains : and @).
  • widgetId is the widgetId of the widget.
Request Headers
Authorization: Bearer {API token}
Content-Type: application/json
Request Body
{
  "option": 1
}

Send exactly one of these:

  • option is the index of the chosen entry in the widget’s options, starting at 0.
  • text is your own reply instead of one of the options.
    Maximum length: 5000 characters.
Responses
  • 202 Accepted — the answer was sent as your message. It shows on the stream as a message event with this messageId.

    {
      "chatId": "c-u12345-email:[email protected]:a1e7ec87-b9ba-41ba-8c99-c9da82cb63d1-bot:muse",
      "email": "[email protected]",
      "messageId": "f20f589c-1c71-46b4-8bb9-8b6a753d23e3"
    }
    
  • 400 Bad Request — neither or both of option and text were sent, option is not one of the widget’s options, or the widget’s kind cannot be answered.

    {
      "error": "option must be 0 to 1",
      "code": 400
    }
    
    {
      "error": "Pass either option (the index of one of the widget options) or text",
      "code": 400
    }
    
  • 401 Unauthorized

    Invalid API token.

    {
      "error": "useapi.net ⁝ Unauthorized",
      "code": 401
    }
    
  • 403 Forbidden — the chatId was issued to a different API token.

    {
      "error": "This chat id does not belong to this API token",
      "code": 403
    }
    
  • 404 Not Found — the widget is not in this chat, or the account that holds the chat is not added.

    {
      "error": "Widget not found in this chat",
      "code": 404
    }
    
  • 429 Too Many Requests — Muse is limiting requests on the account. Retry in about a minute.

  • 502 Bad Gateway — Muse did not accept the answer. Retry.

    {
      "error": "Muse did not accept the message (HTTP 400). Please retry",
      "code": 502
    }
    
  • 503 Service Unavailable — Muse could not be reached, and nothing was sent. Retry shortly.

  • 504 Gateway Timeout — the connection to Muse dropped while the answer was being sent. It may have arrived: check GET /chats/chatId before you send it again.

  • 596 Account Error

    The account needs your attention, and it is paused until you add it again. You receive an email when this happens. There are two causes.

    Meta asks for a security check on the account. Sign in once at muse.ai in a browser, complete the check, then add the account again with POST /accounts using the same email and password. The API keeps using its session, so no new sign-in is needed.

    {
      "error": "Account [email protected]: Meta asks for a security check on account [email protected] (\"This account must complete a security checkpoint on the web\"). Sign in once at https://muse.ai in a browser, complete the check, then add the account again with POST /accounts",
      "code": 596
    }
    

    Or Muse refused to sign the account in again when its session needed renewing, most often because the password was changed. Add the account again with the current password.

    {
      "error": "Account [email protected]: Muse refused to sign in account [email protected] (…). Update its password with POST /accounts, or sign in at https://muse.ai to clear the block, then re-add it",
      "code": 596
    }
    
Model
{ // TypeScript, all fields are optional
  chatId: string
  email: string             // the account the chat runs on
  messageId: string | null  // Muse's id of your answer, as the stream's `message` event shows it
  error?: string            // errors only
  code?: number             // errors only
}
Examples
  • CHATID="c-u12345-email:[email protected]:a1e7ec87-b9ba-41ba-8c99-c9da82cb63d1-bot:muse"
    WIDGETID="widget-f8a4ee04-d3bb-4cc3-b9d5-108a39ba58c4"
    curl -X POST "https://api.useapi.net/v1/muse/chats/$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "$CHATID")/widgets/$WIDGETID" \
       -H "Content-Type: application/json" \
       -H "Authorization: Bearer …" \
       -d '{"option": 1}'
    
  • const token = "API token";
    const chatId = "chatId from POST /chats";
    const widgetId = "widgetId from the widget event";
    const apiUrl = `https://api.useapi.net/v1/muse/chats/${encodeURIComponent(chatId)}/widgets/${encodeURIComponent(widgetId)}`;
    const response = await fetch(apiUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${token}`,
      },
      body: JSON.stringify({ option: 1 })
    });
    console.log("response", response.status, await response.json());
    
  • import requests
    from urllib.parse import quote
    token = "API token"
    chatId = "chatId from POST /chats"
    widgetId = "widgetId from the widget event"
    apiUrl = f"https://api.useapi.net/v1/muse/chats/{quote(chatId, safe='')}/widgets/{quote(widgetId, safe='')}"
    headers = {
        "Content-Type": "application/json",
        "Authorization" : f"Bearer {token}"
    }
    response = requests.post(apiUrl, headers=headers, json={"option": 1})
    print(response, response.json())
    
Try It

Fill in either option or text.