> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tai42.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Send an operator message into a conversation thread

> Send a message BY HAND into ``thread_id`` on ``route_name`` as the route identity, and
return ``{"message_id", "thread_id"}``. No turn runs: the message is stored already
``answered`` and delivered through the same machine a produced answer takes.

``media`` (a list of ``{"kind", "url", "caption"?, "filename"?}`` display items),
``location`` (a shared map pin ``{"latitude", "longitude", "name"?, "address"?}``),
``template`` (a pre-approved ``{"name", "language", "header_media"?, "body_parameters"?,
"buttons"?}`` out-of-window template), ``options`` (a list of FLAT tappable option objects —
each a ``{"kind": "reply", "text"}`` reply or a ``{"kind": "link", "label", "url"}`` link
action), ``sections`` (the SECTIONED tappable-options alternative — titled groups of reply
rows), ``header``/``footer`` (a media header / trailing line composing an interactive
message) and ``schema`` (an ask-less form's
answer schema — the channel renders ``text`` as the form's prompt, and the participant's
submission enters the conversation as an ordinary inbound message) are OPTIONAL
richer-send forms — FULL parity with the flow answer path's ``AnswerPart`` vocabulary —
delivered ALONGSIDE ``text`` — the message is then stored and delivered as one rich
part, exactly as a produced rich answer is, including the delivery machine's capability
gate (a channel that does not advertise the matching ``supports_*_notifications`` flag
never receives the part; the record fails loudly instead of the field dropping). A
contract-invalid value (an empty list/dict, an over-cap value, or a combination the shared
composition matrix refuses — ``options`` XOR ``sections``, ``schema`` excludes both,
``header``/``footer`` require a choice surface, ``template`` standalone) is a loud 400;
omit them all for a plain text send.

Allowed in either mode and it never flips the mode. Blank ``text`` is a loud 400, and a
present-but-blank ``address`` is a 400. The thread-belongs-to-route guard is the thread
delete's: a route-keyed id must carry the route's ``bridge:{route_name}:`` prefix (400
otherwise), a person thread must be on the named route (404 otherwise). ``address`` picks
the send target on a LINKED person's aggregated thread — it must be one of the person's
addresses (400 otherwise); with no ``address`` the target is the thread's newest record,
and an empty person thread with no ``address`` is a 400. For an agent target that holds
thread memory the message is appended to the thread's checkpoint as an ``assistant`` reply
BEFORE the record is created; an append that fails is a loud 500 and no record is created.
The send takes the thread's per-thread FIFO, so it waits behind an in-flight turn and
never interleaves it; a full queue is a loud, retriable 503. As a live-caller sync door the
wait to acquire the slot is bounded by ``sync_door_wait_seconds``: a wait past it — behind a
turn possibly HITL-paused on another worker — is the loud, retriable 503 ``ThreadBusyError``
rather than a block past the proxy timeout.

Caller authority is the door's grantable ``write`` action — the same write grant that
forgets threads — and the record names the calling operator. An unauthenticated caller
(access control disabled or unbound) is a loud 501, since an operator action must be
attributable.



## OpenAPI

````yaml /openapi.json post /api/conversations/{route_name}/thread/messages
openapi: 3.1.0
info:
  description: The operator HTTP surface served under /api/*.
  title: tai42-skeleton API
  version: 14.0.2
servers: []
security: []
paths:
  /api/conversations/{route_name}/thread/messages:
    post:
      tags:
        - conversations
      summary: Send an operator message into a conversation thread
      description: >-
        Send a message BY HAND into ``thread_id`` on ``route_name`` as the route
        identity, and

        return ``{"message_id", "thread_id"}``. No turn runs: the message is
        stored already

        ``answered`` and delivered through the same machine a produced answer
        takes.


        ``media`` (a list of ``{"kind", "url", "caption"?, "filename"?}``
        display items),

        ``location`` (a shared map pin ``{"latitude", "longitude", "name"?,
        "address"?}``),

        ``template`` (a pre-approved ``{"name", "language", "header_media"?,
        "body_parameters"?,

        "buttons"?}`` out-of-window template), ``options`` (a list of FLAT
        tappable option objects —

        each a ``{"kind": "reply", "text"}`` reply or a ``{"kind": "link",
        "label", "url"}`` link

        action), ``sections`` (the SECTIONED tappable-options alternative —
        titled groups of reply

        rows), ``header``/``footer`` (a media header / trailing line composing
        an interactive

        message) and ``schema`` (an ask-less form's

        answer schema — the channel renders ``text`` as the form's prompt, and
        the participant's

        submission enters the conversation as an ordinary inbound message) are
        OPTIONAL

        richer-send forms — FULL parity with the flow answer path's
        ``AnswerPart`` vocabulary —

        delivered ALONGSIDE ``text`` — the message is then stored and delivered
        as one rich

        part, exactly as a produced rich answer is, including the delivery
        machine's capability

        gate (a channel that does not advertise the matching
        ``supports_*_notifications`` flag

        never receives the part; the record fails loudly instead of the field
        dropping). A

        contract-invalid value (an empty list/dict, an over-cap value, or a
        combination the shared

        composition matrix refuses — ``options`` XOR ``sections``, ``schema``
        excludes both,

        ``header``/``footer`` require a choice surface, ``template`` standalone)
        is a loud 400;

        omit them all for a plain text send.


        Allowed in either mode and it never flips the mode. Blank ``text`` is a
        loud 400, and a

        present-but-blank ``address`` is a 400. The thread-belongs-to-route
        guard is the thread

        delete's: a route-keyed id must carry the route's
        ``bridge:{route_name}:`` prefix (400

        otherwise), a person thread must be on the named route (404 otherwise).
        ``address`` picks

        the send target on a LINKED person's aggregated thread — it must be one
        of the person's

        addresses (400 otherwise); with no ``address`` the target is the
        thread's newest record,

        and an empty person thread with no ``address`` is a 400. For an agent
        target that holds

        thread memory the message is appended to the thread's checkpoint as an
        ``assistant`` reply

        BEFORE the record is created; an append that fails is a loud 500 and no
        record is created.

        The send takes the thread's per-thread FIFO, so it waits behind an
        in-flight turn and

        never interleaves it; a full queue is a loud, retriable 503. As a
        live-caller sync door the

        wait to acquire the slot is bounded by ``sync_door_wait_seconds``: a
        wait past it — behind a

        turn possibly HITL-paused on another worker — is the loud, retriable 503
        ``ThreadBusyError``

        rather than a block past the proxy timeout.


        Caller authority is the door's grantable ``write`` action — the same
        write grant that

        forgets threads — and the record names the calling operator. An
        unauthenticated caller

        (access control disabled or unbound) is a loud 501, since an operator
        action must be

        attributable.
      operationId: post_api_conversations_route_name_thread_messages
      parameters:
        - in: path
          name: route_name
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ThreadMessageSend'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    $ref: '#/components/schemas/ThreadMessageAck'
                required:
                  - data
                type: object
          description: Success.
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Malformed request.
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Missing or invalid api key.
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Resource not found.
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Internal server error.
        '501':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Error.
        '503':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            A dependency this route needs is temporarily unavailable; retry
            shortly.
      security:
        - ApiKeyAuth: []
components:
  schemas:
    ThreadMessageSend:
      description: >-
        The operator-send door's JSON body. ``thread_id`` and ``text`` are
        required and hold at

        least one non-whitespace character; the remaining fields are optional
        richer-send forms

        delivered alongside ``text``. The ``schema`` attribute is suffixed to
        avoid shadowing a

        ``BaseModel`` member; the wire key stays ``schema`` via the alias.


        Spec metadata only — the door parses this body at the HTTP edge.
      properties:
        address:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          description: >-
            On a linked person's aggregated thread, the person address to
            target; omitted, the target is the thread's newest record.
          title: Address
        footer:
          anyOf:
            - type: string
            - type: 'null'
          default: null
          description: A trailing line composing an interactive message.
          title: Footer
        header:
          anyOf:
            - additionalProperties:
                $ref: '#/components/schemas/JsonValue'
              type: object
            - type: 'null'
          default: null
          description: A media header composing an interactive message.
          title: Header
        location:
          anyOf:
            - additionalProperties:
                $ref: '#/components/schemas/JsonValue'
              type: object
            - type: 'null'
          default: null
          description: A shared map pin ``{latitude, longitude, name?, address?}``.
          title: Location
        media:
          anyOf:
            - items:
                additionalProperties:
                  $ref: '#/components/schemas/JsonValue'
                type: object
              type: array
            - type: 'null'
          default: null
          description: Display media items delivered alongside the text.
          title: Media
        options:
          anyOf:
            - items:
                additionalProperties:
                  $ref: '#/components/schemas/JsonValue'
                type: object
              type: array
            - type: 'null'
          default: null
          description: Flat tappable option objects — reply or link actions.
          title: Options
        schema:
          anyOf:
            - additionalProperties:
                $ref: '#/components/schemas/JsonValue'
              type: object
            - type: 'null'
          default: null
          description: >-
            An ask-less form's answer schema; the channel renders ``text`` as
            the prompt.
          title: Schema
        sections:
          anyOf:
            - items:
                additionalProperties:
                  $ref: '#/components/schemas/JsonValue'
                type: object
              type: array
            - type: 'null'
          default: null
          description: >-
            Titled groups of tappable reply rows — the sectioned options
            alternative.
          title: Sections
        template:
          anyOf:
            - additionalProperties:
                $ref: '#/components/schemas/JsonValue'
              type: object
            - type: 'null'
          default: null
          description: A pre-approved out-of-window template to deliver.
          title: Template
        text:
          description: The message text to send.
          minLength: 1
          pattern: \S
          title: Text
          type: string
        thread_id:
          description: The thread to send into, as the send door returned it.
          minLength: 1
          pattern: \S
          title: Thread Id
          type: string
      required:
        - thread_id
        - text
      title: ThreadMessageSend
      type: object
    ThreadMessageAck:
      description: The stored id of an operator message sent by hand into a thread.
      properties:
        message_id:
          title: Message Id
          type: string
        thread_id:
          title: Thread Id
          type: string
      required:
        - message_id
        - thread_id
      title: ThreadMessageAck
      type: object
    Error:
      properties:
        code:
          description: >-
            Stable machine-readable reason a client keys a dedicated error state
            on, present on refusals that opt in (e.g. a 501 not-configured
            refusal). Optional: absent when the error carries only a
            human-readable message.
          type: string
        error:
          type: string
      required:
        - error
      type: object
    JsonValue: {}
  securitySchemes:
    ApiKeyAuth:
      in: header
      name: x-api-key
      type: apiKey

````