{"openapi":"3.0.3","info":{"title":"Vouchfield API","version":"1.1","description":"Website audits for agencies. Auth: Authorization: Bearer <API key>. Each key may make 120 requests a minute (429 with Retry-After beyond that). Errors are JSON {detail, error: {status, code, message}}. Lists are paged with limit and cursor. Full notes: /v1/docs."},"servers":[{"url":"https://vouchfield.com"}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","properties":{"detail":{"description":"The sentence (a list of fields for 422)."},"error":{"type":"object","properties":{"status":{"type":"integer"},"code":{"type":"string"},"message":{"type":"string"}},"required":["status","code","message"],"additionalProperties":false}},"required":["detail","error"],"additionalProperties":false},"Business":{"type":"object","properties":{"name":{"type":"string","nullable":true},"town":{"type":"string","nullable":true},"st":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"industry":{"type":"string","nullable":true},"matched_listing":{"type":"boolean","nullable":true},"context":{"type":"object","nullable":true}},"required":["name","town","st","phone","industry","matched_listing","context"],"additionalProperties":false},"Scan":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["queued","running","done","failed"]},"url":{"type":"string","nullable":true},"started_at":{"type":"string","nullable":true,"description":"When the audit was started (UTC, YYYY-MM-DDTHH:MM:SS)."},"finished_at":{"type":"string","nullable":true,"description":"When it reached done or failed (UTC). Null while it runs, and for audits finished before 27 September 2026."},"failure_reason":{"type":"string","enum":["site_unreachable","error"],"nullable":true,"description":"Only for status failed: site_unreachable when we could not reach or read the website, otherwise error."},"report_url":{"type":"string"},"business":{"$ref":"#/components/schemas/Business"},"score":{"type":"number","nullable":true},"categories":{"type":"object","nullable":true},"top_fixes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"detail":{"type":"string","nullable":true},"fix":{"type":"string","nullable":true},"problem":{"type":"string","nullable":true,"description":"What is wrong, in plain words, when the check failed or needs work (for example \"The site doesn't use HTTPS\"). Null when it passed or was not checked, and for a question added to a template by hand (use name then)."}}}},"summary":{"type":"string"},"ai_audit":{"type":"string","nullable":true},"note":{"type":"string"},"credit_returned":{"type":"boolean","description":"True when the audit failed because we could not reach the website and its credit was given back."},"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"detail":{"type":"string","nullable":true},"fix":{"type":"string","nullable":true},"weight":{"type":"number","nullable":true},"problem":{"type":"string","nullable":true,"description":"What is wrong, in plain words, when the check failed or needs work (for example \"The site doesn't use HTTPS\"). Null when it passed or was not checked, and for a question added to a template by hand (use name then)."},"manual":{"type":"boolean","description":"True for a question the agency added to its template and answered by hand: its status is the agency's answer, not something the audit measured. False for every check the audit ran."}}}}},"required":["id","status","url","started_at","finished_at","failure_reason","report_url","business","score","categories","top_fixes","summary","ai_audit","note","credit_returned","checks"],"additionalProperties":false},"ScanSummary":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["queued","running","done","failed"]},"url":{"type":"string","nullable":true},"started_at":{"type":"string","nullable":true,"description":"When the audit was started (UTC, YYYY-MM-DDTHH:MM:SS)."},"finished_at":{"type":"string","nullable":true,"description":"When it reached done or failed (UTC). Null while it runs, and for audits finished before 27 September 2026."},"failure_reason":{"type":"string","enum":["site_unreachable","error"],"nullable":true,"description":"Only for status failed: site_unreachable when we could not reach or read the website, otherwise error."},"report_url":{"type":"string"},"business":{"$ref":"#/components/schemas/Business"},"score":{"type":"number","nullable":true},"categories":{"type":"object","nullable":true},"top_fixes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"detail":{"type":"string","nullable":true},"fix":{"type":"string","nullable":true},"problem":{"type":"string","nullable":true,"description":"What is wrong, in plain words, when the check failed or needs work (for example \"The site doesn't use HTTPS\"). Null when it passed or was not checked, and for a question added to a template by hand (use name then)."}}}},"summary":{"type":"string"},"ai_audit":{"type":"string","nullable":true},"note":{"type":"string"},"credit_returned":{"type":"boolean","description":"True when the audit failed because we could not reach the website and its credit was given back."}},"required":["id","status","url","started_at","finished_at","failure_reason","report_url","business","score","categories","top_fixes","summary","ai_audit","note","credit_returned"],"additionalProperties":false},"ScanList":{"type":"object","properties":{"scans":{"type":"array","items":{"$ref":"#/components/schemas/ScanSummary"}},"count":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Pass as cursor for the next page; null on the last page."}},"required":["scans","count","has_more","next_cursor"],"additionalProperties":false},"ScanStarted":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["queued","running","done","failed"]},"status_url":{"type":"string"},"report_url":{"type":"string"}},"required":["id","status","status_url","report_url"],"additionalProperties":false},"Key":{"type":"object","properties":{"kind":{"type":"string","enum":["main","extra"]},"id":{"type":"string","nullable":true},"label":{"type":"string"}},"required":["kind","id","label"],"additionalProperties":false},"Me":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"key":{"$ref":"#/components/schemas/Key"},"usage":{"type":"object"},"quota_ok":{"type":"boolean"},"quota_note":{"type":"string"},"webhook_url":{"type":"string","description":"Main key only; empty for an extra key."},"webhook_events":{"type":"array","items":{"type":"string"},"description":"Main key only; empty for an extra key."},"webhook_status":{"type":"string","enum":["none","on","switched_off",""],"description":"Main key only; empty for an extra key."},"signature":{"type":"string"}},"required":["id","name","key","usage","quota_ok","quota_note","webhook_url","webhook_events","webhook_status","signature"],"additionalProperties":false},"Plan":{"type":"object","properties":{"plan":{"type":"string"},"label":{"type":"string"},"daily_limit":{"type":"integer"},"monthly_credits":{"type":"integer"},"trial_credits":{"type":"integer"},"status":{"type":"string"},"until":{"type":"string"},"cancel_at":{"type":"string"},"active":{"type":"boolean"}},"required":["plan","label","daily_limit","monthly_credits","trial_credits","status","until","cancel_at","active"],"additionalProperties":false},"Seat":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"key_prefix":{"type":"string"},"active":{"type":"boolean"},"ts":{"type":"string"},"last_used":{"type":"string"}},"required":["id","label","key_prefix","active","ts","last_used"],"additionalProperties":false},"SeatList":{"type":"object","properties":{"seats":{"type":"array","items":{"$ref":"#/components/schemas/Seat"}},"count":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Pass as cursor for the next page; null on the last page."}},"required":["seats","count","has_more","next_cursor"],"additionalProperties":false},"SeatCreated":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"api_key":{"type":"string","description":"Shown once."},"key_prefix":{"type":"string"},"note":{"type":"string"}},"required":["id","label","api_key","key_prefix","note"],"additionalProperties":false},"Revoked":{"type":"object","properties":{"revoked":{"type":"string"}},"required":["revoked"],"additionalProperties":false},"Slack":{"type":"object","properties":{"slack":{"type":"boolean"}},"required":["slack"],"additionalProperties":false},"TestMessage":{"type":"object","properties":{"event":{"type":"string"},"test":{"type":"boolean"},"message":{"type":"string"}},"required":["event","test","message"],"additionalProperties":false}}},"security":[{"bearer":[]}],"paths":{"/v1/scan":{"post":{"summary":"Start a website audit. Uses one credit.","operationId":"post_v1_scan","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":100},"description":"Send the same value again (for example after a timeout) and you get the same audit back instead of a new one, with no second credit."}],"responses":{"202":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStarted"}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This API key cannot do that.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request is too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}},"503":{"description":"The service is not available right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"name":{"type":"string"},"town":{"type":"string"},"st":{"type":"string"},"profile":{"type":"string","description":"An audit template id from your workspace."},"business_type":{"type":"string","enum":["auto","local","service_area","software","online"],"default":"auto"},"webhook_url":{"type":"string","description":"Main key only. Saves the workspace's webhook address (https only)."},"private":{"type":"boolean","default":false,"description":"true keeps the report to your workspace (share it with a client link). Without it, anyone with report_url can open the report."}},"required":["url"],"additionalProperties":false}}}}}},"/v1/scan/{id}":{"get":{"summary":"One audit with every check.","operationId":"get_v1_scan_id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This API key cannot do that.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}}}},"/v1/scans":{"get":{"summary":"Your audits, most recently changed first, a page at a time (for polling).","operationId":"get_v1_scans","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["queued","running","done","failed","any"],"default":"done"},"description":"Only audits with this status."},{"name":"site","in":"query","required":false,"schema":{"type":"string"},"description":"Only audits of this website, for example example.com (subdomains included)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Page size."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanList"}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This API key cannot do that.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}}}},"/v1/me":{"get":{"summary":"Your workspace, this key, usage and credits. Works on every plan.","operationId":"get_v1_me","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}}}},"/v1/plan":{"get":{"summary":"Your plan, monthly credits and daily limit. Works on every plan.","operationId":"get_v1_plan","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plan"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}}}},"/v1/seats":{"get":{"summary":"Your extra API keys, oldest first, a page at a time. An extra key sees only itself.","operationId":"get_v1_seats","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["all","active","revoked"],"default":"all"},"description":"Only keys in this state."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":100},"description":"Page size."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeatList"}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This API key cannot do that.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}}},"post":{"summary":"Make an extra API key (main key only; at most 25 switched on).","operationId":"post_v1_seats","parameters":[],"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeatCreated"}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This API key cannot do that.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request is too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","maxLength":60}},"required":[],"additionalProperties":false}}}}}},"/v1/seats/{id}":{"delete":{"summary":"Switch off an extra API key (main key only).","operationId":"delete_v1_seats_id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Revoked"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This API key cannot do that.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}}}},"/v1/integrations/slack":{"post":{"summary":"Post finished-audit notes to a Slack incoming webhook (main key only; an empty url disconnects).","operationId":"post_v1_integrations_slack","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Slack"}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This API key cannot do that.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request is too large.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait."}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"],"additionalProperties":false}}}}}},"/v1/openapi.json":{"get":{"summary":"This description of the API.","operationId":"get_v1_openapi_json","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}},"security":[]}},"/v1/docs":{"get":{"summary":"This API reference, as a web page.","operationId":"get_v1_docs","parameters":[],"responses":{"200":{"description":"OK","content":{"text/html":{"schema":{"type":"string"}}}}},"security":[]}}},"x-webhooks":{"scan.completed":{"description":"An audit finished. The body is the same JSON as GET /v1/scan/{id} (status done). Headers X-Audit-Event, X-Audit-Id, X-Audit-Timestamp and X-Audit-Signature (v1=HMAC-SHA256(secret, timestamp + '.' + body)). See /v1/docs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"scan.failed":{"description":"An audit failed. The body is the same JSON as GET /v1/scan/{id} with status failed and failure_reason; credit_returned is true when we could not reach the website and gave the credit back. Sent only when the owner ticks this event in Settings -> Webhooks. Headers X-Audit-Event, X-Audit-Id, X-Audit-Timestamp and X-Audit-Signature (v1=HMAC-SHA256(secret, timestamp + '.' + body)). See /v1/docs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Scan"}}}},"webhook.test":{"description":"A test message the owner sends from Settings -> Webhooks. Always the same small body. Headers X-Audit-Event, X-Audit-Id, X-Audit-Timestamp and X-Audit-Signature (v1=HMAC-SHA256(secret, timestamp + '.' + body)). See /v1/docs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestMessage"}}}}}}