Added

Get a filter's matching records in the same call

Every refine-filter endpoint now takes a results parameter and returns the records the filter matches inline. results: true gives you ids; results: {fields: ["id", "email_address"]} gives you a projection. That covers POST /api/v2/workspaces/{workspace_id}/contacts/filters (generate a filter from a plain-English description), GET and PATCH /api/v2/refine_filters/{id}, and POST /api/v2/workspaces/{workspace_id}/refine_filters — so building a filter and reading its audience is one request instead of a filter plus a paginated crawl. Saved order filters support it too.

The response is column-oriented: fields states the column order once and rows carries bare value arrays, which roughly halves the payload for a projection of a few scalars. You set the size, not the server. results.max_bytes and results.limit are yours; the defaults (3 KB of rows, 500 rows) are deliberately conservative for clients with small response limits, so raise them if yours can hold more. Every response reports bytes, bytes_per_record and the max_bytes actually applied, so one call is enough to size every call after it. When a response is truncated, stop_reason says which budget stopped it, next_after is the cursor to continue from, and guidance spells out the cheaper options in order. Add count: true only when you need the total — counting a truncated set costs another evaluation of the filter.

Each projected value renders exactly as the resource's own record endpoint renders that column, so you can compare a row against a record you read elsewhere without converting anything: money is a string at the currency's scale ("100.00"), and null means the record has no value in that column.

Two limits to design around. The projection offers scalar fields only; association-backed data such as tags or custom attributes still comes from the list endpoint, and that is exactly what lets a projection skip the extra work. And a filter selecting more than can be evaluated in one request answers 422 rather than hanging — that one is deterministic, so narrow it instead of retrying. A 503 mentioning the request budget is the opposite case and is worth retrying. These reads are database-heavy, so two run at a time per access token and workspace and a third answers 429; the delay is in the body as retry_after as well as in the Retry-After header, so you can back off correctly whatever your client can read.

Agent examples: describe an audience in plain English and get the email addresses back in one call · pull the ids for a filtered set and act on each without paging · project just id to fit tens of thousands of records into one response.

Resources: Generate a Contact Filter · Fetch Refine Filter · RefineFilter

Building with an AI agent? Point it at the Refine Filters skill.