Conversations API
Conversations is the shared dialogue layer for the platform: one place for human↔human, human↔agent, and agent↔agent threads, with conversation participants (users, agents, operators, contacts), IAM-scoped project and conversation zones, durable activity events for audit and automations, and WebSocket streams for conversation events and per-principal list refreshes. Conversation projects live at `org/{company_id}/conversation-project/{project_id}` and conversation threads live at `org/{company_id}/conversation/{conversation_id}`; project_id is a product grouping field, not a nested IAM path. Conversations can be linked to an external subject (subject_type/subject_id, e.g. a workflow item) for discovery, opened toward outbound contacts (to), and carry per-caller capability flags so UIs can hide actions the principal cannot perform. Use Channel mode to create outbound email conversations with channel='email', from_channel_address_id, to[], and first_message. Non-workflow create and reply require channels:addresses:send on the selected Channels address. Messages can carry rich tool/model metadata, structured content blocks, and Buckets-backed attachments. Inbound email persists placeholder AttachmentRefs immediately, then streams message.updated while each import advances through pending, downloading, uploading, and a terminal or processing state. Listing the company feed is grant-filtered: results are limited to conversations the caller holds conversations:conversations:view on (participation grants view, but an explicit grant does too), while by-subject discovery is intentionally not participant-gated. Each project and conversation also exposes a paginated activity feed and a single-event lookup. Activity and automations catalogs expose event slugs such as conversations.conversation.created, conversations.conversation.updated, conversations.conversation.deleted, conversations.message.added, conversations.participant.added, conversations.participant.removed, conversations.project.created, conversations.project.updated, conversations.project.deleted, plus conversations.attachment.uploaded and conversations.attachment.failed. Conversations owns episodic continuity: bounded granular notes, one living digest, an embedding-only notes/ export, close/idle settlement, and participant/privacy-scoped prior-conversation digest recall used by Agents and inferred starters. The public notes tool returns granular notes; digest/context/recall adapters are internal service contracts. IAM checks use the conversations:* permission namespace (conversations:projects:*, conversations:conversations:*, conversations:messages:create, conversations:participants:manage, conversations:attachments:create, conversations:activity:view, conversations:settings:{view,manage}, conversations:privacy:manage, conversations:notes:{view,manage}, conversations:permissions:manage) on org, conversation-project, and conversation resources (see each route). Holders of agents:conversations:view on a participating agent get derived read access to that agent's public conversations.
/api/v1/conversationsAutomation triggers: view every Conversations event, payload field, and predicate.
Projects
Projects group conversations under a company (slug, branding, optional Buckets folder, agent system-prompt append). Use them when you want a stable container for many threads rather than ad-hoc DMs. Projects are created and listed per company; id-addressed reads, updates, and deletes use the `/projects/{project_id}` routes.
/api/v1/conversations/companies/{company_id}/projectsCreate Project
Create a project for the company. Writes a conversations.project.created activity event.
Bearer token required. Permission: conversations:projects:create on org/{company_id}
Path Parameters
| Name | Type | Description |
|---|---|---|
company_id* | string (UUID) | Organization ID |
Request Body
| Name | Type | Description |
|---|---|---|
company_id* | string (UUID) | Must match the path company_id |
slug* | string | Unique slug within the company |
name* | string | Display name |
description | string | null | Optional description |
icon | string | null | Optional icon key or URL |
color | string | null | Optional theme color |
bucket_id | string (UUID) | null | Linked Buckets folder |
system_prompt_append | string | null | Text appended to agent system prompts in this project |
Response Fields
| Name | Type | Description |
|---|---|---|
id* | UUID | Project ID |
company_id* | UUID | Organization ID |
slug* | string | Slug |
name* | string | Display name |
description* | string | null | Description |
icon* | string | null | Icon |
color* | string | null | Color |
bucket_id* | UUID | null | Folder link |
system_prompt_append* | string | null | Prompt append |
created_by* | UUID | null | Creator principal |
conversation_count* | integer | Number of conversations in this project (defaults to 0) |
created_at* | datetime | Created at |
updated_at* | datetime | Updated at |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/companies/{company_id}/projects \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"slug": "customer-success",
"name": "Customer Success",
"description": "Shared inbox and agent threads"
}'Response
201 Created{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"slug": "customer-success",
"name": "Customer Success",
"description": "Shared inbox and agent threads",
"icon": null,
"color": null,
"bucket_id": null,
"system_prompt_append": null,
"created_by": "u1111111-2222-3333-4444-555555555555",
"conversation_count": 0,
"created_at": "2026-04-21T10:00:00Z",
"updated_at": "2026-04-21T10:00:00Z"
}/api/v1/conversations/companies/{company_id}/projectsList Projects
Paginated projects for a company, ordered by name, each with a conversation_count.
Bearer token required. Permission: conversations:projects:view on each conversation-project resource
Path Parameters
| Name | Type | Description |
|---|---|---|
company_id* | string (UUID) | Organization ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Page size (1–200)Default: 50 |
offset | integer | OffsetDefault: 0 |
q | string | null | Case-insensitive name filter |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | ProjectOut[] | Project rows |
total* | integer | Total matching projects |
curl "https://platform.ergondata.ai/api/v1/conversations/companies/{company_id}/projects?limit=50&offset=0" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"slug": "customer-success",
"name": "Customer Success",
"description": null,
"icon": null,
"color": null,
"bucket_id": null,
"system_prompt_append": null,
"created_by": "u1111111-2222-3333-4444-555555555555",
"conversation_count": 3,
"created_at": "2026-04-21T10:00:00Z",
"updated_at": "2026-04-21T10:00:00Z"
}
],
"total": 1
}/api/v1/conversations/projects/{project_id}Get Project
Fetch one project with its live conversation_count.
Bearer token required. Permission: conversations:projects:view on org/{company_id}/conversation-project/{project_id}
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
Response Fields
| Name | Type | Description |
|---|---|---|
(ProjectOut)* | object | Same fields as the create-project response |
curl https://platform.ergondata.ai/api/v1/conversations/projects/{project_id} \
-H "Authorization: Bearer {token}"Response
200 OK{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"slug": "customer-success",
"name": "Customer Success",
"description": null,
"icon": null,
"color": null,
"bucket_id": null,
"system_prompt_append": null,
"created_by": "u1111111-2222-3333-4444-555555555555",
"conversation_count": 3,
"created_at": "2026-04-21T10:00:00Z",
"updated_at": "2026-04-21T10:00:00Z"
}/api/v1/conversations/projects/{project_id}Update Project
Patch project fields. Writes conversations.project.updated.
Bearer token required. Permission: conversations:projects:edit on org/{company_id}/conversation-project/{project_id}
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
Request Body
| Name | Type | Description |
|---|---|---|
name | string | null | Display name |
slug | string | null | Slug |
description | string | null | Description |
icon | string | null | Icon |
color | string | null | Color |
bucket_id | string (UUID) | null | Folder link |
system_prompt_append | string | null | Prompt append |
Response Fields
| Name | Type | Description |
|---|---|---|
(ProjectOut)* | object | Updated project, same fields as the create-project response |
curl -X PATCH https://platform.ergondata.ai/api/v1/conversations/projects/{project_id} \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"name": "Customer Success (EMEA)"}'Response
200 OK{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"slug": "customer-success",
"name": "Customer Success (EMEA)",
"description": null,
"icon": null,
"color": null,
"bucket_id": null,
"system_prompt_append": null,
"created_by": "u1111111-2222-3333-4444-555555555555",
"conversation_count": 3,
"created_at": "2026-04-21T10:00:00Z",
"updated_at": "2026-04-21T11:30:00Z"
}/api/v1/conversations/projects/{project_id}Delete Project
Remove a project after logging conversations.project.deleted.
Bearer token required. Permission: conversations:projects:delete on org/{company_id}/conversation-project/{project_id}
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
curl -X DELETE https://platform.ergondata.ai/api/v1/conversations/projects/{project_id} \
-H "Authorization: Bearer {token}"Response
204 No ContentConversations
Create and manage threads. DMs require exactly two principals and auto-deduplicate for human-only pairs (no agent on either side) so the same two people do not get parallel empty threads; agent-inclusive DMs always create a new row. Group chats need at least two participants and a title. For Channel mode email, supply channel='email', from_channel_address_id, to[], and normally first_message; non-workflow create and reply require channels:addresses:send through Channels. The company feed is filtered to conversations where the caller is an active participant, while by-subject discovery is not participant-gated.
/api/v1/conversations/conversationsCreate Conversation
Open a DM or group conversation, optionally seeding a first message, linking a subject, or addressing outbound contacts. Channel mode email uses channel='email', from_channel_address_id, to[], and first_message. Non-workflow email requires channels:addresses:send; item-bound outbound threads instead require workflows:items:start_conversation.
Bearer token required. Permission: conversations:conversations:create on org/{company_id} (or on the target conversation-project for project threads)
Request Body
| Name | Type | Description |
|---|---|---|
company_id* | string (UUID) | Tenant |
type | string | dm or groupDefault: dm |
channel | string | Delivery mode: direct or email (Channel mode)Default: direct |
project_id | string (UUID) | null | Optional project scope |
title | string | null | Required for group conversations |
metadata | object | null | Opaque JSON metadata. One reserved key is read by the agents runtime: agent_model_override = { "<agent_id>": { "llm_config_id": "<uuid>", "model_slug": "<slug>" } } pins, per conversation, which model a given agent answers with (sticky until changed). The override is honored only when that agent holds agents:llm-configs:use on the config and it belongs to the agent's company; otherwise the runtime falls back to the agent's default model. |
participants | ParticipantCreate[] | Each item: principal_id (UUID), principal_type (user | agent | operator | contact), role (default member). Defaults to [] |
subject_type | string | null | External subject type to link (e.g. workflow.item) |
subject_id | string (UUID) | null | External subject id to link |
to | ContactRecipient[] | Outbound contact recipients, each with address (required) and optional display_name. Defaults to [] |
first_message | FirstMessage | null | Optional initial message: content (string | null) and attachments (AttachmentRef[] | null) |
from_channel_address_id | string (UUID) | null | Required for email. Channels address used to send; non-workflow callers need channels:addresses:send |
auto_grant_enabled | boolean | Auto-grant view access to added participantsDefault: true |
privacy_level | string | null | public or private. Omitted defaults to private. Ignored when the request dedupes into an existing human DM (the existing row's setting wins). Always-public invariant: all-agent conversations (only agent participants) and any conversation that includes a privacy-locked agent are forced public regardless of this value. Also patchable later via PATCH /conversations/{id} |
note_taking_cadence | object | null | Per-conversation cadence override (keys: every_messages, every_minutes, every_tokens, min_messages). Omitted or null inherits the company default. Also patchable later via PATCH /conversations/{id} |
starter_mode | "user-talks-first" | "preset" | "infer" | Opening behavior. Presets are deterministic; infer uses authorized participant-scoped digest context.Default: "user-talks-first" |
Response Fields
| Name | Type | Description |
|---|---|---|
id* | UUID | Conversation ID |
company_id* | UUID | Tenant |
project_id* | UUID | null | Project if set |
type* | string | dm or group |
channel* | string | Channel |
status* | string | Lifecycle status |
title* | string | null | Title |
metadata* | object | null | Metadata |
correlation_id* | UUID | null | Correlation id if set |
started_by* | UUID | null | Creator principal |
subject_type* | string | null | Linked subject type |
subject_id* | UUID | null | Linked subject id |
from_channel_address_id* | UUID | null | Outbound channel address |
bucket_id* | UUID | null | Backing attachment folder if any |
privacy_level* | string | public or private (default private) |
note_taking_cadence | object | null | null inherits the company default cadence |
starter_mode* | string | Resolved conversation starter behavior |
capabilities* | ConversationCapabilities | Per-caller flags: view, send_message, edit, delete, manage_participants, manage_security, upload_attachment |
participants* | ParticipantOut[] | Participant rows |
created_at* | datetime | Created at |
updated_at* | datetime | Updated at |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"type": "dm",
"channel": "direct",
"participants": [
{"principal_id": "u1111111-2222-3333-4444-555555555555", "principal_type": "user"},
{"principal_id": "u2222222-3333-4444-5555-666666666666", "principal_type": "user"}
]
}'Response
201 Created{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"project_id": null,
"type": "dm",
"channel": "direct",
"status": "open",
"title": null,
"metadata": null,
"correlation_id": null,
"started_by": "u1111111-2222-3333-4444-555555555555",
"subject_type": null,
"subject_id": null,
"from_channel_address_id": null,
"bucket_id": null,
"capabilities": {
"view": true,
"send_message": true,
"edit": true,
"delete": true,
"manage_participants": true,
"manage_security": false,
"upload_attachment": true
},
"participants": [
{
"id": "p1000000-0000-4000-8000-000000000001",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"principal_id": "u1111111-2222-3333-4444-555555555555",
"principal_type": "user",
"role": "member",
"display_name": "Jane Doe",
"avatar_url": null,
"address": null,
"joined_at": "2026-04-21T12:00:00Z",
"left_at": null,
"last_read_at": null
},
{
"id": "p1000000-0000-4000-8000-000000000002",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"principal_id": "u2222222-3333-4444-5555-666666666666",
"principal_type": "user",
"role": "member",
"display_name": "John Roe",
"avatar_url": null,
"address": null,
"joined_at": "2026-04-21T12:00:00Z",
"left_at": null,
"last_read_at": null
}
],
"created_at": "2026-04-21T12:00:00Z",
"updated_at": "2026-04-21T12:00:00Z"
}/api/v1/conversations/companies/{company_id}/conversationsList Conversations
Conversations the caller can view, newest activity first. Results are grant-filtered: each row is included only when the caller holds conversations:conversations:view on it (participation grants view, but an explicit security-page grant works too). Participant membership is a product filter layered on top, not the authorization source.
Bearer token required. Permission: conversations:conversations:view on each conversation resource
Path Parameters
| Name | Type | Description |
|---|---|---|
company_id* | string (UUID) | Organization ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Page size (1–200)Default: 50 |
offset | integer | OffsetDefault: 0 |
project_id | string (UUID) | null | Filter by project |
channel | string | null | Exact channel string |
type | string | null | dm or group |
status | string | null | Status filter |
participant_id | string (UUID) | null | Must include this principal |
principal_type | string | null | Filter participant_id by principal type (user | agent | operator | contact) |
q | string | null | Case-insensitive title substring |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | ConversationSummary[] | Conversation summaries (no embedded messages) |
total* | integer | Total matching rows |
curl "https://platform.ergondata.ai/api/v1/conversations/companies/{company_id}/conversations?limit=20" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"project_id": null,
"type": "dm",
"channel": "direct",
"status": "open",
"title": null,
"metadata": null,
"started_by": "u1111111-2222-3333-4444-555555555555",
"subject_type": null,
"subject_id": null,
"from_channel_address_id": null,
"bucket_id": null,
"capabilities": {
"view": true,
"send_message": true,
"edit": false,
"delete": false,
"manage_participants": false,
"manage_security": false,
"upload_attachment": true
},
"participants": [],
"created_at": "2026-04-21T12:00:00Z",
"updated_at": "2026-04-21T12:05:00Z"
}
],
"total": 1
}/api/v1/conversations/conversations/{conversation_id}Get Conversation
Conversation metadata and participants. By default the messages array is empty (include_messages defaults to false) — load history through the paginated Messages endpoint (GET /conversations/{id}/messages) instead. Pass include_messages=true only for small threads where a single-shot load is acceptable. Requires view permission; reading can be granted explicitly without adding the viewer as a participant.
Bearer token required. Permission: conversations:conversations:view on the conversation
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
include_messages | boolean | When true, eagerly loads the full message history into the messages array. Defaults to false; use the paginated Messages endpoint for real threads.Default: false |
Response Fields
| Name | Type | Description |
|---|---|---|
messages* | MessageOut[] | Messages in storage order. Empty unless include_messages=true was passed. |
participants* | ParticipantOut[] | Participant rows |
auto_grant_enabled* | boolean | Whether new participants are auto-granted view access |
privacy_level* | "private" | "public" | Cross-conversation visibility. private (default) means agents may only surface this thread into another conversation when every human participant here is also present there; public falls back to normal permission checks. |
note_taking_cadence* | object | null | Optional cadence override (every_messages / every_minutes / every_tokens / min_messages). null inherits the company default. |
(other fields)* | ConversationOut fields | Same core fields as the create-conversation response (id, type, status, capabilities, subject_*, etc.) |
curl https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id} \
-H "Authorization: Bearer {token}"Response
200 OK{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"project_id": null,
"type": "dm",
"channel": "direct",
"status": "open",
"title": null,
"metadata": null,
"correlation_id": null,
"started_by": "u1111111-2222-3333-4444-555555555555",
"subject_type": null,
"subject_id": null,
"from_channel_address_id": null,
"bucket_id": null,
"auto_grant_enabled": true,
"privacy_level": "private",
"note_taking_cadence": null,
"capabilities": {
"view": true,
"send_message": true,
"edit": true,
"delete": true,
"manage_participants": true,
"manage_security": false,
"upload_attachment": true
},
"participants": [],
"messages": [],
"created_at": "2026-04-21T12:00:00Z",
"updated_at": "2026-04-21T12:01:00Z"
}/api/v1/conversations/conversations/{conversation_id}Update Conversation
Patch title, status, project_id, metadata, or auto_grant_enabled. Logs conversations.conversation.updated and publishes a stream envelope { type: conversation.updated, changes }.
Bearer token required. Permission: conversations:conversations:edit on the conversation
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Request Body
| Name | Type | Description |
|---|---|---|
title | string | null | Title |
status | string | null | Status |
project_id | string (UUID) | null | Project |
metadata | object | null | Replaces metadata when set. Use the reserved agent_model_override key (see Create Conversation) to pin which model an agent answers with for this conversation; PATCH it to switch the model mid-thread. |
auto_grant_enabled | boolean | null | Toggle auto-granting view access to participants |
privacy_level | "private" | "public" | null | Set the conversation's cross-conversation visibility. private is the default; public opts the thread into normal permission checks for agent surfacing. Setting private is rejected (400) for all-agent conversations or any thread including a privacy-locked agent — those are always public. |
note_taking_cadence | object | null | Cadence override. Recognized keys: every_messages, every_minutes, every_tokens, min_messages (non-negative integers). Unknown keys are rejected; values are clamped to platform floors/ceilings by the worker. null inherits the company default. |
Response Fields
| Name | Type | Description |
|---|---|---|
(ConversationOut)* | object | Updated conversation, same fields as the create-conversation response (including privacy_level and note_taking_cadence) |
curl -X PATCH https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id} \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"title": "Renewal — Acme Corp"}'Response
200 OK{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"project_id": null,
"type": "group",
"channel": "direct",
"status": "open",
"title": "Renewal — Acme Corp",
"metadata": null,
"correlation_id": null,
"started_by": "u1111111-2222-3333-4444-555555555555",
"subject_type": null,
"subject_id": null,
"from_channel_address_id": null,
"bucket_id": null,
"capabilities": {
"view": true,
"send_message": true,
"edit": true,
"delete": true,
"manage_participants": true,
"manage_security": false,
"upload_attachment": true
},
"participants": [],
"created_at": "2026-04-21T12:00:00Z",
"updated_at": "2026-04-21T12:10:00Z"
}/api/v1/conversations/conversations/{conversation_id}Delete Conversation
Hard delete after conversations.conversation.deleted is logged.
Bearer token required. Permission: conversations:conversations:delete on the conversation
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
curl -X DELETE https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id} \
-H "Authorization: Bearer {token}"Response
204 No Content/api/v1/conversations/conversationsList Conversations by Subject
List conversations linked to a specific subject (e.g. a workflow item) so callers can discover threads bound to an entity before joining. Not participant-gated; cross-tenant exposure is prevented by the mandatory company_id filter.
Bearer token required. Permission: conversations:conversations:view (not participant-gated)
Query Parameters
| Name | Type | Description |
|---|---|---|
company_id* | string (UUID) | Organization ID |
subject_type* | string | Subject type (e.g. workflow.item) |
subject_id* | string (UUID) | Subject entity ID |
limit | integer | Page size (1–200)Default: 50 |
offset | integer | OffsetDefault: 0 |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | ConversationSummary[] | Matching conversation summaries |
total* | integer | Total count |
curl "https://platform.ergondata.ai/api/v1/conversations/conversations?company_id={cid}&subject_type=workflow.item&subject_id={item_id}" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"project_id": null,
"type": "dm",
"channel": "direct",
"status": "open",
"title": null,
"metadata": null,
"started_by": "u1111111-2222-3333-4444-555555555555",
"subject_type": "workflow.item",
"subject_id": "a9e1c2b3-d4f5-6789-abcd-ef0123456789",
"from_channel_address_id": null,
"bucket_id": null,
"participants": [],
"created_at": "2026-04-20T10:00:00Z",
"updated_at": "2026-04-20T14:30:00Z"
}
],
"total": 1
}/api/v1/conversations/conversations/bulk-by-subjectBulk List by Subject
Batch-resolve conversation summaries for multiple subjects in one call. Returns a map of subject_id to a summary with conversation_count and the distinct channels seen. Used by list UIs that need counts for many entities at once. Not participant-gated.
Bearer token required. Permission: conversations:conversations:view (not participant-gated)
Query Parameters
| Name | Type | Description |
|---|---|---|
company_id* | string (UUID) | Organization ID |
subject_type* | string | Subject type |
subject_ids* | string (comma-separated UUIDs) | Subject IDs to resolve |
Response Fields
| Name | Type | Description |
|---|---|---|
by_subject_id* | object | Map of subject_id to SubjectConversationsSummary { conversation_count: integer, channels: string[] } |
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/bulk-by-subject?company_id={cid}&subject_type=workflow.item&subject_ids={id1},{id2}" \
-H "Authorization: Bearer {token}"Response
200 OK{
"by_subject_id": {
"a9e1c2b3-d4f5-6789-abcd-ef0123456789": { "conversation_count": 2, "channels": ["direct", "email"] },
"b8d2e3f4-a5c6-7890-bcde-f01234567890": { "conversation_count": 0, "channels": [] }
}
}/api/v1/conversations/conversations/bulk-deleteBulk Delete Conversations
Delete multiple conversations at once. The delete permission is checked per id; ids the caller cannot delete land in forbidden and unknown ids in not_found instead of aborting the request. Permanently removes the conversations and their messages, participants, and attachment references.
Bearer token required. Permission: conversations:conversations:delete (checked per id)
Request Body
| Name | Type | Description |
|---|---|---|
conversation_ids* | string[] (UUID) | Array of conversation IDs to delete |
Response Fields
| Name | Type | Description |
|---|---|---|
deleted_ids* | string[] | IDs that were deleted |
forbidden* | string[] | IDs the caller lacked permission to delete (defaults to []) |
not_found* | string[] | IDs that did not exist (defaults to []) |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/bulk-delete \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"conversation_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479", "a1b2c3d4-e5f6-7890-abcd-ef1234567890"]}'Response
200 OK{
"deleted_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
"forbidden": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
"not_found": []
}Messages
Append messages to a thread. Attachments must already exist as conversation-scoped Buckets files (upload via the attachment routes first). Tool, token, and content-block fields are stored for agent traces.
/api/v1/conversations/conversations/{conversation_id}/messagesCreate Message
Post a message as an active participant. Defaults sender_participant_id to the caller's row; an explicit value must identify that same participant. This public route is also the conversations.messages.create automation capability, so automation principals receive the same participant, privacy, attachment, and channel reply checks. Emits conversations.message.added and streams message.created. Non-workflow Channel mode email requires channels:addresses:send; item-bound threads require workflows:items:reply_conversation.
Bearer token required. Permission: conversations:messages:create on the conversation; must be an active participant
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Request Body
| Name | Type | Description |
|---|---|---|
content | string | null | Plain text body |
role | string | Message roleDefault: user |
kind | string | Message kindDefault: message |
sender_participant_id | string (UUID) | null | Defaults to caller's participant id |
content_blocks | array | null | Structured content blocks |
attachments | AttachmentRef[] | null | AttachmentRef objects (file_id, filename, content_type, optional size, visibility, promoted_to_bucket_id, upload_batch_id, status, process_skip_reason) |
tool_call_id | string | null | Tool call correlation |
tool_name | string | null | Tool name |
tool_args | object | null | Serialized arguments |
tool_result | any | null | Tool output |
model_used | string | null | Model id |
input_tokens | integer | null | Prompt tokens |
output_tokens | integer | null | Completion tokens |
cost_usd | number | null | Estimated USD cost snapshot for the turn (tokens x model catalog price). Normally written by the agents runtime on the final assistant message. |
channel_ref | object | null | External channel reference payload |
Response Fields
| Name | Type | Description |
|---|---|---|
id* | UUID | Message ID |
conversation_id* | UUID | Conversation |
sender_participant_id* | UUID | null | Sender participant row |
role* | string | Role |
kind* | string | Message kind (default message) |
content* | string | null | Body |
content_blocks* | array | null | Blocks |
attachments* | AttachmentRef[] | null | Attachment refs. Inbound email refs expose status and process_skip_reason and update over message.updated stream events |
tool_call_id* | string | null | Tool call id |
tool_name* | string | null | Tool name |
tool_args* | object | null | Tool args |
tool_result* | any | null | Tool result |
model_used* | string | null | Model |
input_tokens* | integer | null | In tokens |
output_tokens* | integer | null | Out tokens |
cost_usd* | number | null | Estimated USD cost snapshot for the turn (tokens x model catalog price) |
channel_ref* | object | null | Channel ref |
origin* | object | null | Cross-conversation provenance for relayed / cross-posted messages (server-set). null for ordinary in-thread messages. |
created_at* | datetime | Created at |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/messages \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"content": "Please draft a renewal summary.", "role": "user"}'Response
201 Created{
"id": "m3000000-0000-4000-8000-000000000003",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"sender_participant_id": "p1000000-0000-4000-8000-000000000001",
"role": "user",
"kind": "message",
"content": "Please draft a renewal summary.",
"content_blocks": null,
"attachments": null,
"tool_call_id": null,
"tool_name": null,
"tool_args": null,
"tool_result": null,
"model_used": null,
"input_tokens": null,
"output_tokens": null,
"cost_usd": null,
"channel_ref": null,
"origin": null,
"created_at": "2026-04-21T12:15:00Z"
}/api/v1/conversations/conversations/{conversation_id}/messagesList Messages
Keyset-paginated message history. Items are returned oldest-first within a page. Walk backwards through history with the before cursor (pass the previous page's oldest_cursor) and catch up on new messages with after (pass newest_cursor). This is the recommended way to load a thread; Get Conversation no longer returns messages by default.
Bearer token required. Permission: conversations:conversations:view on the conversation. Exposed to agents as the conversations.messages.list tool.
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Page size (1-200, clamped server-side).Default: 50 |
before | string (UUID) | Return messages older than this message id (exclusive). Unknown cursor returns 404. |
after | string (UUID) | Return messages newer than this message id (exclusive). Unknown cursor returns 404. |
since_ts | string (datetime) | Inclusive lower time bound. Naive timestamps are treated as UTC. |
until_ts | string (datetime) | Inclusive upper time bound. Naive timestamps are treated as UTC. |
include_tool_payloads | boolean | When false, nulls out heavy tool_args/tool_result/content_blocks fields for a lighter scan.Default: true |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | MessageOut[] | Messages for the page, oldest-first. |
has_more* | boolean | Whether more messages exist in the paging direction. |
oldest_cursor* | string (UUID) | null | Message id of the oldest item; pass as before to page older. |
newest_cursor* | string (UUID) | null | Message id of the newest item; pass as after to page newer. |
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/messages?limit=50" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [
{
"id": "m3000000-0000-4000-8000-000000000003",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"sender_participant_id": "p1000000-0000-4000-8000-000000000001",
"role": "user",
"kind": "message",
"content": "Hello!",
"content_blocks": null,
"attachments": null,
"tool_call_id": null,
"tool_name": null,
"tool_args": null,
"tool_result": null,
"model_used": null,
"input_tokens": null,
"output_tokens": null,
"cost_usd": null,
"channel_ref": null,
"created_at": "2026-04-21T12:01:00Z"
}
],
"has_more": false,
"oldest_cursor": "m3000000-0000-4000-8000-000000000003",
"newest_cursor": "m3000000-0000-4000-8000-000000000003"
}Notes
Whenever an agent participates, the note-taker summarizes bounded new windows into granular notes (topic, what happened, decisions, open questions, entities) and folds eligible notes into one living digest. Close events and the idle settlement sweeper capture the final remainder. Granular Markdown is exported to an embedding-only notes/ folder and backs conversations.notes.search; the authoritative rows and living digest remain in Conversations.
/api/v1/conversations/conversations/{conversation_id}/notesSearch Notes
Keyset-paginated notes for a conversation with hybrid lexical/semantic ranking and optional time-window filters. Items are ordered deterministically. Exposed to agents as conversations.notes.search; cross-conversation surfacing is subject to the conversation's privacy_level.
Bearer token required. Permission: conversations:conversations:view OR the granular conversations:notes:view on the conversation (notes:view lets a principal read notes without full thread access). For public conversations, agents:conversations:view on a participating agent also grants derived read.
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
q | string | Hybrid lexical/semantic query across topic, what_happened, decisions, open_questions, and entities. |
limit | integer | Page size (1-200, clamped server-side).Default: 50 |
before | string (UUID) | Return notes older than this note id (exclusive). Unknown cursor returns 404. |
after | string (UUID) | Return notes newer than this note id (exclusive). Unknown cursor returns 404. |
since_ts | string (datetime) | Only notes whose covered window overlaps at/after this time (falls back to created_at when window bounds are null). |
until_ts | string (datetime) | Only notes whose covered window overlaps at/before this time. |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | NoteOut[] | Notes for the page. Each note has topic, what_happened, decisions[], open_questions[], entities[], window_start, window_end, message_count, and optional bucket_id/file_id pointers. |
has_more* | boolean | Whether more notes exist in the paging direction. |
oldest_cursor* | string (UUID) | null | Note id of the oldest item; pass as before to page older. |
newest_cursor* | string (UUID) | null | Note id of the newest item; pass as after to page newer. |
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/notes?q=billing&limit=20" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [
{
"id": "n1000000-0000-4000-8000-000000000001",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"topic": "Renewal billing terms",
"what_happened": "Customer asked about proration on the annual renewal; agent confirmed mid-term upgrades are prorated.",
"decisions": ["Send renewal quote with prorated line items"],
"open_questions": ["Confirm PO number before invoicing"],
"entities": ["Acme Corp", "Annual Plan"],
"window_start": "2026-04-21T12:00:00Z",
"window_end": "2026-04-21T12:20:00Z",
"message_count": 14,
"bucket_id": "b1000000-0000-4000-8000-000000000001",
"file_id": "f1000000-0000-4000-8000-000000000001",
"created_at": "2026-04-21T12:20:05Z"
}
],
"has_more": false,
"oldest_cursor": "n1000000-0000-4000-8000-000000000001",
"newest_cursor": "n1000000-0000-4000-8000-000000000001"
}Participants
Membership changes and read-position updates. Adding a third participant to a DM upgrades it to a group (auto-title if missing). Removing sets left_at instead of deleting history. Participant rows are enriched with display_name, avatar_url, and (for contacts) address.
/api/v1/conversations/conversations/{conversation_id}/participantsAdd Participant
Invite a principal. 409 when they are already active. Emits conversations.participant.added and streams participant.added.
Bearer token required. Permission: conversations:participants:manage on the conversation
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Request Body
| Name | Type | Description |
|---|---|---|
principal_id* | string (UUID) | Principal to add |
principal_type* | string | user | agent | operator | contact |
role | string | Membership roleDefault: member |
Response Fields
| Name | Type | Description |
|---|---|---|
(ParticipantOut)* | object | Participant row: id, conversation_id, principal_id, principal_type, role, display_name, avatar_url, address, joined_at, left_at, last_read_at |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/participants \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"principal_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"principal_type": "agent"
}'Response
201 Created{
"id": "p2000000-0000-4000-8000-000000000002",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"principal_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"principal_type": "agent",
"role": "member",
"display_name": "Support Agent",
"avatar_url": null,
"address": null,
"joined_at": "2026-04-21T12:20:00Z",
"left_at": null,
"last_read_at": null
}/api/v1/conversations/conversations/{conversation_id}/participants/{participant_id}Update Participant
Currently supports advancing last_read_at for read receipts. Requires conversation view permission plus active membership.
Bearer token required. Permission: conversations:conversations:view; must be an active participant
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
participant_id* | string (UUID) | Participant row ID |
Request Body
| Name | Type | Description |
|---|---|---|
last_read_at | string (ISO 8601 datetime) | null | Read cursor timestamp |
Response Fields
| Name | Type | Description |
|---|---|---|
(ParticipantOut)* | object | Updated participant row |
curl -X PATCH https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/participants/{participant_id} \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"last_read_at": "2026-04-21T12:25:00+00:00"}'Response
200 OK{
"id": "p1000000-0000-4000-8000-000000000001",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"principal_id": "u1111111-2222-3333-4444-555555555555",
"principal_type": "user",
"role": "member",
"display_name": "Jane Doe",
"avatar_url": null,
"address": null,
"joined_at": "2026-04-21T12:00:00Z",
"left_at": null,
"last_read_at": "2026-04-21T12:25:00Z"
}/api/v1/conversations/conversations/{conversation_id}/participants/{participant_id}Remove Participant
Soft-remove by setting left_at. Emits conversations.participant.removed and streams participant.removed.
Bearer token required. Permission: conversations:participants:manage on the conversation
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
participant_id* | string (UUID) | Participant row ID |
curl -X DELETE https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/participants/{participant_id} \
-H "Authorization: Bearer {token}"Response
204 No ContentAttachments
Broker uploads to Buckets as conversation-scoped files (the service stores references only). Optional X-Correlation-ID header is forwarded. Oversize files return 413; Buckets failures return 502 with a logged conversations.attachment.failed event. The returned AttachmentRef is embedded in the next message.
/api/v1/conversations/conversations/{conversation_id}/attachmentsUpload Attachment
Multipart upload with a single file field. Returns an AttachmentRef to embed in the next message. Requires attachment permission and active membership.
Bearer token required. Permission: conversations:attachments:create on the conversation; must be an active participant
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Request Body
| Name | Type | Description |
|---|---|---|
file* | file (multipart) | Binary payload (field name file) |
Response Fields
| Name | Type | Description |
|---|---|---|
file_id* | UUID | Buckets file id |
filename* | string | Filename |
content_type* | string | MIME type |
size* | integer | null | Byte length |
visibility* | string | Buckets visibility (default bucket) |
promoted_to_bucket_id* | UUID | null | Folder promotion if any |
upload_batch_id* | UUID | null | Batch id when applicable |
status* | string | null | Inbound import or Buckets state: pending, downloading, uploading, processing, skipped, oversized, or failed |
process_skip_reason* | string | null | Reason an attachment was skipped, oversized, retrying, or failed |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/attachments \
-H "Authorization: Bearer {token}" \
-H "X-Correlation-ID: corr-7f3c9a2b-4d1e-4c88-9aa1-001122334455" \
-F "file=@./brief.pdf;type=application/pdf"Response
201 Created{
"file_id": "d4000000-0000-4000-8000-000000000004",
"filename": "brief.pdf",
"content_type": "application/pdf",
"size": 524288,
"visibility": "bucket",
"promoted_to_bucket_id": null,
"upload_batch_id": null,
"status": "processing",
"process_skip_reason": null
}/api/v1/conversations/conversations/{conversation_id}/attachments/batchUpload Attachments (Batch)
Multipart request with repeated files field for N attachments. Accepted files share upload_batch_id; skipped files list Buckets decisions without failing the whole batch.
Bearer token required. Permission: conversations:attachments:create on the conversation; must be an active participant
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Request Body
| Name | Type | Description |
|---|---|---|
files* | file[] (multipart) | One or more parts named files |
Response Fields
| Name | Type | Description |
|---|---|---|
upload_batch_id* | UUID | Shared batch id |
attachments* | AttachmentRef[] | AttachmentRef for each accepted file |
skipped* | AttachmentSkipped[] | Skipped rows (file_id, decision, reason). Defaults to [] |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/attachments/batch \
-H "Authorization: Bearer {token}" \
-F "files=@./page1.png;type=image/png" \
-F "files=@./notes.txt;type=text/plain"Response
201 Created{
"upload_batch_id": "b5000000-0000-4000-8000-000000000005",
"attachments": [
{
"file_id": "d4000000-0000-4000-8000-000000000004",
"filename": "page1.png",
"content_type": "image/png",
"size": 98234,
"visibility": "bucket",
"promoted_to_bucket_id": null,
"upload_batch_id": "b5000000-0000-4000-8000-000000000005"
}
],
"skipped": [
{
"file_id": "d4999999-9999-4999-8999-999999999999",
"decision": "rejected",
"reason": "unsupported_mime"
}
]
}Settings
Company-scoped note-taking cadence (the compute-egress pattern): service-owned config surfaced under IAM → Settings. Notes run automatically whenever an agent participates; note_taking_cadence controls the company windowing policy, with empty keys falling back to built-in defaults. Company context comes from the token (or the X-Company-Id header).
/api/v1/conversations/settings/note-takingGet Company Note-Taking Settings
Return both the company's sparse note-taking cadence overrides and the fully resolved effective cadence. Automatic notes are on for every conversation with an active agent; the platform-managed feature flag is an operational kill switch, not a tenant opt-in.
Bearer token required. Permission: conversations:settings:view on org/{company_id}
Response Fields
| Name | Type | Description |
|---|---|---|
company_id* | string (UUID) | Resolved company id |
note_taking_cadence* | object | Company cadence policy. Keys: every_messages, every_minutes, every_tokens, min_messages. Empty keys fall back to built-in defaults. |
effective_note_taking_cadence* | object | Fully resolved every_messages, every_minutes, every_tokens, and min_messages values after applying platform defaults. Use these values for placeholders and previews. |
curl https://platform.ergondata.ai/api/v1/conversations/settings/note-taking \
-H "Authorization: Bearer {token}"Response
200 OK{
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"note_taking_cadence": {
"every_messages": 20,
"min_messages": 4
},
"effective_note_taking_cadence": {
"every_messages": 20,
"every_minutes": 15,
"every_tokens": 4000,
"min_messages": 4
}
}/api/v1/conversations/settings/note-takingUpdate Company Note-Taking Settings
Upsert the company's note-taking cadence. Unknown cadence keys are rejected; values are validated as non-negative integers.
Bearer token required. Permission: conversations:settings:manage on org/{company_id}
Request Body
| Name | Type | Description |
|---|---|---|
note_taking_cadence* | object | Cadence policy (every_messages, every_minutes, every_tokens, min_messages). |
Response Fields
| Name | Type | Description |
|---|---|---|
(CompanyNoteSettingsOut)* | object | The stored settings, same shape as the GET response. |
curl -X PUT https://platform.ergondata.ai/api/v1/conversations/settings/note-taking \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"note_taking_cadence": { "every_messages": 20, "min_messages": 4 }
}'Response
200 OK{
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"note_taking_cadence": { "every_messages": 20, "min_messages": 4 },
"effective_note_taking_cadence": { "every_messages": 20, "every_minutes": 15, "every_tokens": 4000, "min_messages": 4 }
}/api/v1/conversations/settings/a2aGet Agent-to-Agent Settings
Return the company's agent-to-agent (A2A) messaging policy: whether bounded relay of agent-sent messages to other agent participants is enabled, plus the loop-safety budgets. Values are already clamped to platform limits; defaults are returned when no row exists. A2A is off by default.
Bearer token required. Permission: conversations:settings:view on org/{company_id}
Response Fields
| Name | Type | Description |
|---|---|---|
company_id* | UUID | Company |
enabled* | boolean | Whether A2A relay is enabled (default false). |
max_consecutive_agent_turns* | integer | Trailing agent-turn budget before the exchange pauses (clamped to the platform cap). |
agent_messages_per_window* | integer | Agent messages allowed per rolling window (clamped to the platform cap). |
window_seconds* | integer | Rolling window (seconds) for the rate budget. Platform constant. |
curl https://platform.ergondata.ai/api/v1/conversations/settings/a2a \
-H "Authorization: Bearer {token}"Response
200 OK{
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"enabled": false,
"max_consecutive_agent_turns": 6,
"agent_messages_per_window": 30,
"window_seconds": 600
}/api/v1/conversations/settings/a2aUpdate Agent-to-Agent Settings
Set the company's A2A messaging policy. When enabled, an agent-sent message can be relayed to the other agent participants of a thread, bounded by the budgets. Requested budgets above the platform hard caps are clamped — the response reflects the stored (clamped) values. Default is off.
Bearer token required. Permission: conversations:settings:manage on org/{company_id}
Request Body
| Name | Type | Description |
|---|---|---|
enabled | boolean | Turn A2A relay on/off (default false). |
max_consecutive_agent_turns | integer | null | Trailing agent-turn budget. null uses the platform default; values above the cap are clamped. |
agent_messages_per_window | integer | null | Agent messages per window. null uses the default; values above the cap are clamped. |
Response Fields
| Name | Type | Description |
|---|---|---|
(CompanyA2ASettingsOut)* | object | The stored, clamped settings, same shape as the GET response. |
curl -X PUT https://platform.ergondata.ai/api/v1/conversations/settings/a2a \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{ "enabled": true, "max_consecutive_agent_turns": 6 }'Response
200 OK{
"company_id": "c0ffee00-cafe-babe-dead-beefcafebabe",
"enabled": true,
"max_consecutive_agent_turns": 6,
"agent_messages_per_window": 30,
"window_seconds": 600
}Access Management
Project and conversation security-page endpoints. They proxy IAM grants inside the selected resource boundary and require `conversations:permissions:manage` on that project or conversation. Each boundary exposes eligible principals, the grantable resource-type/permission tree, and a grants collection.
/api/v1/conversations/access/conversation-projects/grants/batchCreate Project Access Grants Batch
Create grants across conversation-project roots with ordered, independent outcomes. The grouped operations expand resources first and permission IDs second, to at most 200 grants total. Validation failures do not block valid items. The response remains 200 for partial success; retry failed items or safely retry the whole request—existing natural grant tuples return already_exists. A side_effect_error reports failed post-write reconciliation, not a failed grant. ToolDef slug: conversations.access_grants.create_project_grants_batch.
Bearer token required. Permission: conversations:permissions:manage on every target conversation-project root.
Request Body
| Name | Type | Description |
|---|---|---|
operations* | array | 1–200 grouped operations; resources × permission_ids across all operations must expand to at most 200 grants |
Response Fields
| Name | Type | Description |
|---|---|---|
results* | array | One result per expanded grant, ordered by operation, then resource, then permission |
summary* | object | Outcome counts |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/access/conversation-projects/grants/batch \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"operations":[{"client_ref":"reviewers","principal_type":"member","principal_id":"{principal_id}","resources":["org/{company_id}/conversation-project/{project_id}"],"permission_ids":["{permission_id}"],"effect":"allow"}]}'Response
200 OK{
"results": [{
"index": 0,
"client_ref": "reviewers",
"status": "created",
"principal_type": "member",
"principal_id": "{principal_id}",
"permission_id": "{permission_id}",
"resource": "org/{company_id}/conversation-project/{project_id}",
"effect": "allow",
"grant": {"id": "{grant_id}", "permission_id": "{permission_id}", "name": "conversations:projects:view", "resource": "org/{company_id}/conversation-project/{project_id}", "effect": "allow", "is_system": false, "granted_at": "2026-07-15T16:30:00Z"},
"error_status": null,
"error_detail": null,
"side_effect_error_status": null,
"side_effect_error_detail": null
}],
"summary": {"created": 1, "already_exists": 0, "failed": 0}
}/api/v1/conversations/access/conversations/grants/batchCreate Conversation Access Grants Batch
Create grants across conversation roots with the same ordered BatchCreateGrantsRequest contract and 200-expanded-grant limit. Items succeed or fail independently and partial success returns 200. Whole-request retries are safe: existing natural grant tuples return already_exists; retry side_effect_error outcomes to reconcile post-write work because their grants already exist. ToolDef slug: conversations.access_grants.create_conversation_grants_batch.
Bearer token required. Permission: conversations:permissions:manage on every target conversation root.
Request Body
| Name | Type | Description |
|---|---|---|
operations* | array | 1–200 grouped operations; resources × permission_ids across all operations must expand to at most 200 grants |
Response Fields
| Name | Type | Description |
|---|---|---|
results* | array | One result per expanded grant, ordered by operation, then resource, then permission |
summary* | object | Outcome counts |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/access/conversations/grants/batch \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"operations":[{"principal_type":"agent","principal_id":"{principal_id}","resources":["org/{company_id}/conversation/{conversation_id}"],"permission_ids":["{permission_id}"]}]}'Response
200 OK{
"results": [{
"index": 0,
"client_ref": null,
"status": "already_exists",
"principal_type": "agent",
"principal_id": "{principal_id}",
"permission_id": "{permission_id}",
"resource": "org/{company_id}/conversation/{conversation_id}",
"effect": "allow",
"grant": {"id": "{grant_id}", "permission_id": "{permission_id}", "name": "conversations:messages:create", "resource": "org/{company_id}/conversation/{conversation_id}", "effect": "allow", "is_system": false, "granted_at": "2026-07-15T16:30:00Z"},
"error_status": null,
"error_detail": null,
"side_effect_error_status": null,
"side_effect_error_detail": null
}],
"summary": {"created": 0, "already_exists": 1, "failed": 0}
}/api/v1/conversations/projects/{project_id}/access/eligibleList Project Eligible Principals
Return IAM principals that can receive Conversations grants on this project.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation-project/{project_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
Response Fields
| Name | Type | Description |
|---|---|---|
[]* | EligiblePrincipal[] | Eligible principals with principal_type, principal_id, and label |
curl https://platform.ergondata.ai/api/v1/conversations/projects/{project_id}/access/eligible \
-H "Authorization: Bearer {token}"Response
200 OK[
{
"principal_type": "member",
"principal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"label": "Jane Doe"
},
{
"principal_type": "agent",
"principal_id": "d4e5f6a7-b8c9-0123-def0-456789012345",
"label": "Support Agent"
}
]/api/v1/conversations/projects/{project_id}/access/resource-typesList Project Grantable Permissions
Return Conversations resource types and permissions grantable from the project boundary. Project access pages exclude top-level create permissions and conversation-thread permissions.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation-project/{project_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
Response Fields
| Name | Type | Description |
|---|---|---|
resource_types* | ResourceTypeNode[] | Conversations resource type tree |
resource_type_edges* | ResourceTypeEdgeNode[] | Resource type parent/child edges (defaults to []) |
permissions* | PermissionOption[] | Permissions grantable from this project boundary |
curl https://platform.ergondata.ai/api/v1/conversations/projects/{project_id}/access/resource-types \
-H "Authorization: Bearer {token}"Response
200 OK{
"resource_types": [
{ "id": "rt-project", "name": "conversation-project", "slug": "conversation-project", "parent_id": null, "children": [] }
],
"resource_type_edges": [],
"permissions": [
{
"id": "perm-project-view",
"name": "conversations:projects:view",
"friendly_name": "View conversation projects",
"description": "View this conversation project",
"resource_type_id": "rt-project",
"parent_resource_type_slug": null,
"scope_anchor": "instance",
"display_order": 10
}
]
}/api/v1/conversations/projects/{project_id}/access/grantsList Project Access Grants
List IAM grants scoped to this conversation project boundary.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation-project/{project_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
page | integer | Page numberDefault: 1 |
limit | integer | Page size (1-500)Default: 100 |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | GrantResponse[] | Grants in this page |
total* | integer | Total grants |
page* | integer | Current page |
limit* | integer | Page size |
curl "https://platform.ergondata.ai/api/v1/conversations/projects/{project_id}/access/grants?page=1&limit=100" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [
{
"id": "grant-001",
"permission_id": "perm-project-view",
"name": "conversations:projects:view",
"resource": "org/c0ffee00-cafe-babe-dead-beefcafebabe/conversation-project/b2c3d4e5-f6a7-8901-bcde-f12345678901",
"effect": "allow",
"is_system": false,
"granted_at": "2026-05-17T12:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 100
}/api/v1/conversations/projects/{project_id}/access/grantsCreate Project Access Grant
Grant a project-scoped Conversations permission to a principal. The optional resource override must stay within the project resource prefix.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation-project/{project_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
Request Body
| Name | Type | Description |
|---|---|---|
principal_type* | string | IAM principal type (member | api_key | agent | automation). Distinct from conversation participant types such as user/operator/contact. |
principal_id* | string | IAM principal UUID |
permission_id* | string | IAM permission UUID |
resource | string | null | Defaults to the project boundary; must stay within it |
effect | string | Grant effect (allow | deny)Default: allow |
Response Fields
| Name | Type | Description |
|---|---|---|
id* | string | Grant ID |
permission_id* | string | Permission ID |
name* | string | Permission slug |
resource* | string | Resource path |
effect* | string | Grant effect |
is_system* | boolean | Whether IAM marked the grant as system-managed |
granted_at* | datetime | Creation time |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/projects/{project_id}/access/grants \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"principal_type": "member",
"principal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"permission_id": "perm-project-view"
}'Response
201 Created{
"id": "grant-001",
"permission_id": "perm-project-view",
"name": "conversations:projects:view",
"resource": "org/c0ffee00-cafe-babe-dead-beefcafebabe/conversation-project/b2c3d4e5-f6a7-8901-bcde-f12345678901",
"effect": "allow",
"is_system": false,
"granted_at": "2026-05-17T12:00:00Z"
}/api/v1/conversations/projects/{project_id}/access/grants/{grant_id}Delete Project Access Grant
Revoke an IAM grant through the project security page.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation-project/{project_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
grant_id* | string | Grant ID |
curl -X DELETE https://platform.ergondata.ai/api/v1/conversations/projects/{project_id}/access/grants/{grant_id} \
-H "Authorization: Bearer {token}"Response
204 No Content/api/v1/conversations/conversations/{conversation_id}/access/eligibleList Conversation Eligible Principals
Return IAM principals that can receive Conversations grants on this conversation.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation/{conversation_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Response Fields
| Name | Type | Description |
|---|---|---|
[]* | EligiblePrincipal[] | Eligible principals with principal_type, principal_id, and label |
curl https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/access/eligible \
-H "Authorization: Bearer {token}"Response
200 OK[
{
"principal_type": "member",
"principal_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"label": "Jane Doe"
}
]/api/v1/conversations/conversations/{conversation_id}/access/resource-typesList Conversation Grantable Permissions
Return Conversations resource types and permissions grantable from the conversation boundary (conversation, message, participant, attachment, and permissions-management permissions).
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation/{conversation_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Response Fields
| Name | Type | Description |
|---|---|---|
resource_types* | ResourceTypeNode[] | Conversations resource type tree |
resource_type_edges* | ResourceTypeEdgeNode[] | Resource type parent/child edges (defaults to []) |
permissions* | PermissionOption[] | Permissions grantable from this conversation boundary |
curl https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/access/resource-types \
-H "Authorization: Bearer {token}"Response
200 OK{
"resource_types": [
{ "id": "rt-conversation", "name": "conversation", "slug": "conversation", "parent_id": null, "children": [] }
],
"resource_type_edges": [],
"permissions": [
{
"id": "perm-message-create",
"name": "conversations:messages:create",
"friendly_name": "Post messages",
"description": "Post messages in this conversation",
"resource_type_id": "rt-conversation",
"parent_resource_type_slug": null,
"scope_anchor": "instance",
"display_order": 20
}
]
}/api/v1/conversations/conversations/{conversation_id}/access/grantsList Conversation Access Grants
List IAM grants scoped directly to one conversation.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation/{conversation_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
page | integer | Page numberDefault: 1 |
limit | integer | Page size (1-500)Default: 100 |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | GrantResponse[] | Grants in this page |
total* | integer | Total grants |
page* | integer | Current page |
limit* | integer | Page size |
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/access/grants?page=1&limit=100" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [],
"total": 0,
"page": 1,
"limit": 100
}/api/v1/conversations/conversations/{conversation_id}/access/grantsCreate Conversation Access Grant
Grant a conversation-local permission. Conversation pages accept conversation, message, participant, attachment, and permissions-management grants; project permissions are rejected.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation/{conversation_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Request Body
| Name | Type | Description |
|---|---|---|
principal_type* | string | IAM principal type (member | api_key | agent | automation) |
principal_id* | string | IAM principal UUID |
permission_id* | string | IAM permission UUID |
resource | string | null | Defaults to the conversation boundary; must stay within it |
effect | string | Grant effect (allow | deny)Default: allow |
Response Fields
| Name | Type | Description |
|---|---|---|
id* | string | Grant ID |
permission_id* | string | Permission ID |
name* | string | Permission slug |
resource* | string | Resource path |
effect* | string | Grant effect |
is_system* | boolean | Whether IAM marked the grant as system-managed |
granted_at* | datetime | Creation time |
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/access/grants \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"principal_type": "agent",
"principal_id": "d4e5f6a7-b8c9-0123-def0-456789012345",
"permission_id": "perm-message-create"
}'Response
201 Created{
"id": "grant-002",
"permission_id": "perm-message-create",
"name": "conversations:messages:create",
"resource": "org/c0ffee00-cafe-babe-dead-beefcafebabe/conversation/f47ac10b-58cc-4372-a567-0e02b2c3d479",
"effect": "allow",
"is_system": false,
"granted_at": "2026-05-17T12:05:00Z"
}/api/v1/conversations/conversations/{conversation_id}/access/grants/{grant_id}Delete Conversation Access Grant
Revoke an IAM grant through the conversation security page.
Bearer token required. Permission: conversations:permissions:manage on org/{company_id}/conversation/{conversation_id}.
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
grant_id* | string | Grant ID |
curl -X DELETE https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/access/grants/{grant_id} \
-H "Authorization: Bearer {token}"Response
204 No ContentActivity
Durable, append-only audit log of conversation and project lifecycle events. Each project and conversation exposes a paginated feed (filterable by event_type and correlation_id), and a single event can be fetched by id. Events carry actor, correlation, and event-specific payload data.
/api/v1/conversations/conversations/{conversation_id}/activityList Conversation Activity
Paginated activity events for one conversation, newest first. Optionally filter by event_type and correlation_id.
Bearer token required. Permission: conversations:activity:view on the conversation
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
event_type | string | null | Exact event slug filter (e.g. conversations.message.added) |
correlation_id | string (UUID) | null | Filter by correlation id |
page | integer | Page number (>= 1)Default: 1 |
limit | integer | Page size (1-100)Default: 50 |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | ActivityEventItem[] | Events: id, event_type, conversation_id, project_id, actor_id, actor_type, correlation_id, data, created_at |
total* | integer | Total matching events |
page* | integer | Current page |
limit* | integer | Page size |
curl "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/activity?page=1&limit=50" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [
{
"id": "e1a2b3c4-d5e6-7890-abcd-ef0123456789",
"event_type": "conversations.message.added",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"project_id": null,
"actor_id": "u1111111-2222-3333-4444-555555555555",
"actor_type": "user",
"correlation_id": null,
"data": { "message_id": "m3000000-0000-4000-8000-000000000003" },
"created_at": "2026-04-21T12:15:00Z"
}
],
"total": 1,
"page": 1,
"limit": 50
}/api/v1/conversations/projects/{project_id}/activityList Project Activity
Paginated activity events for one project, newest first. Optionally filter by event_type and correlation_id.
Bearer token required. Permission: conversations:activity:view on the project
Path Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string (UUID) | Project ID |
Query Parameters
| Name | Type | Description |
|---|---|---|
event_type | string | null | Exact event slug filter (e.g. conversations.project.updated) |
correlation_id | string (UUID) | null | Filter by correlation id |
page | integer | Page number (>= 1)Default: 1 |
limit | integer | Page size (1-100)Default: 50 |
Response Fields
| Name | Type | Description |
|---|---|---|
items* | ActivityEventItem[] | Activity events scoped to the project |
total* | integer | Total matching events |
page* | integer | Current page |
limit* | integer | Page size |
curl "https://platform.ergondata.ai/api/v1/conversations/projects/{project_id}/activity?page=1&limit=50" \
-H "Authorization: Bearer {token}"Response
200 OK{
"items": [
{
"id": "e2b3c4d5-e6f7-8901-bcde-f01234567890",
"event_type": "conversations.project.updated",
"conversation_id": null,
"project_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"actor_id": "u1111111-2222-3333-4444-555555555555",
"actor_type": "user",
"correlation_id": null,
"data": { "changes": { "name": "Customer Success (EMEA)" } },
"created_at": "2026-04-21T11:30:00Z"
}
],
"total": 1,
"page": 1,
"limit": 50
}/api/v1/conversations/activity/{event_id}Get Activity Event
Fetch a single activity event by id. Authorization is resolved against the event's conversation, project, or org resource.
Bearer token required. Permission: conversations:activity:view on the event's conversation/project/org resource
Path Parameters
| Name | Type | Description |
|---|---|---|
event_id* | string (UUID) | Activity event ID |
Response Fields
| Name | Type | Description |
|---|---|---|
id* | UUID | Event ID |
event_type* | string | Event slug |
conversation_id* | UUID | null | Conversation the event belongs to |
project_id* | UUID | null | Project the event belongs to |
actor_id* | UUID | null | Actor principal |
actor_type* | string | null | Actor principal type |
correlation_id* | UUID | null | Correlation id if set |
data* | object | null | Event-specific payload |
created_at* | datetime | Created at |
curl https://platform.ergondata.ai/api/v1/conversations/activity/{event_id} \
-H "Authorization: Bearer {token}"Response
200 OK{
"id": "e1a2b3c4-d5e6-7890-abcd-ef0123456789",
"event_type": "conversations.message.added",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"project_id": null,
"actor_id": "u1111111-2222-3333-4444-555555555555",
"actor_type": "user",
"correlation_id": null,
"data": { "message_id": "m3000000-0000-4000-8000-000000000003" },
"created_at": "2026-04-21T12:15:00Z"
}Real-Time Stream
WebSocket fan-out for live UX: server pushes message.created, message.updated (including inbound attachment progress), conversation.updated, participant.added, participant.removed envelopes, heartbeats (ping), and relays client typing and read events to other subscribers on the same routing key.
/api/v1/conversations/ws/conversations/{conversation_id}/streamConversation Stream (WebSocket)
Connect with JWT via ?token=, or send the token as the first text frame (raw token or JSON {"token": "..."}). Direct viewers require conversations:conversations:view and active participant membership; holders of agents:conversations:view may observe a participating agent’s public conversation without joining. Only active participants may send typing/read frames. Client may send JSON {type: typing}, {type: read, participant_id, timestamp}, or {type: pong}.
JWT via query or first frame. Active participant with conversations:conversations:view, or read-only derived access to a public participating-agent conversation
Path Parameters
| Name | Type | Description |
|---|---|---|
conversation_id* | string (UUID) | Conversation ID |
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
"https://platform.ergondata.ai/api/v1/conversations/ws/conversations/{conversation_id}/stream?token={token}"Response
101{
"type": "message.created",
"message": {
"id": "m3000000-0000-4000-8000-000000000003",
"conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"role": "user",
"content": "Hello!",
"created_at": "2026-04-21T12:15:00Z"
}
}