asyncapi: 3.0.0

info:
  title: Apollo API — Messaging WebSocket surface
  version: 0.1.0
  description: >
    Real-time messaging over WebSocket: a bidirectional session for live chat
    UIs, complementing the HTTP and SSE transports of
    `POST /messaging/v1/messages`. The REST API is described by the OpenAPI
    document; this AsyncAPI document is the contract for the WebSocket
    session.


    Send a `message` frame to submit a turn — when `thread_id` is omitted, a
    new thread is created and announced back. The agent reply streams as
    `event` token deltas, terminated by a final `message` frame. If the
    connection drops, use the `seq`-based resume protocol to recover any
    missed events.


    **Operation perspective:** operations are described from the API's point
    of view. `receive` covers the frames you send to the API; `send` covers
    the sequenced event envelopes the API sends to you.


    **Authentication:** exchange your publishable key for a short-lived
    access token at `POST /management/v1/auth/token`, then open the socket
    with `Authorization: Bearer <access_token>`.


    **Close codes:** `1008` — authentication or permission failure; `1011` —
    internal error.

servers:
  production:
    host: api-v3.aui.io
    pathname: /apollo-api
    protocol: wss
    description: Production API endpoint.
    security:
      - $ref: '#/components/securitySchemes/bearerToken'

channels:
  session:
    address: /messaging/v1/session
    title: Messaging session
    # Fern audience: the WS session belongs to the messaging SDK client,
    # matching the x-fern-audiences tags on the REST /messaging/v1 routes.
    x-fern-audiences:
      - messaging
    description: >
      One bidirectional session per socket. Send a `message` frame to submit a
      turn (a new thread is auto-created when `thread_id` is omitted); the agent
      reply streams back as `event` token deltas terminated by a `message`
      frame.
    bindings:
      ws:
        method: GET
        bindingVersion: 0.1.0
        query:
          type: object
          additionalProperties: false
          properties:
            include_trace:
              type: boolean
              default: true
              description: When true, the final `message` frame carries `data.trace_info` (the reasoning Trace).
            events:
              type: string
              enum: [all, verbose]
              description: >
                Omit for the minimal stream (token deltas + completion). `all`/`verbose`
                opts into the full workflow/lifecycle event set.
            resume_after:
              type: integer
              minimum: -1
              description: >
                With `task_id`, replays buffered envelopes with `seq > resume_after`
                on connect (gap recovery after a reconnect).
            task_id:
              type: string
              description: Thread/task id to replay from on connect (pairs with `resume_after`).
    messages:
      message:
        $ref: '#/components/messages/Message'
      resume:
        $ref: '#/components/messages/Resume'
      threadAnnouncement:
        $ref: '#/components/messages/ThreadAnnouncement'
      agentMessage:
        $ref: '#/components/messages/AgentMessage'
      forwardedEvent:
        $ref: '#/components/messages/ForwardedEvent'
      errorFrame:
        $ref: '#/components/messages/ErrorFrame'

operations:
  sessionReceive:
    action: receive
    channel:
      $ref: '#/channels/session'
    x-fern-audiences:
      - messaging
    title: Client → API (submit / resume)
    messages:
      - $ref: '#/channels/session/messages/message'
      - $ref: '#/channels/session/messages/resume'
  sessionSend:
    action: send
    channel:
      $ref: '#/channels/session'
    x-fern-audiences:
      - messaging
    title: API → client (sequenced envelope stream)
    messages:
      - $ref: '#/channels/session/messages/threadAnnouncement'
      - $ref: '#/channels/session/messages/agentMessage'
      - $ref: '#/channels/session/messages/forwardedEvent'
      - $ref: '#/channels/session/messages/errorFrame'

components:
  securitySchemes:
    bearerToken:
      type: http
      scheme: bearer
      description: >
        Short-lived bearer access token obtained by exchanging a publishable
        key at `POST /management/v1/auth/token` (`publishable_key` grant).
        Sent on the WebSocket upgrade request as
        `Authorization: Bearer <access_token>`.

  messages:
    # ---- client → API ---------------------------------------------------------
    # Named `message` (not submitMessage) so Fern's send<MessageName> convention
    # yields sendMessage, matching the frame's own type: "message". The payload
    # type keeps the SubmitMessageRequest name to avoid clashing with the REST
    # client's SendMessageRequest.
    Message:
      name: message
      title: Submit a message turn
      summary: Append to a thread (or auto-create one) and run the agent.
      contentType: application/json
      payload:
        $ref: '#/components/schemas/SubmitMessageRequest'
    Resume:
      name: resume
      title: Resume / replay
      summary: Replay buffered envelopes with seq > resume_after for the active thread.
      contentType: application/json
      payload:
        $ref: '#/components/schemas/ResumeRequest'

    # ---- API → client (EventEnvelope variants) --------------------------------
    ThreadAnnouncement:
      name: threadAnnouncement
      title: Thread announced
      summary: The resolved thread id, sent once per thread per connection.
      contentType: application/json
      payload:
        $ref: '#/components/schemas/ThreadEnvelope'
    AgentMessage:
      name: agentMessage
      title: Agent message (turn terminal)
      summary: The completed agent reply; carries trace_info when include_trace=true.
      contentType: application/json
      payload:
        $ref: '#/components/schemas/MessageEnvelope'
    ForwardedEvent:
      name: forwardedEvent
      title: Streaming event
      summary: A streaming event emitted while the agent works (token deltas, lifecycle, …).
      contentType: application/json
      payload:
        $ref: '#/components/schemas/EventEnvelope'
    ErrorFrame:
      name: errorFrame
      title: Error
      summary: A recoverable error; the socket stays open.
      contentType: application/json
      payload:
        $ref: '#/components/schemas/ErrorEnvelope'

  schemas:
    # ---- client → API payloads ------------------------------------------------
    SubmitMessageRequest:
      type: object
      additionalProperties: false
      required: [type, agent_id]
      properties:
        type:
          type: string
          const: message
        agent_id:
          type: string
          pattern: '^[0-9a-fA-F]{24}$'
          description: Target agent (required, billable).
        thread_id:
          type: [string, "null"]
          description: Present → append to this thread; absent → auto-create one and announce it.
        user_id:
          type: [string, "null"]
          description: End-user ref for an auto-created thread.
        text:
          type: string
          default: ""
        image_url:
          type: [string, "null"]
          description: Signed image URL for vision input.
        context:
          type: [object, "null"]
          additionalProperties: false
          properties:
            url:
              type: [string, "null"]
              description: Originating page URL.
            welcome_message:
              type: [string, "null"]
        agent_variables:
          type: object
          additionalProperties: true
          default: {}
          description: Arbitrary runtime variables for the turn.

    ResumeRequest:
      type: object
      additionalProperties: false
      required: [type, resume_after]
      properties:
        type:
          type: string
          const: resume
        resume_after:
          type: integer
          description: Replay buffered envelopes with seq > resume_after for the active thread.

    # ---- API → client: the EventEnvelope --------------------------------------
    EventEnvelopeBase:
      type: object
      required: [seq, type, data]
      properties:
        seq:
          type: integer
          description: Monotonic sequence for gap detection / replay. Locally-generated errors use -1.
        type:
          type: string
          enum: [thread, message, event, error]
        data:
          description: Shape depends on `type`.

    ThreadEnvelope:
      allOf:
        - $ref: '#/components/schemas/EventEnvelopeBase'
      properties:
        type:
          const: thread
        data:
          type: object
          required: [thread_id]
          properties:
            thread_id:
              type: string

    MessageEnvelope:
      description: type=message — the completed agent reply (Message + optional trace_info).
      allOf:
        - $ref: '#/components/schemas/EventEnvelopeBase'
      properties:
        type:
          const: message
        data:
          allOf:
            - $ref: '#/components/schemas/Message'
            - type: object
              properties:
                trace_info:
                  $ref: '#/components/schemas/Trace'

    EventEnvelope:
      description: type=event — a streaming event emitted while the agent works.
      allOf:
        - $ref: '#/components/schemas/EventEnvelopeBase'
      properties:
        type:
          const: event
        data:
          $ref: '#/components/schemas/StreamEvent'

    ErrorEnvelope:
      description: type=error — either a session-level error (seq -1) or an error raised while processing the turn.
      allOf:
        - $ref: '#/components/schemas/EventEnvelopeBase'
      properties:
        type:
          const: error
        data:
          oneOf:
            - type: object
              description: Session-level error (invalid frame, insufficient balance, connection loss).
              required: [error]
              properties:
                error:
                  type: string
                code:
                  type: integer
                  description: Equivalent HTTP status (e.g. 402 insufficient balance, 400 bad request).
            - type: object
              description: Error raised while processing the turn.
              properties:
                message:
                  type: string
                status_code:
                  type: integer
                error_code:
                  type: string

    StreamEvent:
      type: object
      additionalProperties: true
      description: >
        Streaming event. Which event types are delivered depends on the
        `events` query parameter chosen when opening the session.
      properties:
        channel:
          type: object
          properties:
            event_name:
              type: string
              description: >
                Default stream: thread-message-text-content-updated (token delta),
                thread-message-text-content-completed (stream end).
                ?events=all|verbose adds the full workflow/lifecycle set.
        scope:
          type: object
          additionalProperties: true
        data:
          description: Event-specific payload. Token deltas carry `data.text`.

    # ---- Message (same shape as the REST threads surface) ---------------------
    Message:
      type: object
      description: One message in a thread transcript.
      required: [id]
      properties:
        id:
          type: string
          description: Interaction id (or message id for interaction-less messages).
        created_at:
          type: [string, "null"]
          description: ISO-8601.
        text:
          type: [string, "null"]
        sender:
          $ref: '#/components/schemas/MessageParty'
        receiver:
          $ref: '#/components/schemas/MessageParty'
        cards:
          type: array
          default: []
          items:
            $ref: '#/components/schemas/MessageCard'
        welcome_message:
          type: [string, "null"]
        followup_suggestions:
          type: array
          default: []
          items:
            type: string
        url:
          type: [string, "null"]
        input_tokens:
          type: integer
          default: 0
        output_tokens:
          type: integer
          default: 0

    MessageParty:
      type: [object, "null"]
      properties:
        id:
          type: [string, "null"]
        type:
          type: [string, "null"]

    MessageCard:
      type: object
      properties:
        name:
          type: [string, "null"]
        category:
          type: [string, "null"]
        rendered_jsx:
          type: [string, "null"]
        is_recommended:
          type: [boolean, "null"]
        index:
          type: [integer, "null"]

    Trace:
      type: object
      description: >
        Reasoning trace for the interaction, embedded only when
        include_trace=true. Same shape as the REST `Trace` model
        (GET /messaging/v1/threads/{threadId}/trace); summarized here.
      properties:
        input:
          type: object
          properties:
            message:
              type: [string, "null"]
        understanding:
          type: object
          additionalProperties: true
        decisions:
          type: array
          items:
            type: object
            additionalProperties: true
        computed_parameters:
          type: array
          items:
            type: object
            additionalProperties: true
        response:
          type: object
          additionalProperties: true
        rules_evaluations:
          type: array
          items:
            type: object
            additionalProperties: true
