https://api.useagentshop.com/api/v1/headless-seo
The crawler endpoint is a sibling of that base — https://api.useagentshop.com/api/v1/crawler-hits.
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).
GET {base}/pixel
Returns where to load the storefront analytics script from, and what the analytics destination needs. See Storefront analytics.
string
required
Fully-formed URL of the pixel script, token included. All
<AgentShopPixel /> needs.string
required
The store’s pixel token. Browser-visible by design — it already rides in
scriptUrl, and pixel ingestion is a public endpoint.string
required
Where the analytics destination posts events.
string
required
The store’s Shopify domain, as ingestion expects to receive it.
Cache-Control: no-store: the response is a per-store credential on a URL that is identical for every store — only the Authorization header differs — so no HTTP cache may ever replay it across keys. Freshness comes from the SDK re-resolving per render (the Next helper caches for 5 minutes in your app, not in HTTP). Returns 404 when the store has no pixel configured.
The analytics destination and the served script both POST events to eventsUrl. Every event is accepted and captured whether or not it carries an AI acquisition signal — the response is {"success": true, "attributed": <boolean>}, true when the server resolved the event to an AI source (sent by the served script, or inferred server-side from the raw signals). Unattributed events don’t surface as AI-referred visits — see why you may see no events. The one drop is bot and search-crawler user agents, answered with {"success": true, "skipped": true}. event_data is an allowlisted flat projection (product_id, product_title, price, quantity, variant_id, handle, collection_id, search_term — whichever apply to the event); the raw Shopify bus payload is deliberately not forwarded. See Storefront analytics for the full picture.
POST /api/v1/crawler-hits
Records one AI-crawler request to your storefront. See AI crawler visibility.
An accepted hit returns 202, with { "recorded": true } when it was handed to the analytics pipeline or { "recorded": false } if that failed after acceptance — in both cases there is nothing to retry.
202 is not the only outcome. The request body is validated strictly, so any of these return 400: a url or referrer over 2048 characters or on a non-http(s) scheme, method over 16, statusCode outside 100–599, a blank or over-long userAgent, or any field not listed above. A bad or missing key returns 401, and exceeding the crawler-hits budget returns 429. If you wire “202 means done”, long product URLs disappear with no signal — check the status, not just the body.
string
required
Absolute URL of the request. Must be
http: or https: — other schemes are rejected.string
required
HTTP method.
string
required
The
User-Agent header, verbatim. Identification happens server-side.number
Response status, when your runtime knows it. Omit rather than guess — see Status codes.
string
The
Referer header, if present. Must be http: or https: if sent — other schemes are rejected, not stripped, so omit rather than guess if you’re unsure.Response shape (SeoBundle)
object
required
The schema.org JSON-LD graph, e.g.
{ "@context": "https://schema.org", "@graph": [Product, BreadcrumbList] }. Serialize with serializeJsonLd before embedding — see Security.string
required
The page title, from your Shopify SEO fields.
string
required
The meta description.
string
required
The canonical URL for the page.
object
required
{ title, description, type: "product" | "website", url, siteName?, image? }object
required
{ 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. Two responses differ: /pixel sends no-store (its body is a credential), and a stale-but-served sitemap sends private, no-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
Rate limits are per key (i.e. per store), in separate pools:
Crawler reporting has its own, larger budget on purpose: a multi-bot recrawl after a sitemap update is a burst, and it must not starve the same store’s SEO-artifact serving — or be starved by it. Over the limit returns
429. Standard RateLimit-* headers are on every response.
Status codes
The SDK treats any non-2xx as a
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).
