Catalog Indexing
Catalog indexing is how your existing products — wherever they live (Shopify, Sanity, Postgres, a JSON file) — become discoverable by AI agents on the 0G network. You push your catalog to Coal, Coal snapshots it to 0G Storage and mirrors it to 0G KV, and agents across the web can find and buy from you.
No migration. No rebuilding your store. One endpoint or one React component.
How it works
1Your backend / CMS Coal 0G Network2────────────────── ────── ──────────3Products in your DB → POST /api/agent/publish-catalog → 0G Storage (immutable log)4 ↓ 0G KV (mutable mirror)5 Upsert into Coal's product index ↓6 ↓ Agents query KV for fresh data7 Dedup via payload hash Agents verify via log root hash8 ↓9 Return { storageUri, storageRoot }
- You call
POST /api/agent/publish-catalogwith your products (or use<CoalAgentPublisher>/publishCoalCatalog()) - Coal upserts each product keyed by
(merchantId, externalId)— idempotent, safe to replay - Coal rebuilds your merchant profile bundle and publishes it to 0G Storage (Log layer) as an immutable, content-addressed JSON artifact
- Coal mirrors the same profile to 0G KV at
merchant:profile:latestfor low-latency agent discovery - Agents hitting
api.usecoal.xyz/api/agent/discoveror your/.well-known/agent-card.jsonsee the fresh catalog
Three integration paths
Path 1: React component (recommended for Next.js)
Install coal-react and drop <CoalAgentPublisher> onto any page that renders your products:
1import { CoalProvider, CoalAgentPublisher } from 'coal-react';23const products = await db.products.findMany(); // your own data45<CoalProvider merchantId="your-merchant-id">6 <CoalAgentPublisher7 products={products.map(p => ({8 externalId: p.id,9 name: p.name,10 description: p.description,11 price: p.price,12 image: p.imageUrl,13 tags: p.tags,14 }))}15 showStatus16 />17</CoalProvider>
The component posts to /api/coal/publish-catalog on YOUR server, which calls publishCoalCatalog() from coal-react/server with your API key. See React SDK — CoalAgentPublisher for full props.
Path 2: Server-side function (recommended for webhooks / cron)
Call publishCoalCatalog() from your backend whenever products change:
1import { publishCoalCatalog } from 'coal-react/server';23// In a Prisma hook, Shopify webhook, Sanity GROQ subscription, etc.4await publishCoalCatalog({5 merchantId: process.env.COAL_MERCHANT_ID!,6 apiKey: process.env.COAL_API_KEY!,7 products: updatedProducts,8 mode: 'replace', // archives external products not in this payload9});
This is the strongest pattern — no browser-facing route to secure, no debouncing, publishes exactly when your data changes.
Path 3: Direct API call (any language)
1curl -X POST https://api.usecoal.xyz/api/agent/publish-catalog \2 -H "x-api-key: coal_live_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "products": [6 {7 "externalId": "sku-1",8 "name": "Pro Plan",9 "price": 29.99,10 "description": "Monthly API access",11 "image": "https://cdn.example.com/pro.png",12 "tags": ["api", "subscription"]13 }14 ],15 "mode": "upsert"16 }'
Works from Python, Go, Ruby, PHP, or any HTTP client. No SDK required.
Product shape
Each product in the products array:
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Your own stable product ID. Max 128 chars. Used as the idempotency key. |
name | string | Yes | Product display name. Max 200 chars. |
price | number | Yes | Price in USDC. Positive, max 1,000,000, max 6 decimals. |
description | string | No | Short description. Max 2000 chars. |
image | string | No | Primary image URL. |
images | string[] | No | Additional image URLs. Max 6. |
sku | string | No | SKU code. Max 100 chars. |
tags | string[] | No | Discovery tags. Max 10 items, 50 chars each. |
billingType | string | No | one_time (default) or subscription. |
billingInterval | string | No | Required when subscription: day, week, month, year. |
Publish modes
| Mode | Behavior |
|---|---|
upsert (default) | Only touches products listed in the payload. Existing products not in the payload are left alone. |
replace | Upserts the listed products AND archives (deactivates) any existing external products whose externalId is NOT in the payload. Console-sourced products are never touched. |
Use replace for full catalog syncs (e.g. a nightly Shopify webhook that sends the complete product list). Products you remove from Shopify will get archived in Coal.
Use upsert for partial updates (e.g. a single product was just edited). Only the changed product is touched.
Rate limiting
Publishing is rate-limited to 1 request per minute per merchant. This is enforced server-side regardless of which integration path you use.
If you hit the limit, the API returns 429 RATE_LIMITED. The coal-react helpers surface this as a CoalPublishError with err.status === 429.
0G integration
Every successful publish triggers Coal's existing 0G pipeline:
-
0G Storage (Log layer): The merchant profile is published as an immutable, content-addressed JSON artifact. You get back a
storageRoot(Merkle root hash) and astorageUri(0g://log/0x...). Anyone with the root hash can fetch and cryptographically verify the exact bytes. -
0G KV (mutable mirror): The same profile is written to the key
merchant:profile:latestunder the merchant's KV stream. Agents hitting the discovery API get this fresh copy. Writes to KV are fire-and-forget — a failure doesn't block the log-layer publish. -
Deduplication: If the profile's SHA-256 payload hash matches the last published artifact, Coal skips the 0G write entirely. Republishing an unchanged catalog is free.
The response includes the 0G details:
1{2 "zeroG": {3 "published": true,4 "skipped": false,5 "storageUri": "0g://log/0x7e59c818e4b92bd9534a2ce0003fbd6ab10af6d016c7c0563c7e07488f615578",6 "storageRoot": "0x7e59c818...",7 "storageTxHash": "0x...",8 "payloadHash": "0x...",9 "kv": {10 "streamId": "0x7a11955a...",11 "key": "merchant:profile:latest"12 }13 }14}
How agents discover your catalog
Once published, your products are findable via multiple channels:
| Channel | How agents reach it |
|---|---|
| Coal Discovery API | GET api.usecoal.xyz/api/agent/discover — returns all merchants with top products |
| Merchant Profile API | GET api.usecoal.xyz/api/agent/merchant-profiles/{merchantId} — full profile with 0G proof |
| A2A Agent Card | /.well-known/agent-card.json on your site (via createAgentCardRoute from coal-react/next) |
| llms.txt | /llms.txt on your site (via createLlmsTxtRoute) |
| x402 Bazaar manifest | /.well-known/x402.json on your site (via createX402ManifestRoute) |
| 0G Storage directly | Any 0G-native agent can fetch the profile by root hash from the decentralized storage network |
| Schema.org JSON-LD | <CoalSchemaOrg /> on your product pages — ChatGPT / Perplexity / Google AI Overviews cite this |
Security considerations
-
Never put your Coal API key in the browser. Use the proxy route pattern (Path 1) or server-side calls (Path 2/3).
-
Add auth to your proxy route. The
<CoalAgentPublisher>component posts to YOUR server. Without auth on that route, any visitor can publish junk products. Use session auth, CSRF tokens, or scrap the browser path entirely. -
Replace mode can be destructive. A
replacecall with an emptyproductsarray will archive ALL of your external products. Be careful in automated pipelines. -
Rate limiting is your safety net. Even if an attacker reaches your proxy, Coal's 1/min rate limit caps the damage. But don't rely on it as your only defense.
Next steps
- React SDK — component reference for
<CoalAgentPublisher>,publishCoalCatalog(), and the Next.js route helpers - Agent Discovery — how the discovery API works
- 0G Setup Guide — configure all 5 0G components from scratch
