Searching resources
How the POST /search endpoints work: the search_fields and filter_fields body, what each resource accepts, and the rules that decide whether a field is applied or quietly dropped.
Why this matters
A list endpoint takes query parameters and answers “give me the next page”. A search endpoint takes a body and answers “find the ones that look like this”. Multiple resources expose one, all on the same body shape, so once you can build the body for invoices you can build it for subscriptions, products or workflow executions.
The body has two parts:
search_fieldsis text search. It matches on names, references and other free-text attributes, with wildcards and case handling you control.filter_fieldsis exact filtering. It matches ids, statuses, types and date or amount ranges.
Both are lists, both are optional, and a resource has to match all the entries you send. There is no OR between entries.
The request
That reads: active standard subscriptions of one customer whose reference starts with “ACME”, ignoring case.
The response is the standard paginated wrapper, the same one the list endpoints return.
Text search with search_fields
The two wildcard flags combine into four kinds of match:
Sending several search_fields entries narrows the result: each one has to match.
Omitting name is not supported everywhere, and some resources have no text search at all. Each endpoint’s Search fields table below lists the names it takes, with an omitted row when a nameless entry is allowed.
Exact filtering with filter_fields
When to use value and when to use values
A field reads one of the two, and the three behaviours look identical from outside, which is why this is the part that most often silently returns the wrong rows:
- Single-valued fields such as
customer_id,idandtypereadvalue. Avalueslist is ignored. - Multi-valued fields read
values, and accept a singlevalueas a one-item list. A status filter is usually one of these. - A few multi-valued fields read
valuesonly. Sent asvalue, the filter is dropped and the request returns everything, with no error to tell you.
The Reads column in each endpoint’s filter table says which of the three a field is. Check it rather than assuming: a dropped filter looks exactly like a filter that matched a lot.
A name the endpoint does not know is not always an error. Most endpoints validate the names in both lists and answer 422 naming the entry; a few ignore the entry and return the unfiltered result, which looks like a working request that returns too much. Each subsection below says which of the two its endpoint does, so check your field names there rather than against the response.
Enum values
Where a filter is backed by an enum, the endpoint’s table below lists every value it accepts. Whether a value outside that list is refused depends on the endpoint: most validate it and answer 422 naming the field, while some match it as a plain string, so an unknown value returns no rows instead of an error. Each subsection says which.
Send enum values in upper case. The comparison is case-sensitive unless the subsection says the endpoint upper-cases for you, so active is not ACTIVE: a 422 on a validated field, and silently empty on one that is not validated.
Ranges
A range field takes its bounds as a single string in value, in the same bracket notation the list endpoints use:
A square bracket includes the bound and a curly bracket excludes it, so the two can be mixed: [100,*} is “100 or more”. Omitting both brackets is an exact match, and one bracket without the other is rejected. Dates are ISO 8601; amounts are plain numbers.
Custom fields
A custom_field filter matches a custom field by reference and value. It is written in one of two forms, and each endpoint’s filter table says which form it takes.
One form puts the reference in the reference field, which is the only place reference is read:
The other carries both halves in value, separated by a single colon:
A value with no colon, or with more than one, is rejected with custom fields needs to be formatted as key:value. A reference that matches no custom field on that resource type is rejected with Not able to find custom field with reference ….
To search custom field content as text rather than match it exactly, use the custom_field.value entry in search_fields instead. That entry carries no reference in either form, so it searches across every custom field the resource has.
Paging and ordering
Paging and ordering stay in the query string, not in the body:
page defaults to 1 and limit to 50. The fields you can order by are listed under Query parameters. A few search endpoints return a fixed order of their own and ignore both parameters; the subsection below says so where that is the case.
Supported fields per endpoint
Each resource below lists its search_fields names and its filter_fields names, with every value an enum filter accepts.
Customers
Search fields
Filter fields
An unknown or repeated name in either list is 422. role and processing_only are validated; status and type are not, so an unknown value returns no rows.
Pricing plans
Search fields
Filter fields
An unknown or repeated name in either list is 422, as is an unknown type or a customer_id that does not resolve. status and variant are not validated, so an unknown value there returns no rows. When the body also carries selection_rule_fields, a type filter has to be STANDARD.
Pricing plan subscriptions
POST /v1/pricing-plan-subscriptions/search
Search fields
Filter fields
An unknown or repeated name in either list is 422. Values are not validated, so an unknown status or type returns no rows. An empty values list is dropped rather than matching nothing.
status is the one filter on the platform that reads values and nothing else. Sent as value it is dropped silently, and the response carries every status rather than the one you asked for.
Searching by id in a search box. A search_fields entry with no name whose value is a valid resource id is treated as an exact filter rather than as text: a cust_… id filters on customer_id, and a ppsu_… id filters on the subscription’s own id. This exists so a single search box can take either a name or a pasted id. An id of any other type matches nothing. No other endpoint does this.
Products
Search fields
Filter fields
name is required on every search_fields entry. An unknown name in either list is 422, as is an unknown status, an unknown category reference or a customer_id that does not resolve.
Product items
Search fields
Filter fields
name is required on every search_fields entry. An unknown or repeated name is 422, and so is an unknown type, model_type or status. These three are upper-cased before they are checked, so case does not matter here.
Product categories
POST /v1/product-categories/search
Search fields
Filter fields
name is required on every search_fields entry, and an unknown name in either list is 422.
Invoices
Search fields
Filter fields
Every enum above is validated: an unknown value is 422 naming the field. An unknown name, though, is ignored rather than refused, so a typo in a field name quietly widens the result. customer.reference resolves to a customer and conflicts with a customer_id naming a different one. Pair invoice_amount_including_tax.quantity with invoice_amount_including_tax.currency to find every invoice over a threshold in one currency.
Reports
Search fields
Not supported. A search_fields entry with a name is 422.
Filter fields
Reports and report generate requests share one list of accepted names, so integration_id and status are accepted here without error but have no effect on a report search. Any other name is 422. report_code is passed through without validation, so an unknown code returns no rows. Results come back ordered by report_code descending unless you pass order_by.
Report generate requests
POST /v1/report-generate-requests/search
Search fields
Not supported. A search_fields entry with a name is 422.
Filter fields
An unknown or repeated name is 422, as is an unknown status. status is upper-cased before it is checked, so its case does not matter.
Bulk actions
Search fields
Not supported. A search_fields entry is ignored.
Filter fields
An unknown name is ignored. Both enums are validated. status is upper-cased first, so its case does not matter; type is not, so it has to be sent in upper case. A type entry needs a value.
Workflows
Search fields
Not supported.
Filter fields
Both are validated, so an unknown status or type is 422. A status entry with neither value nor values is 422 as well. workflowType is accepted as an older spelling of type, and a type sent as values is only read when the list holds exactly one entry. This endpoint returns its own order and ignores order_by and order_direction.
Workflow triggers
POST /v1/workflow-triggers/search
Search fields
Not supported.
Filter fields
All three read value; a values list is ignored. Any other name is 422, as is an unknown type. This endpoint returns its own order and ignores order_by and order_direction. See Workflow triggers.
Workflow action executions
POST /v1/workflow-action-executions/search
Search fields
Not supported.
Filter fields
Any other name is 422, as is an unknown status. The id filters take the first entry of values when value is absent. This is the endpoint to use for “what did this workflow do to this invoice”, since it filters on the action, the trigger and the status together. See Monitoring and audit.
API keys
This one does not use the shared body, and it does not page: it looks up one API key and returns it.
Sending neither username nor resource_id is rejected with Neither resource_id nor username are set, and a lookup that matches nothing answers 404.
Edge cases
- Searching needs the same
VIEWpermission as reading the resource. A key that cannot read invoices cannot search them either. - An empty body is valid on every endpoint that takes the shared body, and returns the first page of everything, which makes
POST /searchwith{}a usable list call. - Text search runs against stored values, so it will not find a resource by a figure that is calculated at read time.
- Sending the same field name twice is rejected with
field … already specifiedon every endpoint that validates names. Combine the values into one entry instead of repeating the entry. - The two lists are validated independently. A name that is valid in
filter_fieldsis not necessarily valid insearch_fields, and the error names which list it came from.