# Retrieve a conversion

`GET /v1/leads/{lead_id}/conversion`

Returns the conversion row of one lead, exactly as the list returns it.

**Scope:** `leads:read`

The same row, field visibility included, as `GET /v1/campaigns/{campaign_id}/conversions`. The lead must belong to a campaign the key may reach and be one the key's member can see in DialEcho, otherwise `404`.

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `lead_id` | `UUID` | Yes | The lead. |

## Response

**Status:** `200 OK`

| Name | Type | Description |
| --- | --- | --- |
| `lead_id` | `UUID` | The lead. |
| `campaign_id` | `UUID` | The lead's campaign. |
| `created_at` | `timestamp` | When the lead was created in DialEcho (RFC 3339, UTC). |
| `contact` | `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. |
| `contact.first_name` | `string \| null` | First name. |
| `contact.last_name` | `string \| null` | Last name. |
| `contact.email` | `string \| null` | Email address. |
| `contact.phone` | `string \| null` | Phone number in E.164 (`+14155550132`); a stored number that is not a valid phone number is returned as stored. |
| `contact.city` | `string \| null` | City. |
| `contact.state` | `string \| null` | State. |
| `contact.postal_code` | `string \| null` | ZIP or postal code. |
| `contact.email_sha256` | `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. |
| `contact.phone_sha256` | `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. |
| `attribution` | `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`. |
| `milestones` | `object` | How far the lead got. Timestamps are RFC 3339 in UTC; `null` until the lead reaches the milestone. |
| `milestones.first_call_at` | `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. |
| `milestones.first_call_outcome` | `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`. |
| `milestones.appointment_booked_at` | `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. |
| `milestones.appointment_at` | `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. |
| `milestones.appointment_status` | `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`. |
| `milestones.appointment_held_at` | `timestamp \| null` | For a `held` appointment, when the call that reached the lead started; for one only marked as shown up, its scheduled start. |
| `milestones.closed_at` | `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`. |
| `milestones.lost_at` | `timestamp \| null` | The same for a Lost stage. `null` unless `card.deal_status` is `lost`. |
| `card` | `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. |
| `card.id` | `UUID` | The card. |
| `card.pipeline_id` | `string \| null` | The card's pipeline. |
| `card.pipeline_name` | `string \| null` | The pipeline's name. |
| `card.stage` | `string \| null` | Slug of the current stage. |
| `card.stage_name` | `string \| null` | Name of the current stage. |
| `card.deal_status` | `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`. |
| `card.deal_value` | `number \| null` | The deal value, shared by the main card and its follow-through cards, or `null`. |

## Example request

**curl**

```bash
curl "https://development.agencydevworks.ai/v1/leads/aaaaaaaa-0000-4000-8000-000000000002/conversion" \
  -H "Authorization: Bearer dei_your_key"
```

**JavaScript**

```js
const response = await fetch("https://development.agencydevworks.ai/v1/leads/aaaaaaaa-0000-4000-8000-000000000002/conversion", {
  headers: {
    "Authorization": "Bearer dei_your_key",
  },
});
const data = await response.json();
```

## Example response

**Status:** `200 OK`

```json
{
  "lead_id": "aaaaaaaa-0000-4000-8000-000000000002",
  "campaign_id": "cccccccc-0000-4000-8000-000000000001",
  "created_at": "2026-11-03T10:21:07Z",
  "contact": {
    "first_name": "Sam",
    "last_name": null,
    "email": "sam@example.com",
    "phone": "+18035550142",
    "city": null,
    "state": "SC",
    "postal_code": null,
    "email_sha256": "cd25a6171969f2a3c6e35c7667e3908ef1bd2424241db04411a0eec454ca6c16",
    "phone_sha256": "70d177f4d52fc4982e5a28ed456060c15b9e4654a415c25be1eb9ade17a0a2ea"
  },
  "attribution": {
    "form_name": "quote-step-1",
    "page_url": "https://www.example.com/get-a-quote",
    "referrer": null,
    "utm_source": "google",
    "utm_medium": "cpc",
    "utm_campaign": "fall-promo",
    "utm_term": null,
    "utm_content": null,
    "utm_id": null,
    "adset_id": null,
    "ad_id": null,
    "fbclid": null,
    "fbc": null,
    "fbp": null,
    "gclid": "Cj0KCQexampleGclid",
    "meta_event_id": null
  },
  "milestones": {
    "first_call_at": "2026-11-03T10:21:30Z",
    "first_call_outcome": "voicemail",
    "appointment_booked_at": null,
    "appointment_at": null,
    "appointment_status": null,
    "appointment_held_at": null,
    "closed_at": null,
    "lost_at": null
  },
  "card": {
    "id": "dddddddd-0000-4000-8000-000000000001",
    "pipeline_id": "eeeeeeee-0000-4000-8000-000000000001",
    "pipeline_name": "Sales",
    "stage": "contacted",
    "stage_name": "Contacted",
    "deal_status": "open",
    "deal_value": null
  }
}
```

## Errors

| HTTP | Code | When |
| --- | --- | --- |
| 403 | `insufficient_scope` | The key does not have `leads:read`. |
| 404 | `not_found` | The lead does not exist or the key cannot reach it. |
| 422 | `invalid_request` | `lead_id` is not a UUID. |
| 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](/api/#errors) in the overview.

## Related endpoints

- `GET /v1/campaigns/{campaign_id}/conversions` · [List conversions](/api/reporting/list-conversions)
- `GET /v1/leads/{lead_id}` · [Retrieve a lead](/api/leads/get)
