Importing conversations
Move a transcript from another system into Runtype so an Agent’s next turn continues where the old system left off. Runtype stores the transcript on the conversation and replays it on the first turn after the import.
An imported conversation is an API conversation, so the conversation that the API
returns has source: "app". It isn’t added to a Persona widget visitor’s history, and
you can’t continue a widget conversation with a stored-history turn. For widget history, see
Conversation history in an embedded widget.
Before you begin
You need the following:
- A Runtype API key with the Records (Write) permission to create and update
conversations, Records (Read) to read them back, and Agents (Execute) to run
the Agent with
POST /v1/agents/{id}/execute. To run it withPOST /v1/dispatchinstead, the key needs Flows (Execute). For more information, see Managing API keys. - A saved Agent that you own. Claude Managed Agents and external Agents keep their own history, so they can’t continue an imported conversation.
Import a conversation
To import a conversation and continue it, follow these steps:
- Create the conversation with
POST /v1/conversations. Sendsource,agentId, and the first chunk ofmessages. The response contains the conversationid. - Optional: Send each later chunk with
PUT /v1/conversations/{id}andmessagesMode: "append", in order. For more information, see Import a long transcript in chunks. - Read the conversation back to check the import. For more information, see Check the import.
- Run the next turn with
history: "stored". For more information, see Continue the conversation.
The following request imports a conversation in which the assistant called a tool, with the tool result in place, so that the next turn can replay the whole exchange:
TypeScript
cURL
Replace the following:
YOUR_API_KEY: your Runtype API key.YOUR_AGENT_ID: the ID of the Agent that continues the conversation.
The Runtype MCP server’s create_conversation and update_conversation tools accept
the same top-level fields in snake case (agent_id, owner_id, messages_mode).
Conversation fields
POST /v1/conversations and PUT /v1/conversations/{id} accept the following fields:
We recommend that you set ownerId to the end user’s ID in the source system. You can
then list one user’s imported conversations.
Message fields
Each entry in messages has the following fields:
author records who wrote the message in the system that you’re migrating from, so a
role: "user" turn stays attributable to a specific person. Runtype stores and returns
it as you sent it. Runtype doesn’t send it to a model.
Runtype stores an imported reasoning part but leaves it out of the replay. A model’s
thinking signature is bound to the provider that produced it, so a foreign one would
fail the next turn. This is true even when the imported message names a model.
A reply that a stored-history turn appends is different. Runtype stores the Agent’s own
transcript: one assistant message and one tool message for each model round, in order,
with reasoning parts and model on each assistant message. When the next turn runs on
that same model, Runtype replays those reasoning parts. When the next turn runs on
another model, Runtype removes them. Runtype marks these messages itself, and drops that
mark from any message you write, so only a reply the Agent wrote can replay its reasoning.
Transcript validation rules
Model providers reject a tool call without a matching result. Runtype checks the transcript when you write it, so a bad transcript fails at import and not at the next turn. A transcript must follow these rules:
- User and system messages must have non-empty content.
- Each message
idmust be unique in the transcript. - A
toolmessage must carry at least onetoolResultsentry. - Every assistant
toolCallsentry must be answered by atoolmessage before the next non-tool message. - Each
toolCallIdcan be issued only once, and answered only once. - A result can’t answer a
toolCallIdthat no earlier assistant message issued. toolCallsis valid only on an assistant message, andtoolResultsonly on a tool message.
If the source system doesn’t record tool calls, import the text turns only.
A transcript that breaks a rule returns 400 with a body similar to the following:
Each entry in details.issues names the message index and one of the following
reason values:
Bind an Agent and record the source
agentId binds the conversation to one Agent. It must name an Agent that you own.
Otherwise, the API returns 404. source records where the transcript came from, with
the following fields:
Runtype stores both on the conversation’s metadata, as metadata.agentId and
metadata.importSource.
A stored-history turn must run the bound Agent. A turn that names another Agent
returns 409 with code CONVERSATION_AGENT_MISMATCH. If the conversation has no
agentId, the first stored-history turn binds it to the Agent that runs it. To remove
the binding, send agentId: null in a PUT request.
Re-run an import safely
source.externalId is an idempotency key. A fresh create returns 201 with
"imported": true. If a conversation with the same source.system and
source.externalId already exists, Runtype doesn’t create a second one. It returns
200 with the existing conversation and "imported": false:
Runtype returns the existing conversation unchanged and doesn’t write the messages in
the repeated request. Runtype still validates those messages first, so a repeated
request with an invalid transcript returns 400. To add turns, use PUT with
messagesMode: "append".
If two requests with the same source.system and source.externalId arrive at the
same time, Runtype creates only one conversation. Use the source system’s conversation
ID as externalId, and you can restart a migration script from the beginning without
duplicates. Runtype doesn’t deduplicate a create without source.externalId.
Import a long transcript in chunks
PUT /v1/conversations/{id} takes messagesMode:
replace(the default) replaces the whole stored transcript with the messages that you send.appendadds your messages to the end of the stored transcript.
In append mode, Runtype skips a message whose id the conversation already stores. A
chunk that you resend after a timeout adds nothing. Give every message the source
system’s message ID so that you can retry each chunk on its own.
Runtype validates the stored transcript and the incoming chunk together. A tool
message in the chunk can answer a toolCallId that the conversation already stores. A
result that answers nothing still returns 400 CONVERSATION_TRANSCRIPT_INVALID.
Keep an assistant toolCalls message and the tool message that answers it in the
same chunk. A chunk that ends on an unanswered tool call is rejected.
The following request appends one message to the conversation:
TypeScript
cURL
Replace YOUR_CONVERSATION_ID with the id that the create request returned.
If another write changes the conversation during an append, the API returns 409 with
code CONVERSATION_MODIFIED and writes nothing. Read the conversation again and resend
the chunk.
Check the import
To check the import, read the conversation back with GET /v1/conversations/{id}:
In the CLI, run runtype conversations get <conversation-id>.
Check the number of messages, the id of the last message, and
metadata.importSource. If a chunked import stopped partway, the last message id
tells you which chunk to send next.
Size limits
Limits apply to the whole conversation after the write. An append counts the stored transcript plus the incoming messages. The following table lists the limits:
A write that exceeds any of these returns 400 and changes nothing. Split a transcript
that doesn’t fit across several conversations, or drop the oldest turns.
Every stored-history turn adds messages to the conversation. Leave room under the limits. A turn that would exceed them doesn’t run, and a reply that would exceed them isn’t stored.
Attachments
To import an attachment, send it inline as an image or file content part with
base64 data. Runtype moves inline media larger than 256 KB to asset storage when the
write lands. Runtype doesn’t fetch attachment URLs for you.
When you create a conversation, Runtype applies the 1 MB per-message limit to the
inline content before it moves the media. The base64 data of a message’s attachments
must fit in 1 MB, which is about 750 KB of source file. PUT moves the media before it
checks the limit, so it doesn’t have this restriction.
For a larger file, upload it first with POST /v1/assets, which needs the
Assets (Write) permission. Then reference it with an asset_ref part that has the
following fields:
type:asset_ref.assetId: theassetIdthat the upload returned.refKind:imageorfile.mimeType: the file’s content type.sizeBytes: the file’s size in bytes.orgKey: required, but Runtype replaces it with your organization’s key on write.filename: optional.
What import doesn’t do
An import is a write, not a run. It doesn’t fire surface webhooks, start an Agent turn, or count toward execution usage.
The transcript doesn’t carry an Agent’s long-term memory. Long-term facts that an Agent
needs from the start are seeded through the memory API
(POST /v1/runtime/memory/save, read back with POST /v1/runtime/memory/recall), not
through messages. Those routes are an Enterprise-plan feature.
Continue the conversation
The turn after an import calls POST /v1/agents/{id}/execute with history: "stored",
the conversationId from the import, and only the new user message. Runtype replays
the stored transcript ahead of the new message, so the imported tool pairs reach the
provider:
POST /v1/dispatch takes the same history field, with agent.agentId and
conversationId:
With the default history: "inline", your application keeps the transcript and sends
the whole messages array on every turn.
A stored-history turn has the following requirements on both routes:
- The request names a saved Agent that isn’t a Claude Managed or external Agent. Inline Agents and flow dispatches are refused.
- The request includes
conversationId. Without it, the API returns400with codeSTORED_HISTORY_REQUIRES_CONVERSATION. messagescontains only the new messages and ends with ausermessage. The stored transcript plus the new messages must also follow the transcript validation rules. Otherwise, the API returns400with codeSTORED_HISTORY_DELTA_INVALID.- The request doesn’t send
Prefer: respond-asyncor a steer request (x-runtype-concurrency: steer). OnPOST /v1/dispatch, it also isn’t a resume or a client-tool continuation.
Other refusals return 400 with code STORED_HISTORY_UNSUPPORTED.
When Runtype accepts the request, it appends your new messages to the stored
transcript before the model runs. A failed turn still keeps the question. When the turn
completes, Runtype appends the Agent’s reply, including any tool calls and results. An
appended reply message can carry metadata.executionId, the ID of the execution that
wrote it.
Runtype doesn’t append a reply for a turn that fails, or for a turn that stops to wait
for a client-side tool result or an approval. To keep that reply in the transcript,
read it from the execution’s events and append it with messagesMode: "append".
A stored-history turn can also return the following errors. Nothing runs in these cases:
Dispatch messages also accept the tool role with toolCalls and toolResults, in
the same shape that the transcript uses. An application that keeps history: "inline"
can still replay full tool turns on every request. The messages override on
POST /v1/dispatch/resume and POST /v1/dispatch/continue accepts the same shape.
Answer every toolCalls entry with the tool message that follows it.
Next steps
Continue with these guides:
- Creating and configuring agents: set up the Agent that an imported conversation is bound to.
- Agent tools: give the Agent the tools that an imported transcript’s tool pairs refer to.
- Create a conversation: every field and response of the conversations API.
- Steer an active agent execution: add a message to a turn that is already running.
- What are Agents?: how Agents use tools, system prompts, and conversation history.