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

# Send a message to the agent and wait for the reply

> Run one turn of conversation on the agent through the ordinary Chattler runtime: the agent's system prompt, knowledge base, tools, and dialog history all apply, and the reply is paid for from the owner's balance exactly like any other channel. The conversation is identified by the API key and `external_user_id`: every message from the same external user through the same key lands in the same conversation, and another key never sees it.

## Authentication
- **Authorization**: `Bearer cht_live_<key_id>.<secret>`; the key must have the `full` permission.

## Headers
- **Idempotency-Key** (required): any client-chosen value of 1-255 characters, unique per logical request. Retrying with the same value and the same body returns the original result without a second reply or charge.

## Path Parameters
- **agent_id**: The agent the key is bound to

## Request Body
- **external_user_id**: stable id of the end user on your side, 1-255 characters
- **message**: the user's text; must not be empty. Attachments are not accepted in this release, and unknown fields are rejected.

## Example Request
```bash
curl -X POST "/v1/agents/{agent_id}/chat" \
  -H "Authorization: Bearer cht_live_{key_id}.{secret}" \
  -H "Idempotency-Key: 7f20a5f0-5dde-48cf-94aa-38acbbd218e0" \
  -H "Content-Type: application/json" \
  -d '{"external_user_id": "customer-123", "message": "I want to know the price"}'
```

## Response
- **request_id**: UUID of this request; also returned as `X-Request-ID`
- **conversation_id**: the conversation the exchange was stored in
- **message**: the persisted assistant reply: `id`, `role` (`assistant`), `content`, `created_at`
- **usage**: `prompt_tokens`, `completion_tokens`, `total_tokens`, and `charged_usd` (decimal string) as metered and charged for this request

A replayed request returns the original `request_id`, message, and usage.

## Errors
Envelope: `{"error": {"code": "...", "message": "...", "request_id": "..."}}`.
- `invalid_api_key` (401), `agent_mismatch` (403)
- `permission_denied` (403): the key is `read-only`
- `agent_not_found` (404) / `agent_disabled` (403): the agent no longer exists or is switched off
- `validation_error` (422): missing `Idempotency-Key`, empty message, or unknown body fields
- `idempotency_conflict` (409): the same `Idempotency-Key` was used with a different body
- `idempotency_in_progress` (409, `Retry-After`): the original request is still running
- `conversation_busy` (409, `Retry-After`): another message for this external user is being answered
- `insufficient_balance` (402): the owner's balance cannot cover the reply; nothing was charged
- `rate_limit_exceeded` (429), `api_rate_limit_unavailable` (503), `service_unavailable` (503), `internal_error` (500)



## OpenAPI

````yaml /openapi.json post /v1/agents/{agent_id}/chat
openapi: 3.1.0
info:
  title: Chattler Agent API
  version: 1.0.0
  description: >-
    Public REST API for talking to a Chattler agent with an API key. Every
    request runs the agent's ordinary runtime (system prompt, knowledge base,
    tools, dialog history) and is billed to the owner's balance.
servers:
  - url: https://api.chattler.ai
security:
  - ApiKeyBearer: []
paths:
  /v1/agents/{agent_id}/chat:
    post:
      tags:
        - Public API
      summary: Send a message to the agent and wait for the reply
      description: >-
        Run one turn of conversation on the agent through the ordinary Chattler
        runtime: the agent's system prompt, knowledge base, tools, and dialog
        history all apply, and the reply is paid for from the owner's balance
        exactly like any other channel. The conversation is identified by the
        API key and `external_user_id`: every message from the same external
        user through the same key lands in the same conversation, and another
        key never sees it.


        ## Authentication

        - **Authorization**: `Bearer cht_live_<key_id>.<secret>`; the key must
        have the `full` permission.


        ## Headers

        - **Idempotency-Key** (required): any client-chosen value of 1-255
        characters, unique per logical request. Retrying with the same value and
        the same body returns the original result without a second reply or
        charge.


        ## Path Parameters

        - **agent_id**: The agent the key is bound to


        ## Request Body

        - **external_user_id**: stable id of the end user on your side, 1-255
        characters

        - **message**: the user's text; must not be empty. Attachments are not
        accepted in this release, and unknown fields are rejected.


        ## Example Request

        ```bash

        curl -X POST "/v1/agents/{agent_id}/chat" \
          -H "Authorization: Bearer cht_live_{key_id}.{secret}" \
          -H "Idempotency-Key: 7f20a5f0-5dde-48cf-94aa-38acbbd218e0" \
          -H "Content-Type: application/json" \
          -d '{"external_user_id": "customer-123", "message": "I want to know the price"}'
        ```


        ## Response

        - **request_id**: UUID of this request; also returned as `X-Request-ID`

        - **conversation_id**: the conversation the exchange was stored in

        - **message**: the persisted assistant reply: `id`, `role`
        (`assistant`), `content`, `created_at`

        - **usage**: `prompt_tokens`, `completion_tokens`, `total_tokens`, and
        `charged_usd` (decimal string) as metered and charged for this request


        A replayed request returns the original `request_id`, message, and
        usage.


        ## Errors

        Envelope: `{"error": {"code": "...", "message": "...", "request_id":
        "..."}}`.

        - `invalid_api_key` (401), `agent_mismatch` (403)

        - `permission_denied` (403): the key is `read-only`

        - `agent_not_found` (404) / `agent_disabled` (403): the agent no longer
        exists or is switched off

        - `validation_error` (422): missing `Idempotency-Key`, empty message, or
        unknown body fields

        - `idempotency_conflict` (409): the same `Idempotency-Key` was used with
        a different body

        - `idempotency_in_progress` (409, `Retry-After`): the original request
        is still running

        - `conversation_busy` (409, `Retry-After`): another message for this
        external user is being answered

        - `insufficient_balance` (402): the owner's balance cannot cover the
        reply; nothing was charged

        - `rate_limit_exceeded` (429), `api_rate_limit_unavailable` (503),
        `service_unavailable` (503), `internal_error` (500)
      operationId: chat_v1_agents__agent_id__chat_post
      parameters:
        - name: agent_id
          in: path
          required: true
          schema:
            type: string
            title: Agent Id
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 255
            description: >-
              Client-chosen value, 1-255 characters, unique per logical request;
              a retry with the same value and body returns the stored result
            title: Idempotency-Key
          description: >-
            Client-chosen value, 1-255 characters, unique per logical request; a
            retry with the same value and body returns the stored result
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiChatRequest'
      responses:
        '200':
          description: The persisted assistant reply with its usage
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiChatResponse'
        '401':
          description: invalid_api_key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '402':
          description: insufficient_balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '403':
          description: agent_mismatch or permission_denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '404':
          description: agent_not_found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '409':
          description: idempotency_conflict, idempotency_in_progress or conversation_busy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '422':
          description: validation_error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '429':
          description: rate_limit_exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
        '503':
          description: api_rate_limit_unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
      security:
        - ApiKeyBearer: []
components:
  schemas:
    ApiChatRequest:
      properties:
        external_user_id:
          type: string
          maxLength: 255
          minLength: 1
          title: External User Id
          description: Stable id of the end user on the caller's side
        message:
          type: string
          minLength: 1
          title: Message
      additionalProperties: false
      type: object
      required:
        - external_user_id
        - message
      title: ApiChatRequest
      description: |-
        Text-only chat input; attachments are outside this release, so any
        extra field is a validation error rather than silently ignored.
    ApiChatResponse:
      properties:
        request_id:
          type: string
          title: Request Id
        conversation_id:
          type: string
          title: Conversation Id
        message:
          $ref: '#/components/schemas/ApiChatMessage'
        usage:
          $ref: '#/components/schemas/ApiChatUsage'
      type: object
      required:
        - request_id
        - conversation_id
        - message
        - usage
      title: ApiChatResponse
    ApiErrorResponse:
      properties:
        error:
          $ref: '#/components/schemas/ApiErrorDetail'
      type: object
      required:
        - error
      title: ApiErrorResponse
      description: The one envelope every public API failure answers with.
    ApiChatMessage:
      properties:
        id:
          type: string
          title: Id
          description: Persisted assistant message id
        role:
          type: string
          const: assistant
          title: Role
          default: assistant
        content:
          type: string
          title: Content
        created_at:
          type: string
          format: date-time
          title: Created At
      type: object
      required:
        - id
        - content
        - created_at
      title: ApiChatMessage
    ApiChatUsage:
      properties:
        prompt_tokens:
          type: integer
          title: Prompt Tokens
        completion_tokens:
          type: integer
          title: Completion Tokens
        total_tokens:
          type: integer
          title: Total Tokens
        charged_usd:
          type: string
          title: Charged Usd
          description: Decimal string, 8 places, charged to the balance
      type: object
      required:
        - prompt_tokens
        - completion_tokens
        - total_tokens
        - charged_usd
      title: ApiChatUsage
    ApiErrorDetail:
      properties:
        code:
          type: string
          title: Code
          description: Stable machine-readable error code
        message:
          type: string
          title: Message
          description: Human-readable explanation
        request_id:
          type: string
          title: Request Id
          description: UUID of this request, also in X-Request-ID
      type: object
      required:
        - code
        - message
        - request_id
      title: ApiErrorDetail
  securitySchemes:
    ApiKeyBearer:
      type: http
      description: 'Agent API key: Authorization: Bearer cht_live_<key_id>.<secret>'
      scheme: bearer

````