Skip to content

FAQ

Ordered roughly by how often integrations hit them.

Plan and access

We subscribe, but we get 403 and plan_required

contract=v1 requires the Pro plan or above. Basic and Free receive a 403.

If you have only just upgraded, check that Shopify's billing approval went through — until it does, the shop is still on its previous plan.

Dropping contract=v1 avoids the 403, but what comes back is the app's internal response, whose shape changes without notice. Do not build on it.

How do we tell a lapsed subscription from a temporary failure?

They are deliberately distinguishable.

SituationStatusMarker
Plan is insufficient403{"code": "plan_required"}
Outage or warm-up503 / 200degraded: true (normal body)

On plan_required, treat it as a contract problem rather than a broken search. Falling back to the theme's own search usually does less damage than showing a shopper an error.

degraded is temporary and resolves on its own.

Calling the endpoint

Can we call it from our own server, or from Hydrogen?

No. The endpoint runs through the Shopify App Proxy, on the store's own domain only. We send no CORS headers, so a page on another domain cannot read the response.

Server-side calls (curl) do reach it, but use from outside the store is not supported today. Tell us if you need it.

It works with curl but the browser cannot read it

That is the CORS behaviour above. Call it from JavaScript running on that store's own pages. Local development served from a different origin fails for the same reason.

contract=v1 on type=suggest returns 400

v1 covers the confirmed search (type=search) only. We have not written down the autocomplete response shape, so we do not return it as a contract (unsupported_contract_type).

Autocomplete works without contract, but that shape changes without notice. Tell us if you need it specified.

The response

Right after installing, everything returns total: 0

Check degraded. Every new store goes through this on day one.

json
{
  "degraded": true,
  "degraded_reason": "sync_pending",
  "total": 0,
  "results": []
}

It means the first product sync has not finished. An integration that ignores degraded looks like "it doesn't work right after installing".

The merchant's search analytics stay empty

One of two causes. Both leave search working normally, which is what makes them easy to miss.

1. session_id is not being sent. A search request without it is treated as not-a-shopper and excluded from analytics.

2. Shopify.analytics.publish is not implemented. Marutto Search does not render the product cards, so click-through and search-attributed revenue exist only through those events. Include measurement_proof.

See §7 of the specification.

?variant= does not select the variant

variants[].id is the numeric id ("44001"). Given a GID, Shopify ignores it silently rather than erroring.

js
// ✅
`/products/${item.handle}?variant=${variant.id}`;

// ❌ a GID is ignored
`/products/${item.handle}?variant=gid://shopify/ProductVariant/44001`;

The Cart AJAX API takes the same numeric form. Build a GID with "gid://shopify/ProductVariant/" + variant.id when you need one.

Prices do not match what the shopper sees

price_min / price_max are in the shop's base currency. On a store using Shopify Markets for multiple currencies, that is not the presentment currency.

Convert on your side, or fetch prices for rendering from Shopify's Storefront API. Tell us if multi-currency support matters to you.

We need a field that isn't in the specification

Fields can be added within v1 safely — existing integrations are unaffected. Tell us what you need.

What cannot change is the name, type or meaning of a field already published, and removal.

Filtering

filter_metafield does nothing and everything comes back

A metafield the merchant has not enabled for filtering in the admin is ignored rather than rejected. (A shared or bookmarked URL keeps naming a filter the merchant has since switched off; refusing it would leave shoppers stuck with a filter they cannot clear.)

While developing, a typo in the key name looks identical. Check it against facets.metafields[].name in the response first.

q=* returns no tag or variant facets

Browsing everything is a separate path that reads only the products table, so facets.tags, content_types and variant_options are always empty there — the keys are still present.

For the same reason filter_content_type=article always returns zero. To list articles or pages, use type=search with an actual search term.

Every other filter — vendor, price, metafields and so on — works under q=*.

Paging and load

"Load more" never disappears and duplicates keep appending

offset is capped at 2,000; larger values are clamped. Testing next < data.total alone keeps refetching the same page once you reach the ceiling.

js
const MAX_OFFSET = 2000;
if (next < data.total && next < MAX_OFFSET) {
  /* fetch the next page */
}

We are getting 429

Requests are capped per 10-second window. Autocomplete without debouncing reaches the per-visitor cap (40 per 10s) easily — 250–300ms is recommended.

There is also a combined cap across all stores, so a 429 you cannot predict from your own request rate is possible in principle (we have never reached it in production). Tell us in advance if you plan a load test.

Fast typing shows stale results

The response echoes back query, offset, limit and sort from the request. Discard any response whose values do not match what should currently be on screen.

Responses feel slow

The Server-Timing: marutto;dur=NN response header is our own processing time in milliseconds. Compare it against the total request time to see where the time goes.

A small dur with a slow total means the network path, not us.

Development

How do we try it before going live?

Install on a development store and apply the Pro plan. It responds at the same URL, in the same shape, as production.

We just want something that runs

Drop the reference implementation into the theme and it works. The rendering is meant to be replaced with your own, but three things should stay: contract=v1, the degraded check, and both Shopify.analytics.publish calls.

Our question isn't here

Contact us through the app in the Shopify admin. We reply within 12 hours, weekends included.

Marutto Search