> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shopsniffer.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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](/guides/ai-agent-python).

## Discovery files

Three files at well-known locations describe the API to agents and LLM crawlers:

| Path | Purpose |
| --- | --- |
| [`/.well-known/agents.json`](https://shopsniffer.com/.well-known/agents.json) | Agent capability manifest: endpoints, pricing, supported auth methods |
| [`/.well-known/ai-plugin.json`](https://shopsniffer.com/.well-known/ai-plugin.json) | OpenAI plugin manifest (legacy format, still supported) |
| [`/api/openapi.json`](https://shopsniffer.com/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](/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](/api/chat).

## Working examples

**Python**

```python
    import 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)
    ```
  **TypeScript**

```typescript
    const 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);
    ```
  **WebSocket progress**

```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 `errored` status** — the scrape failed. Use `POST /jobs/:id/retry` to create a new attempt; if it fails twice, escalate to a human.
- **429** on any endpoint — back off using `retry_after`. See [rate limits](/rate-limits).

## Next steps

**[Python agent tutorial](/guides/ai-agent-python)**

Full end-to-end tutorial with a real agent framework.
  **[x402 tutorial](/guides/pay-with-x402)**

Account-free per-request payment for fully autonomous agents.
  **[Chat API](/api/chat)**

Stream AI analysis of any report.
  **[API overview](/api)**

Full endpoint reference.