List conversions
GET /
Returns one conversion row per lead of a campaign, oldest first.
Scope: leads:read
Rows are ordered by created_at, oldest first, so a lead created while you page shows up on a later page and rows already returned are never repeated. Use it to see which marketing leads became customers and to send those outcomes back to an ad platform.
A deal is closed when its card is in a stage whose outcome is set to Won in the pipeline's stage settings, and lost in a stage whose outcome is Lost; the stage's name does not matter. Set the outcome of every winning and losing stage in DialEcho; leads already in those stages then report the close from when they entered the stage.
The campaign must be one the key may reach, otherwise 404. Reading does not need the member's permission to manage leads.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
campaign_id | UUID | Yes | A campaign the key may reach. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
created_after | timestamp | No | Only leads created at or after this time. ISO 8601 with a UTC offset, for example 2026-11-01T00:00:00Z (URL-encode + as %2B). |
created_before | timestamp | No | Only leads created before this time. After created_after. |
milestone | string | No | Only leads that reached it: called (first_call_at set), appointment_booked, appointment_held (appointment_status is held), closed (closed_at set) or lost (lost_at set). One of called, appointment_booked, appointment_held, closed, lost. |
limit | integer | No | Rows per page. 1 to 100. Default: 50. |
cursor | string | No | The next_cursor of the previous page, unchanged. Opaque: never build or edit one. Up to 4,096 characters. |
Response
Status: 200 OK
A lead that has not got anywhere yet has every milestone null and, if it is not on the board, "card": null. Every key is always present.
| Name | Type | Description |
|---|---|---|
data | array of objects | Conversion rows, oldest first. |
data. | UUID | The lead. |
data. | UUID | The lead's campaign. |
data. | timestamp | When the lead was created in DialEcho (RFC 3339, UTC). |
data. | object | The lead's details as GET /v1/leads/{lead_id} returns them, with the same field visibility: a field the member's role hides is null, one it masks comes back masked. |
data. | string | null | First name. |
data. | string | null | Last name. |
data. | string | null | Email address. |
data. | string | null | Phone number in E.164 (+14155550132); a stored number that is not a valid phone number is returned as stored. |
data. | string | null | City. |
data. | string | null | State. |
data. | string | null | ZIP or postal code. |
data. | string | null | SHA-256 (lowercase hex) of the email trimmed and lowercased: Meta's hashed em. null without an email, or unless the member sees the email in full. |
data. | string | null | SHA-256 (lowercase hex) of the number's digits with country code and no + (14155550132): Meta's hashed ph. null without a valid number, or unless the member sees the phone in full. |
data. | object | null | The attribution stored on the lead under the 16 attribution keys POST /v1/leads accepts (utm_source, utm_medium, utm_campaign, utm_term, utm_content, utm_id, adset_id, ad_id, fbclid, fbc, fbp, gclid, meta_event_id, page_url, form_name, referrer), whether sent through the API or set another way such as an import. Every key is present, null when never set. The whole object is null when the member's role hides custom fields; when it masks them, set values read Restricted. |
data. | object | How far the lead got. Timestamps are RFC 3339 in UTC; null until the lead reaches the milestone. |
data. | timestamp | null | When the first outbound call to the lead by one of the campaign's AI agents started: a POST /v1/calls call, a campaign or a pipeline agent's call. Calls by your team and calls the lead made are not counted. |
data. | string | null | How that call went: reached (a real conversation), answered (picked up but no conversation), voicemail, no_answer (not answered, busy or not connected) or in_progress. One of in_progress, reached, answered, voicemail, no_answer. |
data. | timestamp | null | When the lead's first appointment in the campaign was booked, however it was booked, even if it was later moved or cancelled. |
data. | timestamp | null | When the appointment is or was scheduled: the first one that was held; if none was held, the latest one not cancelled; if all were cancelled, the latest one. |
data. | string | null | That appointment's status. Attendance your team records decides (shown up is held, no-show is no_show); otherwise held when your team's appointment call reached the lead and no_show when your team recorded nobody answered. An unanswered call with nothing recorded leaves it scheduled. One of scheduled, held, no_show, cancelled. |
data. | timestamp | null | For a held appointment, when the call that reached the lead started; for one only marked as shown up, its scheduled start. |
data. | timestamp | null | When the card entered the Won stage it is in now (a deal reopened and won again reports the latest win). null unless card.deal_status is won. |
data. | timestamp | null | The same for a Lost stage. null unless card.deal_status is lost. |
data. | object | null | The card GET /v1/leads/{lead_id} returns as card, or null if the lead has none the member can open: its main card (the deal), or, for a member who may open only a follow-through card of the lead, that card with its own pipeline, stage, deal_status, closed_at and lost_at and the deal value it shares with the main card. Nothing of a card the member may not open is shown. Always shown, whatever the member's field visibility. |
data. | UUID | The card. |
data. | string | null | The card's pipeline. |
data. | string | null | The pipeline's name. |
data. | string | null | Slug of the current stage. |
data. | string | null | Name of the current stage. |
data. | string | abandoned when your team marked the deal abandoned, otherwise the outcome set on the card's stage in DialEcho (Won, Lost or Open), whatever the stage is called. A follow-through card the deal moves into after it is won is reported only to a member who may open that card and not the main card. One of open, won, lost, abandoned. |
data. | number | null | The deal value, shared by the main card and its follow-through cards, or null. |
next_cursor | string | null | Pass it back as cursor for the next page; null when there is none. |
has_more | boolean | true while more pages may follow. Keep paging until it is false. |
Example request
curl
bash
curl "https://development.agencydevworks.ai/v1/campaigns/cccccccc-0000-4000-8000-000000000001/conversions?created_after=2026-11-01T00:00:00Z&milestone=closed&limit=50" \
-H "Authorization: Bearer dei_your_key"JavaScript
js
const response = await fetch("https://development.agencydevworks.ai/v1/campaigns/cccccccc-0000-4000-8000-000000000001/conversions?created_after=2026-11-01T00:00:00Z&milestone=closed&limit=50", {
headers: {
"Authorization": "Bearer dei_your_key",
},
});
const data = await response.json();Example response
Status: 200 OK
json
{
"data": [
{
"lead_id": "aaaaaaaa-0000-4000-8000-000000000001",
"campaign_id": "cccccccc-0000-4000-8000-000000000001",
"created_at": "2026-11-02T15:55:55Z",
"contact": {
"first_name": "Pat",
"last_name": "Lee",
"email": "pat@example.com",
"phone": "+14155550132",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105",
"email_sha256": "fe9733fcc96501276776fc927df08a395835461a2c5b12c65ebab3454877e227",
"phone_sha256": "11072303eb5c8cb5c5a1ebb762f11b37b07131265b46fdfe1276196bfbd3ca51"
},
"attribution": {
"form_name": "quote-step-1",
"page_url": "https://www.example.com/get-a-quote",
"referrer": null,
"utm_source": "facebook",
"utm_medium": "paid_social",
"utm_campaign": "fall-promo",
"utm_term": null,
"utm_content": null,
"utm_id": "120200000000000001",
"adset_id": "120200000000000002",
"ad_id": "120200000000000003",
"fbclid": "IwAR0exampleClickId",
"fbc": null,
"fbp": "fb.1.1791900000000.1234567890",
"gclid": null,
"meta_event_id": null
},
"milestones": {
"first_call_at": "2026-11-02T15:56:04Z",
"first_call_outcome": "reached",
"appointment_booked_at": "2026-11-02T15:59:41Z",
"appointment_at": "2026-11-04T18:00:00Z",
"appointment_status": "held",
"appointment_held_at": "2026-11-04T18:02:13Z",
"closed_at": "2026-11-09T16:30:00Z",
"lost_at": null
},
"card": {
"id": "dddddddd-0000-4000-8000-000000000001",
"pipeline_id": "eeeeeeee-0000-4000-8000-000000000001",
"pipeline_name": "Sales",
"stage": "won",
"stage_name": "Won",
"deal_status": "won",
"deal_value": 18500.0
}
}
],
"next_cursor": null,
"has_more": false
}Errors
| HTTP | Code | When |
|---|---|---|
| 403 | insufficient_scope | The key does not have leads:read. Checked before the query is validated. |
| 404 | not_found | The campaign does not exist or the key cannot reach it. |
| 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 from the first page. |
| 422 | invalid_cursor | The cursor was not issued to this key for this request, was changed, or was sent with different parameters than the page it came from. |
| 422 | invalid_request | A parameter is invalid: campaign_id not a UUID, a timestamp without a UTC offset, created_before not after created_after, an unknown milestone, a limit outside 1 to 100 or a cursor over 4,096 characters. error.fields names it (query.milestone, campaign_id, ...). |
| 503 | dependency_unavailable | The report or the access check is temporarily unavailable. Retry the same request with backoff. |
Every endpoint can also answer 401 invalid_api_key, 429 rate_limited and 503 dependency_unavailable. See Errors in the overview.
Sending conversions to Meta
DialEcho does not send anything to Meta. Your server, or your marketing team's tool, reads this report and sends the events to Meta's Conversions API (POST https://graph.facebook.com/{api-version}/{pixel-id}/events), so Meta's optimization learns from leads that became appointments and sales. Keep the event_ids you have sent and send each event once.
Pull at least daily: Meta only accepts recent event_times (7 days for most events) and deals often close weeks after the lead. A daily job requesting ?created_after=<today minus your longest sales cycle>&milestone=closed (and milestone=appointment_booked) catches every close while it is still recent.
action_source says where the conversion happened: phone_call for an appointment or deal made on a call (the usual case here), website for a form or a booking your site made (Meta then requires the visitor's user agent and page, which the report does not have, so send those from your website's server when they happen), other for in person.
Meta event_name | Send it for rows where | event_time (Unix seconds) | event_id | custom_data |
|---|---|---|---|---|
Lead | Only leads your website's server did not already send, for example a lead that phoned you. | created_at | attribution. when set (deduplicates against the Pixel), otherwise dialecho-lead-{lead_id} | none |
Schedule | milestones. is set | appointment_booked_at | dialecho-schedule-{lead_id} | none |
Purchase | milestones. is set | closed_at | dialecho-purchase-{lead_id} | {"value": card.deal_value, "currency": "USD"}; DialEcho stores no currency and shows deal values in US dollars |
Meta user_data from a row
Send these user_data keys with every event. Omit a key whose source is null.
| Meta key | From the row |
|---|---|
em | [contact.email_sha256] (already hashed) |
ph | [contact.phone_sha256] (already hashed) |
fn, ln | SHA-256 of contact.first_name / last_name, trimmed and lowercased |
ct | SHA-256 of contact.city, lowercased with spaces and punctuation removed |
st | SHA-256 of contact.state as a lowercase 2-letter code (ca) |
zp | SHA-256 of the first 5 digits of contact.postal_code (US) |
country | SHA-256 of us for US leads |
external_id | SHA-256 of lead_id, the same on every event of a lead |
fbc | attribution. when set; otherwise, when attribution.fbclid is set, fb.1.{created_at in Unix milliseconds}.{fbclid}. Not hashed. |
fbp | attribution. when set. Not hashed. |
Related endpoints
- GET
/· Retrieve a conversionv1/ leads/ {lead_id}/ conversion - POST
/· Create or update a leadv1/ leads - GET
/· Retrieve the pipelinev1/ campaigns/ {campaign_id}/ pipeline