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_capabilities": [
"<string>"
],
"client_message_id": "<string>",
"message": "",
"model_tier": "FAST"
}
'import requests
url = "https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages"
payload = {
"attachments": [
{
"blob_path": "<string>",
"mime": "<string>",
"alt": "<string>",
"filename": "<string>"
}
],
"client_capabilities": ["<string>"],
"client_message_id": "<string>",
"message": "",
"model_tier": "FAST"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
attachments: [
{blob_path: '<string>', mime: '<string>', alt: '<string>', filename: '<string>'}
],
client_capabilities: ['<string>'],
client_message_id: '<string>',
message: '',
model_tier: 'FAST'
})
};
fetch('https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'attachments' => [
[
'blob_path' => '<string>',
'mime' => '<string>',
'alt' => '<string>',
'filename' => '<string>'
]
],
'client_capabilities' => [
'<string>'
],
'client_message_id' => '<string>',
'message' => '',
'model_tier' => 'FAST'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages"
payload := strings.NewReader("{\n \"attachments\": [\n {\n \"blob_path\": \"<string>\",\n \"mime\": \"<string>\",\n \"alt\": \"<string>\",\n \"filename\": \"<string>\"\n }\n ],\n \"client_capabilities\": [\n \"<string>\"\n ],\n \"client_message_id\": \"<string>\",\n \"message\": \"\",\n \"model_tier\": \"FAST\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"attachments\": [\n {\n \"blob_path\": \"<string>\",\n \"mime\": \"<string>\",\n \"alt\": \"<string>\",\n \"filename\": \"<string>\"\n }\n ],\n \"client_capabilities\": [\n \"<string>\"\n ],\n \"client_message_id\": \"<string>\",\n \"message\": \"\",\n \"model_tier\": \"FAST\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"attachments\": [\n {\n \"blob_path\": \"<string>\",\n \"mime\": \"<string>\",\n \"alt\": \"<string>\",\n \"filename\": \"<string>\"\n }\n ],\n \"client_capabilities\": [\n \"<string>\"\n ],\n \"client_message_id\": \"<string>\",\n \"message\": \"\",\n \"model_tier\": \"FAST\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"chat_turn_id": "<string>",
"since_cursor": "<string>",
"user_message_id": "<string>",
"audience": "MSP",
"idle_reset_minutes": 123,
"show_agent_progress_steps": true,
"trigger_mode": "<string>"
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timings_ms": {},
"pagination": {
"has_more": true,
"next_cursor": "<string>"
},
"warnings": [
"<string>"
]
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}Append a user message + enqueue a chat turn
Persists the user message and enqueues a ChatTurnWorkflow. Returns 202 Accepted with the persisted message id + the Temporal workflow id. The agent’s streamed response is consumed via GET /chat/sessions/<id>/events. Pass client_message_id for idempotent retries — a duplicate returns 409 with the original message id and no new turn is enqueued.
The response also includes since_cursor: the SSE stream id snapshotted right before this turn was enqueued. Pass it as ?since=<since_cursor> on the very next call to /chat/sessions/<id>/events so the tail skips envelopes from prior turns on this session — skipping the cursor on a 2nd+ turn against the same session causes the tail to return the previous turn’s final state.
To include images or documents, first upload each via POST /chat/sessions/<id>/attachments/upload-url, then pass the returned blob_path values in attachments. message may be empty when at least one attachment is provided. Attachments are surfaced on later GET .../messages replays.
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_capabilities": [
"<string>"
],
"client_message_id": "<string>",
"message": "",
"model_tier": "FAST"
}
'import requests
url = "https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages"
payload = {
"attachments": [
{
"blob_path": "<string>",
"mime": "<string>",
"alt": "<string>",
"filename": "<string>"
}
],
"client_capabilities": ["<string>"],
"client_message_id": "<string>",
"message": "",
"model_tier": "FAST"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
attachments: [
{blob_path: '<string>', mime: '<string>', alt: '<string>', filename: '<string>'}
],
client_capabilities: ['<string>'],
client_message_id: '<string>',
message: '',
model_tier: 'FAST'
})
};
fetch('https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'attachments' => [
[
'blob_path' => '<string>',
'mime' => '<string>',
'alt' => '<string>',
'filename' => '<string>'
]
],
'client_capabilities' => [
'<string>'
],
'client_message_id' => '<string>',
'message' => '',
'model_tier' => 'FAST'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages"
payload := strings.NewReader("{\n \"attachments\": [\n {\n \"blob_path\": \"<string>\",\n \"mime\": \"<string>\",\n \"alt\": \"<string>\",\n \"filename\": \"<string>\"\n }\n ],\n \"client_capabilities\": [\n \"<string>\"\n ],\n \"client_message_id\": \"<string>\",\n \"message\": \"\",\n \"model_tier\": \"FAST\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"attachments\": [\n {\n \"blob_path\": \"<string>\",\n \"mime\": \"<string>\",\n \"alt\": \"<string>\",\n \"filename\": \"<string>\"\n }\n ],\n \"client_capabilities\": [\n \"<string>\"\n ],\n \"client_message_id\": \"<string>\",\n \"message\": \"\",\n \"model_tier\": \"FAST\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.neoagent.io/public-api/chat/sessions/{session_id}/messages")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"attachments\": [\n {\n \"blob_path\": \"<string>\",\n \"mime\": \"<string>\",\n \"alt\": \"<string>\",\n \"filename\": \"<string>\"\n }\n ],\n \"client_capabilities\": [\n \"<string>\"\n ],\n \"client_message_id\": \"<string>\",\n \"message\": \"\",\n \"model_tier\": \"FAST\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"chat_turn_id": "<string>",
"since_cursor": "<string>",
"user_message_id": "<string>",
"audience": "MSP",
"idle_reset_minutes": 123,
"show_agent_progress_steps": true,
"trigger_mode": "<string>"
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"timings_ms": {},
"pagination": {
"has_more": true,
"next_cursor": "<string>"
},
"warnings": [
"<string>"
]
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {}
},
"meta": {
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}Authorizations
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
Body
Images or documents to include with this turn. Each must first be uploaded via POST /chat/sessions/<id>/attachments/upload-url. At most 6 per turn.
Show child attributes
Show child attributes
Interactive stream parts this client renders. Supported values: 'user-questions' — declare it only when you render the data-user-questions part as tappable answers, including each question's multi_select flag (tick several, then send them as one message with one pick per line — never comma-joined, because a pick can itself contain a comma) and a payload of several questions (each question's text on its own line, then its picked values one per line after '- '). 'suggested-replies' — the older event, which carries one question and rides beside the new one for every single-question payload; declare it when you render data-suggested-replies alone, and the agent keeps naming the options in its text whenever it asks several questions at once, which that event cannot carry. Declaring either stops the agent naming the options for a payload you can draw. Omit both (the default) to keep every question fully readable as plain text.
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.
FAST, SMART Response
Success.
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."
Show child attributes
Show child attributes
Show child attributes
Show child attributes
