Conversations
A conversation is a first-class, listable handle on a thread of
runs: create one, drive turns inside it, and read its transcript
back — so an in-app chat tab gets a history list, titles, and delete without
storing any of that yourself. It maps onto the OpenAI Conversations API, so
the openai client library talks to it unchanged.
A conversation is independent of memory. Memory is durable facts a smith carries across every conversation; a conversation is one thread of turns. A conversation works whether or not the smith has any memory, and deleting one never touches memory.
The object
{
"id": "cnv_7Yh…",
"object": "conversation",
"created_at": 1718900000,
"updated_at": 1718900042,
"title": "What did I spend on travel in May?",
"smith_id": "smt_3xK…",
"metadata": {}
}
created_at / updated_at are unix seconds (every OpenAI object is). title,
smith_id, and updated_at are Ingram Cloud extensions — OpenAI's object has no
title and no list endpoint, both of which a console history list needs. title
is null until you run inside the conversation over the Responses API (passing
its id as conversation), which auto-sets it from the first user message; set it
yourself any time.
A conversation belongs to one smith. A smith token acts on its own
conversations; a tenant-admin token names the smith with an IC-Smith-Id header,
or names none and reaches every conversation in the project (see
Your app's inbox).
Conversations on a deployment open themselves: the first
WhatsApp, Telegram, Slack, Discord or email message from a person, the first
message on a hosted page, and the first call on an MCP deployment each start one,
with metadata.channel and metadata.deployment_id set. It stays untitled, since
a channel message carries its sender details.
Create one
# Authorization: smith token (browser-safe, scoped to one smith)
curl https://api.cloud.ingram.tech/v1/conversations \
-H "Authorization: Bearer $IC_SMITH_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "title": "Travel spend" }'
title and metadata are both optional — POST /v1/conversations with an empty
body mints an untitled conversation you can run in straight away.
Run inside it
Pass the cnv_ id as conversation on the Responses API.
You send only the new input; Ingram Cloud replays the conversation's prior turns
for the model, stamps the run onto the conversation, and auto-titles it from the
first user turn.
# Authorization: smith token (browser-safe, scoped to one smith)
curl https://api.cloud.ingram.tech/v1/responses \
-H "Authorization: Bearer $IC_SMITH_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "model": "", "conversation": "cnv_7Yh…",
"input": "What did I spend on travel in May?" }'
You can also drive the same thread over Chat Completions
by passing the conversation's id as IC-Thread-Id: history replay, the activity
stamp, and auto-titling all work the same. Both hold in the client-tool loop too: a
stateful turn that sends tools still lands on the conversation, tool steps and
all. Any run whose thread is a conversation's id stamps that conversation's
activity, channel conversations included.
Only one turn runs at a time, and the limit is the smith, not the
conversation — running in a second conversation while the first is still going
returns 400 conversation_locked. See
one run at a time.
Read the transcript
GET /v1/conversations/{cnvId}/items returns the conversation's transcript,
reconstructed from its runs and the items appended to it, in OpenAI's list
envelope — oldest first, or
?order=desc for newest first. When has_more is true, pass the page's
last_id as ?after= to fetch the next page. The transcript is faithful to the run: alongside
message items it carries the tool steps, so a chat UI can re-render a turn in
full on reload —
function_call/function_call_output— a client-side tool the model asked you to run and the result you sent back (call_id,name,arguments,output).mcp_call— a tool the run loop executed server-side for you (name,arguments,output,server_label).file_search_call— a hosted file search the run made (queries).mcp_approval_request— a call that waits for, or was given, an approval (name,arguments,server_label; itsidis"<run_id>::<tool_call_id>").
A user message that carried a file or image lists each part in order: input_text,
input_file (file_id, filename) and input_image (file_id, or image_url for
a hosted image). A file inlined as base64 lists the same way, under the file_id
it was stored as.
{
"object": "list",
"data": [
{ "id": "run_9….i0", "type": "message", "role": "user",
"status": "completed",
"content": [{ "type": "input_text", "text": "What did I spend on travel in May?" }] },
{ "id": "run_9….o0", "type": "mcp_call", "status": "completed",
"server_label": "ingram-cloud", "name": "search_expenses",
"arguments": "{\"month\":\"May\"}", "output": "{\"total\":412}" },
{ "id": "run_9….o1", "type": "message", "role": "assistant",
"status": "completed",
"content": [{ "type": "output_text", "text": "€412 across 3 trips." }] }
],
"first_id": "run_9….i0",
"last_id": "run_9….o1",
"has_more": false
}
A message that arrived on a deployment channel lists as its sender wrote it; the sender line Ingram Cloud adds for the agent (who wrote, and whether they own the channel) is left out.
Every item carries created_at (unix seconds; an Ingram Cloud extension): when the
turn that produced it started, or when it was appended. A run's items come in the
order the run produced them, so text before a tool call precedes it and text after
follows it. An item's id is <run_id>.o<k> for its position k in the run's
output, the same id the response carries, so ids may skip numbers where the response
held items a conversation leaves out (reasoning, a server's tool list). Ids from
before this scheme (….mcp0, ….fc0, ….o) no longer exist.
Append messages outside a run
When a person takes over (a person on your team replying, or the end user writing while one handles the conversation), append those messages with OpenAI's create-items call. They join the transcript, and the next turn replays them as history, so the agent picks up knowing what was said:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/conversations/cnv_7Yh…/items \
-H "Authorization: Bearer $IC_TENANT_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "items": [
{ "type": "message", "role": "user", "content": "Can a person help?" },
{ "type": "message", "role": "assistant", "content": "Ana here. It ships Monday." }
] }'
Up to 20 messages per call. user and assistant messages replay as history.
system and developer messages apply as instructions on every later turn, after
the agent's own and the request's, and need a tenant token: a smith token gets
403 tenant_token_required. The response lists the stored items in the same list
envelope.
Attach a file to a turn by id
Upload the file once, then reference it from the turn. A tenant-admin token names
the smith with IC-Smith-Id on both calls, so the file is that smith's:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/files \
-H "Authorization: Bearer $IC_TENANT_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "IC-Smith-Id: smt_3xK…" \
-F purpose=user_data -F file=@q3-report.pdf
# → { "id": "file_8Qm…", "object": "file", "filename": "q3-report.pdf", … }
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/responses \
-H "Authorization: Bearer $IC_TENANT_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "IC-Smith-Id: smt_3xK…" \
-H "Content-Type: application/json" \
-d '{ "model": "", "conversation": "cnv_7Yh…",
"input": [{ "role": "user", "content": [
{ "type": "input_text", "text": "Summarise this report." },
{ "type": "input_file", "file_id": "file_8Qm…" } ] }] }'
The model reads the file on this turn and on later turns that replay it, within a 32 MB per-prompt budget for files (see openai-compat). The items list shows it back on the user message:
{ "type": "message", "role": "user", "status": "completed",
"content": [
{ "type": "input_text", "text": "Summarise this report." },
{ "type": "input_file", "file_id": "file_8Qm…", "filename": "q3-report.pdf" } ] }
A file_id outside the caller's reach is refused with
400 file_not_found; see openai-compat.
Your app's inbox
A tenant token that names no smith lists every conversation in the project, most
recent activity first. agent_id keeps the conversations of that agent's smiths,
channel conversations included; each object carries its smith_id. Get, update,
delete and the items calls work by id alone:
# Authorization: tenant-admin token (server-side only)
curl "https://api.cloud.ingram.tech/v1/conversations?agent_id=agt_…&limit=50" \
-H "Authorization: Bearer $IC_TENANT_TOKEN" -H "IC-Api-Version: 2026-05-01"
List, rename, delete
# Authorization: smith token (browser-safe, scoped to one smith)
# List: most recent activity first, in OpenAI's list envelope; pass next_cursor back as ?cursor=
curl "https://api.cloud.ingram.tech/v1/conversations?limit=20" \
-H "Authorization: Bearer $IC_SMITH_TOKEN" -H "IC-Api-Version: 2026-05-01"
# Rename (or merge metadata)
curl https://api.cloud.ingram.tech/v1/conversations/cnv_7Yh… \
-H "Authorization: Bearer $IC_SMITH_TOKEN" -H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" -d '{ "title": "May travel" }'
# Delete — removes the handle; the underlying runs are untouched
curl -X DELETE https://api.cloud.ingram.tech/v1/conversations/cnv_7Yh… \
-H "Authorization: Bearer $IC_SMITH_TOKEN" -H "IC-Api-Version: 2026-05-01"
Delete returns the OpenAI { "id": …, "object": "conversation.deleted", "deleted": true }
acknowledgement. A channel conversation (one with metadata.channel) lives as long
as its deployment: deleting it returns 409 channel_conversation.
The list returns next_cursor (an Ingram Cloud extension) beside OpenAI's
envelope. Pass it back as ?cursor= for the next page: it keeps its place while
conversations gain activity and move up. ?after= resumes from the named
conversation's current activity, so a conversation that moved since the last page
lists again. Pass one or the other; both is 400 invalid_request.
Scopes
conversations:read for GET; conversations:write for create, update, delete,
and appending items. A smith token carries the scopes minted into it; see
Auth & tokens.