Build
Updated
Conversations
Create a conversation, label it with metadata, read and edit the items in it, and hand its id to a response.
A conversation is the history the API keeps for you. Every response belongs to one, so a later turn sends only the new input rather than replaying what came before.
You do not have to create one. A plain POST /v1/responses keeps your history
for you, and previous_response_id continues it. Create one yourself when you
want to hold its id, label it, seed it with turns that happened somewhere else,
or read its history back.
The Node and Python examples use a client set up for this API.
Create a conversation
Section titled “Create a conversation”curl https://api.helping.io/v1/conversations \ -H "Authorization: Bearer $HELPINGIO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"metadata": {"customer": "acme"}}'const conversation = await client.conversations.create({ metadata: { customer: "acme" },});console.log(conversation.id);conversation = client.conversations.create(metadata={"customer": "acme"})print(conversation.id){ "id": "conv_…", "object": "conversation", "created_at": 1758330000, "metadata": { "customer": "acme" }}created_at is in seconds since the Unix epoch. metadata is always
present; a conversation with no labels reports {}.
You can seed the conversation at the same time by passing items, up to 20 of
them. See Items.
Retrying safely
Section titled “Retrying safely”Creating a conversation, updating its metadata and adding items all honor the
Idempotency-Key header, with the same rules as creating a response. A retry
with the same key and the same body returns the first answer and stores
nothing new, so seeded items never appear twice. The same key with a different
body is a 409. Each of the three keeps its own keys, and a key is remembered
for 24 hours. See
Idempotency.
A retry answers with the conversation as it is now, not a copy of the first
answer: if its metadata changed since, the retry shows the change. A retry of
adding items answers with the items that request added, leaving out any you
have removed since. A retry of creating or updating a conversation that it cannot
read, because the conversation has been deleted since or because the retry came
from a different API key, is a 409 with the code idempotency_conflict. A
retry of adding items to a conversation deleted since is a 404, the answer
any request naming that conversation gets.
Use a conversation on a response
Section titled “Use a conversation on a response”Pass its id as conversation. The conversation’s items are the context for
the response, and the response’s input and output are added to it.
curl https://api.helping.io/v1/responses \ -H "Authorization: Bearer $HELPINGIO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-5", "input": "What did I ask you first?", "conversation": "conv_…" }'conversation also accepts the object form, {"id": "conv_…"}. The response
comes back carrying "conversation": {"id": "conv_…"}, so you can read the id
off the response instead of tracking it yourself. A request that names no
conversation gets "conversation": null, matching the OpenAI API.
A conversation your key did not create, one that does not exist, and one you
have deleted are all the same 404. A conversation is never created
implicitly by naming it: a stale id is an error rather than a fresh,
empty history.
The first response on a conversation you created needs a model, exactly like
any first turn. After that the conversation keeps its model, and later turns may omit it.
Read, label and delete
Section titled “Read, label and delete”# readcurl https://api.helping.io/v1/conversations/conv_… \ -H "Authorization: Bearer $HELPINGIO_API_KEY"
# replace the labelscurl https://api.helping.io/v1/conversations/conv_… \ -H "Authorization: Bearer $HELPINGIO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"metadata": {"customer": "globex"}}'
# deletecurl -X DELETE https://api.helping.io/v1/conversations/conv_… \ -H "Authorization: Bearer $HELPINGIO_API_KEY"await client.conversations.retrieve("conv_…");await client.conversations.update("conv_…", { metadata: { customer: "globex" },});await client.conversations.delete("conv_…");client.conversations.retrieve("conv_…")client.conversations.update("conv_…", metadata={"customer": "globex"})client.conversations.delete("conv_…")An update replaces metadata rather than merging into it. Send the whole
map, or {"metadata": {}} to clear it. metadata is required: an update
without it is a 400.
A delete answers {"id": "conv_…", "object": "conversation.deleted", "deleted": true}. The conversation and its history stop being readable and it
can no longer be continued. Deleting the same conversation twice is not an
error.
Its content (messages, the model requests and responses, tool inputs and outputs, stored outputs and attached files) is erased 30 days after the delete. Deleting a conversation does not remove what you were charged or the record of the calls that produced it. Your spend history and invoices are unaffected.
Metadata limits
Section titled “Metadata limits”| Rule | Limit |
|---|---|
| Pairs per conversation | 16 |
| Key length | 64 characters |
| Value length | 512 characters |
| Value type | string |
Anything outside these is a 400 naming the rule it broke. Metadata is stored
for you to read back; no route filters or searches on it, and it is never sent
to a model.
The items of a conversation are its turns: the messages, the tool calls, the tool results and the reasoning that make up its history.
List items
Section titled “List items”curl "https://api.helping.io/v1/conversations/conv_…/items?limit=20" \ -H "Authorization: Bearer $HELPINGIO_API_KEY"{ "object": "list", "data": [{ "type": "message", "id": "msg_…", "role": "user", "status": "completed", "content": [{ "type": "input_text", "text": "hello" }] }], "has_more": false, "first_id": "msg_…", "last_id": "msg_…"}| Parameter | Meaning |
|---|---|
limit |
Items per page, 1 to 500. Defaults to 20 |
order |
asc for oldest first, the default, or desc |
after |
Continue after this item. Pass the previous page’s last_id |
include |
reasoning.encrypted_content to include reasoning payloads |
include is accepted repeated (include=a&include=b), bracketed
(include[]=a), or comma separated, and it works the same way on the
single-item read and when adding items. A value this API does not define is a
400 naming it, so a request that asks for something unavailable is told rather
than answered as though it had asked for nothing.
An item that is stored but could not be read is left out of data and named
in helpingio:unreadable by its id. The rest of the page is returned, it
still counts toward has_more and can be the page’s last_id, and retrieving
it returns the item_unreadable error.
after is exclusive, so no item appears on two pages. An after that names
no item in this conversation is a 400 rather than an empty page, so a client
never mistakes a bad cursor for the end of the history.
Add items
Section titled “Add items”Up to 20 per call. This is how you hand the API a conversation that started somewhere else.
Every item you add is stored whole and reads back exactly as you sent it. What the next response’s model sees is less than that, and how much less depends on the kind, so the table below says it per kind rather than promising one rule.
curl https://api.helping.io/v1/conversations/conv_…/items \ -H "Authorization: Bearer $HELPINGIO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "items": [ {"type": "message", "role": "user", "content": "What is the capital of France?"}, {"type": "message", "role": "assistant", "content": "Paris."} ] }'The answer is the items you just added, in an item list.
Unlike the input of a response, items here may carry the assistant,
system and developer roles, and may be function_call,
function_call_output or reasoning items. An item reference is a 400,
here and when you create the conversation with items: send the item itself.
What the model sees, per kind
Section titled “What the model sees, per kind”| Item you add | What the next response’s model sees |
|---|---|
message with role user or assistant |
The text, as that role |
message with role system or developer |
The text, but as a user turn rather than as an instruction. To steer the model, send instructions on the response |
function_call paired with its function_call_output |
A real tool call and its result, with the tool’s name and the correlation between them intact |
function_call with no matching output, or an output with no call |
Nothing. A call with no result asks the model to answer something the history does not contain, and a result with no call refers to nothing |
reasoning |
Nothing. It is stored and reads back in full, and no part of it is sent. If the model needs what the reasoning concluded, add that as an assistant message |
Apart from a paired tool exchange, the text of an item is what reaches the
model. An item carrying an image, file or video part is refused with a 400
naming it, because media input is not supported.
Read and remove one item
Section titled “Read and remove one item”curl https://api.helping.io/v1/conversations/conv_…/items/msg_… \ -H "Authorization: Bearer $HELPINGIO_API_KEY"
curl -X DELETE https://api.helping.io/v1/conversations/conv_…/items/msg_… \ -H "Authorization: Bearer $HELPINGIO_API_KEY"A delete answers with the conversation, not a deleted-object shape.
What the item list does not show
Section titled “What the item list does not show”Two kinds of thing in a conversation are absent from the list:
- A tool call that failed on our side. The response that made the call
reports the failure as a
function_call_outputcarryinghelpingio:error(When this API refuses a call), but the conversation’s item list does not show it. - Response lifecycle. Whether a response started, finished, failed or was canceled is on the response object, not in the conversation’s items.