Ethora AI Widget API

AI Widget integration endpoints. ℹ️ The widget is the embeddable chat experience served by `assistant.js`; visitors are anonymous (XMPP-only Mongo Users tagged `isVisitor:true`), conversations are persistent MUC rooms tagged `chats.type='widget'`, and the AI bot is the App's configured agent.

GitHub OpenAPI

Operations 5

POST /v2/widget/sessions Provision an AI Widget visitor session #
GET /v2/widget/config Public AI Widget config for an app #
GET /v2/apps/{appId}/widget/appearance Get the stored AI Widget appearance for an app #
PUT /v2/apps/{appId}/widget/appearance Replace the stored AI Widget appearance for an app #
GET /v2/apps/{appId}/widget/conversations List AI Widget conversations for an app #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/ethora-com:ethora-com-ai-widget-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

ethora-com-ai-widget-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Platform AI Widget API
  description: 'Ethora Platform API.


    ## Getting Started

    - For new backend and tenant integrations, start with the `/v2/...` endpoints.

    - Use `/v2/apps/{appId}/...` when your backend/admin flow manages a specific target app explicitly.

    - Some legacy operations still exist only under `/v1/...`; when so, the operation description will say so.


    ## Integration Tracks

    - `Frontend / app runtime`: app-auth login/sign-up flows, then user-auth chats/files/sharelinks.

    - `Tenant admin / B2B backend`: explicit app management and app-scoped admin flows under `/v2/apps/{appId}/...`.


    ## Auth Token Legend

    - `App JWT`: app-scoped runtime token for sign-up/login/runtime app operations.

    - `User JWT`: end-user or admin-user session token.

    - `Refresh JWT`: refresh-only token for access token renewal.

    - `Client JWT`: legacy client-side token used only for chat/client credential exchange compatibility flows.

    - `B2B Server JWT`: tenant/backend integration token. Prefer `Authorization: Bearer `; `x-custom-token` is still accepted for backward compatibility.


    ## Which Token Where

    - `App JWT`: `/v1/users/login-with-email`, `/v2/users/sign-up-with-email`, similar app-runtime auth endpoints.

    - `User JWT`: user session endpoints like chats, files, sharelinks, profile/session management.

    - `B2B Server JWT`: tenant/backend integration endpoints, especially explicit `/v2/apps/{appId}/...` admin flows.

    - `Client JWT`: only where the endpoint explicitly says `Client JWT` and typically only for legacy compatibility.


    ## Auth Approach

    - `/v2/...` endpoints are the canonical API surface for new integrations.

    - Endpoints may support one or more token types depending on the actor and scope.

    - App-scoped admin/integration endpoints use `/v2/apps/{appId}/...` so the target app is always explicit.

    - When an endpoint supports both `User JWT` and `B2B Server JWT`, Swagger will show both under Security.

    - Public Swagger excludes internal-only, operational, and experimental surfaces.'
  contact:
    name: Dappros developers team
  version: 26.11.05
servers:
- url: https://api.chat.ethora.com
security:
- JwtAuth: []
tags:
- name: AI Widget
  description: 'AI Widget integration endpoints.

    ℹ️ The widget is the embeddable chat experience served by `assistant.js`; visitors are anonymous (XMPP-only Mongo Users tagged `isVisitor:true`), conversations are persistent MUC rooms tagged `chats.type=''widget''`, and the AI bot is the App''s configured agent.'
paths:
  /v2/widget/sessions:
    post:
      tags:
      - AI Widget
      summary: Provision an AI Widget visitor session
      description: 'Public, unauthenticated endpoint called by the embedded `ethora-ai-chat-widget`

        on mount. Mints (or resumes) an anonymous visitor user in ejabberd, creates a

        persistent MUC room, pre-affiliates the visitor and the App''s AI bot as members,

        and returns the credentials needed for the widget to SASL-bind and join.


        Visitor JIDs follow the convention `${appId}_widget-` so mod_ethora''s

        per-app prefix guard accepts them as members of the room. Rooms follow

        `${appId}_` and are tagged in the platform DB as `chats.type=''widget''`.


        The endpoint is rate-limited per IP (30/minute) since it''s reachable from any

        browser without auth. It validates that the App exists and has an AI bot

        configured before provisioning anything.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - appId
              properties:
                appId:
                  type: string
                  description: The Ethora App _id whose widget the visitor is opening.
                resumeXmppUsername:
                  type: string
                  description: 'Optional resume hint. When supplied, the server reuses the existing

                    visitor identity instead of minting a new one. The widget reads this

                    from localStorage so a returning browser keeps its identity (and its

                    conversation history is reachable to operators by visitor _id).

                    The value MUST start with `${appId}_widget-` or the request 422s.'
      responses:
        '200':
          description: "```\n{\n  visitor: {\n    xmppUsername: string,   // bare localpart, e.g. \"${appId}_widget-<uuid>\"\n    xmppPassword: string,\n    uuid: string,\n    jid?: string             // fully-qualified, \"<localpart>@<host>\"\n  },\n  room: {\n    name: string,           // bare room name, e.g. \"${appId}_<roomUuid>\"\n    chatId: string,         // Mongo _id of the chats row\n    jid?: string            // fully-qualified, \"<name>@conference.<host>\"\n  },\n  bot: {\n    xmppUsername: string,\n    jid?: string\n  },\n  xmpp?: {\n    host: string,           // ejabberd host, e.g. \"xmpp.example.com\"\n    service: string,        // MUC service, e.g. \"conference.xmpp.example.com\"\n    wsUrl: string           // canonical WS URL, e.g. \"wss://xmpp.example.com/ws\"\n  }\n}\n```"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: 'App not found.

            ```

            { error: ''App not found'', code: ''APP_NOT_FOUND'' }

            ```'
        '409':
          description: 'App has no AI bot configured.

            ```

            { error: ''App has no AI bot configured'', code: ''AI_BOT_NOT_CONFIGURED'' }

            ```'
        '422':
          description: 'Validation error (e.g. missing appId, malformed resumeXmppUsername).

            ```

            { error: string, code: ''VALIDATION_ERROR'' }

            ```'
        '429':
          description: 'Rate limit exceeded (default 30 requests / minute / IP).

            ```

            { error: ''Too many requests'', code: ''RATE_LIMITED'' }

            ```'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '502':
          description: 'Underlying ejabberd registration or room-create call failed.

            ```

            { error: string, code: ''XMPP_REGISTER_FAILED'' | ''XMPP_ROOM_CREATE_FAILED'' }

            ```'
      operationId: post_v2_widget_sessions
  /v2/widget/config:
    get:
      tags:
      - AI Widget
      summary: Public AI Widget config for an app
      description: 'Public, unauthenticated endpoint the embedded widget calls at load, before it

        draws the launcher. Returns the appearance the App''s admins saved with

        `PUT /v2/apps/{appId}/widget/appearance`.


        An unknown or inactive App returns an empty `appearance` rather than an

        error, so the endpoint does not reveal which App ids exist. Everything it

        returns is already public on any page that carries the widget snippet.


        Responses carry `Cache-Control: public, max-age=60`, so a change can take a

        minute to reach visitors. Rate-limited per IP (default 300 requests / minute).'
      operationId: v2_widget_config_get
      security: []
      parameters:
      - in: query
        name: appId
        description: The Ethora App _id (24 hex characters) whose widget is loading.
        required: true
        schema:
          type: string
          pattern: ^[0-9a-fA-F]{24}$
      responses:
        '200':
          description: "```\n{\n  ok: true,\n  appearance: { [attribute: string]: string }   // e.g. { 'data-primary-color': '#0052cc' }, {} when none\n}\n```"
        '400':
          description: '`appId` missing or not a 24-hex id.

            ```

            { ok: false, error: ''appId required'' }

            ```'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          description: 'Rate limit exceeded.

            ```

            { success: false, error: ''Too many requests'', code: ''RATE_LIMITED'' }

            ```'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/apps/{appId}/widget/appearance:
    get:
      tags:
      - AI Widget
      summary: Get the stored AI Widget appearance for an app
      description: 'Returns the website widget appearance stored for the App: a map of the

        widget''s `data-*` attribute names to string values, holding only the

        values that differ from the widget''s defaults.


        Auth: tenantActor, with App settings read access (the App''s creator

        always has it).'
      operationId: v2_apps_widget_appearance_get
      parameters:
      - in: path
        name: appId
        description: 'Target App ID. Must be an app the calling actor owns or has been

          delegated access to.

          '
        required: true
        schema:
          type: string
      security:
      - UserJwtAuth: []
      - B2BServerJwtAuth: []
      responses:
        '200':
          description: '```

            { ok: true, appearance: { [attribute: string]: string } }   // {} when nothing is stored

            ```'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Caller has no access to this app.
        '404':
          description: '```

            { error: ''App not found'', code: ''APP_NOT_FOUND'' }

            ```'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
      - AI Widget
      summary: Replace the stored AI Widget appearance for an app
      description: 'Replaces the stored appearance with `appearance`, a map of `data-*`

        attribute names to values. Send only the values that differ from the

        widget''s defaults; `{}` clears everything. Numbers and booleans are

        accepted and stored as strings; a `null` value drops that attribute.


        The embed reads the stored set at load (through `GET /v2/widget/config`) as

        the lowest layer: attributes written on the snippet and URL parameters

        override it.


        Every attribute is checked against an allowlist, and the whole request is

        rejected (422) if any name is unknown or any value invalid:

        - text: `data-title`, `data-greeting-title` (120 chars), `data-greeting`

        (500), `data-greeting-message` (1000), `data-cta-text` (160); no control

        characters.

        - colors (hex, rgb/rgba/hsl/hsla, or a color name): `data-primary-color`,

        `data-secondary-color`, `data-icons-color`, `data-own-bubble-bg`,

        `data-other-bubble-bg`, `data-input-bg`.

        - `data-launcher-gradient`: two colors separated by a comma.

        - sizes (number with px, %, vw, vh, svh, dvh, rem or em, or a `calc()`):

        `data-font-size`, `data-width`, `data-height`, `data-expanded-width`,

        `data-expanded-height`, `data-expanded-inset`.

        - booleans (`true`/`false`, `1`/`0`, `yes`/`no`, stored as `true` or

        `false`): `data-hide-system-messages`, `data-disable-media`,

        `data-allow-fullscreen`, `data-start-fullscreen`, `data-flat-launcher`,

        `data-launcher-glow`, `data-cta-sparkle`.

        - integers: `data-launcher-size` (24 to 160), `data-cta-delay` (0 to 600000).

        - `data-position`: `left` or `right`.

        - `data-locale`: a language tag such as `en` or `pt-BR`.

        - `data-font-family`: letters, digits, spaces, commas, quotes, `_` and `-`

        (120 chars).

        - `data-google-font`: a Google Fonts family spec (120 chars).

        - `data-launcher-icon`: an http(s) URL.


        Identity attributes (`data-app-id`, `data-api-base`, `data-bot-id`) cannot be

        stored: a saved setting never repoints the widget at another App, bot or

        backend.


        Auth: tenantActor, with App settings update access (the App''s creator

        always has it).'
      operationId: v2_apps_widget_appearance_put
      parameters:
      - in: path
        name: appId
        description: 'Target App ID. Must be an app the calling actor owns or has been

          delegated access to.

          '
        required: true
        schema:
          type: string
      security:
      - UserJwtAuth: []
      - B2BServerJwtAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - appearance
              properties:
                appearance:
                  type: object
                  additionalProperties:
                    oneOf:
                    - type: string
                    - type: number
                    - type: boolean
                  example:
                    data-primary-color: '#0052cc'
                    data-position: left
                    data-greeting: Hi! How can we help?
      responses:
        '200':
          description: 'The stored set, values normalised.

            ```

            { ok: true, appearance: { [attribute: string]: string } }

            ```'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Caller has no access to this app.
        '404':
          description: '```

            { error: ''App not found'', code: ''APP_NOT_FOUND'' }

            ```'
        '422':
          description: '`appearance` missing or not an object, or attributes that are unknown or

            hold invalid values (all named in the message).

            ```

            { error: ''Invalid widget appearance: data-foo (not a widget appearance attribute), ...'', code: ''VALIDATION_ERROR'' }

            ```'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/apps/{appId}/widget/conversations:
    get:
      tags:
      - AI Widget
      summary: List AI Widget conversations for an app
      description: 'Returns paginated rows from the `chats` collection where `type=''widget''` and

        `appId` matches. Each row includes resolved visitor identity (joined via

        `user_to_chats` -> users where `isVisitor:true`) so the admin Widget

        Conversations panel can render visitor handle + first-seen timestamps

        without an additional round-trip per row.


        Auth: tenantActor. Base-app admins acting on a child app via App Switcher

        reach this endpoint through the same path as other app-scope chat reads.'
      parameters:
      - in: path
        name: appId
        description: 'Target App ID. Must be an app the calling actor owns or has been

          delegated access to.

          '
        required: true
        schema:
          type: string
      - in: query
        name: limit
        description: 'Maximum number of items to return per page. Server caps vary per

          endpoint; the response carries the actual page size.

          '
        schema:
          type: integer
          minimum: 1
          maximum: 200
          default: 50
      - in: query
        name: offset
        description: 'Number of items to skip from the beginning of the result set. Use

          with `limit` for paging.

          '
        schema:
          type: integer
          minimum: 0
          default: 0
      security:
      - UserJwtAuth: []
      - B2BServerJwtAuth: []
      responses:
        '200':
          description: "```\n{\n  results: [\n    {\n      _id: string,            // Mongo chat _id\n      name: string,           // bare room name, \"${appId}_<roomUuid>\"\n      title: string,\n      type: 'widget',\n      createdAt: ISODate,\n      updatedAt: ISODate,\n      visitor: {\n        _id: string,\n        uuid: string,\n        xmppUsername: string,\n        firstSeenAt: ISODate\n      } | null\n    }\n  ],\n  pagination: { limit: number, offset: number, total: number },\n  total: number, limit: number, offset: number,\n  items: [...same as results...]\n}\n```"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: Missing / invalid token.
        '403':
          description: Caller has no access to this app.
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
      operationId: get_v2_apps_appid_widget_conversations
components:
  responses:
    UnprocessableEntity:
      description: Unprocessable Entity
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Not Found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalServerError:
      description: Internal Server Error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BadRequest:
      description: Bad Request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          description: Always false for error responses.
          example: false
        error:
          type: string
          description: A brief, human-readable message about the error.
        code:
          type: string
          description: A stable machine-readable error code.
          example: VALIDATION_ERROR
        details:
          type: object
          description: Optional, more specific details about the error (e.g., validation errors).
        errors:
          description: Legacy validation errors (if applicable).
        requestId:
          type: string
          description: Request correlation id for debugging/support.
      required:
      - error
      example:
        success: false
        error: Validation Failed
        code: VALIDATION_ERROR
        details:
          field: Reason
        requestId: '...'
  securitySchemes:
    JwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Authorization: Bearer <JWT>'
    AppJwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'App JWT (data.type="app"). Use this token to call app-authorized endpoints (e.g. create user, login, signup). Authorization: Bearer <JWT>. Wrong token type returns code=INVALID_TOKEN_TYPE.'
    UserJwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'User JWT (data.type="user"). Use this token for user-authorized endpoints (e.g. chats/files/sharelinks/me). Authorization: Bearer <JWT>. Wrong token type returns code=INVALID_TOKEN_TYPE.'
    RefreshJwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Refresh JWT (data.type="refresh"). Use this token only for refresh flow. Authorization: Bearer <JWT>. Wrong token type returns code=INVALID_TOKEN_TYPE.'
    ClientJwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Client JWT (data.type="client"). Legacy compatibility token used only for client credential exchange endpoints. Preferred header: x-custom-token; Authorization Bearer may also be accepted by some deployments.'
    B2BServerJwtAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'B2B Server JWT (data.type="server"). Use this token for tenant/backend integration endpoints. Preferred header: Authorization: Bearer <JWT>. Legacy header x-custom-token is also accepted for backward compatibility.'