Contact enrichment
Ask for contacts alongside your records. Same 1 credit per record, whether we found a contact or not.
Who this is for
Lead-gen teams who need a person to contact, not just a project to read about.
The pricing rule, first, because it is the question everyone asks
How to ask
Enrichment is a per-request toggle, not an account setting. Add include=contacts to a request that can run asynchronously. It calls out to third-party sources and cannot complete inside a synchronous request, so a synchronous route returns 400 enrichment_requires_async and names the endpoint to use.
The same search, with contact enrichment switched on
curl -X POST 'https://api.boardwalkai.com/api/v1/projects/ai-search/jobs' \
-H 'Authorization: Bearer bwk_live_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"query": "multifamily projects that were continued for parking concerns",
"states": [
"UT"
],
"limit": 50,
"include": "contacts",
"contactOptions": {
"minConfidence": "high"
}
}'Response · HTTP 202
{
"requestId": 918509,
"status": "pending",
"pollUrl": "/api/v1/projects/ai-search/jobs/918509",
"meta": {
"creditsReserved": 50,
"limit": 50,
"trialTruncation": null
}
}The response shape on this page is the one the endpoint builds — the keys, their nesting and their types are asserted against the shipping code by a test. The numbers inside it are the scenario being walked through, not a measurement of the corpus.
| Option | Values | Effect |
|---|---|---|
contactOptions.minConfidence | high, medium | The floor for a contact to ship. Defaults to medium. There is no low — a contact we are not reasonably confident about is withheld rather than labelled, and contactSummary.lowConfidenceWithheld counts them. |
contactOptions.cachedOnly | boolean | Only return contacts we already hold. No new lookups. |
contactOptions.companyOnly | boolean | Company-level contacts only. |
contactOptions.maxProjects | positive integer | Cap how many of the returned projects are enriched. |
An unknown key or an unknown value is a 400 that costs nothing, before anything runs. Sending cachedonly instead of cachedOnly fails loudly rather than quietly running the most expensive path on the platform.
Reading the result
Every record carries a contactEnrichment block whether or not you asked for enrichment, so clients branch on its value rather than its existence:
contactEnrichment.status | Meaning |
|---|---|
not_requested | You did not ask. The contacts array still carries anyone named on the filing itself. |
pending | Requested, not finished. |
completed | We looked. contactsFound says what we found — including zero. |
failed | The lookup could not be completed. You were still charged 1 credit for the record, which you received. |
Contacts carry their own provenance. contactProvenance: "filing" means the person was named in the public record itself; an enriched channel names its source and the time it was established, and each channel carries its own confidence.
The rules that do not bend
- Government staff contact details are never returned, on any tier. Planners, clerks and commissioners appear in
publicOfficialswith their name, title and organisation, and no contact channel. This is not a plan limitation and there is no tier that unlocks it. - Free-trial keys do not receive external contact channels. Names, titles, roles and companies still ship, with
contactsWithheldReasonstating why the channel is absent. - Suppressed contacts are removed everywhere. Anyone can ask us to stop distributing their contact details, and once suppressed they do not ship to anyone.
Next
Ready to make a call?
A free Boardwalk trial includes API access and a sandbox key. Counting, filtering, the location tree and the analytics plane cost nothing, so you can evaluate the data before you spend a credit.