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:agents:conversations:view on a participating agent get derived read access to that agent's public conversations.

Base URL/api/v1/conversations

Automation 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.

POST/api/v1/conversations/companies/{company_id}/projects

Create 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

NameTypeDescription
company_id*
string (UUID)Organization ID

Request Body

NameTypeDescription
company_id*
string (UUID)Must match the path company_id
slug*
stringUnique slug within the company
name*
stringDisplay name
description
string | nullOptional description
icon
string | nullOptional icon key or URL
color
string | nullOptional theme color
bucket_id
string (UUID) | nullLinked Buckets folder
system_prompt_append
string | nullText appended to agent system prompts in this project

Response Fields

NameTypeDescription
id*
UUIDProject ID
company_id*
UUIDOrganization ID
slug*
stringSlug
name*
stringDisplay name
description*
string | nullDescription
icon*
string | nullIcon
color*
string | nullColor
bucket_id*
UUID | nullFolder link
system_prompt_append*
string | nullPrompt append
created_by*
UUID | nullCreator principal
conversation_count*
integerNumber of conversations in this project (defaults to 0)
created_at*
datetimeCreated at
updated_at*
datetimeUpdated 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"
}
GET/api/v1/conversations/companies/{company_id}/projects

List 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

NameTypeDescription
company_id*
string (UUID)Organization ID

Query Parameters

NameTypeDescription
limit
integerPage size (1–200)Default: 50
offset
integerOffsetDefault: 0
q
string | nullCase-insensitive name filter

Response Fields

NameTypeDescription
items*
ProjectOut[]Project rows
total*
integerTotal 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
}
GET/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

NameTypeDescription
project_id*
string (UUID)Project ID

Response Fields

NameTypeDescription
(ProjectOut)*
objectSame 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"
}
PATCH/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

NameTypeDescription
project_id*
string (UUID)Project ID

Request Body

NameTypeDescription
name
string | nullDisplay name
slug
string | nullSlug
description
string | nullDescription
icon
string | nullIcon
color
string | nullColor
bucket_id
string (UUID) | nullFolder link
system_prompt_append
string | nullPrompt append

Response Fields

NameTypeDescription
(ProjectOut)*
objectUpdated 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"
}
DELETE/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

NameTypeDescription
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 Content

Conversations

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; their title is optional and can be generated from the opening message. 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.

POST/api/v1/conversations/conversations

Create 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

NameTypeDescription
company_id*
string (UUID)Tenant
type
stringdm or groupDefault: dm
channel
stringDelivery mode: direct or email (Channel mode)Default: direct
project_id
string (UUID) | nullOptional project scope
title
string | nullOptional initial title; generated from the opening message for groups and agent threads. People-only DMs stay untitled.
metadata
object | nullOpaque 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 []
agent_reply_mode
"mentions" | "smart"Group-agent activation policy. mentions runs only explicitly mentioned active agents; smart continuously runs every active agent for every human message. Ignored for DMs.Default: "mentions"
subject_type
string | nullExternal subject type to link (e.g. workflow.item)
subject_id
string (UUID) | nullExternal subject id to link
to
ContactRecipient[]Outbound contact recipients, each with address (required) and optional display_name. Defaults to []
first_message
FirstMessage | nullOptional initial message: content (string | null), attachments (AttachmentRef[] | null), and mentioned_principal_ids (active participant UUIDs)
from_channel_address_id
string (UUID) | nullRequired for email. Channels address used to send; non-workflow callers need channels:addresses:send
auto_grant_enabled
booleanAuto-grant view access to added participantsDefault: true
privacy_level
string | nullpublic 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 | nullPer-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}

Response Fields

NameTypeDescription
id*
UUIDConversation ID
company_id*
UUIDTenant
project_id*
UUID | nullProject if set
type*
stringdm or group
channel*
stringChannel
status*
stringLifecycle status
title*
string | nullTitle
metadata*
object | nullMetadata
correlation_id*
UUID | nullCorrelation id if set
started_by*
UUID | nullCreator principal
subject_type*
string | nullLinked subject type
subject_id*
UUID | nullLinked subject id
from_channel_address_id*
UUID | nullOutbound channel address
bucket_id*
UUID | nullBacking attachment folder if any
privacy_level*
stringpublic or private (default private)
note_taking_cadence
object | nullnull inherits the company default cadence
capabilities*
ConversationCapabilitiesPer-caller flags: view, send_message, edit, delete, manage_participants, manage_security, upload_attachment
participants*
ParticipantOut[]Participant rows
created_at*
datetimeCreated at
updated_at*
datetimeUpdated 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"
}
GET/api/v1/conversations/companies/{company_id}/conversations

List Conversations

Conversations the caller can view, newest activity first. Each summary includes the authenticated user's personal is_pinned, is_archived, and last_opened_at state. The sidebar and agent widget show Pinned, then Recents (about five 1:1/agent chats, including newly created ones), then Groups. Remaining threads are in View all. Inbound channel mail stays out of Recents until opened. Request pinned_only=true as a separate projection so pins remain complete without changing offset pagination in the activity list; opened_only=true returns only threads the caller has opened, newest last_opened_at first. archived=true switches from the active inbox to archived chats. Results are grant-filtered: each row is included only when the caller holds conversations:conversations:view on it.

Bearer token required. Permission: conversations:conversations:view on each conversation resource

Path Parameters

NameTypeDescription
company_id*
string (UUID)Organization ID

Query Parameters

NameTypeDescription
limit
integerPage size (1–200)Default: 50
offset
integerOffsetDefault: 0
project_id
string (UUID) | nullFilter by project
channel
string | nullExact channel string
type
string | nulldm or group
status
string | nullStatus filter
participant_id
string (UUID) | nullMust include this principal
principal_type
string | nullFilter participant_id by principal type (user | agent | operator | contact)
q
string | nullCase-insensitive title substring
pinned_only
booleanReturn only conversations pinned by the authenticated userDefault: false
opened_only
booleanReturn only conversations the authenticated user has opened, newest last_opened_at firstDefault: false
archived
booleanReturn archived conversations instead of active chatsDefault: false

Response Fields

NameTypeDescription
items*
ConversationSummary[]Conversation summaries (no embedded messages), including personal is_pinned, is_archived, and last_opened_at
total*
integerTotal 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,
      "is_pinned": false,
      "is_archived": false,
      "last_opened_at": 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
}
GET/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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Query Parameters

NameTypeDescription
include_messages
booleanWhen 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

NameTypeDescription
messages*
MessageOut[]Messages in storage order. Empty unless include_messages=true was passed.
participants*
ParticipantOut[]Participant rows
auto_grant_enabled*
booleanWhether 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 | nullOptional cadence override (every_messages / every_minutes / every_tokens / min_messages). null inherits the company default.
agent_reply_mode*
"mentions" | "smart"Group-agent activation policy. Stored on every conversation but ignored for DMs.
(other fields)*
ConversationOut fieldsSame 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"
}
PATCH/api/v1/conversations/conversations/{conversation_id}

Update Conversation

Patch title, status, project_id, metadata, auto_grant_enabled, or group agent participation. 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
title
string | nullTitle
status
string | nullStatus
project_id
string (UUID) | nullProject
metadata
object | nullReplaces 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 | nullToggle auto-granting view access to participants
agent_reply_mode
"mentions" | "smart" | nullGroup-agent activation policy. mentions is the safe default; smart continuously runs every active agent for every human message. Ignored for DMs.
privacy_level
"private" | "public" | nullSet 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 | nullCadence 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

NameTypeDescription
(ConversationOut)*
objectUpdated 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"
}
PUT/api/v1/conversations/conversations/{conversation_id}/preferences

Set Conversation Preferences

Pin/unpin, archive/restore, or record that the authenticated user opened a chat. Archiving clears its pin; pinning restores it. last_opened_at drives Recents (last viewed by me), stays monotonic so delayed retries cannot move it backwards, and does not update the conversation activity timestamp or affect other participants.

Bearer token required. Permission: conversations:conversations:view on the conversation

Path Parameters

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
is_pinned
booleanWhether this chat is pinned
is_archived
booleanWhether this chat is archived
last_opened_at
string (date-time)When the caller last opened this thread

Response Fields

NameTypeDescription
conversation_id*
string (UUID)Conversation ID
is_pinned*
booleanPersisted personal pin state
is_archived*
booleanPersisted personal archive state
last_opened_at
string (date-time)When the caller last opened this thread
curl -X PUT https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/preferences \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"is_pinned": true}'

Response

200 OK
{
  "conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "is_pinned": true,
  "is_archived": false,
  "last_opened_at": null
}
POST/api/v1/conversations/whatsapp/start

Start WhatsApp Conversation

Create a WhatsApp conversation and send the opening message with an approved utility template. Requires conversation creation and address send permissions. Cold outreach must start from a template; free-form messages unlock after the customer replies and the 24-hour session window opens. An active episode for the same address and recipient is rejected. A temporary IAM provisioning failure returns 503 and rolls back the episode for safe retry.

Bearer token required. Permission: conversations:conversations:create on org/{company_id}

Request Body

NameTypeDescription
company_id*
string (UUID)Tenant company ID
from_address_id*
string (UUID)Send-capable WhatsApp address to send from
to*
stringRecipient phone number in E.164 format
template*
objectApproved utility template: name, language, and optional components with parameters
title
stringConversation title (max 500 characters)
project_id
string (UUID)Project to file the conversation under
subject_type
workflow.itemBind the conversation to a workflow item
subject_id
string (UUID)Workflow item subject; required with subject_type

Response Fields

NameTypeDescription
conversation*
ConversationThe created WhatsApp conversation
message*
MessageThe queued opening template message
curl -X POST https://platform.ergondata.ai/api/v1/conversations/whatsapp/start \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "company_id": "{company_id}",
    "from_address_id": "a1d2e3f4-5566-7788-99aa-bbccddeeff00",
    "to": "+5511999999999",
    "template": {"name": "order_update", "language": "pt_BR", "components": []}
  }'

Response

201 Created
{
  "conversation": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "company_id": "{company_id}",
    "conversation_type": "channel",
    "channel": "whatsapp",
    "status": "active",
    "title": "+5511999999999"
  },
  "message": {
    "id": "0e02b2c3-d479-4372-a567-f47ac10b58cc",
    "conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "message_type": "text",
    "created_at": "2026-04-03T14:22:00Z"
  }
}
POST/api/v1/conversations/conversations/{conversation_id}/channel-agent/takeover

Take Over Channel Agent

Pause the channel agent on this conversation so a human can reply directly. The agent participant stays in the conversation but stops auto-replying until resumed.

Bearer token required. Permission: conversations:conversations:edit on the conversation

Path Parameters

NameTypeDescription
conversation_id*
string (UUID)Channel-backed conversation ID

Response Fields

NameTypeDescription
conversation_id*
string (UUID)The conversation
channel*
stringChannel of the conversation (e.g. whatsapp, email)
state*
stringResulting agent state (paused)
agent_principal_id
string (UUID) | nullThe paused agent principal, when one is bound
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/channel-agent/takeover \
  -H "Authorization: Bearer {token}"

Response

200 OK
{
  "conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "channel": "whatsapp",
  "state": "paused",
  "agent_principal_id": "9c1de3f4-5566-7788-99aa-bbccddeeff22"
}
POST/api/v1/conversations/conversations/{conversation_id}/channel-agent/resume

Resume Channel Agent

Reactivate the paused channel agent on this conversation so it resumes auto-replying to inbound customer messages.

Bearer token required. Permission: conversations:conversations:edit on the conversation

Path Parameters

NameTypeDescription
conversation_id*
string (UUID)Channel-backed conversation ID

Response Fields

NameTypeDescription
conversation_id*
string (UUID)The conversation
channel*
stringChannel of the conversation (e.g. whatsapp, email)
state*
stringResulting agent state (active)
agent_principal_id
string (UUID) | nullThe resumed agent principal, when one is bound
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/channel-agent/resume \
  -H "Authorization: Bearer {token}"

Response

200 OK
{
  "conversation_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "channel": "whatsapp",
  "state": "active",
  "agent_principal_id": "9c1de3f4-5566-7788-99aa-bbccddeeff22"
}
DELETE/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

NameTypeDescription
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
GET/api/v1/conversations/conversations

List 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

NameTypeDescription
company_id*
string (UUID)Organization ID
subject_type*
stringSubject type (e.g. workflow.item)
subject_id*
string (UUID)Subject entity ID
limit
integerPage size (1–200)Default: 50
offset
integerOffsetDefault: 0

Response Fields

NameTypeDescription
items*
ConversationSummary[]Matching conversation summaries
total*
integerTotal 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
}
GET/api/v1/conversations/conversations/bulk-by-subject

Bulk 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

NameTypeDescription
company_id*
string (UUID)Organization ID
subject_type*
stringSubject type
subject_ids*
string (comma-separated UUIDs)Subject IDs to resolve

Response Fields

NameTypeDescription
by_subject_id*
objectMap 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": [] }
  }
}
POST/api/v1/conversations/conversations/bulk-delete

Bulk 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

NameTypeDescription
conversation_ids*
string[] (UUID)Array of conversation IDs to delete

Response Fields

NameTypeDescription
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.

POST/api/v1/conversations/companies/{company_id}/audio/transcriptions

Transcribe New Conversation Audio

Convert a microphone recording into editable text before a conversation exists. This endpoint does not create or send a conversation.

Bearer token required. Permission: conversations:conversations:create on the organization

Path Parameters

NameTypeDescription
company_id*
string (UUID)Organization ID

Request Body

NameTypeDescription
audio*
binary (audio/*)Browser recording, up to 25 MB by default

Response Fields

NameTypeDescription
text*
stringTranscribed text for review
curl -X POST https://platform.ergondata.ai/api/v1/conversations/companies/{company_id}/audio/transcriptions \
  -H "Authorization: Bearer {token}" \
  -F "audio=@recording.webm;type=audio/webm"

Response

200 OK
{
  "text": "Transcribed voice input"
}
POST/api/v1/conversations/conversations/{conversation_id}/audio/transcriptions

Transcribe Conversation Audio

Convert a short microphone recording into editable text. This endpoint does not create or send a message; the caller reviews the returned transcript first.

Bearer token required. Permission: conversations:messages:create on the conversation

Path Parameters

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
audio*
binary (audio/*)Browser recording, up to 25 MB by default

Response Fields

NameTypeDescription
text*
stringTranscribed text for review
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/audio/transcriptions \
  -H "Authorization: Bearer {token}" \
  -F "audio=@recording.webm;type=audio/webm"

Response

200 OK
{
  "text": "Transcribed voice input"
}
POST/api/v1/conversations/conversations/{conversation_id}/messages

Create 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
content
string | nullPlain text body
role
stringMessage roleDefault: user
kind
stringMessage kindDefault: message
sender_participant_id
string (UUID) | nullDefaults to caller's participant id
content_blocks
array | nullStructured content blocks
mentioned_principal_ids
string (UUID)[] | nullDurable identity-safe mentions. Every UUID must identify an active participant; group routing uses these IDs instead of parsing display text.
attachments
AttachmentRef[] | nullAttachmentRef objects (file_id, filename, content_type, optional size, visibility, promoted_to_bucket_id, upload_batch_id, status, process_skip_reason)
tool_call_id
string | nullTool call correlation
tool_name
string | nullTool name
tool_args
object | nullSerialized arguments
tool_result
any | nullTool output
model_used
string | nullModel id
input_tokens
integer | nullPrompt tokens
output_tokens
integer | nullCompletion tokens
cost_usd
number | nullEstimated 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 | nullExternal channel reference payload

Response Fields

NameTypeDescription
id*
UUIDMessage ID
conversation_id*
UUIDConversation
sender_participant_id*
UUID | nullSender participant row
role*
stringRole
kind*
stringMessage kind (default message)
content*
string | nullBody
content_blocks*
array | nullBlocks
mentioned_principal_ids*
UUID[] | nullValidated active-participant mentions persisted with the message
attachments*
AttachmentRef[] | nullAttachment refs. Inbound email refs expose status and process_skip_reason and update over message.updated stream events
tool_call_id*
string | nullTool call id
tool_name*
string | nullTool name
tool_args*
object | nullTool args
tool_result*
any | nullTool result
model_used*
string | nullModel
input_tokens*
integer | nullIn tokens
output_tokens*
integer | nullOut tokens
cost_usd*
number | nullEstimated USD cost snapshot for the turn (tokens x model catalog price)
channel_ref*
object | nullChannel ref
origin*
object | nullCross-conversation provenance for relayed / cross-posted messages (server-set). null for ordinary in-thread messages.
created_at*
datetimeCreated 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"
}
POST/api/v1/conversations/conversations/{conversation_id}/stop

Stop Agent Generation

Request durable, best-effort cancellation of every active agent run in the conversation. Repeated requests are safe, and cancellation intent is preserved if it races worker registration.

Bearer token required. Permission: conversations:messages:create; caller must be an active participant.

Path Parameters

NameTypeDescription
conversation_id*
string (UUID)Conversation ID
curl -X POST "https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/stop" -H "Authorization: Bearer {token}"

Response

202 Accepted
{"status":"cancel_requested","run_ids":[]}
GET/api/v1/conversations/conversations/{conversation_id}/messages

List 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Query Parameters

NameTypeDescription
limit
integerPage 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
booleanWhen false, nulls out heavy tool_args/tool_result/content_blocks fields for a lighter scan.Default: true

Response Fields

NameTypeDescription
items*
MessageOut[]Messages for the page, oldest-first.
has_more*
booleanWhether more messages exist in the paging direction.
oldest_cursor*
string (UUID) | nullMessage id of the oldest item; pass as before to page older.
newest_cursor*
string (UUID) | nullMessage 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.

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

Search 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:agents:conversations:view on a participating agent also grants derived read.

Path Parameters

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Query Parameters

NameTypeDescription
q
stringHybrid lexical/semantic query across topic, what_happened, decisions, open_questions, and entities.
limit
integerPage 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

NameTypeDescription
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*
booleanWhether more notes exist in the paging direction.
oldest_cursor*
string (UUID) | nullNote id of the oldest item; pass as before to page older.
newest_cursor*
string (UUID) | nullNote 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. Add/remove operations also persist actor-attributed, non-triggering participant_event messages so membership notices appear live and after reload. Participant rows are enriched with display_name, avatar_url, and (for contacts) address.

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

Add Participant

Invite a principal. 409 when they are already active. Emits conversations.participant.added, streams participant.added, and streams the persisted actor/subject membership notice through message.created.

Bearer token required. Permission: conversations:participants:manage on the conversation

Path Parameters

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
principal_id*
string (UUID)Principal to add
principal_type*
stringuser | agent | operator | contact
role
stringMembership roleDefault: member

Response Fields

NameTypeDescription
(ParticipantOut)*
objectParticipant 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
}
PATCH/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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID
participant_id*
string (UUID)Participant row ID

Request Body

NameTypeDescription
last_read_at
string (ISO 8601 datetime) | nullRead cursor timestamp

Response Fields

NameTypeDescription
(ParticipantOut)*
objectUpdated 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"
}
DELETE/api/v1/conversations/conversations/{conversation_id}/participants/{participant_id}

Remove Participant

Soft-remove by setting left_at. Emits conversations.participant.removed, streams participant.removed, and streams the persisted actor/subject membership notice through message.created. Self-removal is rendered as leaving the conversation.

Bearer token required. Permission: conversations:participants:manage on the conversation

Path Parameters

NameTypeDescription
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 Content

Attachments

Broker uploads and generated PDF documents to Buckets as conversation-scoped files (the service stores references only). Optional X-Correlation-ID is forwarded. Generated PDF validation, limit, and timeout failures preserve their 422, 413, and 504 statuses; unexpected Buckets failures return 502. Every failure logs conversations.attachment.failed. Agents embed the returned AttachmentRef file_id in an ergon-files block.

POST/api/v1/conversations/conversations/{conversation_id}/documents/pdf

Generate PDF Document

Renders simple Markdown to a bounded A4 PDF in the conversation's managed files folder. Supports text, headings, emphasis, lists, links, code, and simple tables. Raw HTML, remote images, scripts, and executable templates are rejected.

Bearer token required. Permission: conversations:attachments:create on the conversation; must be an active participant and conversation attachments must be enabled

Path Parameters

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
filename*
stringOutput filename (1-240 characters); .pdf is appended when absent
markdown*
stringFinal simple Markdown source (1-100,000 characters)

Response Fields

NameTypeDescription
file_id*
UUIDGenerated Buckets file id
filename*
stringNormalized .pdf filename
content_type*
stringapplication/pdf
size*
integer | nullRendered byte length
visibility*
stringBuckets visibility
promoted_to_bucket_id*
UUIDManaged conversation bucket id
upload_batch_id*
nullNot used for generated documents
status*
string | nullOptional Buckets processing state
process_skip_reason*
string | nullOptional processing skip reason
curl -X POST https://platform.ergondata.ai/api/v1/conversations/conversations/{conversation_id}/documents/pdf \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"filename":"quarterly-report","markdown":"# Quarterly Report\n\n- Revenue increased\n- Backlog decreased"}'

Response

201 Created
{
  "file_id": "d4000000-0000-4000-8000-000000000004",
  "filename": "quarterly-report.pdf",
  "content_type": "application/pdf",
  "size": 8421,
  "visibility": "bucket",
  "promoted_to_bucket_id": "b3000000-0000-4000-8000-000000000003",
  "upload_batch_id": null,
  "status": "pending",
  "process_skip_reason": null
}
POST/api/v1/conversations/conversations/{conversation_id}/attachments

Upload 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
file*
file (multipart)Binary payload (field name file)

Response Fields

NameTypeDescription
file_id*
UUIDBuckets file id
filename*
stringFilename
content_type*
stringMIME type
size*
integer | nullByte length
visibility*
stringBuckets visibility (default bucket)
promoted_to_bucket_id*
UUID | nullFolder promotion if any
upload_batch_id*
UUID | nullBatch id when applicable
status*
string | nullInbound import or Buckets state: pending, downloading, uploading, processing, skipped, oversized, or failed
process_skip_reason*
string | nullReason 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
}
POST/api/v1/conversations/conversations/{conversation_id}/attachments/batch

Upload 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
files*
file[] (multipart)One or more parts named files

Response Fields

NameTypeDescription
upload_batch_id*
UUIDShared 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).

GET/api/v1/conversations/settings/note-taking

Get 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

NameTypeDescription
company_id*
string (UUID)Resolved company id
note_taking_cadence*
objectCompany cadence policy. Keys: every_messages, every_minutes, every_tokens, min_messages. Empty keys fall back to built-in defaults.
effective_note_taking_cadence*
objectFully 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
  }
}
PUT/api/v1/conversations/settings/note-taking

Update 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

NameTypeDescription
note_taking_cadence*
objectCadence policy (every_messages, every_minutes, every_tokens, min_messages).

Response Fields

NameTypeDescription
(CompanyNoteSettingsOut)*
objectThe 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 }
}
GET/api/v1/conversations/settings/a2a

Get 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

NameTypeDescription
company_id*
UUIDCompany
enabled*
booleanWhether A2A relay is enabled (default false).
max_consecutive_agent_turns*
integerTrailing agent-turn budget before the exchange pauses (clamped to the platform cap).
agent_messages_per_window*
integerAgent messages allowed per rolling window (clamped to the platform cap).
window_seconds*
integerRolling 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
}
PUT/api/v1/conversations/settings/a2a

Update 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

NameTypeDescription
enabled
booleanTurn A2A relay on/off (default false).
max_consecutive_agent_turns
integer | nullTrailing agent-turn budget. null uses the platform default; values above the cap are clamped.
agent_messages_per_window
integer | nullAgent messages per window. null uses the default; values above the cap are clamped.

Response Fields

NameTypeDescription
(CompanyA2ASettingsOut)*
objectThe 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.

POST/api/v1/conversations/access/conversation-projects/grants/batch

Create 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

NameTypeDescription
operations*
array1–200 grouped operations; resources × permission_ids across all operations must expand to at most 200 grants

Response Fields

NameTypeDescription
results*
arrayOne result per expanded grant, ordered by operation, then resource, then permission
summary*
objectOutcome 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}
}
POST/api/v1/conversations/access/conversations/grants/batch

Create 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

NameTypeDescription
operations*
array1–200 grouped operations; resources × permission_ids across all operations must expand to at most 200 grants

Response Fields

NameTypeDescription
results*
arrayOne result per expanded grant, ordered by operation, then resource, then permission
summary*
objectOutcome 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}
}
GET/api/v1/conversations/projects/{project_id}/access/eligible

List 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

NameTypeDescription
project_id*
string (UUID)Project ID

Response Fields

NameTypeDescription
[]*
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"
  }
]
GET/api/v1/conversations/projects/{project_id}/access/resource-types

List 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

NameTypeDescription
project_id*
string (UUID)Project ID

Response Fields

NameTypeDescription
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
    }
  ]
}
GET/api/v1/conversations/projects/{project_id}/access/grants

List 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

NameTypeDescription
project_id*
string (UUID)Project ID

Query Parameters

NameTypeDescription
page
integerPage numberDefault: 1
limit
integerPage size (1-500)Default: 100

Response Fields

NameTypeDescription
items*
GrantResponse[]Grants in this page
total*
integerTotal grants
page*
integerCurrent page
limit*
integerPage 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
}
POST/api/v1/conversations/projects/{project_id}/access/grants

Create 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

NameTypeDescription
project_id*
string (UUID)Project ID

Request Body

NameTypeDescription
principal_type*
stringIAM principal type (member | api_key | agent | automation). Distinct from conversation participant types such as user/operator/contact.
principal_id*
stringIAM principal UUID
permission_id*
stringIAM permission UUID
resource
string | nullDefaults to the project boundary; must stay within it
effect
stringGrant effect (allow | deny)Default: allow

Response Fields

NameTypeDescription
id*
stringGrant ID
permission_id*
stringPermission ID
name*
stringPermission slug
resource*
stringResource path
effect*
stringGrant effect
is_system*
booleanWhether IAM marked the grant as system-managed
granted_at*
datetimeCreation 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"
}
DELETE/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

NameTypeDescription
project_id*
string (UUID)Project ID
grant_id*
stringGrant 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
GET/api/v1/conversations/conversations/{conversation_id}/access/eligible

List 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Response Fields

NameTypeDescription
[]*
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"
  }
]
GET/api/v1/conversations/conversations/{conversation_id}/access/resource-types

List 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Response Fields

NameTypeDescription
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
    }
  ]
}
GET/api/v1/conversations/conversations/{conversation_id}/access/grants

List 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Query Parameters

NameTypeDescription
page
integerPage numberDefault: 1
limit
integerPage size (1-500)Default: 100

Response Fields

NameTypeDescription
items*
GrantResponse[]Grants in this page
total*
integerTotal grants
page*
integerCurrent page
limit*
integerPage 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
}
POST/api/v1/conversations/conversations/{conversation_id}/access/grants

Create 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Request Body

NameTypeDescription
principal_type*
stringIAM principal type (member | api_key | agent | automation)
principal_id*
stringIAM principal UUID
permission_id*
stringIAM permission UUID
resource
string | nullDefaults to the conversation boundary; must stay within it
effect
stringGrant effect (allow | deny)Default: allow

Response Fields

NameTypeDescription
id*
stringGrant ID
permission_id*
stringPermission ID
name*
stringPermission slug
resource*
stringResource path
effect*
stringGrant effect
is_system*
booleanWhether IAM marked the grant as system-managed
granted_at*
datetimeCreation 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"
}
DELETE/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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID
grant_id*
stringGrant 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 Content

Activity

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.

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

List 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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID

Query Parameters

NameTypeDescription
event_type
string | nullExact event slug filter (e.g. conversations.message.added)
correlation_id
string (UUID) | nullFilter by correlation id
page
integerPage number (>= 1)Default: 1
limit
integerPage size (1-100)Default: 50

Response Fields

NameTypeDescription
items*
ActivityEventItem[]Events: id, event_type, conversation_id, project_id, actor_id, actor_type, correlation_id, data, created_at
total*
integerTotal matching events
page*
integerCurrent page
limit*
integerPage 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
}
GET/api/v1/conversations/projects/{project_id}/activity

List 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

NameTypeDescription
project_id*
string (UUID)Project ID

Query Parameters

NameTypeDescription
event_type
string | nullExact event slug filter (e.g. conversations.project.updated)
correlation_id
string (UUID) | nullFilter by correlation id
page
integerPage number (>= 1)Default: 1
limit
integerPage size (1-100)Default: 50

Response Fields

NameTypeDescription
items*
ActivityEventItem[]Activity events scoped to the project
total*
integerTotal matching events
page*
integerCurrent page
limit*
integerPage 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
}
GET/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

NameTypeDescription
event_id*
string (UUID)Activity event ID

Response Fields

NameTypeDescription
id*
UUIDEvent ID
event_type*
stringEvent slug
conversation_id*
UUID | nullConversation the event belongs to
project_id*
UUID | nullProject the event belongs to
actor_id*
UUID | nullActor principal
actor_type*
string | nullActor principal type
correlation_id*
UUID | nullCorrelation id if set
data*
object | nullEvent-specific payload
created_at*
datetimeCreated 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.

WS/api/v1/conversations/ws/conversations/{conversation_id}/stream

Conversation 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: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

NameTypeDescription
conversation_id*
string (UUID)Conversation ID
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: {base64_nonce}" \
  "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"
  }
}