SEO Signals API
The SEO Signals API is the canonical endpoint headless storefronts use to fetch Clione's enriched SEO/AEO/GEO signals for a specific entity. It's also what powers the @clione/seo SDK under the hood.
Use it when:
- You're building a headless / custom storefront and want to render Clione's signals in your own template.
- You don't want to install an embed widget or theme app block.
- You need fine-grained control over which signals you read and where you render them.
For non-headless stores (BigCommerce Stencil or Shopify Liquid themes), prefer the Schema Injector (BC) or the theme app block (Shopify) — they handle the same delivery automatically.
Endpoint
GET /api/v1/public/seo-signals/:platform/:entityType/:entityId
Path parameters:
| Param | Values |
|---|---|
platform | bigcommerce | shopify |
entityType | product | category | collection | page |
entityId | The platform-native ID of the entity (BC product ID, Shopify product GID or numeric ID) |
Authentication
Send your Clione public API key in one of three ways (in order of preference):
Authorization: Bearer sk_live_...
x-api-key: sk_live_...
?apiKey=sk_live_...
The key must have products:read scope. Generate keys from Store → Settings → API Keys.
Domain whitelisting applies: if your key has allowed domains set, the request's Origin header (or Referer, fallback) must match. Set the allowed domain to your headless storefront's domain.
Response shape
{
"entity": {
"id": "1234",
"platform": "shopify",
"entityType": "product",
"name": "Linen bed sheet set",
"url": "https://shop.example.com/products/linen-sheet-set"
},
"signals": {
"metaTitle": "Linen Bed Sheet Set — Cool, Breathable, Made in Portugal",
"metaDescription": "Pure European linen sheet set ideal for hot sleepers...",
"canonicalUrl": "https://shop.example.com/products/linen-sheet-set",
"keywords": ["linen sheets", "breathable bedding", "summer bedding", "sábanas de lino"],
"ogTags": {
"title": "Linen Bed Sheet Set",
"description": "Cool, breathable bedding...",
"image": "https://cdn.example.com/linen.jpg",
"type": "product"
},
"jsonLd": {
"@context": "https://schema.org",
"@type": "Product",
"name": "Linen Bed Sheet Set",
"description": "...",
"keywords": "linen sheets, breathable bedding...",
"offers": {
"@type": "Offer",
"price": "129.00",
"priceCurrency": "EUR"
}
}
},
"metadata": {
"enrichedAt": "2026-05-30T11:23:45.000Z",
"version": 4
}
}
Empty fields are omitted (not returned as null). If the entity isn't enriched, signals is {} and metadata.enrichedAt is null.
Example — fetch
const apiKey = process.env.CLIONE_API_KEY
const platform = 'shopify'
const productId = '1234'
const res = await fetch(
`https://api.clione.ai/api/v1/public/seo-signals/${platform}/product/${productId}`,
{
headers: { Authorization: `Bearer ${apiKey}` },
},
)
if (!res.ok) throw new Error(`Clione SEO Signals: ${res.status}`)
const { signals, entity } = await res.json()
Example — curl
curl -s \
-H "Authorization: Bearer sk_live_..." \
"https://api.clione.ai/api/v1/public/seo-signals/shopify/product/1234" | jq .
Rendering in Next.js (Hydrogen / Catalyst / Vue Storefront)
Most frameworks have a head / metadata API. Map the signals onto it:
Next.js App Router
import { getSignals } from '@clione/seo'
export async function generateMetadata({ params }) {
const { signals, entity } = await getSignals('product', params.id, {
apiKey: process.env.CLIONE_API_KEY,
storeId: process.env.CLIONE_STORE_ID,
platform: 'shopify',
})
return {
title: signals.metaTitle,
description: signals.metaDescription,
alternates: { canonical: signals.canonicalUrl },
openGraph: signals.ogTags,
other: {
'script:ld+json': JSON.stringify(signals.jsonLd),
},
}
}
Nuxt / Vue Storefront
import { useClioneSignals } from '@clione/seo/nuxt'
const { signals } = await useClioneSignals('product', productId)
useHead({
title: signals.metaTitle,
meta: [
{ name: 'description', content: signals.metaDescription },
...Object.entries(signals.ogTags).map(([k, v]) => ({ property: `og:${k}`, content: v })),
],
link: [{ rel: 'canonical', href: signals.canonicalUrl }],
script: [{ type: 'application/ld+json', innerHTML: JSON.stringify(signals.jsonLd) }],
})
See @clione/seo for the SDK.
Per-platform notes
The response shape is identical across BigCommerce and Shopify. The path parameter :platform is the only difference.
BigCommerce
entityType: productreturns full JSON-LD withaggregateRatingif reviews are enabled in BC.entityType: categoryreturns meta title + description + keywords. No JSON-LD yet.entityType: pagereturns meta title + description + keywords. No JSON-LD yet.entityIdis the BC numeric ID (e.g.1234), not the slug.
Shopify
entityType: productreturns full JSON-LD.entityType: collectionreturns meta title + description. No JSON-LD yet.entityType: pagereturns meta title + description. No JSON-LD yet.entityIdaccepts both the numeric ID (1234) and the GID (gid://shopify/Product/1234).
Caching
Responses include Cache-Control: public, max-age=60. Cache on your edge (Cloudflare, Vercel) to reduce latency. Clione also caches enriched signals server-side and invalidates on enrichment, so you can safely cache longer if your traffic justifies it.
Error responses
| Status | Cause |
|---|---|
| 401 | Missing or invalid API key. |
| 403 | API key valid but Origin not in allowed domains, or scope missing products:read. |
| 404 | Entity not found (wrong ID, wrong platform, or not synced). |
| 429 | Rate limited. Backoff and retry. Limits are generous (100 req/sec/key) but headless deployments hammering a hot product can hit them. |
| 5xx | Server error. Retry with exponential backoff. |
Troubleshooting
Returns 404 even though I see the product in my dashboard — The product is in Clione's DB but not for the platform you queried. Check that :platform matches the store the product was synced from. Also confirm the entity has been synced (not just created in the platform admin yesterday).
Empty signals object — Product hasn't been enriched. Enrich it first from the dashboard.
JSON-LD missing aggregateRating — Reviews are not enabled in your platform OR no products have ratings yet. See Reviews setup.