https://api.useagentshop.com/api/v1/headless-seo
Authentication
Every request must carry your store’s API key. The key resolves to exactly one store, so there’s no store id or token in the path.x-api-key: ask_xxxxxxxx… header if you prefer. The key is a secret — only call these endpoints from a server (never a browser). Get and rotate it in the dashboard under Settings → API Keys. See Security.
Endpoints
GET {base}/product/{handle}
Returns the product SeoBundle for a product handle (URL-encoded).
GET {base}/collection/{handle}
Returns the collection SeoBundle. Same shape as a product bundle; openGraph.type is "website".
GET {base}/site
Returns the SeoSiteBundle — { jsonLd } only, the site-level Organization + WebSite (+ FAQPage) graph.
GET {base}/sitemap.xml
Returns the product sitemap as XML (application/xml).
Response shape (SeoBundle)
The schema.org JSON-LD graph, e.g.
{ "@context": "https://schema.org", "@graph": [Product, BreadcrumbList] }. Serialize with serializeJsonLd before embedding — see Security.The page title, from your Shopify SEO fields.
The meta description.
The canonical URL for the page.
{ title, description, type: "product" | "website", url, siteName?, image? }{ card: "summary" | "summary_large_image", title, description, image? }Caching
All responses are served with:private — because each response is scoped to a store by a secret key, it must not be stored in a shared or CDN cache. Instead, your framework’s own cache does the work (the Next.js helpers add revalidate: 3600 ISR by default). X-Cache tells you whether the backend served the bundle from its cache.
Rate limits
Requests are rate-limited per key (i.e. per store) to 300 requests per minute. Over the limit returns429. Standard RateLimit-* headers are included on every response.
Status codes
| Code | Meaning |
|---|---|
200 | Bundle / XML returned |
401 | Missing or invalid API key (also a key rotated out more than 24h ago) |
404 | Unknown product or collection handle |
429 | Rate limit exceeded (300/min per key) |
502 | Upstream failure generating the bundle |
null bundle (see the never-throw contract); if you call the endpoints directly, handle non-200 the same way — render the page without SEO tags (except the sitemap route, which should surface the error).
