On this page
For Developers
Integrate ProductIndex into your app or AI agent — your users get AI product search across 700+ synthesized profiles in 23 categories. Your agent gets real-time pricing via the RFQ marketplace: consumer agents can negotiate prices with verified producers on behalf of your users. Integration surfaces: 6 MCP tools, per-product endpoints in HTML / Markdown / JSON-LD, and an A2A-compatible agent card. Free tier available; OAuth 2.1 handles sign-in — clients like Grok and Claude Desktop manage it automatically.
Quick Reference
| MCP endpoint | POST https://mcp.productindex.ai/sse/ |
| Protocol | MCP 2025-11-25 · Streamable HTTP |
| Auth header | Authorization: Bearer <access_token> |
| Scope | mcp |
| Rate limit | 500 requests / day per authenticated user |
| Authorize URL | https://productindex.ai/oauth/authorize/ |
| Token URL | POST https://productindex.ai/api/oauth/token/ |
| Client registration | CIMD (preferred) or DCR at POST https://productindex.ai/api/oauth/register/ |
| OAuth server metadata | /.well-known/oauth-authorization-server.json |
| MCP descriptor | /.well-known/mcp.json |
| Tools | list_categories · list_category_products · get_product · search_products · find_similar · find_retailers |
| CIMD supported | Yes — client_id_metadata_document_supported: true in server metadata |
Six tools over Streamable HTTP (MCP 2025-11-25). All requests are POST to
https://mcp.productindex.ai/sse/ with a JSON-RPC 2.0 body.
OAuth 2.1 Bearer token required on every request.
| Endpoint | POST https://mcp.productindex.ai/sse/ |
| Protocol | MCP 2025-11-25 · Streamable HTTP (POST only) |
| Auth | Authorization: Bearer <access_token> — OAuth 2.1 required |
| Descriptor | /.well-known/mcp.json |
MCP Tools
| Tool | Description |
|---|---|
list_categories | All 23 categories with product counts. No parameters. |
list_category_products | Paginated product list for a category. Params: category (required), limit, offset, sort, on_sale, include_discontinued. |
get_product | Full product profile by slug. Params: category, slug (both required), sections (string array — subset retrieval). |
search_products | Semantic search via pgvector + Cohere reranking with keyword fallback. Params: query (required), category, min_price, max_price, sort, on_sale, include_discontinued, limit. |
find_similar | Nearest-neighbour similarity to a known product. Params: category, slug (both required), max_price, limit. |
find_retailers | Merchants carrying a product — triggers RFQ fanout and returns ranked offers with affiliate-attributed buy links. Params: query (required), product_slug, max_results. |
Request / Response Example
Every call is a POST to the same endpoint. Send initialize first, then any tool call.
// 1. Initialize
POST https://mcp.productindex.ai/sse/
Authorization: Bearer <access_token>
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-app","version":"1.0"}}}
// 200 OK
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{}},"serverInfo":{"name":"ProductIndex","version":"1.0.0"}}}
// 2. Tool call
POST https://mcp.productindex.ai/sse/
Authorization: Bearer <access_token>
Content-Type: application/json
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_products","arguments":{"query":"espresso machine under $500","limit":5}}}
// 200 OK
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"..."}]}} Full setup instructions for Grok, Claude Desktop, Cursor, and Windsurf: MCP Server documentation →
Multi-format content endpoints
Every product and category is available in machine-readable formats for direct RAG ingestion, structured data pipelines, and LLM agents that prefer URL-based retrieval over the MCP server. No authentication required.
| Format | URL pattern | Use case |
|---|---|---|
| HTML — product | /p/{category}/{slug} | Human-readable product page |
| Markdown — product | /p/{category}/{slug}.md | LLM-friendly; verdict, review themes, specs, images, where-to-buy. noindex. |
| JSON-LD — product | /p/{category}/{slug}.json | Schema.org Product object. noindex. |
| HTML — category | /c/{category} | Human-readable category listing with all products |
| Markdown — category | /c/{category}.md | Full dump of every product profile in the category as a single document — suitable for bulk RAG ingestion. noindex. |
ProductIndex publishes a Google Agent-to-Agent (A2A) compatible agent card describing its skills and capabilities in machine-readable JSON. Read automatically by A2A-compatible orchestrators.
| Card URL | /.well-known/agent.json |
| Skills | Product recommendation · Product lookup · Category browse · Product offers · Request for Quote · What changed |
| Registry | Pending — Google A2A registry and Linux Foundation AI Agent Registry |
RFQ Integration — Level 2 Producer Agents
If you operate a retailer or manufacturer and want to receive live Request-for-Quote (RFQ) calls from consumer agents via ProductIndex, register your agent endpoint in the Producer Dashboard → Settings → Agent Integration. ProductIndex will fan out to your endpoint when a consumer agent submits an RFQ for a product you carry.
RFQ Request — What Your Agent Receives
ProductIndex calls your registered offer endpoint with a JSON-RPC 2.0 POST request:
{
"jsonrpc": "2.0",
"method": "message/send",
"id": "<rfq_session_uuid>",
"params": {
"message": {
"messageId": "<uuid>",
"role": "user",
"parts": [{ "kind": "text", "text": "Request for quote: Baratza Encore ESP, qty 1, ship to CA 94103" }],
"metadata": {
"skill": "respond-to-rfq",
"rfq_id": "<rfq_session_uuid>",
"product_slug": "coffee-grinders/baratza-encore-esp",
"quantity": 1,
"delivery_region": "CA-US",
"deadline_iso": "2026-06-01T12:00:00Z",
"requestor": "https://productindex.ai/.well-known/agent.json"
}
}
}
} | Field | Type | Description |
|---|---|---|
metadata.product_slug | string | Canonical ProductIndex slug for the requested product |
metadata.quantity | integer | Units requested (default 1) |
metadata.delivery_region | string | Postal region in STATE-COUNTRY format (e.g. CA-US) |
metadata.deadline_iso | ISO 8601 | Latest acceptable delivery date (optional) |
RFQ Response — What You Must Return
Respond with a JSON object (not JSON-RPC envelope — bare object only) within 4 seconds:
{
"price_usd": 169.99,
"list_price_usd": 189.99,
"in_stock": true,
"estimated_shipping_days": 3,
"shipping_cost_usd": 0,
"offer_id": "<uuid>",
"offer_expires_at": "2026-06-01T12:00:00Z",
"claim_url": "https://yourstore.com/cart?offer=<uuid>",
"terms": "Free returns within 30 days"
} | Field | Required | Description |
|---|---|---|
price_usd | Yes | Your offered price in USD |
in_stock | Yes | Boolean — false causes your offer to be deprioritized |
claim_url | Yes | URL consumer agent can redirect to for purchase. Must be https://. |
offer_expires_at | Yes | ISO 8601 — when the quoted price expires. Max 24 hours from now. |
shipping_cost_usd | No | Shipping cost (0 for free shipping). Omit if unknown — you will rank lower. |
list_price_usd | No | Original MSRP if your offer is discounted |
estimated_shipping_days | No | Business days to deliver. Omit if unknown. |
terms | No | Short return/warranty/conditions string shown to consumer |
Offers are ranked by total cost (price_usd + shipping_cost_usd). Out-of-stock
offers are demoted regardless of price. Responses that arrive after 4 seconds are dropped.
Authentication
Every RFQ call from ProductIndex carries authentication headers so your server can verify the request is genuine. Choose a scheme, generate your own secret, and register it in the Producer Dashboard → Settings → Agent Integration. ProductIndex encrypts and stores the secret — it is never returned after registration.
| Scheme | You generate | Headers sent by ProductIndex | How to verify |
|---|---|---|---|
| Bearer token | Any secret string ≥ 16 chars (use a random token) | Authorization: Bearer <your-token> | Compare header value against your stored token with a constant-time comparison |
| HMAC-SHA256 | A random signing secret ≥ 16 chars (32+ bytes of entropy recommended) | X-ProductIndex-Signature: <hex>X-ProductIndex-Timestamp: <unix_seconds> | Recompute HMAC and compare — see spec below |
Generating a secret
// Node.js — generate a cryptographically random secret
import { randomBytes } from "crypto";
const secret = randomBytes(32).toString("hex"); // 64-char hex string
console.log(secret); // paste this into the dashboard Go to Producer Dashboard → Settings → Agent Integration, select your auth type, paste your secret, and save. ProductIndex will use it to sign all outbound RFQ calls to your endpoint from that point forward.
HMAC Verification
Signature is HMAC-SHA256(secret, "${timestamp}.${rawBodyString}") — the Unix
timestamp in seconds, a literal period, then the raw request body string exactly as received.
Reject requests where |now - timestamp| > 300 seconds (5-minute replay window).
// Node.js verification example
import { createHmac, timingSafeEqual } from "crypto";
function verifyProductIndexRequest(secret, req) {
const timestamp = parseInt(req.headers["x-productindex-timestamp"], 10);
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${req.rawBody}`)
.digest("hex");
// Use timingSafeEqual to prevent timing attacks
return timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-productindex-signature"]));
} Agent Card Requirement
Your agent card (at /.well-known/agent.json on your verified domain) must
declare the respond-to-rfq skill. Minimum declaration:
{
"skills": [{
"id": "respond-to-rfq",
"description": "Responds to price quote requests with current pricing, availability, and shipping terms"
}]
} ProductIndex fetches and validates this card when you register your agent card URL. The card must be on the same domain as your verified producer organization.
ProductIndex derives your RFQ endpoint from your agent card URL by taking the domain root
and appending /message:send. If your card is at
https://mystore.com/.well-known/agent.json, ProductIndex will POST RFQ
requests to https://mystore.com/message:send. Your server must expose a
POST /message:send route at the domain root.
Timeout and Error Handling
Return HTTP 200 with a valid JSON response body within 4 seconds. If you cannot fulfill the
RFQ (out of stock, product not carried), return HTTP 200 with
{"in_stock": false, "price_usd": 0, "claim_url": "", "offer_expires_at": ""}
rather than a non-200 status. Repeated timeouts or errors degrade your integration status
and reduce your ranking priority.
Static files formatted for LLM context windows and RAG pipelines, following the llms.txt convention. No authentication required.
| File | Contents |
|---|---|
| llms.txt | MCP server reference + index of all products with one-line verdicts. Start here. |
| llms-full.txt | Full text of every product profile — suitable for offline RAG ingestion. Large file. |
Discovery Endpoints
Standard .well-known paths for automated discovery by agents and OAuth clients.
All public — no authentication required.
| Path | Purpose |
|---|---|
| /.well-known/mcp.json | MCP descriptor — tool schemas and per-client config blocks |
| /.well-known/agent.json | A2A agent card — skills, capabilities, and MCP extension pointer |
| /.well-known/oauth-authorization-server.json | OAuth 2.1 authorization server metadata (RFC 8414) — includes all endpoint URLs, supported grant types, CIMD support flag |
| /.well-known/oauth-protected-resource.json | OAuth protected resource metadata (RFC 9728) |
Client Authorization — CIMD
ProductIndex supports Client ID Metadata Documents (CIMD) — a stateless alternative to OAuth Dynamic Client Registration (DCR) being standardized in MCP SEP-991 and a companion IETF draft. Instead of pre-registering a client and receiving an opaque client ID, your app hosts a static JSON file at a stable HTTPS URL. That URL is the client ID — no registration step, no database entry on our side.
How it works
Host a JSON file at any stable HTTPS URL on your domain. That URL becomes your
client_id. When you send an OAuth authorization request, ProductIndex fetches
the document, validates that the client_id field inside it exactly matches the
URL it was fetched from, then uses your redirect_uris list to verify the
redirect_uri in your request.
{
"client_id": "https://yourapp.example.com/oauth/client.json",
"client_name": "Your App Name",
"client_uri": "https://yourapp.example.com",
"redirect_uris": [
"https://yourapp.example.com/oauth/callback"
],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
Then start the OAuth flow using that URL as client_id:
GET https://productindex.ai/oauth/authorize/
?client_id=https://yourapp.example.com/oauth/client.json
&redirect_uri=https://yourapp.example.com/oauth/callback
&response_type=code
&code_challenge=<S256 PKCE challenge>
&code_challenge_method=S256
&scope=mcp
&state=<random state> Requirements
| Requirement | Detail |
|---|---|
| URL scheme | https:// only — no http://, no localhost URLs |
client_id self-reference | The client_id field inside the JSON must exactly match the URL it was fetched from |
| Required fields | client_id, client_name, redirect_uris (non-empty array) |
| Auth method | token_endpoint_auth_method: "none" — public clients with PKCE only |
| Response size | Must be under 64 KB |
| Fetch timeout | 5 seconds — serve the file from a CDN or static host |
ProductIndex caches valid CIMD documents for 5 minutes and invalid results for 1 minute. Private IP ranges, loopback addresses, and link-local addresses are blocked before fetch (SSRF protection).
Traditional DCR
If your client cannot host a static HTTPS file (e.g., a CLI tool or local app), register
via Dynamic Client Registration at POST /api/oauth/register/. DCR issues an
opaque UUID as the client_id. CIMD is preferred for web apps and server-side
integrations.
OAuth 2.1 + PKCE Flow
Complete flow for obtaining an access token. All clients are public
(token_endpoint_auth_method: none) — PKCE is the only
security mechanism, no client secret is issued or required.
Step 1 — Generate PKCE pair
// Generate a cryptographically random code_verifier (43–128 URL-safe chars)
const verifier = base64url(crypto.randomBytes(32));
// Derive code_challenge
const challenge = base64url(crypto.createHash('sha256').update(verifier).digest()); Step 2 — Redirect user to authorization endpoint
GET https://productindex.ai/oauth/authorize/
?response_type=code
&client_id=<your-client-id>
&redirect_uri=<your-registered-redirect-uri>
&code_challenge=<challenge>
&code_challenge_method=S256
&scope=mcp
&state=<random-csrf-token>
User signs in (or is auto-approved if they previously granted this client).
ProductIndex redirects to your redirect_uri with ?code=<auth_code>&state=<state>.
Step 3 — Exchange code for tokens
POST https://productindex.ai/api/oauth/token/
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<auth_code_from_redirect>
&redirect_uri=<same-redirect-uri>
&client_id=<same-client-id>
&code_verifier=<verifier>
// 200 OK
{
"access_token": "<token>",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "<refresh_token>"
} Step 4 — Call the MCP server
POST https://mcp.productindex.ai/sse/
Authorization: Bearer <access_token>
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}} Step 5 — Refresh an expired token
POST https://productindex.ai/api/oauth/token/
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=<refresh_token>
&client_id=<same-client-id>
// 200 OK — new access_token and refresh_token issued; old refresh_token revoked | Response header | Value |
|---|---|
X-RateLimit-Remaining | Requests remaining today |
X-RateLimit-Reset | Unix timestamp when the daily quota resets |
Rate limits
OAuth 2.1 with PKCE is required for all requests. AI clients like Grok and Claude Desktop handle the sign-in flow automatically — connect once and they manage token refresh.
| Tier | Limit | How to activate |
|---|---|---|
| Authenticated (free) | 500 requests / day | OAuth 2.1 — clients like Grok and Claude Desktop handle sign-in automatically |
Remaining quota and reset time are returned on every response as
X-RateLimit-Remaining and X-RateLimit-Reset headers.
Questions
For integration questions or to report an issue with the API: hello@productindex.ai
ProductIndex