Querying your site

Every Minamo site exposes one public, read-only endpoint over its own published content — a listing page, a search box, a filtered product grid, all without a token, a server of your own, or touching the admin at all. A theme’s own front-end JavaScript calls it directly, the same way it would call any other API.

GET /search.json

No token required, and not part of the versioned API

Unlike the rest of this agent’s HTTP API (see The v1 API, which most sites never need to touch), this one endpoint needs no bearer token at all - and deliberately lives outside the versioned /v1/ surface entirely, at a stable, unprefixed path. It’s read-only and can only ever return already-published data — drafts and unpublished pages are never indexed — so there’s nothing in its response a site visitor couldn’t already see by browsing the live site. Call it with a plain same-origin fetch() from a section’s own <script>.

The .json is deliberate: it reserves this one exact path, not the whole /search namespace, so a theme is still free to build a real content page at the bare /search URL — the worked example below does exactly that.

One endpoint covers general search, a paginated blog listing, and a filtered product grid:

API
GET /search.json?q=trail
GET /search.json?pageType=blog-article&sort=-publishDate&limit=10
GET /search.json?filter=category:eq:shoes&filter=price:lt:100&sort=-price
ParamRequiredMeaning
qnoFree-text search against title and body. Safe against malformed input (a stray quote, a trailing operator) — never passed raw into the underlying full-text query.
pageTypenoNarrow results to one type value — entirely developer-chosen (a theme's own template discriminator, e.g. page or blog-article), not a fixed set.
filterno, repeatablefield:op:value (or just field:value for equality), ANDed together. A field must be flagged "api": true in its own schema property (see Sections & blocks) — a page's own built-in, optional author/publishDate/tags fields are queryable this way too, with no flag needed. op is eq (default), gt, gte, lt, or lte — the four comparison operators only apply to numeric fields.
sortnoA field name, or -field for descending. Defaults to relevance when q is given, otherwise url order.
limitnoDefault 20, capped at 100.
offsetnoDefault 0.

Returns { results, limit, offset, hasMore }. Each result is { url, title, pageType, fields } — fields holds every one of that page’s own indexed values (not just whichever ones were filtered or sorted on), so a product grid can render a price straight from the search response rather than fetching each page separately. A page carrying several matching instances of the same field (several “product” blocks on one listing page, say) only needs one to match, and each instance’s own value stays independently queryable rather than one shadowing another.

Worked example

See theme/sections/search-demo.liquid in the granite-starter reference theme for a complete, working search box: markup, scoped styling, and the fetch() call that drives it, all in one file — debounced on input, with stale-response handling for out-of-order replies.

Keeping the index fresh

The index this endpoint queries is derived and disposable, rebuilt from the content files on disk — it’s not updated automatically on every publish. That rebuild is a write operation and does need a token, so it’s not something a theme’s own front-end triggers itself; it’s the admin’s job (or your own integration’s, if you’re building one — see The v1 API).

Next

Continue to Hosting & deployment to run a site somewhere real. Building an admin, an automation script, or anything that needs to write content, not just read it? See The v1 API instead — a different, token-based surface most sites never need to touch directly, since the admin app already handles it.