Skip to main content

Keeping an agent inside its budget

The pattern that stops one ambitious prompt spending a month of credits.

AI-native analysts and MCP clientsData and PropTech platforms

The failure this prevents

A user asks an agent for "every apartment project in the thousand largest cities". A naive agent turns that into a thousand metered calls. The API will refuse before it overspends — a paid plan is never partially delivered, so the agent gets a 402 rather than a surprise invoice — but a thousand 402s is a bad afternoon for everyone.

The pattern

  1. 1The agent's first call for any new task is GET /projects/count. It is free, it works at a zero balance, and it returns everything needed to decide: matchCount, billable.credits, affordability.affordable and affordability.maxAffordableRecords.
  2. 2If affordable is false, the agent narrows and counts again rather than attempting the call. Narrowing is free; attempting is a rate-limit slot.
  3. 3When the agent does spend, it reads X-Credits-Remaining off the response rather than maintaining its own counter, because a teammate or another agent may be spending the same pool.
  4. 4On a 402, the agent follows error.details.quote.countUrl — a free URL that prices the exact filter set that just failed — instead of retrying blindly.
  5. 5For questions about shape rather than records, the agent uses the analytics plane, which is free.

Price a broad query — before spending anything

bash
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

json
{
  "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.

Rules of thumb for tool descriptions

  • Tell the model that counting is free and searching is not. Models respect a stated cost asymmetry.
  • Give the model representation: "compact" as its default. It is the same price and roughly a fifth of the fields, which matters for context, not for cost.
  • Never let a model choose a limit above what one task needs. The page ceiling is 100 records; a loop over pages should be a deliberate decision, not a default.
  • Surface meta.warnings to the model. A filter_evidence_unconfirmed warning is why an attribute it filtered on came back empty, and without it the model will report a data bug to its user.

Costs at a glance

Billable — 1 credit per record delivered

MethodPathCostNotes
GET/alerts/{alertId}/matches1 credit / recordRecords already delivered and billed by a scheduled run are re-readable here without a second charge; anything new to you bills once.
POST/alerts/{alertId}/run1 credit / recordA manual run is a real delivery: records new to you bill at the standard rate. Runs are capped per alert per day.
GET/documents/{id}1 credit / recordOne source document, one credit.
POST/projects/ai-search1 credit / recordThe same rate as a structured search. You pay for confirmed matches that are delivered, not for the candidates that were reviewed and rejected.
POST/projects/ai-search/jobs1 credit / recordCredits are reserved up front against the requested limit and settled down to the number of confirmed records actually delivered.
GET/projects/search1 credit / recordCharged for the records actually delivered on the page, after duplicate projects are collapsed and after records you already hold unchanged are excluded. Zero results cost nothing.
GET/projects/sync1 credit / recordOnly records that are new to you, or that changed since you last received them, are billable. A sync page that returns nothing but unchanged records is free.
GET/projects/{id}1 credit / recordOne record, one credit — the same rate as a record inside a search page.

Free — 0 credits

MethodPathCostNotes
GET/account/creditsFreeFree. Your balance, your plan and your rate limit.
GET/alertsFreeFree. Configuring alerts never costs credits; only delivered records do.
POST/alertsFreeFree.
GET/alerts/{alertId}FreeFree.
PATCH/alerts/{alertId}FreeFree.
DELETE/alerts/{alertId}FreeFree.
GET/alerts/{alertId}/deliveriesFreeFree. The delivery history and what each run charged.
GET/alerts/{alertId}/deliveries/{deliveryId}FreeFree.
POST/alerts/{alertId}/dismissals/{projectId}FreeFree. Dismissing a match stops it being re-offered.
DELETE/alerts/{alertId}/dismissals/{projectId}FreeFree.
POST, GET/alerts/{alertId}/previewFreeFree. Preview returns how many records a rule would deliver and what that would cost, not the records themselves.
POST/analytics/aggregateFreeFree. Returns counts and group keys, never a project id, name, address, contact or document.
GET/analytics/datasetsFreeFree. What the analytics plane can and cannot answer, self-described.
POST/analytics/rankingsFreeFree, and subject to the same small-group suppression floor as /aggregate.
GET/healthFreeFree and unauthenticated.
GET/locations/cities/{id}FreeFree.
GET/locations/counties/{id}FreeFree.
GET/locations/statesFreeFree. Resolve the ids you filter with.
GET/locations/states/{id}FreeFree.
GET/locations/states/{id}/countiesFreeFree.
GET/locations/states/{stateId}/counties/{countyId}/citiesFreeFree.
GET/projects/ai-search/jobs/{id}FreePolling is free. The records the job delivers were charged when the job settled, not when you read them.
GET/projects/countFreeFree, always, including when your balance is zero. This is the endpoint that tells you what a query would cost before you buy it.
GET/taxonomyFreeFree. The complete land use taxonomy tree.
GET/taxonomy/action-categoriesFreeFree.
GET/taxonomy/action-typesFreeFree.
GET/taxonomy/asset-classesFreeFree.
GET/taxonomy/project-typesFreeFree.
GET/taxonomy/subtypesFreeFree.

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.