Skip to content

Conversations

Conversations are the core resource in oHallo. Each conversation represents a message thread between a customer contact and oHallo on a single channel.

Required scope: conversations:read for GET endpoints, conversations:write for POST endpoints.

Retrieve conversations for a workspace, optionally filtered by status or contact.

Terminal window
curl "https://api.ohallo.eu/api/conversations?workspaceId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&status=open&limit=50" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Query parameters:

ParameterTypeRequiredDescription
workspaceIdUUIDYes, except for an API key restricted to a brand or workspaceThe workspace whose conversations are listed. A key restricted to a brand or workspace that names none lists every workspace its restriction covers
statusstringNoFilter by status: open, in_progress, awaiting_contact, resolved, closed, abandoned
contactIdUUIDNoFilter by contact
accountIdUUIDNoFilter by account
qstringNoFree-text search across contact name / email / subject
humanManagedbooleanNoFilter to conversations currently owned by a human operator
assigneestringNoFilter to conversations assigned to a specific user id, or me for the calling user (resolved server-side; meaningful for dashboard sessions, where API-key callers should pass an explicit user id)
hasAttentionbooleanNoFilter to conversations with one or more open attention items
limitintegerNoPage size (default 50, max 100)
cursorstringNoOpaque pagination cursor from a previous response’s nextCursor

A request that names no workspaceId answers 400 { "error": "workspaceId is required" }, unless the API key is restricted to a brand or workspace. A contactId or accountId that is not an id, or a cursor that is not a nextCursor this endpoint returned, answers 400 { "error": "Invalid filter or cursor" }, and so does a workspaceId that is not an id. An API key restricted to a brand or workspace receives 404 { "error": "Workspace not found" } for that workspaceId instead, as for any workspace outside its restriction.

Response:

{
"data": [
{
"id": "c9f8e7d6-b5a4-3210-fedc-ba9876543210",
"workspaceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"channelId": "d4e5f6a7-b8c9-4123-8567-890abcdef012",
"contactId": "f7e8d9c0-b1a2-3456-7890-abcdef123456",
"status": "open",
"lastMessagePreview": "Hi, I placed order NP-2026-0142 last week. Can you tell me when...",
"lastMessageAt": "2026-03-15T14:30:00.000Z",
"contactName": "Alex Doe",
"contactEmail": "alex.doe@example.com",
"accountName": "Example Company"
}
],
"nextCursor": "eyJsYXN0SWQiOiJjOWY4ZTdkNi1iNWE0LTMyMTAtZmVkYy1iYTk4NzY1NDMyMTAifQ=="
}

Pagination is cursor-based: pass the nextCursor value from one response as the cursor query param of the next. When the response omits nextCursor, there are no more items. Optional fields (contactName, contactEmail, contactGivenEmail, accountName, humanManaged, formType, autoResolved, lastTurnRoutingPath) are only included on conversations that have those signals. Do not rely on their presence in every item. On a live chat, contactEmail is the contact’s email address on file and contactGivenEmail the address the visitor typed in a live chat, at its start or in the email field the chat shows; it is not verified. contactGivenEmail appears only on live chat conversations.

Retrieve full details for a single conversation: the conversation row, the resolved contact + account records, the message history, the per-turn context entries, and any attention items raised by the agent pipeline.

Terminal window
curl "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Response:

{
"id": "c9f8e7d6-b5a4-3210-fedc-ba9876543210",
"tenantId": "10000000-0000-4000-8000-000000000001",
"workspaceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"channelId": "d4e5f6a7-b8c9-4123-8567-890abcdef012",
"channelType": "email",
"contactId": "f7e8d9c0-b1a2-3456-7890-abcdef123456",
"accountId": "33445566-7788-99aa-bbcc-ddeeff001122",
"status": "in_progress",
"stateSummary": "Customer inquired about delivery date for order NP-2026-0142...",
"outcome": null,
"humanManagingUserId": null,
"linkedConversationId": null,
"lastActivityAt": "2026-03-15T14:35:00.000Z",
"createdAt": "2026-03-15T09:12:00.000Z",
"updatedAt": "2026-03-15T14:35:00.000Z",
"contact": { "id": "f7e8d9c0-...", "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@example.com" },
"account": { "id": "33445566-...", "name": "Example Company" },
"history": [ /* array of message objects — see Get messages below */ ],
"contextEntries": [ /* KB + policy entries the assistant used on the latest turn */ ],
"attentionItems": [ /* attention items raised for this conversation */ ]
}

The contact, account, history, contextEntries, and attentionItems fields are only present when the corresponding upstream fetch succeeds. There is no subject or messageCount field. stateSummary holds the platform-maintained running summary, and the history array length gives you the count. linkedConversationId is the conversation this one continues, or null; on an email conversation that continues a website chat, it is that chat (the first, when several were passed to it).

Returns the 50 most recent messages in the conversation, oldest first.

Terminal window
curl "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/messages" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Response: returns a bare array of message objects, ordered chronologically.

[
{
"id": "11111111-2222-3333-4444-555555555555",
"direction": "inbound",
"speaker": null,
"body": "Hi, I placed order NP-2026-0142 last week. Can you tell me when it will be delivered?",
"sentAt": "2026-03-15T09:12:01.000Z",
"channel": "email",
"fromAddress": "alex.doe@example.com",
"subject": "Question about order #NP-2026-0142"
},
{
"id": "66666666-7777-8888-9999-aaaaaaaaaaaa",
"direction": "outbound",
"speaker": null,
"body": "Hello Alex,\n\nThank you for reaching out...",
"sentAt": "2026-03-15T09:12:45.000Z",
"channel": "email"
}
]

fromAddress and subject are present when the source channel carries them (typically email). On voice channels, every message has direction: "inbound" but speaker is set to "caller" or "ai" so consumers can render call transcripts correctly. On outbound messages sent by a member of your team (speaker: "human_operator"), senderName carries that person’s display name so consumers can attribute the reply; it is absent on all other messages.

Retrieve attachments for a conversation.

Terminal window
curl "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/attachments" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Response: returns a bare array of attachment objects.

[
{
"id": "aaaabbbb-cccc-dddd-eeee-ffffffffffff",
"messageId": "11111111-2222-3333-4444-555555555555",
"fileName": "purchase-order.pdf",
"mimeType": "application/pdf",
"sizeBytes": 245780,
"status": "ready",
"createdAt": "2026-03-15T09:12:01.000Z"
}
]

Send a human-authored reply. This signals the conversation workflow to deliver the message via the original channel. A reply sent with an API key is signed with the workspace’s company details.

Required scope: conversations:write

Terminal window
curl -X POST "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/reply" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \
-H "Content-Type: application/json" \
-d '{
"content": "Alex, I have just confirmed with our warehouse -- your order shipped this morning. Tracking number: TRK-98765432. You should receive it by end of day tomorrow."
}'

Response:

{
"ok": true
}

Mark a conversation as resolved, optionally with an outcome description.

Required scope: conversations:write

Terminal window
curl -X POST "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/resolve" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \
-H "Content-Type: application/json" \
-d '{
"outcome": "Delivery date confirmed, customer satisfied"
}'

Response:

{
"ok": true
}

Pause the agent pipeline and take manual control of the conversation. While taken over, the platform will not process new messages or send automatic replies.

Required scope: conversations:write

Terminal window
curl -X POST "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/take-over" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Release a taken-over conversation back to the agent pipeline. The platform resumes processing on the next inbound message.

Required scope: conversations:write

Terminal window
curl -X POST "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/release" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Start a new outbound conversation. This creates a conversation, resolves or creates the contact, and delivers the first message.

The first message is sent on behalf of your team and carries no satisfaction rating buttons. When called with an API key, it is signed with the workspace’s company details.

Required scope: conversations:write

Terminal window
curl -X POST "https://api.ohallo.eu/api/conversations/compose" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"channelId": "d4e5f6a7-b8c9-4123-8567-890abcdef012",
"contactEmail": "alex.doe@example.com",
"contactName": "Alex Doe",
"content": "Hello Alex,\n\nFollowing up on our conversation about the Q2 catalogue. The new product line is now available. Would you like me to send over the updated pricing?"
}'

Response:

{
"conversationId": "new_conv_id-1234-5678-9abc-def012345678"
}

The message is sent from channelId, which must be a channel of workspaceId. A workspaceId outside your account, or outside the API key’s brand or workspace restriction, answers 404 { "error": "Workspace not found" }; a channelId that is not a channel of that workspace answers 404 { "error": "Channel not found" }. Nothing is created or sent.

Attention items are flags raised by the agent pipeline when something requires human review, for example an MCP tool failure, a discrepancy between KB entries, or a validation failure.

Terminal window
curl "https://api.ohallo.eu/api/attention-items?workspaceId=a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Query parameters:

ParameterTypeRequiredDescription
workspaceIdUUIDNoFilter to a single workspace
statusstringNoopen, acknowledged, resolved, or dismissed (default returns all)
conversationIdUUIDNoFilter to a single conversation

A workspaceId or conversationId that is not an id answers 400 { "error": "Invalid filter" }. An API key restricted to a brand or workspace receives 404 { "error": "Workspace not found" } for that workspaceId instead, as for any workspace outside its restriction.

Response: returns a bare array of attention items.

[
{
"id": "11112222-3333-4444-5555-666677778888",
"tenantId": "10000000-0000-4000-8000-000000000001",
"conversationId": "c9f8e7d6-b5a4-3210-fedc-ba9876543210",
"type": "mcp_failure",
"status": "open",
"context": { "toolName": "get_order", "error": "Connection timeout after 30s" },
"createdAt": "2026-03-15T14:30:00.000Z",
"resolvedAt": null,
"turnId": "98765432-1234-5678-90ab-cdef12345678",
"assignedToUserId": null
}
]

type is one of: validation_failed, approval_gate, escalation, mcp_failure, auth_failure, customer_reply_pending, expiry_warning. The context JSON object varies by type.

Terminal window
curl -X POST "https://api.ohallo.eu/api/attention-items/11112222-3333-4444-5555-666677778888/resolve" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \
-H "Content-Type: application/json" \
-d '{ "reason": "Order delivered, customer confirmed receipt." }'

Response: { "ok": true } once the item is no longer open. An item that was already resolved, or that does not exist in your account, is left as it is and answers the same. An id that is not an attention item id answers 404 { "error": "Attention item not found" }.

Assign a conversation to a specific user. userId is the assignee’s identity-provider user id (the userId returned by the users API), not a database uuid. The conversation must already be paused (taken over); assigning a platform-managed conversation returns 409 with { "error": "must_pause_first" }. If the conversation is already assigned to another user, the call returns 409 with { "error": "already_assigned", "currentAssigneeId": "..." } unless overrideExistingAssignment is set to true.

The assignee must be an active workspace user with the agent, admin, or owner role; otherwise the call returns 422 with { "error": "invalid_assignee" }. When called with a dashboard session, callers with the observer or billing role receive 403.

Required scope: conversations:write

Terminal window
curl -X POST "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/assign" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \
-H "Content-Type: application/json" \
-d '{ "userId": "kp_0123456789abcdef0123456789abcdef", "overrideExistingAssignment": false }'

Response: { "ok": true }. Open attention items on the conversation are retargeted to the new assignee.

Remove the assignment, returning the conversation to the shared queue. The pause state is unchanged. Idempotent: unassigning an unassigned conversation returns 200. Dashboard callers with the observer or billing role receive 403.

Required scope: conversations:write

Terminal window
curl -X POST "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/unassign" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Approve a guardrail-gated tool call waiting for human approval. Resumes the agent pipeline.

Required scope: conversations:write

Terminal window
curl -X POST "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/approve" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Get aggregate conversation counts by status for a workspace.

Required scope: conversations:read

Terminal window
curl "https://api.ohallo.eu/api/workspaces/a1b2c3d4-e5f6-7890-abcd-ef1234567890/conversations/status-counts" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Response:

{
"open": 12,
"in_progress": 8,
"awaiting_contact": 23,
"resolved": 156,
"closed": 4,
"abandoned": 2
}

Retrieve a deep audit report for a single conversation. This is the complete record of how the conversation was handled: the contact and account, every message, every turn (orchestrator decisions, specialist executions, tool calls with their policy decisions, and the composed reply with its validation), attention items, identity events, and the activity trace. The activity trace is derived from the durable conversation event log and lists event-bearing and degraded steps only; each entry names the step (activityType), its turn (turnId) and its provenance (source: "conversation_events").

Required scope: conversations:read

Terminal window
curl "https://api.ohallo.eu/api/conversations/c9f8e7d6-b5a4-3210-fedc-ba9876543210/audit" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

Response (abridged):

{
"meta": {
"conversationId": "c9f8e7d6-b5a4-3210-fedc-ba9876543210",
"workspaceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"channelId": "11223344-5566-4788-99aa-bbccddeeff00",
"generatedAt": "2026-05-13T10:20:00.000Z",
"turnCount": 3,
"messageCount": 6,
"executionCount": 9
},
"contact": {
"contactId": "44556677-8899-4a1b-8c2d-3e4f5a6b7c8d",
"firstName": "Alex",
"companyName": "Example Company",
"identities": [{ "channel": "email", "value": "alex.doe@example.com" }]
},
"account": { "accountId": "...", "name": "Example Company", "country": "DE", "enrichmentStatus": "enriched" },
"conversation": { "status": "resolved", "outcome": "resolved", "workflowId": "...", "createdAt": "...", "lastActivityAt": "..." },
"turns": [
{
"turnNumber": 1,
"triggerType": "inbound_message",
"messages": [{ "direction": "inbound", "body": "When will order 4521 arrive?", "attachments": [] }],
"orchestratorIterations": [{ "iteration": 1, "action": "dispatch", "reasoning": "order status lookup", "agents": [{ "agentName": "Order Lookup" }] }],
"specialistExecutions": [
{ "agentName": "Order Lookup", "status": "completed", "model": "...", "toolCalls": [{ "toolName": "get_order", "policyDecision": "allow", "durationMs": 240 }] }
],
"composedReply": { "messageText": "Order 4521 ships tomorrow.", "validationOutcome": "approved", "validationIssues": [] }
}
],
"activityTrace": [{ "activityType": "runMessageAgent", "status": "completed", "durationMs": 1830, "turnId": "...", "source": "conversation_events" }],
"attentionItems": [],
"identityEvents": [],
"warnings": []
}

The report is assembled from several services. If one is briefly unavailable, its section is omitted and a note is added to warnings; the rest of the report is still returned.