Segments
A segment is a named, saved audience filter. Messages target segments by id — you include one or more, and optionally exclude others. A segment is evaluated live at send time and at read time; there is no stored membership list to refresh.
This page documents the four segment endpoints and the complete filter language: every field, every operator, the grouping model, the validation errors, and how counts behave.
All examples use https://app.openpush.ai, the OpenPush API base URL.
Auth: every route on this page takes X-OP-API-Key — your app's REST key.
The segment object
| Field | Type | Description |
|---|---|---|
id | string | Segment id, e.g. seg_01hq7m9b2f |
app | string | The app it belongs to |
name | string | Display name. Not required to be unique |
status | string | Active or Paused |
rules | array | The filter rows. See Filter language |
rule_text | string | A human description of rules, rendered by the server |
subscriptions | integer | Live count of matching subscriptions |
is_default | integer | 1 for managed built-in segments, which cannot be deleted |
is_target_default | integer | 1 for the segment pre-selected when composing a message |
created_at | number | Unix seconds |
updated_at | number | Unix seconds |
Every new app is seeded with two managed segments: Total Subscriptions (no rules — everyone)
and Active Subscriptions (last session less than 720 hours ago). A third, Testing
Devices, is created the first time you mark a test device; its rule is the ordinary
test_users is true filter, so it counts and targets through the same machinery as any other
segment.
GET /v1/apps/{app_id}/segments
Lists every segment on the app, with a live count on each, plus the machine-readable filter vocabulary.
Auth: X-OP-API-Key
Segments come back with the targeting default first, then most-recently-updated first.
GET /v1/apps/{app_id}/segments/{seg_id} reads one editable segment without
scanning the list; an unknown or other-app ID returns 404.
Example request
Code
Example response
Code
The fields array is the authoritative, machine-readable filter vocabulary — build a segment
editor against it rather than hard-coding the list.
POST /v1/apps/{app_id}/segments
Creates a segment. The rules are compiled and validated before anything is stored, so a segment that saves is a segment that runs.
Auth: X-OP-API-Key
Body
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Display name. Trimmed; must not be empty |
rules | array | no | [] | Filter rows. An empty array matches every subscribed device |
status | string | no | Active | Active or Paused |
is_target_default | boolean | no | false | Make this the segment pre-selected when composing. Setting it clears the flag on the previous default |
Example request
Code
Example response
The full segment row, including the rendered rule_text and a live count:
Code
Errors
| Status | Message | Cause |
|---|---|---|
400 | name required | Missing or blank name |
400 | is_target_default must be a boolean | A string or number was sent |
400 | a filter message | See Validation errors |
401 | bad X-OP-API-Key | Bad or missing REST key |
404 | unknown app '<id>' | No such app |
PATCH /v1/apps/{app_id}/segments/{seg_id}
Updates a segment. Only the keys you send are touched; rules is replaced wholesale, not merged,
and is revalidated.
Auth: X-OP-API-Key
Body
| Parameter | Type | Description |
|---|---|---|
name | string | New display name |
status | string | Active or Paused |
rules | array | Replacement filter rows, revalidated on save |
is_target_default | boolean | Only true is accepted |
An app always has exactly one targeting default, so there is no way to clear the flag — you move
it by setting is_target_default: true on a different segment. Sending false is refused with a
message that says so.
Example request
Code
Example response
Code
Read the segment back with GET /v1/apps/{app_id}/segments if you need the recompiled
rule_text or a fresh count.
Errors
| Status | Message | Cause |
|---|---|---|
400 | nothing to update | Empty patch body |
400 | status must be Active|Paused | Any other status value |
400 | is_target_default must be a boolean | Wrong type |
400 | set another segment as default instead of clearing the current default | is_target_default: false |
400 | a filter message | See Validation errors |
401 | bad X-OP-API-Key | Bad or missing REST key |
404 | unknown segment | No such segment on this app |
DELETE /v1/apps/{app_id}/segments/{seg_id}
Auth: X-OP-API-Key
Code
Code
Managed segments — the seeded Total Subscriptions and Active Subscriptions, and the Testing Devices segment — cannot be deleted. You can still edit their rules through the same routes as any other segment.
If the deleted segment was the targeting default, the flag falls back to Total Subscriptions, or to the oldest segment on the app if that name is gone.
Deleting a segment does not affect messages already sent. A message that referenced it and has not yet been resolved will fail to resolve its audience.
Errors
| Status | Message | Cause |
|---|---|---|
401 | bad X-OP-API-Key | Bad or missing REST key |
404 | unknown segment | No such segment on this app |
409 | managed segments cannot be deleted | The segment is a built-in |
Filter language
rules is a flat JSON array of filter rows. There is no tree, and no nesting beyond the two
levels described under AND and OR.
Code
Row keys
| Key | Type | Required | Description |
|---|---|---|---|
field | string | yes | One of the ten fields below |
op | string | no (defaults to is) | One of the supported operators below |
value | string | number | array | usually | Comparison value or bounded list. Not read by exists / not_exists |
key | string | tag rows only | The tag name to look up |
lat, lng | number | location rows only | Centre of the radius |
group | string | no (defaults to "0") | OR bucket label |
Fields and their operators
field | Label | Allowed op | value |
|---|---|---|---|
first_session | First Session | greater, less | Hours ago (number) |
last_session | Last Session | greater, less | Hours ago (number) |
session_count | Session Count | greater, less, is | Number |
tag | User Tag | is, is_not, exists, not_exists, greater, less, in, not_in | Depends on the operator; also needs key |
country | Country | is, is_not, in, not_in | Country code, e.g. IN |
location | Location | within | Radius in metres; also needs lat and lng |
language | Language | is, is_not, in, not_in | Language code, e.g. en |
test_users | Test Users | is | "true" or "false" |
device_type | Device Type | is, is_not | Android or iOS |
app_version | App Version | is, is_not, in, not_in | Version string, e.g. 1.97 |
in and not_in take a bounded value list. An absent value matches not_in
and does not match in. The same tag comparisons are used by the filters
array on messages and previews. There is no substring or regular
expression operator.
What is not filterable
- Subscription status. It is not a field, because it is hard-wired into every segment query: a segment only ever matches subscriptions whose status is positive and whose address has not been retired. A segment cannot target unsubscribed or uninstalled devices.
- Events. Custom events never reach segments. Behavioural targeting is expressible only inside a journey, which can trigger and branch on events.
- Message engagement — sends, opens, clicks. There is no field for any of them.
Session-recency filters
first_session and last_session take hours ago, and the operator names read from the
perspective of age:
greatermeans further in the past — "at least this long ago".lessmeans more recent than — "within this window".
A row is required to have the column set at all, so users with no recorded session never match either direction.
Everyone who has not opened the app in the last week:
Code
Everyone who installed within the last 24 hours and has been back in the last hour:
Code
Numeric comparison — session_count
greater, less and is map to >, < and =. A user with no recorded session count is
treated as 0.
Players past their tenth session:
Code
Exactly one session — installed, opened once, never returned:
Code
String equality — country, language, device_type, app_version
is is an exact match. country is compared uppercased on both sides and device_type
lowercased on both sides, so in, In and IN are the same filter, as are ios and iOS.
language and app_version are compared as sent.
is_not treats a missing value as "is not". A device that never reported a country matches
country is_not IN. That is usually what you want for an exclusion, and it is worth knowing
before you use is_not as a way to count.
iOS devices in India:
Code
Everyone not yet on the current build:
Code
Tag filters
Tags live on the user, are merged per key on every registration, and are deleted by sending an
empty value. A tag row needs a key as well as an op.
Tag keys must be made of A–Z, a–z, 0–9, _, - and ., with no empty segment between
dots. a..b and a bare . are refused rather than silently building an unusable lookup path.
exists / not_exists — presence only. value is ignored:
Code
Code
is / is_not — string comparison of the stored value. As with the other string fields,
is_not also matches users who do not hold the tag at all:
Code
greater / less — numeric comparison. The tag value is cast to a number, and a tag whose
value is a word simply fails to match rather than erroring:
Code
Because tags are stored as you send them, a numeric tag is only useful with greater/less if
you write it consistently — "5000" and "5,000" are not the same value.
Location radius — within
within takes the radius in metres as value, plus lat and lng for the centre. It
matches subscriptions that have both coordinates stored.
Within 25 km of central Bengaluru:
Code
Location filters only work when the app's location collection switch is on and devices have actually reported coordinates — the switch is off by default, and a device that sent only one of the two coordinates has no stored location at all. See Apps, keys and settings.
Radius must be greater than zero; latitude must be between -90 and 90; longitude between -180 and 180.
Test devices — test_users
test_users matches the devices marked as test subscriptions. is true selects them; a value of
false, 0 or no inverts the filter and excludes them.
Code
Excluding test devices from a production campaign is more often done as an exclude segment on the message than as a rule inside the audience segment — see Messages.
AND and OR
Grouping has exactly two levels:
- Rows that share a
groupvalue are ANDed. - Groups are ORed with each other.
Deeper nesting is not representable. Rows with no group key all collapse into one group, which
is the all-AND behaviour.
Gold players in India OR anyone at all in Brazil:
Code
rule_text renders that as:
Code
Group labels are arbitrary strings — "0", "1", "a", "weekenders" — and their only job is
to say which rows belong together. Group order in the response follows the order the labels first
appear in your array.
Empty rules match everyone. A segment with rules: [] compiles to every subscribed device
and describes itself as Default — every subscribed device.
How segments combine on a message
When a message names segments, includes are ORed together and excludes are subtracted:
Code
An empty include list means every sendable device on the app. Naming a segment id that does not
exist fails the send with unknown segment '<id>'.
Validation errors
Every filter problem is a 400 with a plain-text message in the standard error body:
Code
| Message | Cause |
|---|---|
unknown field '<f>' | field is not one of the ten |
<what> needs a number, got <v> | A numeric position received something unparseable |
<field> supports greater|less | Wrong operator on first_session or last_session |
session_count supports greater|less|is | Wrong operator on session_count |
<field> supports is|is_not | Wrong operator on country, language, device_type or app_version |
location supports is within | Any operator other than within on location |
location distance must be greater than 0 meters | Radius of zero or less |
location latitude must be between -90 and 90 | Out-of-range lat |
location longitude must be between -180 and 180 | Out-of-range lng |
tag needs a key of [A-Za-z0-9_-.] with no empty .segment | Missing or malformed tag key |
tag supports is|is_not|exists|not_exists|greater|less | Wrong operator on tag |
unhandled field '<f>' | A known field reached with no compiler branch |
unknown segment '<id>' | A message or preview referenced a segment id that does not exist |
Validation runs on create, on patch, and again whenever a message resolves its audience — a segment cannot be saved in a state that would fail at send time.
Counts
The subscriptions number on a segment is an exact COUNT of matching subscriptions, computed
when you read it. There is no estimator, no sampling, no timeout, and no "estimated" label
anywhere in the API.
Two things follow from that:
- Counts move between two reads. A segment is a live query, not a materialised list.
- Counting a very broad segment on a very large app is real work. Do not poll the list route in a tight loop for a dashboard.
The count is over subscriptions, not people. A user with a phone and a tablet counts twice. To see the audience a specific message would reach — including the platform and language breakdown, and how many devices the frequency cap and quiet hours would hold back — use the audience preview endpoint documented in Messages.
Limits and notes
- No rate limit applies to any segment route.
- There is no route to duplicate a segment; create a new one with the same
rules. - There is no paging on the segment list. It returns every segment on the app, each with a live count.
rulesis replaced wholesale by a patch. Read, modify, and send the full array back.- Segment names are not unique, and nothing resolves a segment by name — messages target ids.
- A segment cannot reach unsubscribed, uninstalled or retired devices under any rule.
Related
- API overview — auth model and error convention
- Messages — targeting with include and exclude segments, audience preview
- Subscriptions and users — where tags, country, language and location come from
- Segments — filter recipes in prose
- Journeys — event-driven targeting, which segments cannot express