Segments

Segments are dynamic contact groups defined by filter criteria. The contact list is computed at query time based on nested AND/OR filter logic — segments do not store a static list of contacts.

Naming note: Segments are referred to as "boards" in the API. All endpoint paths, request/response field names, and error messages use board/boards. Use the /boards paths when making API calls.

All endpoints require authentication via the X-API-Key header. See Authentication.

Visibility model

  • Team segments (isPrivate: false): Visible to all users in the organization.
  • Private segments (isPrivate: true): Visible only to the segment owner.
  • Admins: Can see all segments, including other users' private segments.

Filter system

Segments define dynamic groups via nested AND/OR filter criteria:

{
  "type": "AND",
  "criteria": [
    { "field": "tags", "operator": "any_of", "value": ["vip"] },
    {
      "type": "OR",
      "criteria": [
        { "field": "email", "operator": "is_not_empty" },
        { "field": "phone", "operator": "is_not_empty" }
      ]
    }
  ]
}

Each criterion has a field, an operator, and a value. Criteria can be nested with additional AND/OR groups.

Supported operators

OperatorDescriptionValue type
equalsExact match.any
not_equalsNot equal.any
containsCase-insensitive substring match.string
not_containsCase-insensitive substring exclusion.string
starts_withCase-insensitive prefix match.string
ends_withCase-insensitive suffix match.string
greater_thanNumeric greater than.number
less_thanNumeric less than.number
greater_than_or_equalNumeric greater than or equal.number
less_than_or_equalNumeric less than or equal.number
is_emptyField is null, empty string, or empty array.—
is_not_emptyField is not null, empty string, or empty array.—
any_ofAt least one value matches.string[]
none_ofNo values match.string[]
all_ofAll values are present.string[]
date_equalsMatches a full day.string or number
date_not_equalsExcludes a full day.string or number
date_afterAfter end of day.string or number
date_beforeBefore start of day.string or number
date_after_or_equalsFrom start of day onward.string or number
date_before_or_equalsThrough end of day.string or number
booleanTrue or false/null/undefined.boolean

Create a segment

POST /boards

Request body

FieldTypeRequiredNotes
namestringYesMust be unique per org.
filtersobjectYesFilter object with type (AND or OR) and criteria array.
isPrivatebooleanYestrue for private (owner-only), false for team-visible.

Example request

curl -X POST https://<host>/api/boards \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "VIP Contacts",
    "filters": {
      "type": "AND",
      "criteria": [
        { "field": "tags", "operator": "any_of", "value": ["vip"] }
      ]
    },
    "isPrivate": false
  }'

Response

{
  "error": false,
  "success": true,
  "message": "Board created successfully",
  "data": {
    "_id": "675a...",
    "orgId": "...",
    "name": "VIP Contacts",
    "count": 0,
    "userId": "...",
    "filters": {
      "type": "AND",
      "criteria": [
        { "field": "tags", "operator": "any_of", "value": ["vip"] }
      ]
    },
    "isPrivate": false,
    "archived": false,
    "createdAt": 1716000000000
  }
}

Errors

StatusMessageCause
400Invalid board nameMissing or empty name.
400Board name is already takenDuplicate name within the org.
400Filters cannot be emptyMissing filters.
400IsPrivate cannot be emptyMissing isPrivate field.

List segments

GET /boards

Returns segments visible to the current user. Each segment includes a dynamically calculated count of matching contacts.

Query parameters

ParameterTypeDefaultNotes
filterstring—team (public + admin-visible private) or private (own private segments only).
searchstring—Case-insensitive search on segment name.
pagenumber00-indexed.
perPagenumber50Items per page.

Example request

curl "https://<host>/api/boards?filter=team&search=vip&perPage=25" \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": [
    {
      "_id": "675a...",
      "name": "VIP Contacts",
      "count": 42,
      "userId": "...",
      "filters": { "type": "AND", "criteria": [...] },
      "isPrivate": false,
      "archived": false,
      "createdAt": 1716000000000
    }
  ],
  "pagination": {
    "page": 0,
    "perPage": 25,
    "totalItems": 5,
    "totalPages": 1
  }
}

Get a segment

GET /boards/:id

Returns a single segment with its dynamically calculated contact count.

Path parameters

ParameterTypeDescription
idstringSegment ID.

Example request

curl https://<host>/api/boards/675a... \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": {
    "_id": "675a...",
    "name": "VIP Contacts",
    "count": 42,
    "userId": "...",
    "filters": { "type": "AND", "criteria": [...] },
    "isPrivate": false,
    "archived": false,
    "createdAt": 1716000000000
  }
}

Errors

StatusMessageCause
404Board not foundNo segment with that ID in your org.

Get segment contacts

GET /boards/:id/contacts

Returns the contacts matching a segment's filters. Contacts are computed dynamically — only non-archived contacts are included.

Path parameters

ParameterTypeDescription
idstringSegment ID.

Query parameters

ParameterTypeDefaultNotes
searchstring—Case-insensitive search across firstName, lastName, email, phone.
pagenumber00-indexed.
perPagenumber50Items per page.

Example request

curl "https://<host>/api/boards/675a.../contacts?search=jane&perPage=25" \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": [
    {
      "_id": "664a...",
      "firstName": "Jane",
      "lastName": "Doe",
      "email": "jane@example.com",
      "tags": ["vip"],
      "archived": false
    }
  ],
  "pagination": {
    "page": 0,
    "perPage": 25,
    "totalItems": 42,
    "totalPages": 2
  }
}

Errors

StatusMessageCause
404Board not foundNo segment with that ID in your org.

Update a segment

PUT /boards/:id

All fields are optional. Segment name must be unique per org if changed.

Path parameters

ParameterTypeDescription
idstringSegment ID.

Request body

FieldTypeRequiredNotes
namestringNoMust be unique per org.
filtersobjectNoNew filter configuration.
isPrivatebooleanNo
archivedbooleanNoArchive or unarchive.
userIdstringNoReassign segment owner.

Example request

curl -X PUT https://<host>/api/boards/675a... \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "VIP Alumni", "isPrivate": true }'

Response

{
  "success": true,
  "data": {
    "_id": "675a...",
    "name": "VIP Alumni",
    "isPrivate": true,
    "filters": { "type": "AND", "criteria": [...] }
  }
}

Errors

StatusMessageCause
400Board name is already takenDuplicate name within the org.
400Validation errorInvalid filters or field values.
404Board not foundNo segment with that ID in your org.

Delete a segment

DELETE /boards/:id

Hard-deletes a segment. Returns the count of deleted records.

Path parameters

ParameterTypeDescription
idstringSegment ID.

Example request

curl -X DELETE https://<host>/api/boards/675a... \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "message": "Board deleted successful",
  "data": 1
}

Errors

StatusMessageCause
400Invalid Board IdInvalid ObjectId format or deletion failed.

Response envelope

All endpoints use the standard envelope:

{
  "error": false,
  "success": true,
  "message": "...",
  "code": 200,
  "data": "...",
  "pagination": {}
}

Error responses:

{
  "error": true,
  "success": false,
  "message": "...",
  "code": 400,
  "data": null
}

data is null for a general failure, such as a database error or an upstream timeout. When the request is rejected because of a specific field, data carries the field(s) at fault instead:

{
  "error": true,
  "success": false,
  "message": "phone: Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)",
  "code": 400,
  "data": {
    "issues": [
      {
        "path": "phone",
        "message": "Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)"
      }
    ]
  }
}

Each issue carries a path (the dotted path into the request body) and a message. A label is added when the path is not self-describing, which today means custom fields only, for example "path": "fields.68b0a1f39c2e4d0012ab77e5", "label": "Class Year". issues is an array, but treat it as holding at most one entry: validation stops at the first failure.

On this page