Skip to main content

Pagination

Offset paging with a hard ceiling, and the cursor-based sync feed for when you need every record.

Data and PropTech platformsHomebuilders and developers

GET /projects/search pages with limit and offset. limit is the page size per request (integer from 1 to 100) — not a hard ceiling on how many rows you can retrieve overall. Values below 1 or non-integers return 400 invalid_request (same posture as an unknown representation). Asking for more than 100 is clamped to 100 and the response says so in meta.warnings rather than failing. Keep paging with offset (credits still apply per returned row). offset must be a non-negative integer — negative values return 400 invalid_request (they are not silently floored to 0). You can also page with page (1-based); when offset is absent it becomes (page - 1) * limit.

CSV output

Default response format is JSON. Pass format=csv to queue a product-core CSV export for the current page of results. That is the same single-value column set as the map Export CSV button (Project ID, name, status, location, taxonomy, dates, Boardwalk link, …). Multi-value and contact groups are not included on the API path, so the file stays spreadsheet-safe.

CSV does not stream in the search response body. You get HTTP 202 with exportJobId, pollUrl (GET /api/v1/exports/{id}), downloadUrl, and webExportsUrl (/exports in the product UI). Credits are reserved on the export job (1 per row), same as a web export. Re-download of a ready file is free. If every match on the page was already delivered and unchanged, no job is queued: the response status is no_billable_rows (not pending) with meta.freeRedeliveryExcluded explaining why.

Know when to continue

meta.pageCount is the number of records in this response and meta.hasMore tells you whether another offset page is retrievable. Continue by adding pageCount to your current offset while hasMore is true. meta.total is a deprecated compatibility field containing offset + pageCount; despite its old name, it is not a total match count.

When you need the size of the whole selection, call the free GET /projects/count route with the same filters. On a search response, the matchCount, matchCountIsExact and matchCountBasis fields describe only what that request's bounded candidate scan could prove. Count on a large state with dateRange=all can take several seconds under load — fine for a one-shot quote, but cache identical queries in agent loops rather than re-hitting count on every page.

The honest ceiling

In practice this only bites on very broad queries. The answer is not deeper paging, it is narrowing — by geography, by date window, or by a measurement bound — and counting each narrowed variant for free until the total is one you can actually retrieve.

When you need every record: use sync

Offset paging over a changing dataset can skip records: if a record is updated and re-sorts while you are on page 4, you can miss it. GET /projects/sync walks by cursor in last-updated order, which makes it lossless, and it refuses sort, order, offset and page because a caller-chosen ordering would break that guarantee while appearing to work.

One net-new record and one that changed since you last saw it

bash
curl 'https://api.boardwalkai.com/api/v1/projects/sync?states=UT&representation=compact&limit=2' \
  -H 'Authorization: Bearer bwk_live_YOUR_KEY'

Response · HTTP 200

json
{
  "data": [
    {
      "id": 184203,
      "recordType": "project",
      "projectName": "Alta Ridge Phase II",
      "boardwalkLink": "https://boardwalkai.com/map/#project=184203",
      "mergedFrom": [
        184203,
        184987
      ],
      "lastUpdated": "2026-06-18T09:22:41Z",
      "meetingDate": "2026-06-17",
      "delivery": {
        "alreadyExported": false,
        "updatedSinceExport": null,
        "lastExportedAt": null,
        "billed": true
      },
      "address": "3600 S Constitution Blvd, West Valley City, UT",
      "city": "West Valley City",
      "county": "Salt Lake",
      "state": "UT",
      "latitude": 40.6916,
      "longitude": -112.0011,
      "propertyType": "residential",
      "propertyTypeLabel": "Residential",
      "landUses": [
        {
          "type": "residential",
          "typeLabel": "Residential",
          "subtype": "multifamily",
          "subtypeLabel": "Multifamily",
          "assetClass": "Apartment Building",
          "assetClassLabel": "Apartment Building",
          "matchedOn": []
        }
      ],
      "status": "approved_with_conditions",
      "statusLabel": "Approved with Conditions"
    },
    {
      "id": 191774,
      "recordType": "project",
      "projectName": "Foothill Commons",
      "boardwalkLink": "https://boardwalkai.com/map/#project=191774",
      "mergedFrom": [
        191774
      ],
      "lastUpdated": "2026-06-24T14:08:03Z",
      "meetingDate": "2026-06-23",
      "delivery": {
        "alreadyExported": true,
        "updatedSinceExport": true,
        "lastExportedAt": "2026-07-19T02:14:55Z",
        "billed": true
      },
      "address": null,
      "city": "Provo",
      "county": "Utah",
      "state": "UT",
      "latitude": 40.2338,
      "longitude": -111.6585,
      "propertyType": "residential",
      "propertyTypeLabel": "Residential",
      "landUses": [
        {
          "type": "residential",
          "typeLabel": "Residential",
          "subtype": "multifamily",
          "subtypeLabel": "Multifamily",
          "assetClass": null,
          "assetClassLabel": null,
          "matchedOn": []
        }
      ],
      "status": "continued",
      "statusLabel": "Continued"
    }
  ],
  "meta": {
    "schemaVersion": "2026-08-13",
    "representation": "compact",
    "limit": 2,
    "creditsUsed": 2,
    "creditsRemaining": 284,
    "delivery": {
      "mode": "all",
      "notPreviouslyExported": 1,
      "previouslyExported": 1,
      "updatedSinceExport": 1,
      "billed": 2,
      "notRebilled": 0,
      "ledgerApplied": true
    },
    "cursor": "eyJ1cGRhdGVkQXQiOiIyMDI2LTA4LTAyVDE0OjA5OjIyWiIsImlkIjoxOTE3NzR9",
    "hasMore": true
  }
}

Record bodies on this page were produced by running Boardwalk's production response mapper over a documented sample project, so the field set, the labels and the empty fields are exactly what the API emits. The project itself is a sample, not a real filing.

Credit cost1 credit per record deliveredOnly 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.

Carry meta.cursor into the next request and stop when hasMore is false. A cursor is only valid for the filter set and entitlement scope it was minted for; using it with a different query returns 400 invalid_cursor rather than quietly walking the wrong set.

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.