Skip to main content

The API-Only playbook

The whole loop — count, buy, read back, enrich, monitor — in the order to run it, with every default that can silently cost money named up front.

Lead generation and business developmentHomebuilders and developers

Who this is for

Teams on the API Only plan (500 credits a month, nationwide) integrating the full loop: find net-new projects that match a thesis, buy them once, enrich them free, and keep watching without paying twice. Every rule below exists because skipping it either overspends the pool or silently narrows your results.

The loop at a glance

  1. 1Price the filter set with the free GET /projects/count.
  2. 2Buy one page at a time with GET /projects/search, sending an explicit limit.
  3. 3On repeat pulls, add deliveryMode=new so records you already own are withheld, never re-billed.
  4. 4Read owned records back by id with the projectIds filter — free while unchanged.
  5. 5Enrich what you bought with POST /enrichments — free, on the monthly allowance.
  6. 6Put the concept-shaped half of the thesis on AI-search jobs and a natural-language alert.

Rule 1 — count first, always

GET /projects/count prices the whole match set for zero credits, even at a zero balance. The search page default is limit=100 — up to 100 credits, 20% of the month, for one curious call. Never send a metered request whose filter set you have not counted, and always send an explicit limit.

Credit costFree — 0 creditsFree, always, including when your balance is zero. This is the endpoint that tells you what a query would cost before you buy it.

Rule 2 — send only the narrowest taxonomy value

bash
# RIGHT: the narrowest value alone
GET /projects/count?propertySubtypes=Single Family&states=AZ,TX&dateRange=all

# WRONG: adding the parent broadens the query and bills more
GET /projects/count?propertyTypes=Residential&propertySubtypes=Single Family&states=AZ,TX&dateRange=all
# → meta.warnings[].code: taxonomy_filter_rolled_up
Taxonomy levels OR-join with each other. Parent plus child is the union, not the intersection.

propertyTypes, propertySubtypes and assetClasses are OR-joined with each other, then ANDed with everything else. Sending Residential beside Single Family therefore rolls up to all of Residential — a larger, more expensive set — and the response says so with a taxonomy_filter_rolled_up warning. Send the narrowest value alone.

Rule 3 — dates are never optional

You wantSendIf you omit it
Everything ever recordeddateRange=allA silent last-12-months window is applied (default_date_window_applied warning).
Calendar 2026meetingDateFrom=2026&meetingDateTo=2026dateRange=2026 is not a token and returns 400 — year and month values belong on the explicit bounds, which snap to the start and end of the named period.
A rolling windowdateRange=last_3_months / last_6_months / last_1_year

Rule 4 — repeat pulls use deliveryMode=new

A record you already own, unchanged, is never billed again on any route — but with the default deliveryMode=all it still comes back and occupies the page. On every scheduled re-run, add deliveryMode=new: records the pool already owns are withheld (disclosed as meta.delivery.excludedPreviouslyDelivered) and the page carries only net-new leads. The full net-new story is here.

Rule 5 — acreage filters see only extracted acreage

acreageMin/acreageMax match only records whose public filing yielded an acreage number — measured at roughly 41% of the corpus (2026-08). A masterplan whose minutes never state the acreage is invisible to an acreage bound, no matter how large it is. For a "very large projects" thesis, run the union: the acreage-bounded pull and a projectSizes=large pull, both with deliveryMode=new so the overlap dedupes itself — a record bought by the first pull is withheld from the second, never billed twice.

bash
# Pull 1: explicit acreage
GET /projects/search?propertySubtypes=Single Family&acreageMin=200&deliveryMode=new&dateRange=all&limit=50

# Pull 2: the size classification catches what pull 1 cannot see
GET /projects/search?propertySubtypes=Single Family&projectSizes=large&deliveryMode=new&dateRange=all&limit=50
Union then dedupe, with deliveryMode=new doing the deduping for free.

Read back what you own — by id, free

The projectIds filter (alias ids, up to 100 distinct ids per request, on search, count and sync) is the canonical way to re-read purchased records: send the ids you bought and the page comes back at 0 credits while the records are unchanged. Add include=contacts with contactOptions={"cachedOnly":true} to carry the enriched contacts on the same free read.

bash
GET /projects/search?projectIds=10754108,10754109,10754110&include=contacts&contactOptions={"cachedOnly":true}
# → meta.creditsUsed: 0 while every record is owned and unchanged
The read-back loop. Owned and unchanged means free, contacts included.

The enrichment loop

Enriching projects you already bought is free — it draws on a monthly allowance of 500 attempts (calendar-month reset) instead of the credit pool. POST /enrichments requires an Idempotency-Key (400 without one; a replayed key answers with the job it already created). Enrichment runs about a minute per lead at roughly two leads per project — budget about 2 minutes per project and poll GET /enrichments/{id} rather than waiting on estimatedCompletion, which is a polling hint, not a deadline. On the poll, found counts the projects where contact info was found — that is the yield number. The full recipe is here.

The concept-shaped half: AI-search jobs

  • Async only — POST /projects/ai-search/jobs, then poll. The job reserves limit × 1 credits until settlement, so submit modest limits, serially: jobs are processed in series, and ten queued 100-record jobs hold 1,000 credits while they wait.
  • partial is a normal terminus — real, billed, verified records that stopped early for a stated reason. Unused reservation is refunded.
  • Results expire 72 hours after completion — poll promptly.
  • A settlement email (counts, credits, poll URL — never record data) goes to the submitter unless you opt out with notify: {"email": false}.
  • Changed your mind? DELETE /projects/ai-search/jobs/{id} — free; a queued job refunds in full, a running one stops at the next batch boundary and keeps only what it confirmed.
  • Phrase the prompt as a concept, not an amenity list — an enumerated-constraint prompt is refused pre-spend. Phrasing guidance and the pass/fail pair.

Standing queries: natural-language alerts

  • specificRequirement (up to 500 characters) puts the concept on a cadence — the same evidence-verified matcher as the jobs.
  • A nationwide alert (no geography at all) is allowed on the API Only plan — its licensed geography is national.
  • Manual runs (POST /alerts/{alertId}/run) require an Idempotency-Key and are capped at 3 per alert per calendar day (the service clock) — the next attempt is 429 manual_runs_exhausted. Replays return the original delivery free and do not spend an attempt.
  • The alert email is a pointer, not the payload — the records come from /alerts/{alertId}/matches, where NL matches carry whyThisMatched and verbatim matchEvidence.

The month's arithmetic

  • 500 credits a month, resetting on the calendar 1st — not on your subscription anniversary. No overage: at the cap, metered requests are refused with 402, never silently billed beyond it.
  • Every 402 insufficient_credits carries a quote: error.details.quote.affordableRecordCount is the limit to retry with, and quote.countUrl re-prices the exact filter set for free.
  • Pages and records that bill 0 credits are served even at a 0 balance — read-backs of owned unchanged records (cached contacts included) keep working after the pool empties. 402 credits_exhausted fires only when the response would actually bill.
  • The pool is shared across every surface and teammate — read X-Credits-Remaining off responses rather than keeping a local counter.

Next

Ready to make a call?

A free Boardwalk trial includes API access and a sandbox key. Counting, taxonomy, location lookups and the analytics plane cost nothing, so you can evaluate the data before you spend a credit.