API Reference

Segments

Build an audience from a filter. Creating a segment sends nothing — it defines who a later bulk send would reach, and returns the count and a sample so you can check the filter did what you meant before anyone approves anything.

Overview

MethodPathDescription
POST/api/v1/segmentsCreate a smart segment from a filter. Returns its resolved audience.

Requires the segments:write scope, which is never granted to an OAuth-connected agent and never inherited by default — see Scopes. The segment is a smart segment: membership is a live query, so it keeps up as contacts change. A bulk send freezes the membership it was reviewed against, so approving one later cannot mail a set nobody looked at.

There is no list, update or delete endpoint. Segments you create appear in the app under Audience → Segments and are managed there.

Create a segment

POST/api/v1/segments

Request body

FieldTypeRequiredDescription
namestringrequiredA human-readable name. Whoever approves a send to this segment sees it, so make it describe the filter.
rulesobjectrequiredThe filter tree — see below. Must narrow the audience; an empty filter is rejected.

Response — 201

FieldTypeDescription
idstring (uuid)Pass this as segment_id to POST /send/bulk.
namestringAs supplied.
recipient_countnumberContacts the filter matches that could actually be mailed — subscribed and not suppressed. It is deliberately not the raw membership count.
sampleobject[]Up to five matched contacts, with the local part of each address masked (a***@acme.com). Enough to confirm the filter, not enough to read out the list. The person approving a send sees the full addresses.
"color:#ff7b72">import { PristineSend } "color:#ff7b72">from "pristinesend"

"color:#ff7b72">const ps = "color:#ff7b72">new PristineSend(process.env.PRISTINESEND_API_KEY!)

"color:#ff7b72">const segment = "color:#ff7b72">await ps.segments.create({
  name: "Seattle actives",
  rules: {
    match: "all",
    conditions: [
      { field: "status", operator: "is", value: "subscribed" },
      { field: "property:city", operator: "is", value: "Seattle" },
      { field: "engagement:opened", operator: "in_last_days", value: 30 },
    ],
  },
})

// Check the filter did what you meant BEFORE asking anyone to approve a send.
console.log(segment.recipient_count, segment.sample)

The filter

rules is a tree. A group has a match of "all" (AND) or "any" (OR) and a list of conditions; each condition is either a leaf { field, operator, value } or another group. Groups nest up to three deep.

FieldMatches on
statussubscribed, unsubscribed, bounced.
first_name / last_name / emailText matching on the contact's own fields.
created_atWhen the contact was added — before / after / in the last N days.
property:<key>Any custom property on your contacts, e.g. property:city.
engagement:opened / clicked / sentBehaviour, e.g. opened anything in the last 30 days. Reads open data, so it means less on contacts whose sends had open tracking off.
verification:stateWhat list verification concluded about the address: valid, risky, invalid, or unverified. "Verification result is valid" is how you build a segment of confirmed-good addresses.

Operators depend on the field: is, is_not, contains, does_not_contain, is_empty, is_not_empty, the comparisons eq/neq/gt/lt/gte/lte, and for dates and engagement before, after, in_last_days and ever. A condition the compiler can't make sense of matches nobody rather than everybody.

Filters that match everyone

A filter with no real conditions — an empty conditions array, or groups containing only empty groups — is rejected with 400 invalid_field on rules. Such a filter is vacuously true, so it would quietly resolve to your entire audience: the difference between a segment of 400 people and one of 40,000 would be a missing line of JSON. Reaching everyone is a deliberate choice you make in the app, not something a malformed filter should be able to do by accident.

Errors

StatuscodeWhen
400missing_fieldname or rules absent.
400invalid_fieldrules is not a filter object, uses an unknown field or operator, nests too deep, or matches everyone (see above). Also when name is not a string.
401unauthorizedMissing or invalid API key.
403insufficient_scopeThe key lacks segments:write.
429rate_limitedPer-key rate limit exceeded.
500internal_errorThe segment could not be created or its membership could not be computed. Nothing partial is left behind.

See Error codes for the envelope and the full list.