curl --request GET \
--url https://api-dev.narrative.io/agents/conversations/{id}/messages \
--header 'Authorization: Bearer <token>'import requests
url = "https://api-dev.narrative.io/agents/conversations/{id}/messages"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api-dev.narrative.io/agents/conversations/{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-dev.narrative.io/agents/conversations/{id}/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api-dev.narrative.io/agents/conversations/{id}/messages"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api-dev.narrative.io/agents/conversations/{id}/messages")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-dev.narrative.io/agents/conversations/{id}/messages")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"current_version": 4,
"messages": [
{
"id": "7c2863a2-6a85-4c3f-b400-c55370e9b04a",
"sequence_no": 2,
"role": "user",
"content_blocks": [
{
"type": "text",
"text": "What is 2 + 2?"
}
],
"run_id": "82eb9cb7-619e-46bb-ac1c-8a32b0112c1c",
"created_at": "2023-11-07T05:31:56Z",
"final_answer_from": [
"b_c08cf800_02"
]
}
]
}{
"error": "Unauthorized",
"error_description": "You are not authorized to use this endpoint."
}{
"type": "https://docs.narrative.io/reference/architecture/agent-conversations/errors/invalid-tool-alias",
"title": "Invalid Tool Alias",
"status": 400,
"detail": "invalid tool aliases (must match ^[a-zA-Z][a-zA-Z0-9]{0,7}$): with_underscore",
"instance": null,
"log_id": "1f6e5d4c-3b2a-1098-7654-3210fedcba98",
"debug": null
}List conversation messages (delta read by version cursor)
Returns the messages in this conversation with sequence_no > since, plus the
conversation’s current_version at the moment the response was generated. The
messages list is ordered by sequence_no ascending and is gap-free — if the
response contains messages with sequence_no 3, 4, 5, you have all three.
Polling pattern
since = 0
loop forever:
r = GET /agents/conversations/{id}/messages?since={since}
process r.messages # may be empty
since = r.current_version # use the head, not r.messages.last.sequence_no
wait briefly
current_version is the conversation’s head at the moment the response was generated
— using it as the next request’s since is the only way to ensure gap-free reads
even when new messages land between the time the response was generated and the time
your client processed it.
When messages get added
Messages are persisted in a single transaction by the workflow’s finalize step (when a run reaches a terminal state). You won’t see partial reads — a run that produces 4 messages either lands all 4 or none.
A run’s id appears in messages[].run_id, so you can correlate messages to the
runs that produced them.
What’s in content_blocks
Five variants, discriminated by type:
text— plain text. The dominant variant.tool_use— the model requesting a tool call. Carriestool_use_id, the aliasedname, and the model’sarguments.tool_result— the result of a tool call. Itscontentholds nested content blocks, and it carries the originaltool_use_idplus anis_errorflag.reasoning/redacted_reasoning— the model’s working, on a turn that asked for thinking.
Blocks the platform produced also carry an id. On an assistant turn with an answer,
final_answer_from names the blocks the answer came from; render those as the reply.
The same tool_use_id appears on the assistant’s tool_use block and on the
subsequent tool (or user, for resume payloads) turn’s tool_result block —
that’s how a UI matches them up.
Permission
agent_conversations resource with read verb.
Example: paging from scratch
curl "$API/agents/conversations/{id}/messages?since=0" \
-H "Authorization: Bearer $TOKEN"
# → { "current_version": 4, "messages": [ <4 entries> ] }
Then later:
curl "$API/agents/conversations/{id}/messages?since=4" \
-H "Authorization: Bearer $TOKEN"
# → { "current_version": 4, "messages": [] } # nothing new
curl --request GET \
--url https://api-dev.narrative.io/agents/conversations/{id}/messages \
--header 'Authorization: Bearer <token>'import requests
url = "https://api-dev.narrative.io/agents/conversations/{id}/messages"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api-dev.narrative.io/agents/conversations/{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-dev.narrative.io/agents/conversations/{id}/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api-dev.narrative.io/agents/conversations/{id}/messages"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api-dev.narrative.io/agents/conversations/{id}/messages")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-dev.narrative.io/agents/conversations/{id}/messages")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"current_version": 4,
"messages": [
{
"id": "7c2863a2-6a85-4c3f-b400-c55370e9b04a",
"sequence_no": 2,
"role": "user",
"content_blocks": [
{
"type": "text",
"text": "What is 2 + 2?"
}
],
"run_id": "82eb9cb7-619e-46bb-ac1c-8a32b0112c1c",
"created_at": "2023-11-07T05:31:56Z",
"final_answer_from": [
"b_c08cf800_02"
]
}
]
}{
"error": "Unauthorized",
"error_description": "You are not authorized to use this endpoint."
}{
"type": "https://docs.narrative.io/reference/architecture/agent-conversations/errors/invalid-tool-alias",
"title": "Invalid Tool Alias",
"status": 400,
"detail": "invalid tool aliases (must match ^[a-zA-Z][a-zA-Z0-9]{0,7}$): with_underscore",
"instance": null,
"log_id": "1f6e5d4c-3b2a-1098-7654-3210fedcba98",
"debug": null
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Path Parameters
The conversation's UUID.
UUID identifying a conversation. Returned from POST /agents/conversations and used
in every other agent endpoint that operates on this conversation.
"bc2505b7-068d-44ed-8055-a6f6ffe54ab1"
Query Parameters
Return messages with sequence_no > since. Use the previous response's
current_version as the next request's since for gap-free incremental reads.
Default 0 returns the entire conversation from the beginning. A negative or
non-integer value returns a 404 with an empty body, because the route stops matching.
x >= 0Response
A page of messages with sequence_no > since, plus the current head version.
Returned by GET /agents/conversations/{id}/messages?since=N. Carries the
conversation's current head version (current_version) plus the requested page of
messages.
Polling pattern:
since = 0
loop:
response = GET /agents/conversations/{id}/messages?since={since}
process response.messages
since = response.current_version
sleep or wait for an event
The current_version is the conversation's head at the moment the response was
generated — using it as the next request's since gives gap-free incremental
reads.
Monotonic per-conversation counter, bumped by 1 (or more) every time a successful run appends messages. Two roles:
- Delta cursor for
GET .../messages?since=N— fetch only messages withsequence_no > N. - Compare-and-swap token for
POST .../runs— setexpected_versionto the conversation's current head; if it doesn't match at run-creation time, you get a Version Conflict (HTTP 409).
Always start a new run cycle by reading the current version from
GET /agents/conversations/{id} rather than caching a value from earlier.
x >= 04
Messages with sequence_no > since. Empty array means no new messages — the
conversation is at version current_version but you've already seen everything.
Show child attributes
Show child attributes
Was this page helpful?

