Support/Developer Platform/Pagination, filtering, rate limits, and versioning

Developer Platform

Pagination, filtering, rate limits, and versioning

Implement list traversal, server-side filters, 429 recovery, and contract pinning from the exact OpenAPI operation.

Open raw .md
Module lists
pageNumber + hasMore
Organization lists
afterAppAccountId cursor
Rate limits
429 + required Retry-After

BuildWithHQ uses one public v1 API with operation-specific list contracts. The OpenAPI operation defines its parameters, defaults, bounds, response shape, credential class, and scopes. A client should not impose one pagination or filter shape on routes that publish different contracts.

Use the declared pagination family

List familyRequestContinue withStop when
Most first-class modulespageNumber, pageSize, and declared filtersIncrement pageNumberhasMore is false
Organization discoveryafterAppAccountId, pageSize, optional searchReturn nextAfterAppAccountId as the next cursorThe next cursor is absent
Tenant-user directoryNo paging or cursor parametersNo follow-up requestThe complete users array is returned
Bounded history/projection routesThe operation's documented limit or other boundOnly as that response declaresThe operation-specific condition is reached

Module list envelopes can report pageNumber, pageSize, totalRecords, totalPages, totalIsExact, hasMore, and items. Drive traversal with hasMore. Treat a total as exact only when totalIsExact is true.

Contacts and Global Search default to includeTotal=false and return a browseLimit of 5,000. Request an uncapped authorized total through the dedicated count endpoint, without blocking row rendering. See progressive loading and exact counts for routes, scopes, cache boundaries and the executable SDK example.

Sync/export migration: a module-list hasMore=false at this cap does not prove a complete directory. For all authorized Contacts records use POST /v1/apps/{saasAppId}/objects/contacts/records/search with page, pageSize (1–100) and a fixed sort, following that operation's continuation. The linked guide explains its generic record shape, native-field hydration and lack of snapshot guarantees.

The tenant-user list returns the whole authorized organization directory in one response. Its exact totalCount equals the length of users; each row includes DataRole and location counts and assignments. Zero selected locations means all locations, not no access. Do not send legacy page or cursor parameters. No per-organization user ceiling is enforced today, so response size grows with users and assignments. The expected case is a few hundred users; large-directory limits or streaming remain pre-launch scale work. A dependency failure returns 503, not an empty list.

Book an eligible tenant user

Call GET /v1/apps/{saasAppId}/modules/reservations/assignees with reservationProductId and startUtc together, plus an optional locationId. Choose an item from items and send its optionId as assignedUserId to Reservation purchase or reschedule; lookupId identifies the lookup contract and is not a user ID. The server rechecks eligibility and capacity on write. On 409 reservations_conflict, refresh the Reservation and assignee choices. There is no generic /modules/reservations/lookups route; use /products and /assignees.

Filter before data reaches the browser

Send only filters declared for the operation, such as search, status, locationId, or a module-specific state. Omit an unused filter. The server applies verified app, account, user, DataRole, Location, and permission scope before the requested filter. IDs and cursors select targets inside that boundary; they do not grant access.

GoClaw authority errors in the tenant runtime

For the signed-in tenant route PUT /api/goclaw/agents/{goClawAgentId}/authority, handle 400 goclaw_authority_invalid_request by correcting the request, 403 goclaw_authority_forbidden as a permission denial, and 404 goclaw_authority_not_found by refreshing the authorized agent list. The route is not an app-scoped Developer API operation. Kit 1.20.70 includes the exact mapping in catalog/tenant-runtime-error-codes.json; framework-level model validation can also return 400 with a different response shape.

Handle tenant page and workflow status failures

The authenticated tenant page-save route POST /api/runtime/pages/{pageKey}/revisions/save requires the verified account owner and the builder's tenant-edit policy. A missing page returns 404 page_revision_not_found; a protected page returns 403 page_revision_not_editable. Require report.Saved=true even when HTTP is 200, and preserve the E020 edit-lease, E021 ID-loss acknowledgement and E022 stale-version reports.

For PUT /api/inbox/items/{inboxItemId}/human-state, keep the current expectedActivityVersion. A pending workflow approval cannot be resolved, closed, snoozed or moved to WaitingCustomer: the server returns 400 inbox_request_invalid without changing state. Complete the authorized workflow decision first; older stranded items can be reopened. Stale revisions return 409 inbox_stale_context. IDs and refreshes never grant edit or approval authority. Kit 1.20.70 records these codes in catalog/tenant-runtime-error-codes.json.

Honor the rate-limit contract

Every published 429 Too Many Requests response declares a required Retry-After header containing whole seconds with a minimum of one. Wait that long, then retry within a bounded attempt count. Use capped exponential backoff with jitter for transient 502, 503, and 504 responses.

const waitSeconds = Number(response.headers.get("Retry-After"));
if (response.status === 429 && Number.isInteger(waitSeconds) && waitSeconds >= 1) {
  await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));
}

GET and HEAD requests can be retried safely. Retry a write only when its operation supports an idempotency key or explicitly defines repeat-safe behavior. Reuse the same key and payload. Preserve the API error correlationId when the bounded attempts end.

Pin and update the contract

Generate against the OpenAPI file in the immutable Developer Kit. For an app with marketplace services, call GET /v1/apps/{saasAppId}/openapi.json and pin X-BuildWithHQ-Contract-SHA256 or the matching document fingerprint. Regenerate when it changes. Clients must ignore additive response properties they do not recognize; breaking changes require an explicit API version and migration guidance.

Run the offline example

The Developer Kit includes examples/api-client-policy.mjs with a bounded request loop, numbered-page iterator, and organization cursor iterator. Its test uses mocked HTTP responses and needs no credential:

node --test examples/api-client-policy.test.mjs
Note

Page-size bounds and allowed filters belong to each OpenAPI operation. Never copy one route's maximum, default, status values, or cursor semantics into another client method.

Capability review: 2026-09-14. See current features and setup requirements, the generated API Map and first-class module inventory.