{"openapi":"3.1.0","info":{"title":"SnipeWise Agent API","version":"1.31.0","description":"Machine-readable API for SnipeWise, an eBay auction-sniping app. Create a snipe with an eBay item number/URL and a max bid; SnipeWise fires the bid automatically a few seconds before the auction ends via eBay's Trading API. Bids are legally binding — always confirm the item title and max bid with the user before creating a snipe, and report the dry-run state from /api/agent/health."},"servers":[{"url":"/","description":"This SnipeWise instance (APP_URL)"}],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"paths":{"/api/agent/health":{"get":{"summary":"Health check (public, cheap to poll)","security":[],"responses":{"200":{"description":"Server health","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"version":{"type":"string"},"env":{"type":"string","enum":["sandbox","production"]},"dryRun":{"type":"boolean","description":"true = snipes are logged, never bid"},"serverTime":{"type":"string","format":"date-time"}}}}}}}}},"/api/agent/openapi.json":{"get":{"summary":"This OpenAPI specification (public)","security":[],"responses":{"200":{"description":"OpenAPI 3.1 document"}}}},"/api/agent/keys":{"get":{"summary":"List your agent API keys (hashes never exposed)","description":"Requires the keys:write scope.","responses":{"200":{"description":"Key list","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"Per-user agent API key (hashes are never exposed)","properties":{"id":{"type":"integer"},"name":{"type":"string"},"key_prefix":{"type":"string","example":"skw_9f2c41ab"},"scopes":{"type":"array","items":{"type":"string","enum":["*","snipes:read","snipes:write","billing:read","billing:topup","billing:subscribe","webhooks:write","reviews:write","account:read","keys:write"]},"description":"'*' = full access (default). Scoped keys can only use routes in their scopes."},"daily_cap_cents":{"type":"integer","description":"Per-key 24h money cap in cents. Default 10000 ($100). 0 = unlimited."},"created_at":{"type":"integer"},"last_used_at":{"type":"integer","nullable":true}}}}}}}}},"post":{"summary":"Mint a new per-user agent API key (full key shown once)","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"Claude's key"},"scopes":{"type":"array","items":{"type":"string"},"example":["snipes:read","snipes:write","billing:topup","reviews:write"],"description":"Least-privilege scopes. Omit for full access (\"*\")."},"daily_cap_cents":{"type":"integer","example":10000,"description":"Per-key 24h money cap in cents. Default $100. 0 = unlimited."}}}}}},"responses":{"201":{"description":"Created — save `key` now, it is never shown again","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"key":{"type":"string","example":"skw_9f2c41ab…"},"key_prefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"daily_cap_cents":{"type":"integer"},"created_at":{"type":"integer"}}}}}}}}},"/api/agent/keys/{id}":{"patch":{"summary":"Rename a key or change its daily money cap (scopes are immutable)","description":"Requires the keys:write scope.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"daily_cap_cents":{"type":"integer","description":"0 = unlimited"}}}}}},"responses":{"200":{"description":"Updated key","content":{"application/json":{"schema":{"type":"object","description":"Per-user agent API key (hashes are never exposed)","properties":{"id":{"type":"integer"},"name":{"type":"string"},"key_prefix":{"type":"string","example":"skw_9f2c41ab"},"scopes":{"type":"array","items":{"type":"string","enum":["*","snipes:read","snipes:write","billing:read","billing:topup","billing:subscribe","webhooks:write","reviews:write","account:read","keys:write"]},"description":"'*' = full access (default). Scoped keys can only use routes in their scopes."},"daily_cap_cents":{"type":"integer","description":"Per-key 24h money cap in cents. Default 10000 ($100). 0 = unlimited."},"created_at":{"type":"integer"},"last_used_at":{"type":"integer","nullable":true}}}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"delete":{"summary":"Revoke an agent API key","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"boolean"}}}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/actions":{"get":{"summary":"Audit log of what your agents have done (newest first)","description":"Requires the account:read scope. Every propose/confirm, key mint/revoke, and review submit lands here.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Audit entries","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"action":{"type":"string","example":"topup.confirm"},"params":{"type":"object"},"amount_cents":{"type":"integer","nullable":true},"result":{"type":"string","enum":["ok","blocked","error"]},"detail":{"type":"string","nullable":true},"created_at":{"type":"integer"}}}}}}}}}},"/api/agent/billing":{"get":{"summary":"Billing status for agents (balance, plans, spend vs cap)","description":"Requires the billing:read scope. Includes this key’s daily_cap_cents and spent_today_cents.","responses":{"200":{"description":"Billing status"}}}},"/api/agent/billing/topup/propose":{"post":{"summary":"Propose a card credit top-up — step 1 of 2 (quotes, never charges)","description":"Propose a card credit top-up Returns a single-use confirmation token (15 min) plus a `summary` the agent must read to the user verbatim and get an explicit yes for before confirming.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"pack":{"type":"string","enum":["pack10","pack25","pack50","pack100"]}}}}}},"responses":{"201":{"description":"Proposal issued — nothing charged","content":{"application/json":{"schema":{"type":"object","description":"Step 1 of 2. Nothing was charged. The agent must read `summary` to the user verbatim, get an explicit yes, then POST `confirmation_token` to `confirm_endpoint` within 15 minutes.","properties":{"proposal":{"type":"object","properties":{"action":{"type":"string","example":"topup"},"pack":{"type":"string","example":"mid"},"plan":{"type":"string","example":"monthly"},"amount_cents":{"type":"integer","example":2500},"summary":{"type":"string","description":"Plain-language quote — read to the user verbatim"},"confirmation_token":{"type":"string","description":"Single-use, 15-minute token. Never shown again."},"expires_at":{"type":"integer"},"confirm_endpoint":{"type":"string","example":"/api/agent/billing/topup/confirm"}}},"note":{"type":"string"}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/topup/confirm":{"post":{"summary":"Confirm a card credit top-up — step 2 of 2 (executes the approved action)","description":"Confirm a card credit top-up Consumes the single-use confirmation token (bound to user + action, 15-minute TTL), enforces the key’s daily spend cap, and executes. Pass Idempotency-Key to make retries safe.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["confirmation_token"],"properties":{"confirmation_token":{"type":"string","description":"Token from the /propose response"},"idempotency_key":{"type":"string","description":"Optional: retries with the same key replay the original response instead of executing twice (24h)."}}}}}},"responses":{"200":{"description":"Executed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"checkout_url":{"type":"string"}}}}}},"403":{"description":"Daily cap exceeded (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"Token not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"Token expired/used, or payments not connected (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/crypto/topup/propose":{"post":{"summary":"Propose a crypto credit top-up — step 1 of 2 (quotes, never charges)","description":"Propose a crypto credit top-up Returns a single-use confirmation token (15 min) plus a `summary` the agent must read to the user verbatim and get an explicit yes for before confirming.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"pack":{"type":"string"},"option":{"type":"string"}}}}}},"responses":{"201":{"description":"Proposal issued — nothing charged","content":{"application/json":{"schema":{"type":"object","description":"Step 1 of 2. Nothing was charged. The agent must read `summary` to the user verbatim, get an explicit yes, then POST `confirmation_token` to `confirm_endpoint` within 15 minutes.","properties":{"proposal":{"type":"object","properties":{"action":{"type":"string","example":"topup"},"pack":{"type":"string","example":"mid"},"plan":{"type":"string","example":"monthly"},"amount_cents":{"type":"integer","example":2500},"summary":{"type":"string","description":"Plain-language quote — read to the user verbatim"},"confirmation_token":{"type":"string","description":"Single-use, 15-minute token. Never shown again."},"expires_at":{"type":"integer"},"confirm_endpoint":{"type":"string","example":"/api/agent/billing/topup/confirm"}}},"note":{"type":"string"}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/crypto/topup/confirm":{"post":{"summary":"Confirm a crypto credit top-up — step 2 of 2 (executes the approved action)","description":"Confirm a crypto credit top-up Consumes the single-use confirmation token (bound to user + action, 15-minute TTL), enforces the key’s daily spend cap, and executes. Pass Idempotency-Key to make retries safe.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["confirmation_token"],"properties":{"confirmation_token":{"type":"string","description":"Token from the /propose response"},"idempotency_key":{"type":"string","description":"Optional: retries with the same key replay the original response instead of executing twice (24h)."}}}}}},"responses":{"200":{"description":"Executed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"checkout_url":{"type":"string"}}}}}},"403":{"description":"Daily cap exceeded (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"Token not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"Token expired/used, or payments not connected (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/subscribe/propose":{"post":{"summary":"Propose a subscription — step 1 of 2 (quotes, never charges)","description":"Propose a subscription Returns a single-use confirmation token (15 min) plus a `summary` the agent must read to the user verbatim and get an explicit yes for before confirming.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["monthly","annual"]}}}}}},"responses":{"201":{"description":"Proposal issued — nothing charged","content":{"application/json":{"schema":{"type":"object","description":"Step 1 of 2. Nothing was charged. The agent must read `summary` to the user verbatim, get an explicit yes, then POST `confirmation_token` to `confirm_endpoint` within 15 minutes.","properties":{"proposal":{"type":"object","properties":{"action":{"type":"string","example":"topup"},"pack":{"type":"string","example":"mid"},"plan":{"type":"string","example":"monthly"},"amount_cents":{"type":"integer","example":2500},"summary":{"type":"string","description":"Plain-language quote — read to the user verbatim"},"confirmation_token":{"type":"string","description":"Single-use, 15-minute token. Never shown again."},"expires_at":{"type":"integer"},"confirm_endpoint":{"type":"string","example":"/api/agent/billing/topup/confirm"}}},"note":{"type":"string"}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/subscribe/confirm":{"post":{"summary":"Confirm a subscription — step 2 of 2 (executes the approved action)","description":"Confirm a subscription Consumes the single-use confirmation token (bound to user + action, 15-minute TTL), enforces the key’s daily spend cap, and executes. Pass Idempotency-Key to make retries safe.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["confirmation_token"],"properties":{"confirmation_token":{"type":"string","description":"Token from the /propose response"},"idempotency_key":{"type":"string","description":"Optional: retries with the same key replay the original response instead of executing twice (24h)."}}}}}},"responses":{"200":{"description":"Executed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"checkout_url":{"type":"string"}}}}}},"403":{"description":"Daily cap exceeded (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"Token not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"Token expired/used, or payments not connected (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/trial/start/propose":{"post":{"summary":"Propose starting the free trial — step 1 of 2 (quotes, never charges)","description":"Propose starting the free trial Returns a single-use confirmation token (15 min) plus a `summary` the agent must read to the user verbatim and get an explicit yes for before confirming. Card is collected now; the first charge happens only when the trial ends.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"201":{"description":"Proposal issued — nothing charged","content":{"application/json":{"schema":{"type":"object","description":"Step 1 of 2. Nothing was charged. The agent must read `summary` to the user verbatim, get an explicit yes, then POST `confirmation_token` to `confirm_endpoint` within 15 minutes.","properties":{"proposal":{"type":"object","properties":{"action":{"type":"string","example":"topup"},"pack":{"type":"string","example":"mid"},"plan":{"type":"string","example":"monthly"},"amount_cents":{"type":"integer","example":2500},"summary":{"type":"string","description":"Plain-language quote — read to the user verbatim"},"confirmation_token":{"type":"string","description":"Single-use, 15-minute token. Never shown again."},"expires_at":{"type":"integer"},"confirm_endpoint":{"type":"string","example":"/api/agent/billing/topup/confirm"}}},"note":{"type":"string"}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/trial/start/confirm":{"post":{"summary":"Confirm starting the free trial — step 2 of 2 (executes the approved action)","description":"Confirm starting the free trial Consumes the single-use confirmation token (bound to user + action, 15-minute TTL), enforces the key’s daily spend cap, and executes. Pass Idempotency-Key to make retries safe.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["confirmation_token"],"properties":{"confirmation_token":{"type":"string","description":"Token from the /propose response"},"idempotency_key":{"type":"string","description":"Optional: retries with the same key replay the original response instead of executing twice (24h)."}}}}}},"responses":{"200":{"description":"Executed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"checkout_url":{"type":"string"}}}}}},"403":{"description":"Daily cap exceeded (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"Token not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"Token expired/used, or payments not connected (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/subscribe/cancel/propose":{"post":{"summary":"Propose canceling the subscription — step 1 of 2 (quotes, never charges)","description":"Propose canceling the subscription Returns a single-use confirmation token (15 min) plus a `summary` the agent must read to the user verbatim and get an explicit yes for before confirming. Canceling during the free trial forfeits the remaining trial snipes.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"201":{"description":"Proposal issued — nothing charged","content":{"application/json":{"schema":{"type":"object","description":"Step 1 of 2. Nothing was charged. The agent must read `summary` to the user verbatim, get an explicit yes, then POST `confirmation_token` to `confirm_endpoint` within 15 minutes.","properties":{"proposal":{"type":"object","properties":{"action":{"type":"string","example":"topup"},"pack":{"type":"string","example":"mid"},"plan":{"type":"string","example":"monthly"},"amount_cents":{"type":"integer","example":2500},"summary":{"type":"string","description":"Plain-language quote — read to the user verbatim"},"confirmation_token":{"type":"string","description":"Single-use, 15-minute token. Never shown again."},"expires_at":{"type":"integer"},"confirm_endpoint":{"type":"string","example":"/api/agent/billing/topup/confirm"}}},"note":{"type":"string"}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/billing/subscribe/cancel/confirm":{"post":{"summary":"Confirm canceling the subscription — step 2 of 2 (executes the approved action)","description":"Confirm canceling the subscription Consumes the single-use confirmation token (bound to user + action, 15-minute TTL), enforces the key’s daily spend cap, and executes. Pass Idempotency-Key to make retries safe.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["confirmation_token"],"properties":{"confirmation_token":{"type":"string","description":"Token from the /propose response"},"idempotency_key":{"type":"string","description":"Optional: retries with the same key replay the original response instead of executing twice (24h)."}}}}}},"responses":{"200":{"description":"Executed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"checkout_url":{"type":"string"}}}}}},"403":{"description":"Daily cap exceeded (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"404":{"description":"Token not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"Token expired/used, or payments not connected (no charge)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/reviews/submit":{"post":{"summary":"Record a rating + review text on the user’s behalf","description":"Requires the reviews:write scope. Read the rating and text back to the user and get an explicit yes before submitting — it is posted in their name. Accepts request_id (from GET /api/reviews/pending) or snipe_id (a settled snipe). The dashboard stops prompting for this snipe afterwards. Optional per-dimension ratings (service, pricing, agent_friendly, each 1-5) feed the public \"AI Agent Friendly\" score on the main page.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"integer"},"snipe_id":{"type":"integer"},"rating":{"type":"integer","minimum":1,"maximum":5,"example":5},"text":{"type":"string","maxLength":2000,"example":"Flawless last-second bid."},"ratings":{"type":"object","description":"Per-dimension ratings (each 1-5): service, pricing, agent_friendly","properties":{"service":{"type":"integer","minimum":1,"maximum":5},"pricing":{"type":"integer","minimum":1,"maximum":5},"agent_friendly":{"type":"integer","minimum":1,"maximum":5}}}},"required":["rating","text"]}}}},"responses":{"201":{"description":"Review recorded"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"Already reviewed / snipe not settled","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/webhooks":{"get":{"summary":"List your result webhooks (secrets never exposed)","description":"Each webhook receives a signed JSON POST when one of your snipes fires, settles (won/lost), or is cancelled — so your agent learns the result without polling and can notify you.","responses":{"200":{"description":"Webhook list","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["snipe.fired","snipe.settled","snipe.cancelled"]}},"secret_prefix":{"type":"string","example":"whsec_9f2c41ab…"},"active":{"type":"integer"},"created_at":{"type":"integer"},"last_delivery_at":{"type":"integer","nullable":true},"last_delivery_status":{"type":"string","nullable":true}}}}}}}}},"post":{"summary":"Register a result webhook (signing secret shown once)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","example":"https://my-agent.example.com/snipewise-events","description":"https/http callback URL that accepts JSON POSTs"},"events":{"type":"array","items":{"type":"string","enum":["snipe.fired","snipe.settled","snipe.cancelled"]},"description":"Defaults to [\"snipe.settled\"]"}}}}}},"responses":{"201":{"description":"Created — save `secret` now, it is never shown again. Deliveries carry X-SnipeWise-Event, X-SnipeWise-Signature (HMAC-SHA256 of the raw body), and X-SnipeWise-Delivery headers.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"secret":{"type":"string","example":"whsec_9f2c41ab…"},"secret_prefix":{"type":"string"},"active":{"type":"integer"},"created_at":{"type":"integer"},"note":{"type":"string"}}}}}},"400":{"description":"Invalid URL or event name","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/agent/webhooks/{id}":{"delete":{"summary":"Delete a result webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/status":{"get":{"summary":"eBay connection, snipe counts, env flags, subscription","responses":{"200":{"description":"Status","content":{"application/json":{"schema":{"type":"object","properties":{"connected":{"type":"boolean"},"ebayUsername":{"type":"string","nullable":true},"env":{"type":"string"},"dryRun":{"type":"boolean"},"defaultLeadSeconds":{"type":"integer"},"counts":{"type":"object","properties":{"active":{"type":"integer"},"won":{"type":"integer"},"lost":{"type":"integer"}}},"subscription":{"type":"object","properties":{"status":{"type":"string"},"plan":{"type":"string","nullable":true},"current_period_end":{"type":"integer","nullable":true}}}}}}}}}}},"/api/reviews/summary":{"get":{"summary":"Public aggregate review scores (overall + per-dimension)","description":"No auth required. Powers the \"AI Agent Friendly\" rating badge on the main page: average overall score plus per-dimension averages (service, pricing, agent) over all submitted reviews.","responses":{"200":{"description":"Review summary","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer","example":42},"avg_overall":{"type":"number","nullable":true,"example":4.8},"avg_service":{"type":"number","nullable":true,"example":4.9},"avg_pricing":{"type":"number","nullable":true,"example":4.6},"avg_agent":{"type":"number","nullable":true,"example":5}}}}}}}}},"/api/reviews/{id}/submit":{"post":{"summary":"Submit an in-app review (dashboard session)","description":"Session auth only — used by the dashboard review modal. Overall rating (1-5, required) plus optional per-dimension ratings service/pricing/agent (1-5 each); at least one dimension is expected by the UI. Review text is optional here.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rating":{"type":"integer","minimum":1,"maximum":5},"service":{"type":"integer","minimum":1,"maximum":5},"pricing":{"type":"integer","minimum":1,"maximum":5},"agent":{"type":"integer","minimum":1,"maximum":5},"text":{"type":"string","maxLength":2000}},"required":["rating"]}}}},"responses":{"200":{"description":"Review recorded"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"Already reviewed","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/item/lookup":{"get":{"summary":"Preview an eBay listing by URL or numeric item ID","parameters":[{"name":"url","in":"query","schema":{"type":"string"},"description":"Full ebay.com/itm/… link (or use `id`)"},{"name":"id","in":"query","schema":{"type":"string"},"description":"Numeric eBay item ID"}],"responses":{"200":{"description":"Listing preview","content":{"application/json":{"schema":{"type":"object","properties":{"itemId":{"type":"string"},"title":{"type":"string"},"imageUrl":{"type":"string"},"currency":{"type":"string"},"currentBid":{"type":"number"},"bidCount":{"type":"integer"},"endTime":{"type":"string","format":"date-time"}}}}}},"400":{"description":"No item ID found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/snipes":{"get":{"summary":"List your snipes","parameters":[{"name":"status","in":"query","schema":{"type":"string"},"description":"Comma-separated, e.g. `active` or `fired,won,lost`"}],"responses":{"200":{"description":"Snipe list","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"item_id":{"type":"string","description":"eBay numeric item ID"},"title":{"type":"string"},"image_url":{"type":"string","nullable":true},"currency":{"type":"string","example":"USD"},"current_bid":{"type":"number","nullable":true},"bid_count":{"type":"integer","nullable":true},"end_time":{"type":"string","format":"date-time"},"max_bid":{"type":"number"},"lead_seconds":{"type":"integer"},"group_id":{"type":"integer","nullable":true},"status":{"type":"string","enum":["active","fired","won","lost","cancelled","error","skipped"]},"fired_at":{"type":"string","format":"date-time","nullable":true},"result_detail":{"type":"string","nullable":true}}}}}}}}},"post":{"summary":"Create a snipe from an item number/URL and a max bid","description":"Requires an active subscription (402 otherwise) and a connected eBay account (409 otherwise). The scheduler arms the snipe automatically. `confirmation.summary` is a read-back string for the user — confirm it with them before relying on it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["max_bid"],"properties":{"item_id":{"type":"string","description":"Numeric eBay item ID (or item_url)","example":"123456789012"},"item_url":{"type":"string","description":"Full ebay.com/itm/… link (or item_id)"},"max_bid":{"type":"number","description":"Maximum bid in the listing currency","example":60},"lead_seconds":{"type":"integer","minimum":1,"maximum":120,"default":3,"description":"How many seconds before auction end to bid"},"group_id":{"type":"integer","nullable":true,"description":"Win-one-cancel-rest group"}}}}}},"responses":{"201":{"description":"Snipe created and armed","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"id":{"type":"integer"},"item_id":{"type":"string","description":"eBay numeric item ID"},"title":{"type":"string"},"image_url":{"type":"string","nullable":true},"currency":{"type":"string","example":"USD"},"current_bid":{"type":"number","nullable":true},"bid_count":{"type":"integer","nullable":true},"end_time":{"type":"string","format":"date-time"},"max_bid":{"type":"number"},"lead_seconds":{"type":"integer"},"group_id":{"type":"integer","nullable":true},"status":{"type":"string","enum":["active","fired","won","lost","cancelled","error","skipped"]},"fired_at":{"type":"string","format":"date-time","nullable":true},"result_detail":{"type":"string","nullable":true}}},{"type":"object","properties":{"confirmation":{"type":"object","properties":{"summary":{"type":"string","description":"Human read-back of what was armed"},"dry_run":{"type":"boolean"},"fires_at":{"type":"string","format":"date-time","description":"When the bid is scheduled to fire"}}}}}]}}}},"400":{"description":"Bad input or auction already ended","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"402":{"description":"Active subscription required","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"eBay account not connected","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/snipes/{id}":{"get":{"summary":"Get one snipe with an agent-friendly result summary","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Snipe with `result` outcome summary","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"item_id":{"type":"string","description":"eBay numeric item ID"},"title":{"type":"string"},"image_url":{"type":"string","nullable":true},"currency":{"type":"string","example":"USD"},"current_bid":{"type":"number","nullable":true},"bid_count":{"type":"integer","nullable":true},"end_time":{"type":"string","format":"date-time"},"max_bid":{"type":"number"},"lead_seconds":{"type":"integer"},"group_id":{"type":"integer","nullable":true},"status":{"type":"string","enum":["active","fired","won","lost","cancelled","error","skipped"]},"fired_at":{"type":"string","format":"date-time","nullable":true},"result_detail":{"type":"string","nullable":true},"result":{"type":"object","description":"Agent-friendly outcome summary for a snipe","properties":{"outcome":{"type":"string","enum":["won","lost","pending","active","cancelled","error","skipped"],"description":"won/lost = settled; pending = bid placed, auction not settled yet; active = armed, not fired yet"},"summary":{"type":"string","description":"Plain-language result, safe to relay to the user"},"final_price":{"type":"number","nullable":true},"currency":{"type":"string","example":"USD"},"fired_at":{"type":"string","format":"date-time","nullable":true},"fires_at":{"type":"string","format":"date-time","description":"For active snipes: scheduled fire time"},"settled":{"type":"boolean"}}}}}}}},"404":{"description":"Snipe not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}},"patch":{"summary":"Change the max bid of an active snipe","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["max_bid"],"properties":{"max_bid":{"type":"number","example":65}}}}}},"responses":{"200":{"description":"Updated snipe","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"item_id":{"type":"string","description":"eBay numeric item ID"},"title":{"type":"string"},"image_url":{"type":"string","nullable":true},"currency":{"type":"string","example":"USD"},"current_bid":{"type":"number","nullable":true},"bid_count":{"type":"integer","nullable":true},"end_time":{"type":"string","format":"date-time"},"max_bid":{"type":"number"},"lead_seconds":{"type":"integer"},"group_id":{"type":"integer","nullable":true},"status":{"type":"string","enum":["active","fired","won","lost","cancelled","error","skipped"]},"fired_at":{"type":"string","format":"date-time","nullable":true},"result_detail":{"type":"string","nullable":true}}}}}},"404":{"description":"Snipe not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}},"409":{"description":"Snipe is not active","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/snipes/{id}/cancel":{"post":{"summary":"Cancel an active snipe","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"item_id":{"type":"string","description":"eBay numeric item ID"},"title":{"type":"string"},"image_url":{"type":"string","nullable":true},"currency":{"type":"string","example":"USD"},"current_bid":{"type":"number","nullable":true},"bid_count":{"type":"integer","nullable":true},"end_time":{"type":"string","format":"date-time"},"max_bid":{"type":"number"},"lead_seconds":{"type":"integer"},"group_id":{"type":"integer","nullable":true},"status":{"type":"string","enum":["active","fired","won","lost","cancelled","error","skipped"]},"fired_at":{"type":"string","format":"date-time","nullable":true},"result_detail":{"type":"string","nullable":true}}}}}},"404":{"description":"Snipe not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/snipes/{id}/recheck":{"post":{"summary":"Re-verify a fired snipe’s outcome on demand","description":"Runs the settle check against eBay right now and returns the fresh result — the agent’s way to answer \"did my user win?\" without waiting for the automatic pass. Only fired snipes can be re-checked.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Recheck outcome","content":{"application/json":{"schema":{"type":"object","properties":{"rechecked":{"type":"boolean"},"reason":{"type":"string"},"snipe":{"type":"object","properties":{"id":{"type":"integer"},"item_id":{"type":"string","description":"eBay numeric item ID"},"title":{"type":"string"},"image_url":{"type":"string","nullable":true},"currency":{"type":"string","example":"USD"},"current_bid":{"type":"number","nullable":true},"bid_count":{"type":"integer","nullable":true},"end_time":{"type":"string","format":"date-time"},"max_bid":{"type":"number"},"lead_seconds":{"type":"integer"},"group_id":{"type":"integer","nullable":true},"status":{"type":"string","enum":["active","fired","won","lost","cancelled","error","skipped"]},"fired_at":{"type":"string","format":"date-time","nullable":true},"result_detail":{"type":"string","nullable":true}}},"result":{"type":"object","description":"Agent-friendly outcome summary for a snipe","properties":{"outcome":{"type":"string","enum":["won","lost","pending","active","cancelled","error","skipped"],"description":"won/lost = settled; pending = bid placed, auction not settled yet; active = armed, not fired yet"},"summary":{"type":"string","description":"Plain-language result, safe to relay to the user"},"final_price":{"type":"number","nullable":true},"currency":{"type":"string","example":"USD"},"fired_at":{"type":"string","format":"date-time","nullable":true},"fires_at":{"type":"string","format":"date-time","description":"For active snipes: scheduled fire time"},"settled":{"type":"boolean"}}}}}}}},"404":{"description":"Snipe not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/groups":{"get":{"summary":"List win-one-cancel-rest groups","responses":{"200":{"description":"Groups with member snipes","content":{"application/json":{"schema":{"type":"array"}}}}}},"post":{"summary":"Create a group (requires subscription)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"cameras"}}}}}},"responses":{"201":{"description":"Group created"},"402":{"description":"Active subscription required","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/groups/{id}/add":{"post":{"summary":"Add a snipe to a group","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["snipe_id"],"properties":{"snipe_id":{"type":"integer"}}}}}},"responses":{"200":{"description":"Updated snipe"}}}},"/api/groups/{id}":{"delete":{"summary":"Delete a group (members stay active)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Deleted"}}}},"/api/auth/signup":{"post":{"summary":"Register (public)","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8},"referral_code":{"type":"string"}}}}}},"responses":{"201":{"description":"Created — sets sh_session cookie"}}}},"/api/auth/login":{"post":{"summary":"Log in (public)","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"Logged in — sets sh_session cookie"}}}},"/api/auth/me":{"get":{"summary":"Acting user: account, subscription, eBay state","responses":{"200":{"description":"User object"}}}},"/api/auth/ebay/url":{"get":{"summary":"Start eBay OAuth — give the URL to the user to approve in a browser","responses":{"200":{"description":"{ url }","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}}}}},"/api/auth/ebay/disconnect":{"post":{"summary":"Clear the user's eBay tokens","responses":{"200":{"description":"Disconnected"}}}},"/api/billing/topup":{"post":{"summary":"Create a Stripe Checkout session (one-time) to top up sniping credit","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pack"],"properties":{"pack":{"type":"string","enum":["pack10","pack25","pack50","pack100"]}}}}}},"responses":{"200":{"description":"{ url } — redirect the user to Stripe Checkout","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}}}}}}}}},"/api/billing/status":{"get":{"summary":"Sniping credit status (balance, free wins, can-snipe)","responses":{"200":{"description":"credit_balance_cents, trial_snipes_remaining, packs"}}}},"/api/billing/ledger":{"get":{"summary":"Credit ledger (newest first)","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"Array of ledger entries"}}}},"/api/admin/agents/keys":{"get":{"summary":"OPERATOR — every agent API key on the server (all users)","description":"Your AI agent watches and manages all user agents here.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Key list with owner email, scopes, caps, last-used","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","description":"Per-user agent API key (hashes are never exposed)","properties":{"id":{"type":"integer"},"name":{"type":"string"},"key_prefix":{"type":"string","example":"skw_9f2c41ab"},"scopes":{"type":"array","items":{"type":"string","enum":["*","snipes:read","snipes:write","billing:read","billing:topup","billing:subscribe","webhooks:write","reviews:write","account:read","keys:write"]},"description":"'*' = full access (default). Scoped keys can only use routes in their scopes."},"daily_cap_cents":{"type":"integer","description":"Per-key 24h money cap in cents. Default 10000 ($100). 0 = unlimited."},"created_at":{"type":"integer"},"last_used_at":{"type":"integer","nullable":true}}}}}}}}}},"/api/admin/agents/keys/{id}/revoke":{"post":{"summary":"OPERATOR — revoke any user’s agent key","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Revoked"},"404":{"description":"Key not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/admin/agents/actions":{"get":{"summary":"OPERATOR — audit trail of every agent action on the server","description":"Includes admin.* entries: credit adjustments and chargeback handling are logged with via=session|operator_key, so you can always tell what your AI agent did vs the console.","parameters":[{"name":"user_id","in":"query","schema":{"type":"integer"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Audit entries (newest first)"}}}},"/api/admin/agents/confirmations":{"get":{"summary":"OPERATOR — pending money confirmations across all users","description":"Single-use tokens awaiting user approval (15-minute TTL).","responses":{"200":{"description":"Pending confirmations"}}}},"/api/admin/agents/webhooks":{"get":{"summary":"OPERATOR — all result webhooks + delivery health","responses":{"200":{"description":"Webhook list (all users)"}}}},"/api/admin/chargebacks":{"get":{"summary":"OPERATOR — list chargebacks & fraud warnings","description":"Your AI agent triages these: needs_response first (evidence due soonest), then unreviewed. Filter by status.","parameters":[{"name":"status","in":"query","schema":{"type":"string","example":"needs_response"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Dispute list"}}}},"/api/admin/chargebacks/{id}":{"get":{"summary":"OPERATOR — chargeback detail","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Dispute detail"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/admin/chargebacks/{id}/note":{"post":{"summary":"OPERATOR — add an internal note to a chargeback","description":"Notes are internal only — the buyer never sees them.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Updated dispute"}}}},"/api/admin/chargebacks/{id}/evidence":{"post":{"summary":"OPERATOR — submit evidence to Stripe (disputes only, while needs_response)","description":"Moves real money arguments at Stripe. Only while the dispute needs a response; Stripe locks evidence after the due date.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"customer_name":{"type":"string"},"customer_email":{"type":"string"},"product_description":{"type":"string"},"service_date":{"type":"string"},"uncategorized_text":{"type":"string"}}}}}},"responses":{"200":{"description":"Dispute marked evidence_submitted"},"400":{"description":"Not submittable","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}}}}}}}},"/api/admin/chargebacks/{id}/close":{"post":{"summary":"OPERATOR — close a dispute as lost in Stripe (concedes it)","description":"Irreversible at Stripe. Prefer this over letting a weak dispute auto-lose without evidence.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Dispute closed"}}}},"/api/admin/chargebacks/{id}/mark":{"post":{"summary":"OPERATOR — record the dispute outcome internally (won|lost)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["outcome"],"properties":{"outcome":{"type":"string","enum":["won","lost"]}}}}}},"responses":{"200":{"description":"Marked"}}}},"/api/admin/chargebacks/{id}/clawback":{"post":{"summary":"OPERATOR — claw the disputed credit back from the buyer’s balance","description":"Idempotent: safe to retry. No-ops (already_clawed_back) when nothing is owed. Audit-logged with the amount.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Clawback result"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Admin SNIPEWISE_API_KEY (operator mode, supports ?as_user=<email>) or a per-user agent key (skw_…, scoped to its owner)."},"cookieAuth":{"type":"apiKey","in":"cookie","name":"sh_session","description":"JWT session cookie set by /api/auth/login."}}}}