Docs·Segments

POST/api/v1/spreelytics/cohorts

Create a segment

Saves a segment and returns it. A segment is a name, an optional description and a definition: a people-query that decides who belongs. Membership is never stored. It is worked out from the definition each time the segment is used, so it cannot go stale.

A definition holds 1 to 5 conditions, joined by match: all (the default) means a person must pass every condition, any means one is enough. Each condition has "source": "internal" and a kind:

  • event: the person did an event a number of times. event is page_view or a custom event name. minCount (default 1) is compared using countOp: gte (at least, the default), eq (exactly) or lte (at most). Only people who did the event at least once are looked at, so lte never matches someone who never did it. timeframe sets when it must have happened, and propertyFilters narrows which occurrences count.
  • property: something about the person, tested against their visits. property is country (a two-letter code such as US), region, city, browser, os, screen_size (Desktop, Mobile or Tablet), channel, referrer_source, utm_source, utm_medium, utm_campaign, email, identified (true or false), or a field of the contact record: contact_first_name, contact_last_name, contact_company, contact_company_domain, contact_phone, contact_city, contact_state, contact_zip, contact_country or contact_lang_code. values lists one or more values, any of which may match. propertyOp is is (the default) or isNot for an exact match, or contains, notContains, startsWith or endsWith, which ignore case. is means at least one of their visits matched; isNot and notContains mean none did. identified takes only is and isNot, and contact fields are empty for anonymous visitors.
  • revenue: the person's net revenue in your reporting currency is at least minAmount (default 0). At 0 anyone with a tracked event passes, so set an amount above 0 to mean "has paid".

timeframe on an event condition is either { "type": "relative", "value": "30d" } (7d, 30d, 90d, 180d or 365d, counted back from the end of the outer window, or from now) or { "type": "absolute", "start": "2026-01-01", "end": "2026-03-31" } (whole UTC days, both included; leave out end to run to the end of the window). Without one, the condition covers the outer window, which by default is all time.

propertyFilters on an event condition are { "key", "op", "value" } rows, all of which must hold. IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH and ENDS_WITH compare text; EQ, NEQ, GT and LT need a number; AFTER and BEFORE need a YYYY-MM-DD date; IS_TRUE, IS_FALSE, IS_SET and IS_NOT_SET take no value. The negative ones also match an event that lacks the property.

{
  "name": "Engaged US visitors",
  "description": "Three or more page views in 30 days, from the US",
  "definition": {
    "match": "all",
    "conditions": [
      {
        "source": "internal",
        "kind": "event",
        "event": "page_view",
        "minCount": 3,
        "countOp": "gte",
        "timeframe": { "type": "relative", "value": "30d" }
      },
      {
        "source": "internal",
        "kind": "property",
        "property": "country",
        "propertyOp": "is",
        "values": ["US"]
      }
    ]
  }
}

Saving checks the shape of the body, the number of conditions and the outer date range, but not what each condition says: an unknown property or an empty values list is stored as sent. Send the definition to POST /people/query first, which checks every condition and says what is wrong.

Needs the Editor or Admin role in the workspace that owns the audience. A Viewer gets 403.

Keys: an account key. Getting started explains both kinds.

Parameters

PropInTypeNotes
contactListIdquerystringThe audience id, a UUID. With an audience key it must be that key's own audience.

Request body

PropTypeNotes
namestringRequired. Trimmed, and a blank name answers 400. Names do not have to be unique.
description?stringOptional free text, trimmed. An empty one is stored as null.
definitionPeopleQueryRequestDtoRequired. The people-query described above: 1 to 5 conditions, and optionally match, breakdown, startDate and endDate. breakdown plays no part in membership. startDate and endDate are an optional outer window, both or neither, which the people query endpoints honour; when the segment is used as a filter in a chart or on a dashboard they are ignored and only each condition's own timeframe applies.

Response

PropTypeNotes
idstringThe segment id. Use it in a chart filter (inCohort), a cohort series or the analytics cohort_id filter.
namestringThe segment name.
descriptionstringThe description, or null when there is none.
definitionPeopleQueryRequestDtoThe people-query exactly as saved. Send it to POST /people/query to count the members, or to POST /people/query/members to list them.
createdAtstringWhen it was created. Only the create call answers in ISO 8601 (2026-09-30T09:00:00.000Z). Every other call returns the database form (2026-09-30 09:00:00+00), so parse both.
updatedAtstringWhen it last changed. Create and update calls answer in ISO 8601, lists and reads in the database form, as with createdAt.

Request

curl -X POST 'https://app.spreelytics.com/api/v1/spreelytics/cohorts?contactListId=…' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
const res = await fetch('https://app.spreelytics.com/api/v1/spreelytics/cohorts', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ /* see Request body above */ }),
});
const data = await res.json();
import requests

res = requests.post(
    "https://app.spreelytics.com/api/v1/spreelytics/cohorts",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={},  # see Request body above
)
data = res.json()

Response

{
  "id": "string",
  "name": "string",
  "description": "string",
  "definition": {
    "conditions": [
      {
        "source": "internal",
        "kind": "revenue",
        "event": "string",
        "minAmount": 0,
        "minCount": 0,
        "countOp": "gte",
        "propertyFilters": [
          null
        ],
        "property": "string",
        "propertyOp": "is",
        "values": [
          null
        ],
        "timeframe": {
          "type": null,
          "value": null,
          "start": null,
          "end": null
        }
      }
    ],
    "match": "all",
    "breakdown": "channel",
    "startDate": "string",
    "endDate": "string"
  },
  "createdAt": "string",
  "updatedAt": "string"
}

See your own numbers today.

Free up to 100k events a month · no credit card

Contact

Tell us what you need.

A real person reads these. We usually reply within one working day.

Or email us directly at contact@spreelytics.com.