List conversations
GET /
Returns the conversations that changed in a time window, oldest change first.
Scope: inbox:read
Access: The key's member must be able to use the campaign's email inbox in DialEcho (its message and email permissions) and see the contact; other conversations are left out.
A change scan, not the inbox's newest-first view: conversations are ordered by updated_at (then id), oldest first, and updated_at moves on every new message and every read or archive change. The first page fixes the end of the window, returned as scan_through; a conversation that changes after it waits for the next scan.
To keep a copy in sync, scan with updated_since and follow next_cursor until it is null. Start the next scan with updated_since set to the last scan_through minus 5 minutes, and keep the newest copy of each conversation by id. Deleted conversations are never listed: if one you have stored answers 404 later, treat it as deleted.
Only conversations the key's member can see are returned. Each request looks at up to 1,000 conversations, so a page can hold fewer than limit, or none, while next_cursor is still set. Lists carry no totals.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
campaign_id | UUID | No | Only this campaign. It must be a campaign whose inbox the key can read, otherwise 404. |
domain | string | No | Only this inbox. Leave it out for both. One of contact, realestate. |
status | string | No | Only conversations with this status. One of open, needs_review, booked, not_interested, closed, suppressed. |
archived | string | No | false lists conversations that are not archived, true only archived ones, all both. Default: false. One of false, true, all. |
updated_since | timestamp | No | Only conversations whose updated_at is at or after this time. ISO 8601; a time without an offset is read as UTC (URL-encode + as %2B). Leave it out to scan from the first conversation. |
limit | integer | No | Conversations per page. 1 to 100. Default: 50. |
cursor | string | No | The next_cursor of the previous page, unchanged. It carries the filters: send it alone, or with exactly the filters of the first page. Up to 4,096 characters. |
Response
Status: 200 OK
| Name | Type | Description |
|---|---|---|
data | array of objects | Conversations, oldest change first. |
data. | UUID | The conversation. |
data. | UUID | The conversation's campaign. |
data. | string | Which inbox it is in: a contact campaign's (contact) or a real-estate offer campaign's (realestate). One of contact, realestate. |
data. | UUID | null | The CRM contact, or null when it is not linked to one. |
data. | string | null | The thread subject. |
data. | string | null | The other party's address, following the member's email visibility. |
data. | string | The conversation status shown in the inbox. Archiving sets closed and restoring sets open; a suppressed conversation stays suppressed. One of open, needs_review, booked, not_interested, closed, suppressed. |
data. | string | Where the AI email agent is with the thread, independent of status. Archiving sets disabled. One of pending, queued, drafted, sent, auto_replied, rejected, needs_review, disabled. |
data. | timestamp | When the conversation last changed, read and archive changes included (RFC 3339, UTC). |
data. | timestamp | null | Time of its latest message. |
data. | timestamp | null | Time of its latest inbound message. |
data. | boolean | true when it was read at or after its latest inbound message. A conversation without inbound messages is read. |
data. | boolean | true while it is archived. |
data. | object | The two senders a reply can use (its send_as), as they are now. A preview, not a reservation: the sender is checked again when you reply. |
data. | object | The campaign mailbox the thread came from: the mailbox or account that sent the email the contact answered. |
data. | string | null | The sender address; null when none is set up. |
data. | boolean | Whether a reply can be sent from it now. |
data. | string | Only present when available is false: not_configured (no usable sender is set up) or reconnect_required (the account or mailbox must be reconnected in DialEcho). One of not_configured, reconnect_required. |
data. | object | The campaign's connected reply address, set in the campaign's email settings. On a real-estate conversation it is available only when the thread is already on that connected account. |
data. | string | null | The sender address; null when none is set up. |
data. | boolean | Whether a reply can be sent from it now. |
data. | string | Only present when available is false: not_configured (no usable sender is set up) or reconnect_required (the account or mailbox must be reconnected in DialEcho). One of not_configured, reconnect_required. |
next_cursor | string | null | Pass it back as cursor for the next page; null when the window is walked. |
has_more | boolean | true while next_cursor is set. |
scan_through | timestamp | The end of this scan's window (RFC 3339, UTC). Start the next scan from it, minus 5 minutes. |
Example request
curl
bash
curl "https://development.agencydevworks.ai/v1/inbox/conversations?campaign_id=cccccccc-0000-4000-8000-000000000001&updated_since=2026-11-02T00:00:00Z&limit=50" \
-H "Authorization: Bearer dei_your_key"JavaScript
js
const response = await fetch("https://development.agencydevworks.ai/v1/inbox/conversations?campaign_id=cccccccc-0000-4000-8000-000000000001&updated_since=2026-11-02T00:00:00Z&limit=50", {
headers: {
"Authorization": "Bearer dei_your_key",
},
});
const data = await response.json();Example response
Status: 200 OK
json
{
"data": [
{
"id": "44444444-0000-4000-8000-000000000001",
"campaign_id": "cccccccc-0000-4000-8000-000000000001",
"domain": "contact",
"contact_id": "bbbbbbbb-0000-4000-8000-000000000001",
"subject": "Your solar quote",
"participant_email": "pat@example.com",
"status": "open",
"ai_status": "needs_review",
"updated_at": "2026-11-02T16:20:02Z",
"last_message_at": "2026-11-02T16:20:00Z",
"last_inbound_at": "2026-11-02T16:20:00Z",
"read": false,
"archived": false,
"reply_identities": {
"campaign_mailbox": { "email": "jordan@outreach.example.com", "available": true },
"reply_address": { "email": "jordan@example.com", "available": true }
}
}
],
"next_cursor": null,
"has_more": false,
"scan_through": "2026-11-02T16:21:00Z"
}Errors
| HTTP | Code | When |
|---|---|---|
| 403 | insufficient_scope | The key does not have inbox:read. |
| 404 | not_found | campaign_id is not a campaign whose inbox the key can read, or domain names an inbox this DialEcho deployment does not run. |
| 409 | resync_required | The cursor expired (after 30 days) or the key's or its member's access changed since it was issued. Start again without a cursor. |
| 422 | invalid_cursor | The cursor was not issued to this key for this list, was changed, or was sent with different filters. |
| 422 | invalid_request | A parameter is invalid. error.fields names the first invalid filter (campaign_id, domain, status, archived, updated_since), or query.limit or query.cursor. |
| 503 | dependency_unavailable | The inbox or the access check is temporarily unavailable. Retry with backoff. |
Every endpoint can also answer 401 invalid_api_key, 429 rate_limited and 503 dependency_unavailable. See Errors in the overview.
Related endpoints
- GET
/· Retrieve a conversationv1/ inbox/ conversations/ {conversation_id} - GET
/· List inbound messagesv1/ inbox/ inbound-messages - PATCH
/· Update a conversationv1/ inbox/ conversations/ {conversation_id}