Pagination & filtering
Lists are returned by POST /{resource}/search, never by a bare GET /{resource}. Search keeps the
query in the request body (so it can be rich and structured) and returns results in one uniform
cursor envelope.
The envelope
Every list response has the same shape:
{
"data": [ /* the records for this page */ ],
"meta": {
"cursor": { "next": "eyJvIjoyNX0", "has_more": true },
"total": 13658
}
}
data— the records on this page.meta.cursor.next— an opaque string. Pass it back ascursorto get the next page.nullwhen there are no more pages.meta.cursor.has_more—trueif another page exists.meta.total— total matches. May be omitted for large or expensive queries, so never assume it's present; rely onhas_moreto drive your loop.
Cursors are opaque. Don't parse, store long-term, or construct them — they encode internal paging state and may change shape between versions.
Paging loop
Keep calling until has_more is false, threading next into cursor:
import os, requests
URL = "https://papi.trendev.in/v1/contacts/search"
HEADERS = {
"Authorization": f"Bearer {os.environ['PROSPECTCONNECT_TOKEN']}",
"Version": "2026-06-01",
}
cursor, all_contacts = None, []
while True:
body = {"limit": 100}
if cursor:
body["cursor"] = cursor
page = requests.post(URL, headers=HEADERS, json=body, timeout=30).json()
all_contacts.extend(page["data"])
if not page["meta"]["cursor"]["has_more"]:
break
cursor = page["meta"]["cursor"]["next"]
print(f"fetched {len(all_contacts)} contacts")
limit ranges 1–100 (default 25). Larger pages mean fewer round-trips.
Sorting
Control ordering with order_by + sort_direction:
{ "order_by": "lead_score", "sort_direction": "desc", "limit": 50 }
sort_directionisascordesc(defaultdesc).- Common
order_byvalues:created_at,updated_at,name,lead_score,last_contacted_at. Each resource's search operation in the API Reference lists what it supports.
Filtering
filters is an array of structured conditions. Each condition is { field, operator, value } (or
values for multi-value operators):
{
"search_text": "acme",
"filters": [
{ "field": "lifecycle_stage_id", "operator": "eq", "value": "stage_lead" },
{ "field": "owner_id", "operator": "in", "values": ["usr_1", "usr_2"] },
{ "field": "lead_score", "operator": "gte", "value": 50 }
],
"order_by": "lead_score",
"sort_direction": "desc",
"limit": 50
}
Multiple filters are combined with AND. search_text is a separate full-text match across common
fields (name, email, phone, …) and can be used alone or alongside filters.
Operators
| Operator | Meaning | Uses |
|---|---|---|
eq / neq | equals / not equals | value |
in / nin | in / not in a set | values |
contains | substring / membership | value |
starts_with | prefix match | value |
gt / gte / lt / lte | numeric / date comparisons | value |
between | inclusive range | values: [low, high] |
is_set / is_not_set | field present / absent | — |
Projection
Pass fields to return only the attributes you need — smaller payloads, faster responses:
{ "fields": ["id", "first_name", "last_name", "email"], "limit": 100 }
Validation
Bad paging input is rejected before it reaches the data layer: limit out of range, an unknown
operator, or a malformed cursor returns a 422 validation error describing exactly
which field was wrong.