Guide
Querying
Read records with simple query-string filters, structured searches, and database-side aggregates. Every list is cursor-paginated and scoped to what the key's user may read.
GET /v1/objects/{object_type}/records accepts these query parameters:
| Parameter | Description |
|---|---|
limit | Page size, 1–100. Default 25. |
cursor | The previous page's pagination.next_cursor. |
q | Case-insensitive text match on the display field. |
fields | Comma-separated field keys to include in fields (max 20). |
sort | Comma-separated field keys; prefix - for descending (max 3). |
filter[...] | Filters; see below. |
curl -G https://api.preshos.com/v1/objects/companies/records \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
--data-urlencode "fields=name,domain,industry" \
--data-urlencode "sort=-updated_at" \
--data-urlencode "filter[industry]=Manufacturing" \
--data-urlencode "limit=50"filter[field]=value matches equal values. filter[field][operator]=value applies an operator. Up to 16 filters are combined with AND.
| Operator | Matches | Query-string example |
|---|---|---|
eq | Equal (same as filter[field]=value) | filter[status][eq]=active |
ne | Not equal | filter[status][ne]=archived |
lt, lte | Less than (or equal) | filter[amount][lt]=1000 |
gt, gte | Greater than (or equal) | filter[created_at][gte]=2026-01-01T00:00:00Z |
in | Any of 1–100 values (comma-separated) | filter[lifecycle_stage][in]=customer,opportunity |
is_null | Empty (true) or not empty (false) | filter[domain][is_null]=true |
Only use field keys from the object's field contract. Unknown fields, invalid operators, and malformed values fail with 400 invalid_filter; fields the user cannot read fail with 403 permission_denied.
sort=-updated_at,namesorts by up to three fields;-means descending.fields=name,domainreturns those fields in each record'sfields(max 20). Withoutfields, list and search results contain identity properties only (id,display,status, owner, timestamps), which keeps pages small and fast.- Retrieving a single record returns every readable field;
fieldsthere narrows the result.
POST /v1/objects/{object_type}/records/search takes the same filters as a JSON body, plus query, fields, sort, limit, and cursor. It never changes data, so it needs no Idempotency-Key. In JSON, in takes an array and a null value matches empty fields.
const API = 'https://api.preshos.com/v1';
const headers = {
Authorization: `Bearer ${process.env.PRESHOS_API_KEY}`,
'Content-Type': 'application/json',
};
const response = await fetch(`${API}/objects/companies/records/search`, {
method: 'POST',
headers,
body: JSON.stringify({
filters: {
lifecycle_stage: { in: ['customer', 'opportunity'] },
created_at: { gte: '2026-01-01T00:00:00Z' },
domain: { is_null: false },
},
fields: ['name', 'domain', 'lifecycle_stage'],
sort: [{ field: 'created_at', direction: 'desc' }],
limit: 50,
}),
});
const { data, pagination } = await response.json();POST /v1/objects/{object_type}/records/aggregate groups the filtered records by up to three fields and computes one to eight metrics over the complete filtered set, in the database. Operators are count, count_distinct, sum, avg, min, and max; omit field for a row count.
curl -X POST https://api.preshos.com/v1/objects/work-items/records/aggregate \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": { "work_item_type_id": "deliverable" },
"group_by": ["custom_status_id"],
"metrics": [{ "name": "total", "op": "count" }],
"order_by": { "name": "total", "direction": "desc" }
}'{
"data": [{ "custom_status_id": "st_in_progress", "total": 42 }],
"pagination": { "limit": 25, "has_more": false, "next_cursor": null }
}Lists return pagination: { limit, has_more, next_cursor }. To get the next page, send the same request again with cursor set to next_cursor. Stop when next_cursor is null.
- Cursors are opaque. Do not build or modify them.
- A cursor belongs to its query. Changing filters, sort, fields, or the text query while paging fails with
400 invalid_cursor; start again without a cursor. limitis 1–100 (default 25). Use 100 for bulk reads.
const API = 'https://api.preshos.com/v1';
const headers = { Authorization: `Bearer ${process.env.PRESHOS_API_KEY}` };
async function* listAll(objectType, params = {}) {
let cursor = null;
do {
const query = new URLSearchParams({ ...params, limit: '100' });
if (cursor) query.set('cursor', cursor);
const response = await fetch(`${API}/objects/${objectType}/records?${query}`, { headers });
const page = await response.json();
if (!response.ok) throw new Error(page.error.message);
yield* page.data;
cursor = page.pagination.next_cursor;
} while (cursor);
}
for await (const company of listAll('companies', { fields: 'name,domain', sort: 'name' })) {
console.log(company.id, company.fields.name);
}GET /v1/search searches work items, companies, contacts, meetings, events, and custom objects at once, filtered to what the user may read. Each hit carries object_type and id for the record endpoints, plus a url deep link into PRESHos. q needs at least two characters; limit is 1–30 (default 20).
curl -G https://api.preshos.com/v1/search \
-H "Authorization: Bearer $PRESHOS_API_KEY" \
--data-urlencode "q=acme"