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 is the complete reference:
how to discover the vocabulary, every operator and what it does, the full
field allowlists for companies and mining-assets, what publications/people
search actually accept, and how to resolve select-type values.
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:
{
"error": {
"code": "unsupported_filter_field",
"message": "Unknown or unsupported filter field for the public API: 'internal_score'.",
"field": "internal_score",
"resource": "companies"
}
}The same happens for columns, with unsupported_column as the code
instead.
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_endpoint": null,
"label": "Market Cap"
},
{
"field": "commodities",
"operators": ["has_any", "has_only"],
"type": "select",
"unit": 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 }
]
}Per filter entry:
field— the key to use in a searchfiltersclause.operators— the operators valid for this field.type— the field's value type (number,select,date, etc.).unit— the unit a numeric threshold should be expressed in, when applicable (e.g."USD"), otherwisenull.options_endpoint— forselect-type fields, the path to resolve valid values againstGET /v1/options/{path}/(see Resolving option values below);nullfor fields that don't need option resolution.label— a human-readable label for the field.
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 tables below (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 }
]
}Building a search payload
The search endpoints (POST /v1/companies/search/,
POST /v1/mining-assets/search/) take three parts:
filters— a list of clauses:{"field": "...", "operator": "...", "value": ...}. Everyfieldmust be one this resource's/v1/filters/{resource}/lists, and everyoperatormust be one of that field's advertisedoperators.logic— how multiple filters combine. One of:"all"(default) — every filter must match (AND)."any"— at least one filter must match (OR)."custom"— arbitrary boolean grouping, paired with anexpressionstring like"1 AND (2 OR 3)"referencing filters by their 1-based position in thefilterslist.expressionis only used, and only required, whenlogicis"custom"; omitting it whilelogic="custom"is a400.
columns— an optional comma-separated string of field keys to include in each result row. Omit it to get a default column set.
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']."
}
}Companies: full field allowlist
Every key below is valid both as a search filters.field (if it has
operators) and as a columns entry. unit is the unit to pass numeric
threshold values in. As of v1; the live source of truth is
GET /v1/filters/companies/.
| Field key | Type | Operators | Unit | Label |
|---|---|---|---|---|
name | string | — output only | — | Company |
market_cap_in_usd | number | gte, lte | USD | Market Cap |
total_cash_in_usd | number | gte, lte | USD | Cash in Bank |
cash_runway_months | number | gte, lte | months | Cash Runway |
hq_country | string | — output only | — | HQ |
website | string | — output only | — | Website |
exchanges | select | has_any, has_only | — | Exchanges (options: /exchanges/options/) |
industry | select | has_any | — | Industry (options: /industries/options/) |
ownership_type | select | has_any | — | Ownership (options: /ownership-types/options/) |
commodities | select | has_any, has_only | — | Primary commodities (options: /commodities/options/) |
by_products | select | has_any | — | By-products (options: /commodities/options/) |
all_commodities | select | has_any, has_only | — | All commodities (options: /commodities/options/) |
asset_phases | select | has_any, has_only | — | Phases (options: /mining-asset-phases/options/) |
asset_stages | select | has_any, has_only | — | Stages (options: /mining-asset-stages/options/) |
asset_permits | select | has_any | — | Asset permits (options: /mining-asset-permits/options/) |
asset_milestones | select | has_any | — | Asset milestones (options: /mining-asset-milestones/options/) |
stock_price | number | — output only | — | Stock Price |
change_1d … change_5y, change_ytd | number | gte, lte | % | N-period stock price change (1D/1W/1M/3M/6M/1Y/2Y/3Y/4Y/5Y/YTD) |
symbol | string | — output only | — | Symbol |
currency_symbol | string | — output only | — | Currency |
stock_price_currency_symbol | string | — output only | — | Stock Price Currency |
stock_price_ticker | string | — output only | — | Stock Price Listing |
asset_world_regions | select | has_any, has_only | — | World regions (options: /world-regions/options/) |
asset_countries | select | has_any, has_only | — | Countries (options: /countries/options/) |
asset_provinces | select | has_any, has_only | — | Provinces / states (options: /provinces/options/) |
price_sync_date | date | — output only | — | Price Sync Date |
company | select | has_any | — | Filter-only: scope by specific company id (options: /companies/options/); never appears as an output value |
change_1d, change_1w, change_1m, change_3m, change_6m, change_1y,
change_2y, change_3y, change_4y, change_5y, and change_ytd are 11
separate keys, each independently filterable/requestable with the same
gte/lte %-unit shape.
Mining assets: full field allowlist
As of v1; the live source of truth is GET /v1/filters/mining-assets/.
| Field key | Type | Operators | Unit | Label |
|---|---|---|---|---|
name | string | — output only | — | Mining Asset |
primary_commodity | select | has_any | — | Primary Commodity (options: /commodities/options/) |
commodities | select | has_any, has_only | — | All Commodities (options: /commodities/options/) |
by_products | select | has_any | — | By-products (options: /commodities/options/) |
world_regions | select | has_any | — | World Regions (options: /world-regions/options/) |
country | select | has_any | — | Country (options: /countries/options/) |
phase | select | has_any | — | Phase (options: /mining-asset-phases/options/) |
stage | select | has_any | — | Stage (options: /mining-asset-stages/options/) |
activity_status | select | has_any | — | Activity Status (options: /mining-asset-activity-statuses/options/) |
milestones_reached | select | has_any | — | Milestones (options: /mining-asset-milestones/options/) |
granted_permits | select | has_any | — | Permits (options: /mining-asset-permits/options/) |
asset_type | select | has_any | — | Asset Type (options: /asset-types/options/) |
mine_type | select | has_any | — | Mine Type (options: /mine-types/options/) |
deposit_type | select | has_any | — | Deposit Type (options: /deposit-types/options/) |
province | select | has_any | — | Province/State (options: /provinces/options/) |
location | string | — output only | — | Location |
primary_company_name | string | — output only | — | Primary Company |
primary_company_market_cap_in_usd | number | gte, lte | USD | Company Market Cap |
exchanges | select | has_any, has_only | — | Company exchanges (options: /exchanges/options/) |
primary_company_price_sync_date | date | — output only | — | Price Sync Date |
mining_method | string | — output only | — | Mining Method |
processing_method | string | — output only | — | Processing Method |
facility_type | select | has_any | — | Facility Type (options: /facility-types/options/) |
latest_economic_assessment_effective_date | date | gte, lte | — | Study Date |
latest_economic_assessment_study_type | string | — output only | — | Study Type |
latest_economic_assessment_npv_usd | number | gte, lte | USD | Study NPV (post-tax, USD) |
latest_economic_assessment_discount_rate | number | — output only | — | NPV Discount |
latest_economic_assessment_irr | number | gte, lte | % | Study IRR (post-tax) |
latest_economic_assessment_capex_usd | number | gte, lte | USD | Study Initial CapEx |
npv_to_capex | number | — output only | — | NPV / CapEx |
latest_economic_assessment_mine_life_years | number | — output only | — | Mine Life |
latest_drill_results_date | date | gte, lte | — | Latest Drill Results Date |
drillhole_count | number | gte, lte | — | Drillhole Count |
best_interval_value | number | gte, lte | $·m/t | Best Drill Interval Value |
mining_asset | select | has_any | — | Filter-only: scope by specific mining asset id (options: /mining-assets/options/); never appears as an output value |
company | select | has_any | — | Filter-only: scope assets to a set of companies via their current owning relation (options: /companies/options/); never appears as an output value |
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. What follows is transcribed from what
each search actually accepts.
Publications
POST /v1/publications/search/:
| Param | Type | Default | Notes |
|---|---|---|---|
query | string | "" | Free text; empty string is a valid no-op query. |
scope | string | "both" | One of body, title, both. An unrecognized value is a 400. |
company_ids | array of int | null | Filter by one or more company ids. |
mining_asset_id | int | null | Filter by a single asset id. |
content_kind | string or array | null | Must be a valid content-kind value (400 otherwise). |
content_variant | string or array | null | Must be a valid content-variant value. |
topics | array of int | null | Topic ids. |
source_type | string or array | null | Must be a valid source-type value. |
exchange_ids | array of int | null | Filter by exchange id(s). |
published_after / published_before | ISO date string | null | A non-ISO string is a 400. |
order | string | "newest" | One of newest, oldest, relevance. relevance with an empty query is a 400. |
limit | int | 20 | Clamped to 1–50 (non-numeric falls back to the default rather than erroring). |
offset | int | 0 | Clamped to ≥ 0. |
The response adds one key beyond the standard results/count/truncated
shape: scope. This is not an echo of the request's scope string —
it's {"entity_type": "company" | "mining_asset", "id": <id>} when the
query resolves to exactly one company or one asset (via company_ids/
mining_asset_id), otherwise null.
People
POST /v1/people/search/:
| Param | Type | Default | Notes |
|---|---|---|---|
query | string | "" | Free text, whitespace-split; each term is AND-matched (case-insensitive contains) against name, aliases, and primary company. |
limit | int | 10 | Clamped to 1–25. |
offset | int | 0 | Clamped to ≥ 0. |
Response is the standard {results, count, truncated} shape — no extra
keys. Unlike publications, no input here raises a 400 — every
parameter is clamped or defaulted rather than validated. The only 400 you
can get from this endpoint is DRF's own unparseable-JSON ParseError
({"detail": "..."}), not the {"error": {...}} envelope.
Both endpoints can return a 408 (query_timeout) if the query runs past
the server's statement timeout — narrow your search term (or, for
publications, add a company/date filter) and retry.
Resolving option values
GET /v1/options/{path}/
For select-type fields, use the field's options_endpoint (from
/v1/filters/{resource}/, or the tables above) to look up the exact
{id, label} pairs valid for that filter — rather than guessing at
spellings or internal ids. To call it, strip the leading/trailing slashes
from options_endpoint's value and drop it into the {options_path}
segment of this route — an options_endpoint of /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" }
]
}query— the search term to resolve (empty string returns the top matches unfiltered).limit— maximum number of matches to return (default10).
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 |
|---|---|---|
asset-types/options | Mining asset types | string |
case-categories/options | Support case categories | integer |
commodities/extraction-options | Extraction-eligible commodities | integer |
commodities/options | Commodities | integer |
companies/options | Companies | integer |
content-kinds/options | Publication content kinds | string |
content-variants/options | Publication content variants | string |
countries/options | Countries | integer |
deal-statuses/options | Deal statuses | string |
deal-types/options | Deal types | string |
deposit-types/options | Mining asset deposit types | string |
exchanges/options | Stock exchanges | integer |
facility-types/options | Mining asset facility types | string |
hole-types/options | Drill hole types | string |
industries/options | Industries | string |
mine-types/options | Mine types | string |
mining-asset-activity-statuses/options | Mining asset activity statuses | string |
mining-asset-milestones/options | Mining asset milestones | string |
mining-asset-permits/options | Mining asset permits | string |
mining-asset-phases/options | Mining asset phases | string |
mining-asset-stages/options | Mining asset stages | string |
mining-assets/options | Mining assets | integer |
ownership-basis/options | Ownership basis | string |
ownership-types/options | Company ownership types | string |
production-period-types/options | Production period types | string |
production-value-types/options | Production value types | string |
provinces/options | Provinces / states | integer |
shareholder-statuses/options | Shareholder statuses | string |
source-types/options | Publication source types | string |
tenure-statuses/options | Tenure statuses | string |
tenure-types/options | Tenure types | string |
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 Companies: full field
allowlist above) 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.