AI agent integration
How AI agents discover, authenticate, and use the ShopSniffer API — with working Python and JavaScript examples.
Overview
ShopSniffer is built for AI agent discovery and automated usage. Agents can find capability manifests, pick an authentication method (API key or x402), create jobs, stream progress, and fetch results — all without human intervention. This page is the integration guide; for a step-by-step tutorial using a real agent framework, see the Python agent guide.
Discovery files
Three files at well-known locations describe the API to agents and LLM crawlers:
| Path | Purpose |
|---|---|
/.well-known/agents.json | Agent capability manifest: endpoints, pricing, supported auth methods |
/.well-known/ai-plugin.json | OpenAI plugin manifest (legacy format, still supported) |
/api/openapi.json | Full OpenAPI 3.1 spec for automated client generation |
Agents should fetch agents.json first for a high-level capability overview, then openapi.json when they need endpoint-level detail.
Recommended agent workflow
Authenticate
Pick one: API key (if you have persistent credentials), or x402 payment (if you want account-free per-request payment). See authentication.
Create a job
POST /api/jobs with a target domain. Include webhook_url for async notification — avoid polling if you can.
Wait for completion
Subscribe to the WebSocket at /api/ws/:jobId, wait for the webhook, or poll GET /jobs/:id/status every 10 seconds as a fallback.
Fetch the report
Once status is completed, fetch GET /api/reports/:id for the full dataset.
Download artifacts
GET /api/downloads?jobId=…&key=products.csv returns raw file contents. Store locally.
(Optional) Ask questions
For analysis over a report, stream POST /api/chat with the job ID and user questions. See chat API.
Working examples
pythonimport requests, time API_KEY = "ss_your_key_here" BASE = "https://shopsniffer.com/api" headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"} # 1. Create job resp = requests.post( f"{BASE}/jobs", json={ "domain": "allbirds.com", "webhook_url": "https://your-server.com/hook", }, headers=headers, ) job = resp.json()["job"] print(f"Job {job['id']} created for {job['domain']}") # 2. Poll for completion (or wait on your webhook) while True: status = requests.get(f"{BASE}/jobs/{job['id']}/status").json() print(f"Status: {status['status']}") if status["status"] == "completed": break if status["status"] == "errored": raise RuntimeError(f"Job failed: {status}") time.sleep(10) # 3. Get report data report = requests.get(f"{BASE}/reports/{job['id']}").json() print(f"Found {len(report.get('products', []))} products") # 4. Download CSV csv_url = ( f"{BASE}/downloads?jobId={job['id']}&key=products.csv" ) csv_data = requests.get(csv_url).text with open("products.csv", "w") as f: f.write(csv_data)
typescriptconst BASE = "https://shopsniffer.com/api"; const headers = { "X-API-Key": "ss_your_key_here", "Content-Type": "application/json", }; // 1. Create job const { job } = await fetch(`${BASE}/jobs`, { method: "POST", headers, body: JSON.stringify({ domain: "allbirds.com", webhook_url: "https://your-server.com/hook", }), }).then((r) => r.json()); console.log(`Job ${job.id} created`); // 2. Poll until complete let status: { status: string }; do { await new Promise((r) => setTimeout(r, 10_000)); status = await fetch(`${BASE}/jobs/${job.id}/status`).then((r) => r.json()); console.log(`Status: ${status.status}`); if (status.status === "errored") throw new Error("Job failed"); } while (status.status !== "completed"); // 3. Get report const report = await fetch(`${BASE}/reports/${job.id}`).then((r) => r.json()); console.log(`Found ${report.products?.length ?? 0} products`); // 4. Download CSV const csvUrl = `${BASE}/downloads?jobId=${job.id}&key=products.csv`; const csv = await fetch(csvUrl).then((r) => r.text()); await Deno.writeTextFile("products.csv", csv);
typescript// Replace polling with real-time progress via WebSocket const BASE_WS = "wss://shopsniffer.com/api"; // Create job first (as above), then: const ws = new WebSocket(`${BASE_WS}/ws/${job.id}`); ws.addEventListener("message", (evt) => { const msg = JSON.parse(evt.data); switch (msg.type) { case "status": console.log(`Stage: ${msg.step} (${Math.round(msg.progress * 100)}%)`); break; case "complete": console.log(`Done. Report: ${msg.report_url}`); ws.close(); break; case "error": console.error(`Job failed: ${msg.error}`); ws.close(); break; } });
Webhooks vs polling vs WebSocket
| Method | Latency | Cost | Best for |
|---|---|---|---|
Webhook (webhook_url at creation) | Seconds | 1 request per callback | Server-side agents |
WebSocket (/api/ws/:jobId) | Sub-second | 1 long-lived connection | Interactive UIs, streaming progress |
Polling (GET /jobs/:id/status) | 10s+ | N requests until done | Fallback when others unavailable |
Prefer webhooks for autonomous agents — they're push-based and don't consume rate budget.
Error handling
Agents should handle these cases:
- 401/402 on job creation — retry with auth (API key or x402 payment).
- 400 "Not a Shopify store" — the target domain isn't a Shopify store. Fail permanently, don't retry.
- 404 on report fetch — the job is still processing, or the job ID is wrong. Re-check status first.
- Job
erroredstatus — the scrape failed. UsePOST /jobs/:id/retryto create a new attempt; if it fails twice, escalate to a human. - 429 on any endpoint — back off using
retry_after. See rate limits.