Skip to content
View as Markdown

Create or update a lead ​

POST /v1/leads

Creates a lead in a campaign, or merges into the campaign's existing lead with the same phone number.

Scope: leads:write

Access: The key's member must be allowed to manage leads in the campaign.

Phone-first upsert. Within a campaign the phone number, normalized to E.164, identifies the lead: posting a number the campaign already has never creates a duplicate. The new values merge into that lead and the answer is 200 with created: false; a new lead is 201 with created: true. Merging needs a lead the key's member can see in DialEcho, otherwise 404 and nothing is written.

The lead is linked to a CRM contact (created from the phone number when the organization has none yet). If it is not on the campaign's board yet, its card is placed in the first stage of the campaign's default pipeline (created with default stages if the campaign has none) and routed to a rep by the campaign's assignment rules. A lead that already has a card is never moved by POST or PATCH; your team's placement wins. Once the lead has a card, the campaign's automated outreach for it stops.

custom_fields are saved on that pipeline card, in its Custom Fields panel. If the lead has no card and none can be placed, a request with non-blank custom_fields fails with 409 no_pipeline_card; the lead and its other details are still saved, so fix the campaign's pipeline and send the same request again. For a member who may open only a follow-through card of the lead, pipeline_lead_id is that card and the custom_fields are saved on the deal it shares with the main card.

The lead's time zone is set when it is created: from its state, else a state found in the address, else the ZIP in postal_code, else the phone's area code. A later POST or PATCH can fill a missing state or time zone but never changes one the lead already has.

Headers ​

NameTypeRequiredDescription
Idempotency-KeystringNoA retry with the same key and body returns the first answer (201 stays 201) and writes nothing. It is 404 not_found once the lead is gone or the key's member can no longer see it, as for a new request; if they can no longer open the card the first answer named, pipeline_lead_id is the card a new request would name. Reusing the key with a different body is 409 idempotency_conflict. Even without a key, a retry merges into the same lead by phone. 16 to 128 printable ASCII characters, no spaces (a UUID works).

Body ​

Content type: application/json

NameTypeRequiredDescription
campaign_idUUIDYesA campaign the key may write to.
phonestringYesThe lead's phone number, normalized to E.164: (415) 555-0132, 415.555.0132 and +1 415 555 0132 all become +14155550132. Digits and a leading + are kept and everything else is dropped. 10 digits, or 11 starting with 1, are read as US/Canada; any other number must carry its country code. Do not send extensions: their digits become part of the number. A number of impossible length is rejected with field code phone_invalid. Up to 40 characters.
first_namestringNoFirst name. Up to 100 characters.
last_namestringNoLast name. Up to 100 characters.
emailstringNoAn email address that can receive mail. Anything else, including an address over 254 characters, is rejected with field code email_invalid. Up to 254 characters.
addressstringNoStreet address. Up to 500 characters.
citystringNoCity. Up to 100 characters.
statestringNoA US state name or 2-letter code; sets the lead's time zone when it has none. Up to 100 characters.
postal_codestringNoA US ZIP sets the lead's state (and its time zone, if it has none) when neither state nor address names one and none is stored. Up to 20 characters.
companystringNoCompany name. Up to 200 characters.
notesstringNoFree-text notes on the lead. Up to 5,000 characters.
custom_fieldsobjectNoValues for the campaign's own custom fields, keyed by field slug. Each value is a string, number, boolean or null. Saved on the lead's pipeline card as text, exactly as if your team typed it: true/false for checkbox fields, numbers as sent (12 becomes "12"), strings trimmed. A slug that is not one of the campaign's fields, or is a file or image field, is reported as custom_fields.<slug> with not_allowed (upload files with POST /v1/leads/{lead_id}/files); a bad value (another type, or a string too long) is reported once, as custom_fields. Up to 100 entries. Keys 1 to 100 characters; string values up to 2,000 characters.
attributionobjectNoMarketing attribution, stored on the lead under the same key names and returned by the conversion report for ad-platform matching. Any key not listed here is rejected with not_allowed.
attribution.utm_sourcestringNoUTM source of the visit. Up to 500 characters.
attribution.utm_mediumstringNoUTM medium. Up to 500 characters.
attribution.utm_campaignstringNoUTM campaign. Up to 500 characters.
attribution.utm_termstringNoUTM term. Up to 500 characters.
attribution.utm_contentstringNoUTM content. Up to 500 characters.
attribution.utm_idstringNoUTM id, for example the Meta campaign id. Up to 500 characters.
attribution.adset_idstringNoAd set id of the ad the visitor came from. Up to 500 characters.
attribution.ad_idstringNoAd id of the ad the visitor came from. Up to 500 characters.
attribution.fbclidstringNoMeta's click id from the landing page URL. Up to 500 characters.
attribution.fbpstringNoThe visitor's Meta _fbp cookie. Up to 500 characters.
attribution.fbcstringNoThe visitor's Meta _fbc cookie. Up to 500 characters.
attribution.gclidstringNoGoogle's click id. Up to 500 characters.
attribution.meta_event_idstringNoThe event ID your Meta Pixel's browser Lead event used for this submission, so a Lead sent from the conversion report deduplicates against it. Up to 200 characters.
attribution.page_urlstringNoThe page the form was submitted on. Up to 2,048 characters.
attribution.form_namestringNoYour name for the form or form step. Up to 200 characters.
attribution.referrerstringNoThe referring page. Up to 2,048 characters.

Response ​

Status: 201 Created · 200 OK

201 for a new lead; 200 when the campaign already had this phone number and the values were merged. The identifiers are the same on every resubmission.

NameTypeDescription
lead_idUUIDThe lead.
contact_idUUID | nullThe CRM contact the lead is linked to.
pipeline_lead_idUUID | nullThe lead's card on the campaign's board, the card GET /v1/leads/{lead_id} returns as card (for a member who may open only a follow-through card of the lead, that card). null only when the lead could not be placed (for example the first stage only accepts cards placed by a person); sending the same POST again retries the placement.
createdbooleantrue when this request created the lead.

Example request ​

curl

bash
curl -X POST "https://development.agencydevworks.ai/v1/leads" \
  -H "Authorization: Bearer dei_your_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0f0f0f0f-0000-4000-8000-00000000abcd" \
  -d '{
  "campaign_id": "cccccccc-0000-4000-8000-000000000001",
  "phone": "(415) 555-0132",
  "first_name": "Pat",
  "email": "pat@example.com",
  "state": "CA",
  "custom_fields": {
    "roof_age": 12
  },
  "attribution": {
    "utm_source": "facebook",
    "utm_medium": "paid_social",
    "utm_campaign": "fall-promo",
    "fbclid": "IwAR0exampleClickId",
    "fbp": "fb.1.1791900000000.1234567890",
    "page_url": "https://www.example.com/get-a-quote",
    "form_name": "quote-step-1"
  }
}'

JavaScript

js
const response = await fetch("https://development.agencydevworks.ai/v1/leads", {
  method: "POST",
  headers: {
    "Authorization": "Bearer dei_your_key",
    "Content-Type": "application/json",
    "Idempotency-Key": "0f0f0f0f-0000-4000-8000-00000000abcd",
  },
  body: JSON.stringify({
    "campaign_id": "cccccccc-0000-4000-8000-000000000001",
    "phone": "(415) 555-0132",
    "first_name": "Pat",
    "email": "pat@example.com",
    "state": "CA",
    "custom_fields": {
      "roof_age": 12
    },
    "attribution": {
      "utm_source": "facebook",
      "utm_medium": "paid_social",
      "utm_campaign": "fall-promo",
      "fbclid": "IwAR0exampleClickId",
      "fbp": "fb.1.1791900000000.1234567890",
      "page_url": "https://www.example.com/get-a-quote",
      "form_name": "quote-step-1"
    }
  }),
});
const data = await response.json();

Example response ​

Status: 201 Created

json
{
  "lead_id": "aaaaaaaa-0000-4000-8000-000000000001",
  "contact_id": "bbbbbbbb-0000-4000-8000-000000000001",
  "pipeline_lead_id": "dddddddd-0000-4000-8000-000000000001",
  "created": true
}

Errors ​

HTTPCodeWhen
403insufficient_scopeThe key does not have leads:write. Checked before the body is validated.
403permission_deniedThe key's member may not manage leads in this campaign.
404not_foundThe campaign does not exist or the key cannot reach it, or the phone matches a lead the key's member cannot see.
409idempotency_conflictThe Idempotency-Key was already used with a different body. Nothing was written.
409no_pipeline_cardNon-blank custom_fields were sent but the lead has no card and none could be placed. The lead and its other details were saved; the custom fields were not.
413payload_too_largeThe body is larger than 1 MB. It was not read and nothing was written.
422invalid_requestThe body or a header is invalid: unknown or missing fields, values too long, an invalid phone or email, unknown or file/image custom fields, a deal_value, or a malformed Idempotency-Key. error.fields lists every problem.

Every endpoint can also answer 401 invalid_api_key, 429 rate_limited and 503 dependency_unavailable. See Errors in the overview.