The search endpoint
POST /api/v1/search, the request body field by field, what comes back, and why the response is always masked.
A search is a query against the warehouse, so it is a POST with a body rather
than a long query string.
The request
POST /api/v1/search
Authorization: Bearer fac_live_…
Content-Type: application/json
{
"category_ids": [12],
"country": "NO",
"admin1_id": 123, "admin2_id": null, "city_id": 456,
"polygon": null, // GeoJSON Polygon — excludes the ids above
"radius": null, // { lat, lon, metres } — same exclusion
"filters": {
"has_website": true, "has_email": true,
"has_phone": null, "phone_type": null,
"rating_min": 4.0, "reviews_count_min": "1-50",
"exclude_permanently_closed": true
},
"page": 1, "per_page": 50
}
Field by field
| Field | Notes |
|---|---|
category_ids | Array. Look them up with GET /api/v1/categories?q=…. All 4,000+ are searchable on every plan; how many you may combine in one call follows your tier — 100 on Growth, unlimited on Scale and Enterprise. (Starter has no API; in the app its limit is 25.) |
country | ISO-3166 alpha-2. Every level below it is optional — omit them all for a country-wide search. |
admin1_id, admin2_id, city_id | From GET /api/v1/geo/divisions and /geo/cities. Labels differ per country; use the level1_label the API returns rather than hardcoding “state”. |
polygon | GeoJSON Polygon. Mutually exclusive with the admin ids. |
radius | { lat, lon, metres }. Also mutually exclusive with the ids. |
filters | The same twelve as the UI. null = do not filter; false = only rows without it. |
page, per_page | Max 200 per page. |
The response
{
"total": 638,
"data": [ { …establishment…,
"email": "m***@cerv•••••.no",
"phone": "+47 2* ** *0 90" } ],
"credits_to_export": 638
}
totalis the true count over the whole match set, not the page size. Above 100,000 rows it may be an estimate, and then"total_is_estimate": trueis present and you should believe it.credits_to_exportis what exporting this exact search would cost right now. Use it to gate a job before you create it.- Contact fields are always masked, on every plan, for every key. There is no parameter that unmasks them — why. Unmasking happens only through an export, which bills credits.
Searching over the API is free too
Only exports are billed. Poll /search as often as your rate limit allows to
size a market, monitor a segment, or drive a UI of your own.
Looking things up first
GET /api/v1/geo/countries
// -> [{ iso2, name, flag, is_live, establishment_count,
// level1_label, level2_label }]
GET /api/v1/geo/divisions?country=NO&level=1
GET /api/v1/geo/divisions?country=NO&level=2&parent_id=123
GET /api/v1/geo/cities?country=NO&parent_id=456&q=osl
GET /api/v1/categories?q=account
City lookup is a prefix search and returns at most 50, ordered by
establishment_count descending.
Turning a search into a file
Pass the same body as the query field of an export:
creating and downloading exports.
Updated on: