# Segments

A segment is a saved audience filter the project keeps by name: how to create one, read it back, understand its cached size, scroll its contacts, reference it from another filter, and audit where it is actually used.

**Language:** en
**Audience:** developer
**TLDR:** A segment stores a filter, not a list of contacts, so its membership is recomputed when it is used. POST /project/{project}/segment saves a queryFilter under a name (PROJECT_SEGMENT_WRITE, name max 50 chars, description max 256). Sizes on list rows are cached and a null size means unknown, not empty. Scroll a segment with POST /audience/segment/{uid}/scroll, reference one from another filter with an in-segment condition, and find orphans with GET /segment/usage.
**Search keywords:** segment, segments, save a segment, create segment, named segment, dynamic segment, static segment, segment size, how many contacts in a segment, segment membership, segment contacts, scroll segment, in-segment, segment condition, reuse a filter, saved filter, segment usage, orphan segment, unused segment, stale segment, audit segments, queryFilter, PROJECT_SEGMENT_WRITE, target a segment, campaign audience
**Related pages:** /developers/product-api/audience/query-filter, /developers/product-api/audience/counting-and-retrieval
**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/segments/ (HTML) · https://docs.instasent.com/developers/product-api/audience/segments.md (Markdown)

A filter you post to `/audience/search` or `/audience/count` lives for one request. Give it a name and the project keeps it: that is a **segment**. A campaign can target it, an automation can enroll from it, another filter can reference it, and you can ask for its current size whenever you want.

> **Warning**: **A segment does not hold contacts. It holds a question.** Membership is derived from the stored filter every time the segment is used, so a segment created today selects different people next month without anyone editing it. Nothing in the API returns "the contacts that were in this segment when it was created", because that set was never stored.

## Saving a filter

[`POST /project/{project}/segment` - Save an audience filter as a named segment the project keeps.](/developers/product-api/reference)

```json
{
  "name": "Recent Yahoo contacts",
  "description": "Contacts with a Yahoo email imported in the last 90 days.",
  "queryFilter": {
    "version": "0.0.1",
    "root": {
      "type": "group",
      "join": "and",
      "children": [
        {
          "type": "attribute_condition",
          "key": "_email",
          "operator": "contains",
          "values": ["yahoo"]
        },
        {
          "type": "attribute_condition",
          "key": "_date_imported",
          "operator": "range-date-relative",
          "values": {
            "lowerOffset": -90,
            "lowerOffsetPeriod": "day",
            "upperOffset": 0,
            "upperOffsetPeriod": "day"
          }
        }
      ]
    }
  }
}
```

`name` (the label the segment carries in the dashboard, max 50 characters) and `queryFilter` are required; `description` (max 256 characters) is optional. The response returns the stored segment.

`queryFilter` takes exactly the [audience query filter](/developers/product-api/audience/query-filter) grammar and is validated the same way: a root group whose children are attribute conditions, event groups or segment memberships. An invalid filter is refused with a `422` naming the offending node, and nothing is saved.

Saving needs `PROJECT_SEGMENT_WRITE`. It is a scope of its own, so a token can be allowed to keep segments without being given write access to the audience. Reading segments back takes `PROJECT_AUDIENCE_READ` instead. See [Tokens and scopes](/developers/product-api/guide#tokens-and-scopes).

> **Tip**: Compose the filter against `/audience/count` before you save it, and [probe](/developers/product-api/audience/probes) the attributes it uses before that. A segment saved on a condition that matches nobody is indistinguishable, from the outside, from one that works.

> **Note**: Consent and channel reach do not belong inside a saved segment. They are decided when something is sent, under the project's policy for that channel, so a segment stores who the contacts **are** and not who may be messaged. Keep `filterCompliance` for the count and leave it out of the `queryFilter` you save.

## Reading segments back

[`GET /project/{project}/segment` - List the project's segments, with query-string filtering, sorting and pagination.](/developers/product-api/reference)

[`GET /project/{project}/segment/{uid}` - One segment, including its stored filter.](/developers/product-api/reference)

[`GET /project/{project}/segment/dynamic` - Only the dynamic segments that take no parameter.](/developers/product-api/reference)

The list endpoint accepts the [generic query filter](/developers/further-reading/query-filter) conventions: `field_operator=value` for filtering (`name_eq=MySegment`, `type_eq=static`), `_sort=field:direction` for ordering, and `_start` plus `_limit` for pagination. When you pass `_limit` you must also pass `_start`.

### Dynamic segments and their parameter

Some segments are **dynamic**: they carry a parameter that is supplied when the segment is used, so one definition covers a family of audiences. Pass it as the `parameter` query argument, pipe-separated for several values (`parameter=val1|val2`), both when viewing the segment and when scrolling it.

### The size on a segment is cached

Every segment view and list row carries a `contacts` object with the segment's size and the age of that figure.

> **Warning**: **A null size means "not known yet", never "empty".** The list endpoints serve whatever the cache already holds and never compute, so any segment whose size has never been computed or has expired comes back null. The single-segment view computes it. For parameterized dynamic segments it is always null, because the size depends on the parameter.

`totalContacts` repeats the same number for older clients. Prefer `contacts`, which carries the age: a bare total invites reading a cached number as a live one. When you need a number you can act on, count the segment's filter through [`/audience/count`](/developers/product-api/audience/counting-and-retrieval).

## Working with a segment's contacts

[`POST /project/{project}/audience/segment/{uid}/scroll` - Cursor-scroll the contacts of one segment.](/developers/product-api/reference)

The segment's own filter is **merged into** whatever you post, so a body with a `root` filters *within* the segment rather than replacing it. Everything else behaves like [`/audience/scroll`](/developers/product-api/audience/counting-and-retrieval#searching-and-scrolling): `PROJECT_AUDIENCE_LIST`, up to 100 contacts per page, a cursor that expires after one minute of inactivity, and contact fields redacted according to plan and scopes.

To reference a segment from another filter instead of reading its rows, use a `segment_condition` with the `in-segment` or `in-segment-not` operator. That is how a filter says "these people, but not the ones already in the VIP segment". The grammar is on the [query filter](/developers/product-api/audience/query-filter#filter-by-segment-membership) page.

## Finding the segments nobody uses

Segments accumulate. `GET /project/{project}/segment/usage` returns, in one request, where each one is actually referenced.

[`GET /project/{project}/segment/usage` - Campaigns that have targeted each segment and automations that enroll from it.](/developers/product-api/reference)

Per segment it reports `campaigns`, `lastTargetedAt`, `automations` and `activeAutomations`, plus a `computedAt` for the whole map, which is aggregated per project and cached ten minutes.

The interesting part is what it leaves out. **Only segments with at least one reference are returned**, so a segment that appears in the list endpoint and not here is an orphan, and that absence is the point: it makes an audit possible in one request instead of one per segment. Combined with the cached size on each list row, two calls find the orphaned, the empty and the stale.

A segment kept alive only through `automations` that are all inactive is effectively unused too, which is why `activeAutomations` is reported separately. Flows carry no segment reference and are deliberately not counted here.

## What's next

- **[Query filter](/developers/product-api/audience/query-filter)**: the grammar a segment stores.
- **[Counting and retrieval](/developers/product-api/audience/counting-and-retrieval)**: getting a live number, and reading contacts out.
- **[Campaign audience targeting](/developers/product-api/campaigns/audience)**: pointing a campaign at a segment or at a filter.

---

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.
