Budgets, quotes and the 402
Price any request for free before you run it, and know exactly what a request you cannot afford returns.
Boardwalk never overshoots your balance and never partially delivers a paid request. That guarantee is only useful if you can see it coming, so the same quote arithmetic is available for free before the request and is attached to the refusal after it.
The free quote
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.
Reading a quote
| Field | What it tells you |
|---|---|
matchCount | How many distinct projects match. Post-collapse, so it counts developments, not filings. |
matchCountIsExact | false when the scan ceiling was reached and the true total is higher. |
billable.records | How many of those you would actually be charged for. Lower than matchCount when you already hold some unchanged. |
billable.credits | The cost. records × creditRate. |
billable.isUpperBound | Always true, and it is a promise: the real charge can only be lower. |
affordability.affordable | Whether the balance covers it. |
affordability.shortfallCredits | How many more you would need. |
affordability.maxAffordableRecords | How many records the balance can pay for right now. |
truncation.policy | refuse on a paid plan, trial on a trial key. This is what would happen if you called the metered endpoint anyway. |
ordering | The sort that would be applied, so "the first 500" is a defined set rather than an arbitrary one. |
The refusal
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.
error.details.quote is the same arithmetic as the count endpoint — one object, built once, so the number you narrow against and the number a refusal names cannot disagree. countUrl is a free URL that prices this exact filter set, which is what lets an agent recover without a human.
Why not just deliver what I can afford?
Because a short page is indistinguishable from the end of the results. A customer who receives 500 of 5,000 records with no signal concludes our coverage is thin, builds on a partial dataset, and finds out months later. Refusing is louder and cheaper for everyone. Trial keys are the exception, and they say so explicitly in meta.trialTruncation.
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.