Count before you buy
Price any query for zero credits, and see exactly what a request you cannot afford returns.
Who this is for
Everyone. This is not an advanced technique — it is how the product works, and the first thing any client, human or agent, should do with a new filter set.
The scenario
You have 500 credits left. Your criteria match 5,000 projects. What happens?
Step 1 — ask, for free
Price a broad query — before spending anything
curl 'https://api.boardwalkai.com/api/v1/projects/count?states=UT&assetClasses=Apartment%20Building&dateRange=all' \
-H 'Authorization: Bearer bwk_live_YOUR_KEY'Response · HTTP 200
{
"data": {
"matchCount": 5000,
"matchCountIsExact": true,
"matchCountBasis": "enumerated",
"countedAs": "distinct_projects",
"scanCeiling": 10000,
"billable": {
"records": 5000,
"credits": 5000,
"creditRate": 1,
"isUpperBound": true,
"nonBillable": {
"alreadyDeliveredUnchanged": 0,
"ledgerApplied": true
}
},
"affordability": {
"affordable": false,
"blockReason": "insufficient_credits",
"shortfallCredits": 4500,
"maxAffordableRecords": 500,
"boundBy": "period_balance",
"unlimited": false
},
"ordering": {
"sort": "meetingDate",
"order": "desc",
"tiebreak": null,
"meaning": "Most recent meeting evidence first — the date of the meeting the record was extracted from, not when Boardwalk ingested it and not when the project was created."
},
"truncation": {
"policy": "refuse",
"wouldTruncate": false,
"deliverableRecords": null,
"explanation": "Paid plans are never partially delivered. A request that costs more than the balance is refused with 402 and a quote; narrow the filters or top up."
}
},
"meta": {
"schemaVersion": "2026-08-02",
"representation": "standard",
"deliveryMode": "all",
"credits": {
"limit": 500,
"used": 0,
"remaining": 500,
"charged": 0,
"periodStart": "2026-08-01",
"periodEnd": "2026-08-31",
"isTeamPool": true
},
"appliedFilters": {
"states": [
44
],
"assetClasses": [
"Apartment Building"
],
"dateRange": "all"
},
"appliedDefaults": [],
"resolvedFrom": {
"states": {
"UT": 44
}
},
"warnings": [
{
"code": "filter_evidence_unconfirmed",
"message": "These filters currently match our full index. A returned record with a null value for the attribute you filtered on is a record we could not confirm.",
"params": [
"assetClasses"
]
}
]
},
"links": {
"self": "https://api.boardwalkai.com/api/v1/projects/count?states=UT&assetClasses=Apartment%20Building&dateRange=all",
"search": "https://api.boardwalkai.com/api/v1/projects/search?states=UT&assetClasses=Apartment%20Building&dateRange=all",
"docs": "https://boardwalkai.com/docs/api/budgets/"
}
}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.
Read data.affordability. affordable: false, shortfallCredits: 4500, maxAffordableRecords: 500. And read data.truncation.policy: refuse. That is the answer to "what would happen if I just called search" — you would get a 402, not 500 of the 5,000 records.
Step 2 — see what refusal actually looks like
If you call the metered endpoint anyway, this is the entire outcome. No records, no partial page, no charge:
What a request you cannot afford looks like
curl 'https://api.boardwalkai.com/api/v1/projects/search?states=UT&assetClasses=Apartment%20Building&dateRange=all&limit=100' \
-H 'Authorization: Bearer bwk_live_YOUR_KEY'Response · HTTP 402
{
"error": {
"code": "insufficient_credits",
"message": "This request would deliver 5,000 records and cost 5,000 credits. You have 500.",
"param": null,
"details": {
"quote": {
"matchCount": 5000,
"matchCountIsExact": true,
"creditRate": 1,
"creditsRequired": 5000,
"creditsRemaining": 500,
"affordableRecordCount": 500,
"countUrl": "https://api.boardwalkai.com/api/v1/projects/count?states=UT&assetClasses=Apartment%20Building&dateRange=all"
}
},
"docsUrl": "https://boardwalkai.com/docs/api/errors#insufficient_credits",
"requestId": "req_01K1QF3M0000EXAMPLE0001"
}
}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.
The countUrl in the quote is the free URL that prices this exact filter set. An agent that receives this response can recover on its own — it does not need a human to interpret a number.
Step 3 — narrow, and check again
Add one filter and the same query becomes affordable
curl 'https://api.boardwalkai.com/api/v1/projects/count?states=UT&assetClasses=Apartment%20Building&meetingDateFrom=2026-05-01&totalUnitCountMin=50' \
-H 'Authorization: Bearer bwk_live_YOUR_KEY'Response · HTTP 200
{
"data": {
"matchCount": 250,
"matchCountIsExact": true,
"matchCountBasis": "enumerated",
"countedAs": "distinct_projects",
"scanCeiling": 10000,
"billable": {
"records": 250,
"credits": 250,
"creditRate": 1,
"isUpperBound": true,
"nonBillable": {
"alreadyDeliveredUnchanged": 0,
"ledgerApplied": true
}
},
"affordability": {
"affordable": true,
"blockReason": null,
"shortfallCredits": 0,
"maxAffordableRecords": 500,
"boundBy": "period_balance",
"unlimited": false
},
"ordering": {
"sort": "meetingDate",
"order": "desc",
"tiebreak": null,
"meaning": "Most recent meeting evidence first — the date of the meeting the record was extracted from, not when Boardwalk ingested it and not when the project was created."
},
"truncation": {
"policy": "refuse",
"wouldTruncate": false,
"deliverableRecords": null,
"explanation": "This request fits inside the remaining balance, so every matching record is delivered."
}
},
"meta": {
"schemaVersion": "2026-08-02",
"representation": "standard",
"deliveryMode": "all",
"credits": {
"limit": 500,
"used": 0,
"remaining": 500,
"charged": 0,
"periodStart": "2026-08-01",
"periodEnd": "2026-08-31",
"isTeamPool": true
},
"appliedFilters": {
"states": [
44
],
"assetClasses": [
"Apartment Building"
],
"meetingDateFrom": "2026-05-01T00:00:00+00:00",
"totalUnitCountMin": 50
},
"appliedDefaults": [],
"resolvedFrom": {
"states": {
"UT": 44
}
},
"warnings": [
{
"code": "filter_evidence_unconfirmed",
"message": "These filters currently match our full index. A returned record with a null value for the attribute you filtered on is a record we could not confirm.",
"params": [
"assetClasses",
"totalUnitCountMin"
]
}
]
},
"links": {
"self": "https://api.boardwalkai.com/api/v1/projects/count?states=UT&assetClasses=Apartment%20Building&meetingDateFrom=2026-05-01&totalUnitCountMin=50",
"search": "https://api.boardwalkai.com/api/v1/projects/search?states=UT&assetClasses=Apartment%20Building&meetingDateFrom=2026-05-01&totalUnitCountMin=50",
"docs": "https://boardwalkai.com/docs/api/budgets/"
}
}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.
250 records, 250 credits, affordable: true, shortfallCredits: 0. Now the metered call will succeed and will cost exactly what the quote said, or less.
What it costs
Counting is free even when your balance is zero. Telling a customer who has run out that they may not ask what something would cost is the opposite of the point.
The pattern to build
- 1Build the filter set.
- 2
GET /projects/countwith it. Zero credits. - 3If
affordability.affordableis false, narrow — add a date bound, a smaller geography, a minimum unit count — and count again. - 4When it is affordable, send the identical filter set to
GET /projects/search. Thelinks.searchfield in the count response is that exact URL, already built.
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.