Skip to content
View as Markdown

List conversations ​

GET /v1/inbox/conversations

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 ​

NameTypeRequiredDescription
campaign_idUUIDNoOnly this campaign. It must be a campaign whose inbox the key can read, otherwise 404.
domainstringNoOnly this inbox. Leave it out for both. One of contact, realestate.
statusstringNoOnly conversations with this status. One of open, needs_review, booked, not_interested, closed, suppressed.
archivedstringNofalse lists conversations that are not archived, true only archived ones, all both. Default: false. One of false, true, all.
updated_sincetimestampNoOnly 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.
limitintegerNoConversations per page. 1 to 100. Default: 50.
cursorstringNoThe 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

NameTypeDescription
dataarray of objectsConversations, oldest change first.
data.idUUIDThe conversation.
data.campaign_idUUIDThe conversation's campaign.
data.domainstringWhich inbox it is in: a contact campaign's (contact) or a real-estate offer campaign's (realestate). One of contact, realestate.
data.contact_idUUID | nullThe CRM contact, or null when it is not linked to one.
data.subjectstring | nullThe thread subject.
data.participant_emailstring | nullThe other party's address, following the member's email visibility.
data.statusstringThe 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.ai_statusstringWhere 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.updated_attimestampWhen the conversation last changed, read and archive changes included (RFC 3339, UTC).
data.last_message_attimestamp | nullTime of its latest message.
data.last_inbound_attimestamp | nullTime of its latest inbound message.
data.readbooleantrue when it was read at or after its latest inbound message. A conversation without inbound messages is read.
data.archivedbooleantrue while it is archived.
data.reply_identitiesobjectThe 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.reply_identities.campaign_mailboxobjectThe campaign mailbox the thread came from: the mailbox or account that sent the email the contact answered.
data.reply_identities.campaign_mailbox.emailstring | nullThe sender address; null when none is set up.
data.reply_identities.campaign_mailbox.availablebooleanWhether a reply can be sent from it now.
data.reply_identities.campaign_mailbox.reasonstringOnly 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.reply_identities.reply_addressobjectThe 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.reply_identities.reply_address.emailstring | nullThe sender address; null when none is set up.
data.reply_identities.reply_address.availablebooleanWhether a reply can be sent from it now.
data.reply_identities.reply_address.reasonstringOnly 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_cursorstring | nullPass it back as cursor for the next page; null when the window is walked.
has_morebooleantrue while next_cursor is set.
scan_throughtimestampThe 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 ​

HTTPCodeWhen
403insufficient_scopeThe key does not have inbox:read.
404not_foundcampaign_id is not a campaign whose inbox the key can read, or domain names an inbox this DialEcho deployment does not run.
409resync_requiredThe cursor expired (after 30 days) or the key's or its member's access changed since it was issued. Start again without a cursor.
422invalid_cursorThe cursor was not issued to this key for this list, was changed, or was sent with different filters.
422invalid_requestA parameter is invalid. error.fields names the first invalid filter (campaign_id, domain, status, archived, updated_since), or query.limit or query.cursor.
503dependency_unavailableThe 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.