# Counting and retrieval

Which endpoint answers which question about the audience: the exact count of a filter, a quick search, a cursor traverse of a large result set, a single contact by id or identifier, and what the subscription plan lets each of them return.

**Language:** en
**Audience:** developer
**TLDR:** Use /audience/count for an exact total with no rows (works under the anonymized data policy, and takes filterCompliance to count who can actually receive). Use /audience/search for a first page of up to 50 contacts and /audience/scroll for large traverses with a cursor, up to 100 per page; both need PROJECT_AUDIENCE_LIST for scroll and are redacted by plan. Fetch one contact by audience id, user id, phone or email with the direct lookups.
**Search keywords:** count, count contacts, how many contacts, audience count, total contacts, search contacts, find contacts, list contacts, scroll, cursor, pagination, paginate, iterate contacts, export contacts, bulk read, retrieve contact, get contact, lookup by phone, lookup by email, lookup by user id, audience id, aggregations, anonymized, data policy, redacted, PII, which endpoint should I use, 50 limit, 100 limit, filterCompliance, reachable
**Related pages:** /developers/product-api/audience/query-filter, /developers/product-api/audience/segments
**Docs index (every page):** https://docs.instasent.com/llms.txt
**This zone's index:** https://docs.instasent.com/developers/product-api/llms-full.txt
**This page:** https://docs.instasent.com/developers/product-api/audience/counting-and-retrieval/ (HTML) · https://docs.instasent.com/developers/product-api/audience/counting-and-retrieval.md (Markdown)

Once a filter selects the right people, there are four different things you might want back, and they are four different endpoints with four different costs and permissions. Picking the wrong one is the usual cause of a `403` you did not expect or a page-two that never arrives.

## Which endpoint for which question

| The question                  | Endpoint                                                | What comes back                       |
| ----------------------------- | ------------------------------------------------------- | ------------------------------------- |
| How many match this filter?   | `POST /audience/count`                                  | A single exact total, no contact rows |
| Show me a first page          | `POST /audience/search`                                 | Up to 50 contacts, from the beginning |
| Give me all of them           | `POST /audience/scroll`                                 | Up to 100 per page, cursor-paginated  |
| All of them, inside a segment | `POST /audience/segment/{uid}/scroll`                   | Same, restricted to a segment         |
| This one person               | `GET /audience/{audienceId}` and the identifier lookups | One contact                           |
| A breakdown, not rows         | `POST /audience/aggregations`                           | Aggregation results only              |

## Counting

`POST /project/{project}/audience/count` takes the [audience query filter](/developers/product-api/audience/query-filter) and returns only the total. It is the endpoint whose number you may act on: exact, uncapped and live, with no sampling anywhere near it.

[`POST /project/{project}/audience/count` - The exact number of contacts matching a filter.](/developers/product-api/reference)

```bash
curl -X POST "$BASE/audience/count" \
  -H "Authorization: Bearer $INSTASENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "filterCompliance": { "sms": "opt-out" } }'
```

Four things worth knowing:

- **An empty body counts the whole audience.** `limit` and `offset` are ignored.
- **It carries no contact data**, which is why it stays available under the `anonymized` data policy mode, where `/audience/search` is disabled. For restricted credentials it is the supported way to answer "how many contacts match this".
- **To count who can actually *receive*, pass `filterCompliance`.** Without it the total is the raw audience, not a sending figure. The consent and reach rules are the same ones a campaign estimate applies. See [Counting who can receive](/developers/product-api/audience/query-filter#counting-who-can-receive-filtercompliance).
- **It is the endpoint that settles any number a probe suggested.** [Probes](/developers/product-api/audience/probes) report shape and may be sampled; this is the size.

## Searching and scrolling

Both take the same filter. They differ in how much they will give you and what it costs.

[`POST /project/{project}/audience/search` - A first page of matching contacts, up to 50.](/developers/product-api/reference)

`/audience/search` uses offset/limit pagination, but the offset is always forced to `0`: it is built for a quick look from the beginning, not for walking a result set. Its ceiling is 50 contacts per request and it needs `PROJECT_AUDIENCE_READ`.

[`POST /project/{project}/audience/scroll` - A cursor traverse of the whole result set, 100 contacts at a time.](/developers/product-api/reference)

`/audience/scroll` is the one for large traverses. It returns a cursor in the response metadata that you pass back to get the next page, up to 100 contacts per request. The cursor is a base64 string holding the internal query state and **expires after one minute of inactivity**, so a traverse has to keep moving: a job that fetches a page, spends five minutes processing it and comes back will find the cursor gone.

It needs `PROJECT_AUDIENCE_LIST`, which is gated by the subscription plan. That is the usual reason a call that works for one organization is refused for another with the same code.

> **Tip**: Scroll rather than paginate. It is the endpoint designed for volume: one hit per page, generous page size and no offset arithmetic. Reserve `/audience/search` for the interactive "show me a few" case.

## Retrieving one contact

When you already know who you are looking for, four direct lookups skip the filter entirely.

[`GET /project/{project}/audience/{audienceId}` - By audience contact id.](/developers/product-api/reference)

[`GET /project/{project}/audience/user/{userId}` - By the `_user_id` your systems use.](/developers/product-api/reference)

[`GET /project/{project}/audience/search/phone/{userPhone}` - By phone number. URL-encode the leading plus as `%2B`.](/developers/product-api/reference)

[`GET /project/{project}/audience/search/email/{userEmail}` - By email address.](/developers/product-api/reference)

The id these return is the one every other per-contact call takes, including [the contact's events](/developers/product-api/audience/events#reading-a-contacts-events) and direct messaging.

## Aggregations

`POST /audience/aggregations` runs **your** aggregations over the filtered audience and returns results only, with no contact rows and the limit pinned to zero. It needs `PROJECT_AGGREGATIONS`, which is not generally available and is granted manually by Instasent.

If what you want is a distribution over an attribute rather than a bespoke aggregation, [`/audience/coverage`](/developers/product-api/audience/probes) gives you one with the ordinary read scope, at the cost of being a probe: shape, possibly sampled, never a size.

## What the plan lets you see

Two gates decide the fields on a returned contact, and both have to be open: the **scopes** on your token and the **subscription plan** of the organization.

| Level   | Scope                         | Fields returned                             |
| ------- | ----------------------------- | ------------------------------------------- |
| Default | `PROJECT_AUDIENCE_READ`       | Full name and user id only                  |
| Basic   | `PROJECT_AUDIENCE_DATA_BASIC` | Phone, email, country, name, boolean fields |
| Full    | `PROJECT_AUDIENCE_DATA_FULL`  | Full contact data including PII             |

> **Warning**: A field missing from a response is far more often a plan or scope limit than an empty value. Before concluding the data is not there, check the level you are actually getting: contact data is **redacted down**, silently and by design, not refused. `/audience/count` is unaffected, because it returns no contact data at all.

## What's next

- **[Query filter](/developers/product-api/audience/query-filter)**: the filter these endpoints take, including cursor mechanics and aggregation shapes.
- **[Segments](/developers/product-api/audience/segments)**: scrolling a saved segment, and reading its cached size.
- **[Probes](/developers/product-api/audience/probes)**: check the filter is selecting on something real before you count it.

---

This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context.
