Skip to main content

Leads

Somebody you have heard of and not yet qualified: a visitor who booked or wrote in through a lead-routed form, or a person from a list your team sourced elsewhere. A lead is a record of its own, the way it is in Salesforce: it carries the person and their company as text — name, company, title, phone, website, address, tags, where they came from — beside what a capture recorded (the surfaces, first and last seen, marketing consent) and the working state the team keeps: a status, an owner and notes. Nothing else exists until it converts: converting is what makes the contact, and optionally the company and a deal, from what the lead holds.

Scopes are the CRM's — crm:read to read, crm:write to work and convert — and the resource needs a plan that includes the CRM suite, like every resource on companies → Scopes and sites. The site rule is different, and worth reading once.

A lead belongs to a site​

Companies, deals and the rest are organization records that a key reads whole. A lead is not: it lives under the site that captured it, private to that site, and its id is only unique within it — the same address captured on two of your sites is two leads, one per site. So every endpoint here names the site:

  • siteId is a query parameter on every request — GET /v1/leads?siteId=…, GET /v1/leads/{leadId}?siteId=…, and so on. A PATCH or POST may carry it in the body instead.
  • A missing siteId, or one naming a site your organization does not own, is a 400 validation_failed with fields.siteId.

There is no organization-wide list of leads. To read every site's leads, walk your sites and list each one's.

The lead object​

{
"id": "5f3c…e9a1",
"object": "lead",
"siteId": "site_a1b2c3",
"email": "ann@acme.com",
"name": "Ann Lee",
"status": "working",
"statusLabel": "Working",
"ownerUid": "u_9f1c",
"notes": "Asked for a demo on Thursday",
"unqualifiedReason": null,
"company": "Acme Brands",
"jobTitle": "VP Marketing",
"phone": "+15125550107",
"website": "https://acme.com/",
"address": { "city": "Austin", "state": "TX", "country": "US" },
"tags": ["icp2", "a-list"],
"leadSource": "Trade show",
"salutation": "Ms.",
"firstName": "Ann",
"lastName": "Lee",
"mobilePhone": "+15125550108",
"fax": null,
"doNotCall": false,
"industry": "Technology",
"rating": "Warm",
"annualRevenueCents": 125000000,
"currency": "usd",
"numberOfEmployees": 42,
"sources": ["form:contact-us", "api"],
"submissionCount": 2,
"firstSeen": "2026-08-30T15:02:11.000Z",
"lastSeen": "2026-09-04T09:41:07.000Z",
"marketingConsent": true,
"marketingConsentAt": "2026-08-30T15:02:11.000Z",
"convertedContactId": null,
"convertedAt": null,
"companyId": null,
"dealId": null,
"custom": { "budget": 25000, "territory": "west" },
"created": "2026-08-30T15:02:11.000Z",
"updated": "2026-09-04T10:12:40.000Z"
}
FieldTypeNotes
idstringThe lead's id within its site. Stable for the address: a person who submits twice is one lead, seen twice.
objectstringAlways "lead".
siteIdstringThe site that captured the lead. Read-only.
emailstring | nullThe address — the lead's identity within its site. Set on create; read-only after.
namestring | nullThe person's name. Set on create or by a capture; read-only on a PATCH. While firstName or lastName is set, it is the two together ("Ann Lee") and follows them on every write; a lead with only a name keeps it as captured.
statusstringWhat the lead's status MEANS: new, nurturing, working, qualified or unqualified — see Status. What ?status= and every automation read. Writable, with rules — by one of your organization's lead status values or by a meaning.
statusLabelstringYour organization's label for the lead status value the lead holds — one of status's meaning, such as Contacted for working. A lead given only a meaning shows the meaning's default value. Read-only — set it through status.
ownerUidstring | nullThe team member working the lead. Must be a member of your organization. Writable — by uid, or as ownerEmail (see Update a lead).
notesstring | nullFree text, 5,000 characters. Writable.
unqualifiedReasonstring | nullWhy the lead was closed without converting. Present only while status is unqualified. Writable, with status.
companystring | nullThe company's name, as text. A lead names its company the way a business card does; converting is what links or creates the company record, by this name or by the address's domain. Writable.
jobTitlestring | nullWritable.
phonestring | nullE.164 (+15125550107). A number with no country code is read as North American; anything unreadable is a 400 under the field. Writable.
websitestring | nullAn http(s) URL; acme.com is stored as https://acme.com/. Writable.
addressobject | nullline1, line2, city, state, postalCode, country (two-letter code). Writable; a blank address clears.
tagsstring[]Lower-cased, deduplicated, at most 20. Writable — as an array, or a comma-separated string.
leadSourcestring | nullWhere the lead came from — what Salesforce calls Lead Source: one of the organization's lead source values. Writable, and restricted to the list's active values, matched without regard to case and stored as the list spells it; any other value is refused with 400 validation_failed, the allowed values named under fields.leadSource. The value a lead already holds is kept even after it is deactivated. A create that names none starts from the list's default, when it has one. The list holds Salesforce's standard values — Web, Phone inquiry, Email inquiry, Partner referral, Employee referral, External referral, Advertisement, Trade show, Webinar, Word of mouth, Purchased list and Other — Aglyn's own (Website form, Booking, Newsletter sign-up, Site member sign-up, Online purchase, Account sign-up, Sequence, Email campaign; a lead the site captures through one of its doors is given that door's value when it has none) and the organization's own, each grouped Inbound or Outbound or in neither; the group is not part of the lead. Distinct from sources, which the site records.
salutationstring | nullOne of the organization's Salutation values (Mr., Ms., Mrs., Dr., Prof. and its own) — the list contacts use. Writable, restricted to the list's active values like leadSource; the value a lead already holds is kept.
firstName / lastNamestring | nullThe name's parts, at most 59 characters each. Writable; see name.
mobilePhone / faxstring | nullE.164, read as phone is. Writable.
doNotCallbooleanThe person asked not to be phoned — a warning on every call control, never a block. Writable as true or false; false clears it.
industry / ratingstring | nullOne of the organization's Industry or Rating values — the lists companies use, so a conversion carries the value. Writable, restricted to the lists' active values like leadSource.
annualRevenueCentsinteger | nullAnnual revenue in the minor unit of currency, 0 or more. Writable.
currencystringLowercase ISO 4217 code of the revenue; usd when unset. Writable.
numberOfEmployeesinteger | null0 to 99,999,999. Writable.
sourcesstring[]Every surface that produced a capture: signup, booking, form:{formId}, import, manual (the console's New lead), api (this resource). Read-only.
submissionCountnumberHow many captures this lead represents. Read-only.
firstSeen / lastSeenstring | nullISO 8601 — the first and the latest capture. Read-only.
marketingConsentbooleanWhether the person ticked a marketing opt-in on this site; marketingConsentAt is when. Read-only.
convertedContactIdstring | nullThe contact this lead became, once converted; convertedAt is when. Read-only — set by converting.
companyId, dealIdstring | nullThe company the conversion linked or created, and the deal it opened, when it did. Read-only.
customobjectThe organization's lead custom fields, keyed by field key; {} when the lead has none. Judged against the lead definitions: a key that is not one, a retired field, or a value the type cannot hold is a 400 naming custom.<key>. A PATCH merges the keys it sends; null clears one. These values stay on the lead — converting does not copy them onto the contact, whose fields are its own. Writable.
created / updatedstring | nullISO 8601. updated is stamped by the team's writes and by the conversion, not by a repeat capture — watch lastSeen for those.

Status​

StatusMeaning
newCaptured and untouched. A lead nobody has worked reads as new even though nothing has been written on it yet.
nurturingAutomated email is reaching it — a sequence sent it a step, or a campaign email was delivered to it — and nobody has engaged yet. Set by Aglyn over new only; a PATCH may also set it.
workingSomebody is on it. A reply to a sequence moves a new or nurturing lead here.
unqualifiedClosed without converting, with an unqualifiedReason.
qualifiedConverted. Reached only through POST /v1/leads/{id}/convert — never by a PATCH, because the status is a claim that a contact exists and the conversion is what makes it true. A converted lead's status is fixed.

Endpoints​

List leads​

GET /v1/leads?siteId={siteId} — scope crm:read. Paginated, newest lastSeen first — the order a work queue wants, and the order a poll for "what arrived since I last looked" wants.

ParamNotes
siteIdRequired — see above.
statusnew, nurturing, working, qualified or unqualified. Anything else is a 400.
ownerUidLeads this member owns.
limit, cursorStandard pagination.
# the site's open work, newest first
curl "https://app.aglyn.com/api/v1/leads?siteId=site_a1b2c3&status=new" \
-H "Authorization: Bearer aglyn_sk_…"

status and ownerUid are checked on the page, not in the query, so a filtered page can come back short — keep following next_cursor until has_more is false. That is deliberate: a lead nobody has touched carries no status at all and reads as new, which is exactly the lead a query on the stored field would miss.

Create a lead​

POST /v1/leads?siteId={siteId} — scope crm:write. Accepts an Idempotency-Key, scoped to the site.

A lead sourced outside the site — a list, an event, a referral — entered before anyone has qualified it. It creates a lead and nothing else: no contact, no company. Those come from converting it, from what the lead holds by then.

KeyNotes
emailRequired. The lead's identity within its site — one address is one lead per site.
nameThe person's name.
company, jobTitle, phone, website, address, tags, leadSource, salutation, firstName, lastName, mobilePhone, fax, doNotCall, industry, rating, annualRevenueCents, currency, numberOfEmployeesThe lead's own profile, as above. A new lead that names no salutation, industry or rating starts from each list's default, when it has one.
statusOne of your organization's active lead status values of New or Working, by its label in any case, or the meaning new (the default) or working. Stored as the meaning and the label together.
ownerUid / ownerEmailWho works the lead.
notesFree text.
customThe organization's lead fields, keyed by field key, as above.
siteIdInstead of the query parameter.
curl -X POST "https://app.aglyn.com/api/v1/leads?siteId=site_a1b2c3" \
-H "Authorization: Bearer aglyn_sk_…" \
-H "Idempotency-Key: 7c2e…" \
-H "Content-Type: application/json" \
-d '{"email":"ann@acme.com","name":"Ann Lee","company":"Acme Brands","jobTitle":"VP Marketing","leadSource":"Trade show","tags":["icp2"]}'

Returns 201 with the lead. A site that already holds a lead for the address answers 200 with that lead, updated: the fields you sent are written onto it, its sources gain api, and its lastSeen moves — a second create never mints a second row. A blank field in the body is left alone on an update, null clears it.

No marketing consent is recorded. A create over the API is not a box the person ticked; the lead can be included in a campaign audience only if the site already holds a consent for the address.

Retrieve a lead​

GET /v1/leads/{leadId}?siteId={siteId} — scope crm:read. Returns a lead, or 404 not_found ("No such lead").

Update a lead​

PATCH /v1/leads/{leadId}?siteId={siteId} — scope crm:write. No Idempotency-Key. An omitted key is left alone, null clears an optional field, {} is a no-op.

KeyNotes
statusOne of your organization's active lead status values by its label, in any case, or a meaning: new, nurturing, working or unqualified. Stored as the meaning and the label together; the value a lead already holds is kept even after it is deactivated. Not a Qualified value, by any name — convert instead. Anything else is a 400 naming the values allowed.
unqualifiedReasonRequired when status becomes unqualified; may be sent alone to reword the reason of a lead already unqualified. Refused with any other status. Setting the status back to new, nurturing or working drops it.
ownerUidA member's uid, or null to clear.
ownerEmailA member's address, resolved against your organization's roster — for a spreadsheet or a zap that has the address and not the uid. Not with ownerUid in the same request. null clears.
notesFree text, or null.
company, jobTitle, phone, website, address, tags, leadSourceThe lead's own profile, as above; null clears any of them. Writable on a converted lead too — the contact is the record then, but the lead keeps what it knew.
customThe organization's lead fields, as above. The keys sent are merged into the map; null under a key clears it and leaves the rest alone.
curl -X PATCH "https://app.aglyn.com/api/v1/leads/5f3c…e9a1?siteId=site_a1b2c3" \
-H "Authorization: Bearer aglyn_sk_…" \
-H "Content-Type: application/json" \
-d '{"status":"working","ownerEmail":"rep@yourcompany.com","notes":"Demo Thursday"}'

A converted lead answers 409 conflict with code: "lead_converted" to a status; its notes and owner stay writable.

Convert a lead​

POST /v1/leads/{leadId}/convert?siteId={siteId} — scope crm:write. Accepts an Idempotency-Key, scoped to the site.

This is the same conversion the console's Convert dialog performs, through the same code: the lead becomes a contact at the Sales qualified lifecycle stage, carrying the lead's salutation, first and last name, phone, mobile phone, fax, doNotCall, job title, address, tags, notes, company name and marketing consent — but not its custom map, whose fields are the lead's own — joining the existing contact if the address is already one, so the address book stays one row per person — then, optionally, a company is linked or created and a deal opened in your default pipeline. A company the conversion creates takes the lead's industry, rating, annualRevenueCents and currency, numberOfEmployees, website, phone, fax, its address as the billing address, and its leadSource as accountSource; a company it links or reuses takes them only into fields it leaves empty. The deal takes the lead's leadSource and, as campaignId, the campaign the lead was most recently filed under. The lead is stamped qualified once everything it names exists; and what was filed on the lead follows it: its activities and tasks gain the contact, and a sequence enrollment naming the lead re-points itself to the contact. A lead already converted answers 200 with the ids it has and creates nothing more, so a retry never opens a second deal.

Body — every key optional:

KeyNotes
company{ "link": "co_…" } to link an existing company, { "create": { "name": "Acme", "domain": "acme.com" } } to create one (a company your key can see under that name, or at that domain, is reused rather than duplicated), or null for none. The lead's own company text is the name to send.
deal{ "title": "…", "amountCents": 12500, "currency": "usd", "stageId": "prospecting", "type": "New Business" } — title required; amountCents a whole number; currency lowercase ISO 4217, usd when omitted; stageId a stage of the default pipeline, its first open stage when omitted; type one of the organization's deal types, the list's default when omitted — a value outside the active list is a 400 naming deal.type. The deal takes the lead's leadSource and its stage's forecastCategory. null for none. An organization with no pipeline yet gets a Sales pipeline with the default stages.
ownerUid / ownerEmailWho owns the contact and the deal. Defaults to the lead's owner; failing that your organization's assignment rules and the site's default owner decide. A key cannot own a record, so a contact nobody names and no rule assigns stays unassigned — where a person converting from the console would have kept it.
siteIdInstead of the query parameter.
curl -X POST "https://app.aglyn.com/api/v1/leads/5f3c…e9a1/convert?siteId=site_a1b2c3" \
-H "Authorization: Bearer aglyn_sk_…" \
-H "Idempotency-Key: 9a41f0c2-…" \
-H "Content-Type: application/json" \
-d '{"company":{"create":{"name":"Acme","domain":"acme.com"}},"deal":{"title":"Acme — first order","amountCents":12500}}'

Returns 201 with a receipt, or 200 when the lead was already converted (or an Idempotency-Key replays):

{
"object": "lead_conversion",
"id": "5f3c…e9a1",
"siteId": "site_a1b2c3",
"contactId": "k7d2b9f104",
"companyId": "co_8b1e",
"dealId": "d_3c9a",
"alreadyConverted": false,
"lead": { "id": "5f3c…e9a1", "object": "lead", "status": "qualified", "…": "…" }
}

The conversion creates records against the CRM records band — see Usage. On a plan that refuses at the band rather than metering it, a company or a deal that would not fit answers 403 plan_required with code: "crm_records_quota", the contact stands, and the lead stays unconverted so a retry after the upgrade finds it where it was.

A lead whose person the organization has erased at their request — the erasure pending, or already run and the address captured again since — answers 409 conflict with code: "person_erased". The erasure closed the address to capture, and a conversion creates the contact by capturing it; nothing is changed, and a pending erasure removes the lead itself when it runs. Do not retry: no plan change lifts it.

The conversion is logged on the site's activity feed in the console, attributed to your key by its name — API key Zapier — the way a conversion from the console is attributed to the person who made it.

Errors​

StatustypeWhen
400bad_requestcode: "validation_failed" — a missing or foreign siteId; on a create, a missing or unreadable email, a status other than new or working, or an unqualifiedReason; a phone, mobilePhone, fax or website that cannot be read; a salutation, industry or rating outside its list; a doNotCall that is not a boolean; an annualRevenueCents, currency or numberOfEmployees out of shape; a status outside its list, or qualified; a missing unqualifiedReason on an unqualify, or one sent with another status; an ownerUid who is not a member, an ownerEmail no member has, or both at once; a custom entry that is not a lead field, is retired, or does not fit its type (named as custom.<key>); on a conversion, a company that is not exactly one of link/create, a company.link that does not exist, a company.create.domain that is not a domain, a deal with no title, a fractional deal.amountCents or a malformed deal.currency. On the list, a ?status= outside the four. fields names each key — nested ones as deal.title.
403plan_requiredcode: "crm" — the plan doesn't include the CRM suite. code: "crm_records_quota" — a conversion would create a record past the band on a plan that doesn't meter the overage.
403insufficient_scopeKey lacks crm:read / crm:write.
404not_found"No such lead".
405method_not_allowedAllow: GET, POST on /v1/leads, GET, PATCH on one lead, POST on …/convert.
409conflictcode: "lead_ceiling" — the site is at the platform lead limit; the lead was not created. code: "lead_converted" — a status on a converted lead. code: "lead_not_convertible" — the lead's address cannot become a contact. code: "contact_not_created" — the contact could not be created (the site's audience band may be full); nothing was changed. code: "person_erased" — the lead's person was erased from the organization at their request, or an erasure is pending; no contact can be created for the address, and nothing was changed. code: "pipeline_has_no_stages" — the default pipeline has no stage to open the deal in. code: "idempotency_in_progress".

See Conventions → Errors for the shared envelope.

  • Contacts — what a converted lead becomes; convertedContactId is a contact id.
  • Companies and deals — the other two records a conversion can open.
  • Webhooks — the lead event fires the moment a site captures one, and carries the leadId to read it back with.
  • Leads in the console — the same queue and the same conversion, by hand.