Skip to main content
POST
/
public-api
/
chat
/
sessions
/
{session_id}
/
messages
Append a user message + enqueue a chat turn
curl --request POST \
  --url https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "attachments": [
    {
      "blob_path": "<string>",
      "mime": "<string>",
      "alt": "<string>",
      "filename": "<string>"
    }
  ],
  "client_message_id": "<string>",
  "message": "",
  "model_tier": "FAST"
}
'
{
  "data": {
    "chat_turn_id": "<string>",
    "since_cursor": "<string>",
    "user_message_id": "<string>",
    "show_agent_progress_steps": true
  },
  "meta": {
    "request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
    "timings_ms": {},
    "pagination": {
      "has_more": true,
      "next_cursor": "<string>"
    },
    "warnings": [
      "<string>"
    ]
  }
}

Authorizations

Authorization
string
header
required

Authorization: Bearer <token> where <token> is either a neo_sk_<env>_<secret> API key (service account) or a Microsoft Entra ID access token (dashboard user).

Path Parameters

session_id
string
required

Body

application/json
attachments
PublicChatAttachment · object[]

Images to include with this turn. Each must first be uploaded via POST /chat/sessions/<id>/attachments/upload-url. At most 6 per turn.

client_message_id
string | null
message
string
default:""
model_tier
enum<string>
default:FAST

Speed/quality tier for this turn (ENG-5851): FAST keeps the agent's configured model; SMART escalates a single turn to a stronger model. May be flipped per turn to switch mid-conversation. Defaults to FAST.

Available options:
FAST,
SMART

Response

Success.

data
PostMessageResponse · object
required

202 Accepted body — the turn is enqueued, the agent's response streams via SSE.

since_cursor is the Redis-stream event id snapshotted right before this turn was enqueued. Callers tailing /chat/sessions/<id>/events should pass it as ?since=<since_cursor> (or Last-Event-Id: <since_cursor>) so the SSE tail starts strictly after the previous turn's terminal envelopes. Skipping this on a 2nd+ turn against the same session causes the tail to return the previous turn's final state. May be None on the first turn of a fresh session or under Redis failure; consumers treat None as "tail from beginning."

meta
object
required