curl --request POST \
--url https://devapi.prov.ae/v2/public/leads \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"leadPhones": [
"+971501234567"
],
"leadFirstName": "John",
"leadLastName": "Doe"
}
'{
"id": "a1b2c3d4-5566-7788-99aa-bbccddeeff00",
"merged": false,
"contactId": "0f1e2d3c-4455-6677-8899-aabbccddeeff",
"intakeUnresolved": {
"advertisingCampaign": "Google Ads — Q3"
},
"needsIntakeReview": true,
"isRepeatLead": false,
"repeatOfLeadId": null,
"isAgentEnquiry": false,
"agentEnquiryReason": null,
"createdAt": "2026-07-27T06:31:04.512Z",
"assignedTo": null
}Create a CRM lead
Creates a lead in the Provident CRM and attaches it to a contact.
Only hard requirement: at least one entry in leadPhones or leadEmails.
Contact resolution — an existing contact is matched by phone first, then by email; if nothing matches, a new contact is created from the supplied name/company/phone/email fields. Send phone numbers in E.164 (+971501234567) for reliable matching.
Lookup fields (leadType, funnel, source, subSource, eventType, marketingType, campaignName, advertisingCampaign, website, currency, localeCountry, areaOfInterest, propertyTypeInterest, marketSegment, agentCategory, languages, assignedBy, distributionType, listingId/referenceNo) are matched case-insensitively after trimming against lists configured in the CRM. An unmatched value never fails the request — the lead is created and the original text is returned in intakeUnresolved.
Which pipeline the lead lands in is funnel. Omit it and the lead goes to the main sales pipeline, which is where every lead posted to this API went before the field existed; send Leasing, Show Room, PvH, … for an enquiry that belongs to another business line.
Extra fields not defined in this schema are accepted and stored verbatim with the lead rather than rejected.
Not idempotent: the same payload posted twice creates two leads.
Quiz answers — landing pages that ask a question set can send a quiz object. Its questions and options register themselves on first sight, so a new quiz needs no API change. Send a stable questionKey per question and optionKey per dropdown answer: those are the only language-independent identity an answer has, and without them each language registers as a separate question. Free-text numbers are parsed best-effort (2 million, 2,000,000, AED 2M, ٢ مليون → 2000000); ambiguous dates like 03/04/2026 are deliberately refused rather than guessed. Anything unreadable is kept verbatim and flagged, never dropped.
curl --request POST \
--url https://devapi.prov.ae/v2/public/leads \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"leadPhones": [
"+971501234567"
],
"leadFirstName": "John",
"leadLastName": "Doe"
}
'{
"id": "a1b2c3d4-5566-7788-99aa-bbccddeeff00",
"merged": false,
"contactId": "0f1e2d3c-4455-6677-8899-aabbccddeeff",
"intakeUnresolved": {
"advertisingCampaign": "Google Ads — Q3"
},
"needsIntakeReview": true,
"isRepeatLead": false,
"repeatOfLeadId": null,
"isAgentEnquiry": false,
"agentEnquiryReason": null,
"createdAt": "2026-07-27T06:31:04.512Z",
"assignedTo": null
}Authorizations
The access_token returned by POST /oauth2/token, sent as Authorization: Bearer <access_token>. It is an opaque string, not a JWT.
Body
Lead intake payload. At least one entry in leadPhones or leadEmails is required; every other field is optional. Unknown fields are accepted and stored verbatim with the lead.
Phone numbers. Used FIRST to match an existing contact. Use E.164 format for reliable matching. Required unless leadEmails is supplied.
64["+971501234567"]
Email addresses. Used to match an existing contact when no phone matches. Required unless leadPhones is supplied.
255["john.doe@example.com"]
255"John"
255"Ahmad"
Second given name — used when no middle name is supplied.
255"A."
255"Doe"
Company name for corporate enquiries.
255"Acme Real Estate"
LOOKUP. Lead type display name, e.g. Primary Buyer, Secondary Buyer, Primary Buyer and Secondary Buyer, Tenant, Landlord, Broker, Seller, Owner, Mortgages. Ask Provident for the full list. The shorthand Primary / Secondary / Primary and Secondary is accepted as an alias for the corresponding … Buyer type, in either direction. Omit this on a listing enquiry and it is derived from the listing — see listingId.
255"Primary Buyer"
LOOKUP. Which CRM funnel (pipeline) the enquiry belongs to. OMIT IT for the main sales pipeline — that is the default and what almost every lead wants. Send a value only for an enquiry that belongs to a different business line: Leasing, Show Room, PvH, Mortgages, Property Management, Landlord/Sellers Listings, Provident the Agency, Events, Recruitment, Precision Inspections, 811 services, Prism. The display name or the stable key both work (Show Room and show_room are the same funnel), matched case-insensitively and ignoring spaces, hyphens and underscores. Deliberately NOT derived from leadType: a Tenant enquiry can belong to Leasing or PvH and a Primary Buyer to sales, Show Room or the Agency — only the sender knows which. An unrecognised funnel does not fail the request: the lead is created on the main sales pipeline and the value is returned in intakeUnresolved.funnel.
255"Leasing"
Free text priority indicator.
64"High"
LOOKUP. Marketing's segmentation of the lead.
Standard, Luxury, Super Luxury 255"Luxury"
LOOKUP. Marketing's grading of the lead.
A, B, C, D 255"A"
LOOKUP. Location the lead is interested in.
"Dubai Marina"
LOOKUP. Property type the lead is interested in. Matched case-insensitively after trimming, and a regular English plural also matches the singular catalogue entry (Apartments → Apartment, Townhouses → Townhouse), so a form offering plural choices needs no mapping on your side.
255"Apartment"
Minimum budget. Numeric string; a JSON number is also accepted.
"1000000"
Maximum budget. Numeric string; a JSON number is also accepted.
"2500000"
LOOKUP. ISO 4217 currency code for the budget values.
3"AED"
Free-text message or callback reason from the form.
"Looking for a 2BR with sea view, ready to move in Q4."
LOOKUP. Languages the lead speaks, by code or name. Unrecognised entries are ignored.
255["ar", "English"]
LOOKUP. Provident listing UUID. Takes precedence over referenceNo when both are sent.
A resolved listing also fills in what the form did not ask. Each field below is taken from the listing ONLY where this payload leaves it empty — anything you send always wins, and a quiz answer mapped to the same field beats the listing too:
leadType— from the listing category and offering type: Primary -> Primary Buyer, Secondary -> Secondary Buyer, and any rental listing -> Tenant whatever its category.- developer, location, property type, bedrooms, currency — copied from the listing. The location fills the lead's own location and does not overwrite
areaOfInterest. budgetMin— the listing price, unless the price is on application or it would exceed abudgetMaxyou sent.budgetMaxis never derived.
Sending a value we then fail to match (an unknown developer name, say) is still reported in intakeUnresolved even though the listing goes on to fill that field.
The listing's own agent, where it has one, becomes the lead owner and wins over assignedBy.
LOOKUP. Listing reference number, used when listingId is not supplied. A reference number matching MORE THAN ONE listing counts as unresolved, and nothing is inherited. See listingId for what a resolved listing fills in.
128"PR-123456"
LOOKUP. Lead source. Send this — an unrecognised or omitted source leaves the lead with no source attribution.
255"Website"
LOOKUP. Sub-sources. Only those linked to the resolved source are kept; ignored entirely when source did not resolve.
255["Callback Form", "Team Page"]
LOOKUP. Marketing type, e.g. Organic or Paid.
255"Organic"
LOOKUP. What the user did — e.g. Submit Form, Call, Whatsapp Click, DM, Webpush, Gamification.
255"Submit Form"
LOOKUP. CRM campaign name.
255"Spring 2026 Landing Page"
CREATED ON FIRST SIGHT. Advertising campaign name — the one field here that is not a strict lookup: a name Provident has never seen creates the campaign instead of being discarded. Omit it when you send metaFacebook; the Meta campaign fills it.
255"Google Ads — Q3"
LOOKUP. Developer the enquiry is about, matched on name or slug — Sobha Realty or sobha-realty. Values come from GET /public/developers.
255"Sobha Realty"
Developer uuid from GET /public/developers. Takes precedence over developerName; a non-uuid value here is treated as a name instead of being discarded.
"8ecf4fcd-ec63-5764-8524-7cd917494dbe"
LOOKUP. Website NAME or DOMAIN the lead came from — either resolves, matched case-insensitively. Prefer the domain (provident.ae, providentestate.com); it survives a display-name change. Ask Provident to add your domain before you start sending it.
255"provident.ae"
Full URL of the page the submission originated from.
"https://prov.ae/en/dubai-marina?utm_source=google"
Locale segment of the URL path, e.g. en.
32"en"
Google Ads click id.
512Facebook click id.
512512"google"
512"cpc"
512"spring-2026"
512"dubai marina apartments"
512"hero-cta"
Name of the web form the visitor submitted. Falls back to metaFacebook.formName (truncated to 255) when omitted.
255"contact-popup-form"
Ad set / ad group name on your side. Free text — distinct from the campaignName lookup. Falls back to metaFacebook.adgroupName (truncated to 255) when omitted.
255"test ww"
Referring URL the visitor arrived from.
"https://sobha-city.provident.ae/?utm_source=Google+Ads"
Submitter IP. IPv4, IPv6 and proxy chains all fit.
64"217.165.113.16"
Browser user-agent string captured at submission.
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
When the visitor submitted the form on your side. Distinct from the CRM's own createdAt, which is when we received it. An unparseable value is ignored rather than failing the request.
"2026-07-27T13:45:31.728Z"
LOOKUP. Country code (e.g. AE) or full country name. When omitted, the API attempts to infer it from a trailing two-letter segment of pageUrl.
255"AE"
LOOKUP. Assign the lead to a specific Provident agent by email address. Omit to let Provident's automatic assignment engine route the lead. When the lead also resolves a listing that has an agent of its own, the listing's agent wins.
255"agent@providentestate.com"
How you would like this lead routed. This is a HINT, not an instruction: the value is stored on the lead and only affects who receives it if Provident has configured a routing rule that claims it. A value no rule claims changes nothing, so sending one is always safe.
Where a rule does claim the value it OUTRANKS the normal rule order: a lead sent as Location distribution that also carries a campaign is routed by the location rule, not by the campaign rule that would otherwise have matched first. Which types are claimed is configuration on our side and changes — ask before relying on it.
Matched case-insensitively after trimming, on both sides of the comparison, so capitalisation never decides routing; the leading word on its own also works (campaign, personal, developer, language, location, default, roadshow).
An unrecognised value is stored exactly as sent rather than rejected — a lead is never lost over this field, it simply matches no rule.
Personal distribution carries one extra meaning: it marks the lead as having a named owner, and such a lead is never re-assigned automatically and runs no follow-up clock at all. It only takes effect together with a resolved assignedBy — the two fields are one instruction. If assignedBy is missing or names nobody we can match, the lead is still created and routed normally but the marker is dropped, and both values come back in intakeUnresolved.
Omit it and a Meta lead form can carry it instead. A Distribution or Distribution Type entry in metaFacebook.customFields is read as a fallback and normalised the same way, because the value is usually set by whoever built the ad form rather than by whoever posts the lead. A top-level distributionType always wins.
Personal distribution, Campaign distribution, Developer distribution, Language distribution, Location distribution, Default distribution, Developer and Area distribution, Area distribution Secondary, Roadshow distribution 64"Campaign distribution"
Meta / Facebook / Instagram (or TikTok) lead-ads metadata. Send only for leads originating from an ad-platform lead form. The standard fields above still drive routing.
Stored alongside the lead AND copied onto it: metaCampaignId + metaCampaignName become the lead's advertising campaign (matched on the Meta campaign id, and created if Provident has never seen it, so campaign names need no prior agreement); finalPageName, adName/adId, adgroupName/adgroupId and formName/formId become filterable columns on the lead. Matching on the id is what makes a rename safe — rename a campaign in Ads Manager and its leads stay on one campaign in the CRM. campaignName is a different, strictly-matched field: the CRM's own campaign, which drives routing.
The whole object is discarded when metaLeadId is absent. metaLeadId is unique across the CRM: it prevents the metadata being attached twice, but it does NOT prevent a duplicate lead — de-duplicate on your side.
Show child attributes
Show child attributes
A dynamic quiz / survey submitted with the lead — for landing pages that ask a set of questions. Unlike every other field in this API, the questions do NOT have to be agreed with Provident in advance: questions and options register themselves the first time they arrive, so a new quiz starts capturing answers with no API change on either side. Answers are searchable in the CRM, shown on the lead, and — for questions Provident maps to a CRM field — used to fill that field on the lead itself.
Show child attributes
Show child attributes
Your handle for the specific automation behind this lead — a Make scenario name, a webhook id, a form build. Free text: stored verbatim, never matched against anything, and never reported in intakeUnresolved.
It identifies an INTEGRATION, not a submission, so keep it stable across every lead that automation sends; a per-lead unique id makes it useless for grouping. Values are only distinguished within one sender, so two partners can both use scenario-1.
Do NOT put your own name here. The submitting OAuth client is recorded automatically from the access token — there is no field for it, because attribution a sender can write is not attribution.
128"make:meta-leadgen-eu"
Response
Accepted. EITHER a new lead (CreatedLead) OR an enquiry merged into an existing one (MergedLead) — branch on merged before reading any other field. A CreatedLead body carries many further fields that are not part of this contract.
- Option 1
- Option 2
A newly created lead (merged is false). The response actually carries the whole lead record; only the properties listed here are contractual and safe to depend on.
UUID of the created lead. Store this against your own submission record.
UUID of the contact the lead was attached to (matched or newly created).
Map of field name → the original value you sent, for every lookup value that could not be matched. null when everything resolved. Quiz answers that could not be read appear namespaced as quiz.<quizKey>.<questionKey> — unlike a failed lookup, the answer IS still stored on the lead, and a Provident admin can map the value once so every future submission of it resolves automatically.
Show child attributes
Show child attributes
{
"source": "Web-Site",
"quiz.off-plan-investor-2026.free_budget": "somewhere around a lot"
}
true when intakeUnresolved is non-empty.
Always false here. true means the enquiry was folded into an existing lead — see MergedLead.
false True when this person already had a lead older than the merge window. The lead is routed and worked normally; this is a marker, not a routing instruction.
The earlier lead's id when isRepeatLead is true, otherwise null.
True when the enquiry was recognised as coming from a competing agent rather than a buyer. The lead is still created and worked; the flag keeps it out of lead-volume and cost-per-lead reporting.
Why, when isAgentEnquiry is true; null otherwise.
known_agent, prior_lead, competitor_domain, manual, null The agent the lead landed on. NULL means 'not decided yet' — the routing engine runs after this response is sent — NOT 'nobody'. Reliable only when you sent referenceNo/listingId or assignedBy; otherwise use GET /public/leads/{id}/assignment or the lead.assigned webhook.
Show child attributes
Show child attributes