Jobs API

Create store analysis jobs, check status, poll for completion, and stream real-time progress over WebSocket.

Overview

A job is one store analysis run. Creating a job kicks off the full scraping and analysis pipeline (sitemap → audit → product indexing → PageSpeed → export). This section covers job creation, lookup, status polling, retry, and the real-time WebSocket progress stream.

Base URL: https://shopsniffer.com/api

POST /jobs Stable

Create a store analysis job. Starts the full pipeline.

Auth: API key, Better Auth session, or x402 payment.

bash
curl -X POST https://shopsniffer.com/api/jobs \ -H "X-API-Key: ss_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "domain": "allbirds.com", "webhook_url": "https://your-server.com/hooks/shopsniffer", "notify_email": "you@example.com" }'

Request body

domain string required

Shopify store domain, with or without protocol (e.g. allbirds.com or https://allbirds.com). Must be a public Shopify store with an accessible sitemap.

URL to POST a job.completed event to when the job finishes. Single-attempt delivery — see webhooks.

Email address to notify on completion. Single-attempt delivery via the Workers email binding.

type string default: full

Job type. Currently only full is supported.

indexer string default: default

Indexer strategy. Reserved for internal use.

json
{ "job": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "domain": "allbirds.com", "slug": "allbirds-com", "status": "pending", "created_at": "2026-04-12T10:30:00Z", "user_id": "usr_better_auth_id" } }

Response fields

job.id string required

UUIDv4 job identifier. Use this for all subsequent status, report, and download calls.

job.slug string required

URL-safe slug derived from the domain (dots replaced with hyphens).

job.status string required

Initial status is always pending. Transitions: pendingprocessingcompleted | errored.

Better Auth user ID if authenticated via session or API key. Null for anonymous x402 payments.

Error responses

json
{ "error": "Invalid domain format. Please enter a valid domain like 'store.com'." }
json
{ "error": "This domain does not appear to be a Shopify store." }
json
{ "error": "This Shopify store is password-protected and cannot be scanned." }
json
{ "error": "Unauthorized" }
json
{ "error": "Payment Required", "x402": { "version": "1", "accepts": [{ "scheme": "exact", "network": "base", "maxAmountRequired": "9990000", "resource": "https://shopsniffer.com/api/jobs", "payTo": "0x767131d92c41D56546eC72fecD0F1d63900fa9D9", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" }] } }

See x402 payment for handling the 402 flow.


GET /jobs Stable

Look up a job by ID or by most recent for a domain. No authentication required.

id string

Job UUID. Mutually exclusive with domain.

domain string

Store domain. Returns the most recent job for that domain. Mutually exclusive with id.

bash
curl "https://shopsniffer.com/api/jobs?id=a1b2c3d4-e5f6-7890-abcd-ef1234567890"
json
{ "job": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "domain": "allbirds.com", "slug": "allbirds-com", "status": "completed", "created_at": "2026-04-12T10:30:00Z", "completed_at": "2026-04-12T10:33:42Z", "product_count": 156, "collection_count": 24, "page_count": 18, "shop_meta": { "shop_name": "Allbirds", "currency": "USD", "theme_name": "Custom Theme", "theme_id": 123456789, "detected_apps": ["Klaviyo", "Judge.me", "Afterpay"] }, "insights": { "top_vendors": [{ "name": "Allbirds", "count": 156 }], "price_range": { "min": 28, "max": 160, "avg": 98.5 }, "product_types": [{ "type": "Shoes", "count": 82 }] }, "downloads": { "csv": "products.csv", "productsJson": "products.json", "collectionsJson": "collections.json", "pagesJson": "pages.json" } } }

Errors:

json
{ "error": "Provide id or domain parameter" }
json
{ "error": "Job not found" }

GET /jobs/:id/status Stable

Lightweight status check for polling. Returns only status and progress counts to minimize bandwidth. No authentication required.

id string required

Job UUID.

bash
curl https://shopsniffer.com/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status
json
{ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "processing", "domain": "allbirds.com", "product_count": 89, "collection_count": 24, "page_count": 18, "created_at": "2026-04-12T10:30:00Z", "updated_at": "2026-04-12T10:31:47Z" }

For real-time progress updates, use the WebSocket endpoint instead of polling — no rate limits, push-based updates, lower latency.


POST /jobs/:id/retry Stable

Retry a failed or completed job. Creates a new job with a new UUID; the original is left unchanged. Auth: Better Auth session.

id string required

UUID of the job to retry.

json
{ "job": { "id": "new-retry-uuid", "domain": "allbirds.com", "slug": "allbirds-com", "status": "pending", "created_at": "2026-04-12T11:00:00Z" } }

Errors: 400 if the job isn't in a retryable state, 401 if unauthenticated, 404 if the job doesn't exist.


GET /ws/:jobId WebSocket

Real-time job progress stream via WebSocket. Push-based: no polling, sub-second latency, backed by a Cloudflare Durable Object that tracks per-step progress.

Protocol: WebSocket upgrade. Send Upgrade: websocket header.

Auth: None — the endpoint is keyed by job ID.

typescript
const ws = new WebSocket(`wss://shopsniffer.com/api/ws/${jobId}`); ws.addEventListener("message", (event) => { const msg = JSON.parse(event.data); // { type: "status", status: "processing", step: "indexing-products", progress: 0.42 } console.log(msg); }); ws.addEventListener("close", () => { console.log("Job complete or connection closed"); });

Message shape

Messages are JSON with a type discriminator:

json
{ "type": "status", "status": "processing", "step": "indexing-products", "progress": 0.42 }
json
{ "type": "step", "step": "pagespeed", "completed": true }
json
{ "type": "complete", "job_id": "a1b2c3d4-…", "report_url": "https://shopsniffer.com/report/a1b2c3d4-…" }

The connection closes after a complete or error message.


Status lifecycle

1

pending

Job accepted, waiting for the ScrapeShopWorkflow to pick it up (typically <1s).

2

processing

Workflow is actively scraping, auditing, or generating exports. product_count / collection_count / page_count update as items are indexed.

3

completed

All steps finished, downloads are available, webhook and email notifications have been dispatched.

4

errored

A workflow step failed after retries. The job is kept in the database for inspection. Use POST /jobs/:id/retry to create a new attempt.

Next steps

Reports & downloads

Fetch the full report and download exported files.

Learn More
Webhooks

Get notified on completion instead of polling.

Learn More
Rate limits

Limits on job creation and polling.

Learn More
x402 payment

Pay per job without an account.

Learn More
Ask a question... ⌘I