The /v1/conversations/* endpoints expose a user's saved chat history, the same conversations the PrivateMind app sidebar shows. Use them to list, fetch, stream-generate, and delete on behalf of a signed-in user.
Server-side decryption and source redaction are handled automatically.
Authentication: exchanging an application key for a user key
Before calling any /v1/conversations/* or /v1/sources/* endpoint, swap your application key for a user-scoped bearer:
POST /v1/auth/exchange
Request body
{
"api_key": "pmind-..."
}| Field | Type | Notes |
|---|---|---|
api_key |
string | A valid application or admin key. |
Response
{
"success": true,
"user_key": "pminduser_abc123...",
"expires_at": "2026-06-04T16:00:00.000Z"
}| Field | Type | Notes |
|---|---|---|
user_key |
string | Short-lived bearer to use on user-scoped routes. |
expires_at |
string (ISO 8601) | When the key becomes invalid. Typically one hour. |
curl -s "https://api.privatemind.com/v1/auth/exchange" \
-H "Content-Type: application/json" \
-d '{"api_key": "pmind_your_application_key"}'List conversations
GET /v1/conversations returns every non-archived conversation owned by (or shared with) the calling user, newest first. Each item carries its most recent message so a sidebar render needs no follow-up call.
Query parameters
| Param | Type | Notes |
|---|---|---|
project_id |
string | Filter to a project. Pass none to match conversations with no project assigned. Omit for all. |
cloud_agents |
"true" |
If set, returns only cloud-agent conversations. Default excludes them. |
archived |
"true" |
If set, returns only archived conversations (the "Archived" view). Default returns non-archived only. |
Response
{
"success": true,
"body": [
{
"id": 1234,
"user_id": 42,
"title": "Refactor the billing service",
"project_id": null,
"cloud_agent_task_id": null,
"archived_at": null,
"last_message_at": "2026-05-26T14:21:08.123Z",
"source_count": 2,
"messages": [
{
"message": "Here's a sketch of the new structure...",
"role": "assistant",
"created_at": "2026-05-26T14:21:08.123Z",
"conversation_id": 1234
}
]
}
],
"total": { "types": ["chat", "agent"] }
}messages always contains a single entry: the latest message in the conversation. The full transcript is fetched via GET /v1/conversations/{id}.
curl -s "https://api.privatemind.com/v1/conversations" \
-H "Authorization: Bearer $PMIND_USER_KEY"Get one conversation
GET /v1/conversations/{id} returns the full transcript, sources, and participants for a single conversation. The user must own or be a participant in the conversation; otherwise 404.
The branch the user has navigated to (sibling messages, regenerations) is resolved server-side. messages is the linear visible thread, with sibling_ids on each message so a UI can render branch switchers.
Response
{
"success": true,
"conversation": {
"id": 1234,
"user_id": 42,
"title": "Refactor the billing service",
"project_id": null,
"archived_at": null,
"last_message_at": "2026-05-26T14:21:08.123Z",
"sources": [
{
"id": 88,
"source_type": "vectorized_file",
"source_name": "billing-spec.pdf",
"source_config": { },
"is_active": true,
"use_in_tasks": true,
"handler": "vectorizedFileHandler",
"source_description": null,
"is_owner": true
}
],
"participants": [
{
"user_id": 42,
"role": "owner",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"joined_at": "2026-05-20T09:00:00.000Z"
}
],
"messages": [
{
"id": 9001,
"conversation_id": 1234,
"user_id": 42,
"message": "How should we split the invoicing module?",
"reasoning_content": null,
"role": "user",
"created_at": "2026-05-26T14:20:42.000Z",
"is_read": true,
"attachments": [],
"model_name": null,
"parent_message_id": null,
"active_sibling_message": true,
"sibling_ids": [9001],
"metadata": {}
},
{
"id": 9002,
"conversation_id": 1234,
"user_id": 42,
"message": "Here's a sketch of the new structure...",
"reasoning_content": null,
"role": "assistant",
"created_at": "2026-05-26T14:21:08.123Z",
"is_read": true,
"attachments": [],
"model_name": "kimi-k2-6",
"parent_message_id": 9001,
"active_sibling_message": true,
"sibling_ids": [9002],
"metadata": {}
}
]
}
}source_config is redacted server-side based on is_owner: non-owners see the source listing but not its private connection fields.
curl -s "https://api.privatemind.com/v1/conversations/1234" \
-H "Authorization: Bearer $PMIND_USER_KEY"List sources attached to a conversation
GET /v1/conversations/{id}/sources returns the active sources currently attached to a conversation: vectorized files, tabular files, source-library entries, remote DBs, and so on.
Response
{
"success": true,
"body": [
{
"id": 88,
"source_type": "vectorized_file",
"source_name": "billing-spec.pdf",
"source_config": { },
"handler": "vectorizedFileHandler",
"source_description": null,
"is_owner": true
}
],
"total": 1
}Only active, non-deleted source attachments are returned. The full source's source_config follows the same owner-based redaction as the embedded sources on GET /v1/conversations/{id}.
curl -s "https://api.privatemind.com/v1/conversations/1234/sources" \
-H "Authorization: Bearer $PMIND_USER_KEY"Generate an assistant reply
POST /v1/conversations/{id}/generate streams an assistant reply for a previously-saved user message. The response is PrivateMind-shape SSE, not OpenAI deltas. The server pipes upstream chunks through verbatim, including the data: [DONE] terminator.
Prerequisites
To call this endpoint you need a saved message row in the conversation. Create one first by sending a user message through the chat widget or app UI. (Programmatic message creation is on the roadmap but not yet exposed.)
Query parameters
| Param | Type | Notes |
|---|---|---|
model |
string | Model id to generate with (e.g. kimi-k2-6). |
thinking |
"true" / "false" |
Default true. Set false to disable thinking on hybrid models. |
webSearch |
"true" / "false" |
Default false. Honoured only if the org has web search enabled. |
Request body
{
"conversation_id": 1234,
"message_id": 9001
}message_id must reference a message in the conversation owned by the calling user.
Response
Content-Type: text/event-stream. Frames look like:
data: {"conversation_id": 1234, "title": "New Chat"}
data: {"chunk": "Here's"}
data: {"chunk": " a sketch..."}
data: [DONE]The first frame carries the conversation id and current title. Subsequent frames carry model output and any agentic events. On agentic runs (sources attached, shell tools enabled, web search) the frames also include tool-call events.
curl -N "https://api.privatemind.com/v1/conversations/1234/generate?model=kimi-k2-6" \
-H "Authorization: Bearer $PMIND_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"conversation_id": 1234, "message_id": 9001}'-N disables curl's output buffering so SSE frames print as they arrive.
Delete conversations
POST /v1/conversations/delete batch-deletes conversations the calling user owns (or admins on a shared conversation). Deletion is permanent: messages, attachments, vectorized file records, conversation-source maps, participants, and project links are all removed in one transaction.
Request body
{
"conversation_ids": [1234, 1235]
}| Field | Type | Notes |
|---|---|---|
conversation_ids |
number[] | One or more conversation ids. Each must be owned by the user, or the user must hold owner / admin participant role on it. |
Response
{
"success": true,
"message": "Conversations deleted successfully"
}If any id in the batch isn't owned (or share-admin'd) by the user, the whole batch returns 404 and nothing is deleted.
curl -s "https://api.privatemind.com/v1/conversations/delete" \
-H "Authorization: Bearer $PMIND_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"conversation_ids": [1234, 1235]}'Errors
These routes return the standard HTTP codes documented on the Errors page, with a few specifics:
| Code | Meaning |
|---|---|
400 |
Malformed conversation id (path segment must be alphanumeric, -, _, 1–64 chars), or invalid request body. |
403 |
user_key_required: the bearer is an admin or application key. Mint a user key via POST /v1/auth/exchange. |
404 |
Conversation not owned by the user, not shared with the user, or doesn't exist. The same code covers "message not found in conversation" on generate. |
429 |
Per-user-key rate limit. |
502 |
Upstream backend unreachable or returned a 5xx. |
Where next
- Authentication: the standard bearer shape used by
/v1/chat/completionsand friends. - Chat completions: the stateless OpenAI-shape inference endpoint. Use it when you don't need to persist or share the conversation.
- Streaming: how OpenAI-shape SSE works on
/v1/chat/completions. The PM-shape stream on/generateis different but the connection-handling advice carries over. - Errors: retry strategy and the error envelope.