Docs·Segments
/api/v1/spreelytics/cohortsCreate 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.eventispage_viewor a custom event name.minCount(default 1) is compared usingcountOp:gte(at least, the default),eq(exactly) orlte(at most). Only people who did the event at least once are looked at, soltenever matches someone who never did it.timeframesets when it must have happened, andpropertyFiltersnarrows which occurrences count.property: something about the person, tested against their visits.propertyiscountry(a two-letter code such asUS),region,city,browser,os,screen_size(Desktop,MobileorTablet),channel,referrer_source,utm_source,utm_medium,utm_campaign,email,identified(trueorfalse), 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_countryorcontact_lang_code.valueslists one or more values, any of which may match.propertyOpisis(the default) orisNotfor an exact match, orcontains,notContains,startsWithorendsWith, which ignore case.ismeans at least one of their visits matched;isNotandnotContainsmean none did.identifiedtakes onlyisandisNot, and contact fields are empty for anonymous visitors.revenue: the person's net revenue in your reporting currency is at leastminAmount(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
| Prop | In | Type | Notes |
|---|---|---|---|
contactListId | query | string | The audience id, a UUID. With an audience key it must be that key's own audience. |
Request body
| Prop | Type | Notes |
|---|---|---|
name | string | Required. Trimmed, and a blank name answers 400. Names do not have to be unique. |
description? | string | Optional free text, trimmed. An empty one is stored as null. |
definition | PeopleQueryRequestDto | Required. 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
| Prop | Type | Notes |
|---|---|---|
id | string | The segment id. Use it in a chart filter (inCohort), a cohort series or the analytics cohort_id filter. |
name | string | The segment name. |
description | string | The description, or null when there is none. |
definition | PeopleQueryRequestDto | The people-query exactly as saved. Send it to POST /people/query to count the members, or to POST /people/query/members to list them. |
createdAt | string | When 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. |
updatedAt | string | When 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"
}