POST /search
An expressive filter grammar across 5 core resources — contacts, companies, deals, invoices and workorders. The grammar follows the HubSpot pattern: filterGroups are OR'd together, filters inside a group are AND'd.
Endpoints
| Path | Required scope |
|---|---|
| POST /contacts/search | crm.contacts.read |
| POST /companies/search | crm.companies.read |
| POST /deals/search | crm.deals.read |
| POST /invoices/search | sales.invoices.read |
| POST /workorders/search | operations.workorders.read |
Request body
- filterGroups — up to 3 groups. Filters OR'd between groups, AND'd within.
- sorts — up to 3 sort keys. Default is
created_at DESC, id DESC. - query — optional full-text LIKE across the resource's
search_blob(contacts/companies/deals) or fallback columns (invoices/workorders). - limit — default 50, max 200.
- starting_after — cursor from
next_cursoron previous page. - Max 10 filters total across all groups. Max 100 values in IN/NIN.
curl -X POST https://app.staffifyai.com/api/workspace/v1/contacts/search \
-H "Authorization: Bearer $STAFFIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filterGroups": [
{
"filters": [
{ "property": "email", "operator": "CONTAINS", "value": "@acme.nl" },
{ "property": "created_at", "operator": "GTE", "value": "2026-01-01" }
]
},
{
"filters": [
{ "property": "lifecycle_stage", "operator": "EQ", "value": "customer" }
]
}
],
"sorts": [
{ "property": "created_at", "direction": "DESC" }
],
"query": "jan",
"limit": 100
}'Operators
| Operator | Description |
|---|---|
| EQ | Equals. value is a scalar. |
| NEQ | Not equals. |
| GT | Greater than. |
| GTE | Greater than or equal. |
| LT | Less than. |
| LTE | Less than or equal. |
| IN | In list. values is an array (max 100). |
| NIN | Not in list. values is an array (max 100). |
| CONTAINS | Substring match (strings only). |
| STARTS_WITH | Prefix match (strings only). |
| BETWEEN | Range. value is a 2-element array: [from, to]. |
| HAS_PROPERTY | Column IS NOT NULL. No value. |
| NOT_HAS_PROPERTY | Column IS NULL. No value. |
Response
{
"object": "list",
"data": [ /* ...resource objects... */ ],
"has_more": true,
"next_cursor": "7823",
"meta": { "request_id": "req_abc123" }
}Searchable properties
Only these property names are accepted. Unknown properties return 422 UNKNOWN_PROPERTY. This is a defense-in-depth guard against SQL injection — client input never becomes raw SQL.
POST /contacts/search
id, first_name, last_name, email, phone, external_id, crm_company_id, lifecycle_stage, is_archived, do_not_call, vat_number, registration_number, country, job_title, created_at, updated_at
POST /companies/search
id, name, external_id, vat_number, registration_number, website, industry, size, city, postcode, country, phone, email, owner_user_id, annual_revenue, is_archived, created_at, updated_at
POST /deals/search
id, name, value, currency, pipeline_id, stage, probability, close_date, crm_contact_id, crm_company_id, owner_user_id, created_at, updated_at
POST /invoices/search
id, invoice_number, status, crm_contact_id, crm_deal_id, total_amount, currency, due_date, sent_at, paid_at, reference, created_at, updated_at
POST /workorders/search
id, workorder_number, status, workorder_type, priority, assigned_team_member_id, crm_contact_id, hub_customer_id, installation_id, scheduled_at, reported_at, completed_at, signed_at, created_at, updated_at
Errors
422 UNKNOWN_PROPERTY— property is not in the whitelist for this resource.422 INVALID_VALUE— value is wrong type (e.g. non-numeric for number property) or invalid enum.422 INVALID_OPERATOR— operator is incompatible (e.g. CONTAINS on a number property).400 VALIDATION_FAILED— zod violated max limits (filters, groups, sorts, values).