Ingram Cloud

Documentation

Conversations

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; its id is "<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.