marketplace
GET /v1/marketplace/{vertical}/search
Filter, sort and page one vertical's live Zinn®s.
Authentication
This endpoint takes no API key. Anything else it needs, such as a signed request, is described above.
This endpoint takes no organisation id. Your key already identifies the organisation it belongs to, and the response is scoped to it.
Try it
Replace anything in angle brackets with your own values, and the key placeholder with a key from your dashboard.
curl -X GET https://api.zinndigital.com/v1/marketplace/{vertical}/search \
-H "Authorization: Bearer zdk_live_…"Signed in? The API console in your dashboard fills in your real organisation id and your own key, and runs the request against the live API so you can see the actual response. Open this endpoint in the API console
Details
docs/83 §2 step 4. Anonymous, single-variant, and answered entirely from the derived publication projection — never from the org-scoped authoring tables. ⛔ The facet parameters are generic on purpose. Fields are defined by staff in admin, so a typed parameter per filter would mean a spec change, an SDK regeneration and a deploy every time somebody added one — which is the exact property this milestone exists to remove. A filter is facet=key:op:value, repeated; values of the same key are OR-ed, different keys are AND-ed. ⛔ A numeric filter requires type. The promoted slot behind da holds a different field for every listing type, so asking da:gte:50 across a whole vertical would compare a domain authority against whatever the next kind of listing stores in the same column — wrong, plausible, and impossible to notice. It is refused with 422. ⛔ declared/of on every facet are not decoration. A field added in schema v2 does not exist on v1 listings, so filtering on it silently excludes them and "DA ≥ 50" quietly becomes "and authored after the day DA was added". The two numbers are what let the page say "482 of 1,930 Zinn®s declare DA" instead of implying the other 1,448 failed the filter. ⛔ total is probed to 10,000 and total_is_capped says so. An exact count of a large filtered set costs as much as returning it.
Parameters
| Name | Type | Required | What it is |
|---|---|---|---|
vertical (path) | string | Yes | — |
locale (query) | string | No | Render every operator-authored string — field labels, help text, enum option labels — in this locale, falling back to the source language per string. ⛔ A query parameter rather… |
type (query) | string | No | A listing type key. Defaults to the vertical's only live type when it has exactly one; required before any facet filter on a vertical that sells several types. |
category (query) | string | No | A category path. Matches that category AND everything beneath it. |
q (query) | string | No | What the buyer typed. Truncated at 120 characters rather than refused — pasting an essay into a search box is not an attack. ⭐ Answered by hybrid retrieval (docs/83 §2a, owner… |
facet (query) | string[] | No | key:op:value, repeatable. op is gte/lte for numbers and eq for choices and yes/no fields. |
price_min (query) | integer | No | s1006 (Q59). The lowest "from" price (price_minor, the cheapest package), in integer minor units of currency — 5000 is 50.00. Inclusive. |
price_max (query) | integer | No | The highest "from" price, in integer minor units of currency. Inclusive. ⛔ A price bound with no currency is refused (CURRENCY_REQUIRED): comparing one listing's dollars… |
currency (query) | string | No | ISO 4217 code the price band is in. Required with price_min/price_max. |
sort (query) | string<newest, price, -price, rating, sales> | No | — |
cursor (query) | string | No | Keyset page marker from a previous response. Never an offset. |
limit (query) | integer | No | — |
Response
| Name | Type | Required | What it is |
|---|---|---|---|
marketplace | object | Yes | — |
canonical_vertical | string | No | Set when the request used a SUPERSEDED slug — the segment this page should 301 to. null on the normal path. ⛔ A retired slug still RESOLVES rather than 404ing: docs/83 §6 puts… |
listing_type | string | No | — |
results | MarketplaceSearchCard[] | Yes | — |
next_cursor | string | Yes | Empty when there is no next page. Opaque — never build one by hand. |
total | integer | Yes | — |
total_is_capped | boolean | Yes | True when there are more than total results. The page renders "10,000+"; showing a flat "10,000" for a catalogue of two million would be false. |
sort | string | Yes | — |
sorts | string[] | Yes | The sorts this catalogue offers, served with the data so the storefront is not compiled against a copy of the list. |
facets | MarketplaceFacet[] | Yes | — |
query | string | Yes | What was actually searched for, after the filter language took its bite. Empty when no phrase was given. ⭐ Differs from what the buyer typed whenever interpreted is non-empty:… |
interpreted | string[] | Yes | Human-readable chips for filters INFERRED from the phrase rather than ticked on the rail — already in the requested locale. ⛔⛔ Render these. A search box that silently… |
retrieval | string<lexical, trigram, semantic>[] | Yes | Which retrieval paths actually ran for this request (docs/83 §2a, D-W15-9). Empty when no phrase was given, or when the deployment has no retrieval objects. ⛔⛔… |
Errors this endpoint can return
404 · 422