Skip to Content

Conversation Endpoints

Manage agent conversation sessions. Served by the orchestration engine and proxied through the API gateway at /api/v1/conversations.

All routes derive the requesting user from the gateway-injected X-User-Id header (stamped from the R42 perimeter resolve, not raw claims). The list is scoped to that user; PATCH/DELETE are owner-guarded (only the owner — or a not-yet-attributed row — can mutate).

Endpoints

MethodPathDescription
GET/api/v1/conversationsList the current user’s conversations
POST/api/v1/conversationsCreate (or pre-register) a conversation
GET/api/v1/conversations/{id}/messagesGet conversation messages
PATCH/api/v1/conversations/{id}Update title or status
DELETE/api/v1/conversations/{id}Soft-delete a conversation
POST/api/v1/conversations/{id}/attachmentsUpload a message attachment (multipart)
GET/api/v1/conversations/{id}/attachmentsList the conversation’s attachments
DELETE/api/v1/conversations/{id}/attachments/{aid}Delete an attachment

GET /api/v1/conversations

List the authenticated user’s conversations, most recent first. Excludes soft-deleted rows and harness runs (conversation_type of replay or test). Returns 401 when no identity is present.

curl -H "Authorization: Bearer $TOKEN" \ "http://localhost:8000/api/v1/conversations?limit=50"
ParameterTypeDefaultDescription
limitint50Number of results (1–200)
user_idstringFallback identity for local tooling; the X-User-Id header wins

Response (200):

{ "conversations": [ { "id": "conv-abc123", "agent_id": "rule37-agent", "user_id": "6ff89c0b-…", "status": "active", "title": "Spacing exception for Smith Ranch", "preview": "Would you like me to proceed with the offset well analysis…", "conversation_type": "filing_prep", "last_message_at": "2026-07-07T14:35:00+00:00", "created_at": "2026-07-07T14:30:00+00:00", "updated_at": "2026-07-07T14:35:00+00:00", "metadata": null } ], "total": 1 }

title falls back to the first user message when no explicit title was set; preview is the last message’s first 80 characters (for sidebar rows).


POST /api/v1/conversations

Create a conversation. Ownership comes from X-User-Id (body user_id is a fallback for direct harness calls). The UI defers this call until the first message is sent, so abandoned drafts never create rows.

Harnesses (golden capture, smoke runs) pre-register their conversation with an explicit id and conversation_type: "test" so it never appears in a user’s sidebar. The insert is idempotent on a caller-supplied id.

curl -X POST http://localhost:8000/api/v1/conversations \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "rule37-agent", "conversation_type": "filing_prep", "title": "Spacing exception for Smith Ranch" }'
FieldTypeDescription
agent_idstringRequired. Owning agent
idstringOptional caller-supplied id (harness pre-registration)
titlestringOptional; defaults to "New conversation"
conversation_typestringfiling_prep, field_event, or test
user_idstringFallback owner when no X-User-Id header

Response (200): {"id": "conv-new456…", "status": "created"}


GET /api/v1/conversations/{conversation_id}/messages

Retrieve all messages in a conversation, ordered by sequence_number.

curl -H "Authorization: Bearer $TOKEN" \ http://localhost:8000/api/v1/conversations/conv-abc123/messages

Response (200):

{ "messages": [ { "id": "…", "conversation_id": "conv-abc123", "sequence_number": 1, "role": "user", "content": "I need a spacing exception for well 42-383-12345", "user_id": "6ff89c0b-…", "tool_name": null, "metadata": null, "created_at": "2026-07-07T14:30:00+00:00" } ], "total": 1 }

PATCH /api/v1/conversations/{conversation_id}

Update the title and/or status. Owner-guarded: with an X-User-Id present, only the owner (or a not-yet-attributed row) can be updated; otherwise 404.

curl -X PATCH http://localhost:8000/api/v1/conversations/conv-abc123 \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"title": "Updated title"}'

DELETE /api/v1/conversations/{conversation_id}

Soft delete — sets deleted_at and preserves the data for the audit trail. Owner-guarded like PATCH.

curl -X DELETE http://localhost:8000/api/v1/conversations/conv-abc123 \ -H "Authorization: Bearer $TOKEN"

Response (200): {"id": "conv-abc123", "status": "deleted"}


POST /api/v1/conversations/{conversation_id}/attachments

Upload a file for use in a message (R40.5 P3). Multipart form with a single file field. Extraction happens at upload time — the stored artifact is the extracted TEXT (original bytes are not kept), so unreadable files fail here with a human-readable detail the UI shows verbatim.

Limits: PDF / CSV / XLSX / DOCX / TXT, ≤10MB per file, ≤3 attachments per message, extracted text capped at ~8k tokens per attachment (tail-truncated, flagged in the response).

curl -X POST http://localhost:8000/api/v1/conversations/conv-abc123/attachments \ -H "Authorization: Bearer $TOKEN" \ -F "file=@spacing-report.pdf"

Response (200):

{ "id": "0d0e…", "filename": "spacing-report.pdf", "content_type": "application/pdf", "size_bytes": 48213, "token_estimate": 2110, "truncated": false }

Errors: 413 over 10MB · 415 disallowed type · 422 unreadable/empty (encrypted PDF, image-only scan, blank file).

To use attachments in a turn, pass their ids on the send:

  • SSE stream: GET /conversations/{id}/stream?message=…&attachment_ids=<id1>,<id2>
  • Sync: POST /execute body field attachment_ids: ["<id1>", …]

The server splices each file’s extracted text into the turn’s model context (and the audit snapshot); the persisted user message keeps the typed text, with pill refs {"attachments": [{"id", "filename"}]} in the user row’s metadata. Ids are validated against the tenant and conversation — a foreign id fails the request rather than being silently dropped.


Projects (R40.5 P4)

Owner-scoped containers under /api/v1/projects (identity from X-User-Id):

MethodPathDescription
GET/api/v1/projectsList the caller’s projects (+ member counts)
POST/api/v1/projectsCreate — {name, description?, instructions?, defaults?}
GET/api/v1/projects/{id}Project + knowledge file list
PATCH/api/v1/projects/{id}Update name/description/instructions/defaults
DELETE/api/v1/projects/{id}Detaches member conversations (project_id → NULL), deletes knowledge
POST/api/v1/projects/{id}/knowledgeUpload a knowledge file (multipart; ≤5 per project, P3 extraction limits; 409 at the cap)
DELETE/api/v1/projects/{id}/knowledge/{fid}Remove a knowledge file

Conversations gain project_id: optional on POST /conversations (membership is set at creation), returned on the list, and available as a ?project_id= list filter. Turn behavior: each turn refreshes the project payload — its defaults.thinking_mode joins the settings chain (request → project → tenant → env, clamped by the tenant ceiling), instructions + knowledge inject as system messages once per conversation (presence-scan deduped; the injection ledger is marked per the spec contract).

Last updated on