Filters
The curated filter vocabulary, operator semantics, and option lookups
Every resource on the public API exposes a deliberately curated set of filterable fields and response columns — not the full internal field set Pulse's own product UI can filter on. This guide covers the concepts: how to discover the vocabulary, every operator and what it does, how a search payload combines filters, and how to resolve select-type values. The complete field-by-field listing lives in the filter & column reference.
Curated vocabulary
Each resource — companies, mining-assets — has its own allowlist of
field keys it exposes publicly. A field only becomes available once someone
deliberately adds it to that resource's allowlist; new internal fields are
excluded by default, not exposed by default. This keeps the public contract
stable and avoids leaking user-scoped internal concepts (like a saved
company set, which has no meaning for an organization-wide API key) onto the
public surface.
publications and people search have no such allowlist — see
Publications and people search below for
what they actually accept.
If you reference a filter field or request a column outside a resource's
curated set, the request fails with a 400 — unsupported_filter_field
for filters, unsupported_column for columns; see
Common error codes
for the response shape.
Discovering fields
GET /v1/filters/{resource}/
Call this endpoint to get the current filterable fields and available
columns for a resource — the same set that resource's /search endpoint
will accept. resource is one of companies or mining-assets.
curl https://api.pulseintelligence.com/v1/filters/companies/ \
-H "Authorization: Bearer pulse_a1b2c3d4_<secret>"A truncated response for companies looks like:
{
"resource": "companies",
"filters": [
{
"field": "market_cap_in_usd",
"operators": ["gte", "lte"],
"type": "number",
"unit": "USD",
"options": null,
"options_endpoint": null,
"label": "Market Cap"
},
{
"field": "industry",
"operators": ["has_any"],
"type": "select",
"unit": null,
"options": [
{ "id": "mining", "label": "Mining" },
{ "id": "steel", "label": "Steel" }
],
"options_endpoint": null,
"label": "Industry"
},
{
"field": "commodities",
"operators": ["has_any", "has_only"],
"type": "select",
"unit": null,
"options": null,
"options_endpoint": "/commodities/options/",
"label": "Primary commodities"
}
],
"columns": [
{ "key": "market_cap_in_usd", "title": "Market Cap", "type": "number", "sortable": true },
{ "key": "commodities", "title": "Primary Commodities", "type": null, "sortable": false }
]
}Every field of a filter or column entry is documented on the
List filters reference page. The two
that drive the rest of this guide: operators is the set of operators the
field accepts (see Operator semantics below), while a
select field supplies either closed inline options or an options_endpoint
for request-time resolution (see Resolving option values below).
An unsupported resource returns a 400:
{
"error": {
"code": "invalid_argument",
"message": "Unsupported resource 'listings'. Available in the public API: ['companies', 'mining-assets'].",
"resource": "listings"
}
}Operator semantics
The shared filter engine (pulse_table) defines a larger operator set, but
the public API's curated fields only ever advertise these four — every
operators array you'll see from GET /v1/filters/{resource}/ is a subset
of this list:
| Operator | Field types | Value shape | Meaning |
|---|---|---|---|
gte | number, date | a single number, or an ISO date string | Field ≥ value. |
lte | number, date | a single number, or an ISO date string | Field ≤ value. |
has_any | select | non-empty list of option ids | Match if the field's value (or, for a multi-value field, any of its values) is in the given list — an OR match. |
has_only | select | non-empty list of option ids | Exact-set match: matches only rows with no qualifying value outside the given list — the complement of has_any. Every has_only field in the filter & column reference (exchanges, commodities, all_commodities, asset_phases, asset_stages on companies; commodities, exchanges on mining assets) implements this exact-set semantic. |
No public field uses eq, ne, gt, lt, exact, contains, or
range — those exist in pulse_table for internal product filters but
aren't reachable through any field in either resource's curated allowlist.
A gte/lte pair on the same field is how you express a numeric or date
range (there's no separate range operator on the public surface):
{
"filters": [
{ "field": "market_cap_in_usd", "operator": "gte", "value": 100000000 },
{ "field": "market_cap_in_usd", "operator": "lte", "value": 500000000 }
]
}Private company and asset sets from the signed-in application are not public API filter fields. Use the curated fields listed by the filter reference. Custom query expressions remain available here; these requests do not save or modify a saved search.
Building a search payload
The search endpoints (POST /v1/companies/search/,
POST /v1/mining-assets/search/) take a list of filters clauses, a
logic mode that combines them, an optional comma-separated columns
string shaping each result row, and optional limit/offset/sort
paging parameters (covered in
Pagination and enumeration — including what an
empty filters list does) — each parameter is documented in full on
the Search companies and
Search mining assets reference
pages. The part that deserves explanation is logic="custom": it pairs
with an expression string like "1 AND (2 OR 3)" that references
filters by their 1-based position in the filters list, letting you
group AND and OR arbitrarily. expression is only used, and only
required, when logic is "custom" — omitting it there is a 400.
One practical note on columns: omitting it returns the resource's full
default row — several hundred keys, including per-commodity metric and
projection families beyond what the
filter & column reference lists as
filterable. Those extra keys are read-only output, not part of the curated
filter contract. Requesting an explicit columns set keeps rows lean and
your daily row quota usage low.
An invalid logic value is rejected before your filters are ever evaluated:
{
"error": {
"code": "invalid_argument",
"message": "logic must be one of ['all', 'any', 'custom']."
}
}Full field allowlist
The complete allowlist — every filterable field with its operators, unit,
and options endpoint, plus every response column key — is published as the
filter & column reference. It is generated
from the same vocabulary GET /v1/filters/{resource}/ serves, so the two
never disagree; use the live endpoint when you need the vocabulary
programmatically.
Publications and people search
POST /v1/publications/search/ and POST /v1/people/search/ have no
curated vocabulary layer — there's no /v1/filters/ equivalent for them
and no allowlist to reject against. Instead of the filters/logic DSL,
each takes its own structured parameters (a free-text query plus
entity/date/type filters for publications; query/limit/offset for
people), documented in full on their reference pages:
Search publications and
Search people.
Two behavioral quirks worth knowing beyond the schemas: people search
never rejects input — every parameter is clamped or defaulted rather
than validated, so its only possible 400 is an unparseable JSON body —
and both endpoints can return a 408 (query_timeout) if a broad query
runs past the server's statement timeout; narrow the search term (or, for
publications, add a company/date filter) and retry.
Resolving option values
GET /v1/options/{path}/
For select-type fields, first read the field's options array from
/v1/filters/{resource}/.
Closed vocabularies such as company industry and mining-asset phase carry their exact {id, label} pairs inline.
When options is null, use the field's options_endpoint to resolve a database-backed or user-scoped vocabulary rather than guessing at spellings or internal ids.
Strip the leading/trailing slashes from options_endpoint and place it in the {options_path} segment of this route.
For example, /commodities/options/ becomes GET /v1/options/commodities/options/.
curl "https://api.pulseintelligence.com/v1/options/commodities/options/?query=gold&limit=5" \
-H "Authorization: Bearer pulse_a1b2c3d4_<secret>"{
"query": "gold",
"matches": [
{ "id": 1, "label": "Gold (Au)" }
]
}query— the search term to resolve (empty string returns the top matches unfiltered).limit— maximum number of matches to return (default10).
id and label are the guaranteed keys on every match; some option types
return additional informational keys alongside them (commodities, for
example, include a shortLabel). Treat extras as display context — the
id is the only part a filter value needs.
The id type nuance
matches[].id is not always the same type — it's an integer for
options backed by a real database model (companies, mining assets,
commodities, exchanges, countries, provinces, world regions, topics, case
categories), and a string for options backed by a static choice enum
(statuses, types, phases, and similar closed vocabularies). Don't assume
one shape — read the type back from the response and pass it through
unchanged as the filter value.
Allowed options paths
{path} isn't arbitrary — only this bounded set of reference-data
endpoints is resolvable (the same allowlist Pulse's MCP server enforces for
resolve_options):
| Options path | Resolves | id type |
|---|---|---|
case-categories/options | Support case categories | integer |
commodities/extraction-options | Production-extraction commodities | integer |
commodities/options | Commodities | integer |
companies/options | Companies | integer |
content-variants/options | Publication content variants | string |
countries/options | Countries | integer |
exchanges/options | Stock exchanges | integer |
mining-asset-phases/options | Mining asset phases | string |
mining-asset-stages/options | Mining asset stages | string |
mining-assets/options | Mining assets | integer |
provinces/options | Provinces / states | integer |
topics/options | Publication topics | integer |
world-regions/options | World regions | integer |
Calling an endpoint outside this allowlist — or one that's excluded because it's per-user data rather than reference data (like a saved company set) — 400s:
{
"error": {
"code": "invalid_argument",
"message": "Unknown options endpoint: bogus/options",
"options_endpoint": "bogus/options"
}
}A non-integer limit also 400s the same way, rather than silently falling
back to a default:
{
"error": {
"code": "invalid_argument",
"message": "limit must be an integer, got 'abc'"
}
}Worked example: discover, search, resolve
One coherent flow: find what's filterable on companies, build a two-clause
search, then resolve an option id for a select field.
1. Discover the vocabulary:
curl https://api.pulseintelligence.com/v1/filters/companies/ \
-H "Authorization: Bearer pulse_a1b2c3d4_<secret>"This response (see the
filter & column reference for
the full listing) tells you
market_cap_in_usd takes gte/lte with no option resolution needed, and
commodities takes has_any/has_only with options_endpoint: "/commodities/options/".
2. Resolve the commodity you want to filter by:
curl "https://api.pulseintelligence.com/v1/options/commodities/options/?query=gold&limit=3" \
-H "Authorization: Bearer pulse_a1b2c3d4_<secret>"{
"query": "gold",
"matches": [
{ "id": 1, "label": "Gold (Au)" }
]
}3. Build the two-clause search using the resolved id:
curl https://api.pulseintelligence.com/v1/companies/search/ \
-H "Authorization: Bearer pulse_a1b2c3d4_<secret>" \
-H "Content-Type: application/json" \
-d '{
"filters": [
{ "field": "market_cap_in_usd", "operator": "gte", "value": 100000000 },
{ "field": "commodities", "operator": "has_any", "value": [1] }
],
"logic": "all",
"columns": "name,symbol,market_cap_in_usd,commodities"
}'This returns gold companies with a market cap of at least $100M, with just the four requested columns in each result row.
Go deeper
- Rate limits and errors covers the error envelope shape and quota headers referenced throughout this guide.
- The API Reference has the full schema
for
/v1/filters/{resource}/and/v1/options/{path}/, plus every search and dossier endpoint.