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.
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
- 1Price the filter set with the free
GET /projects/count. - 2Buy one page at a time with
GET /projects/search, sending an explicitlimit. - 3On repeat pulls, add
deliveryMode=newso records you already own are withheld, never re-billed. - 4Read owned records back by id with the
projectIdsfilter — free while unchanged. - 5Enrich what you bought with
POST /enrichments— free, on the monthly allowance. - 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.
Rule 2 — send only the narrowest taxonomy value
# 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_uppropertyTypes, 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 want | Send | If you omit it |
|---|---|---|
| Everything ever recorded | dateRange=all | A silent last-12-months window is applied (default_date_window_applied warning). |
| Calendar 2026 | meetingDateFrom=2026&meetingDateTo=2026 | dateRange=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 window | dateRange=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.
# 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=50Read 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.
GET /projects/search?projectIds=10754108,10754109,10754110&include=contacts&contactOptions={"cachedOnly":true}
# → meta.creditsUsed: 0 while every record is owned and unchangedThe 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 reserveslimit× 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. partialis 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 anIdempotency-Keyand are capped at 3 per alert per calendar day (the service clock) — the next attempt is429 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 carrywhyThisMatchedand verbatimmatchEvidence.
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_creditscarries a quote:error.details.quote.affordableRecordCountis thelimitto retry with, andquote.countUrlre-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_exhaustedfires only when the response would actually bill. - The pool is shared across every surface and teammate — read
X-Credits-Remainingoff 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.