{"openapi":"3.1.0","info":{"title":"SiteAssay API","version":"v1","summary":"Start website audits and read their measurements and client-ready reports.","description":"Audits are asynchronous. POST /audits returns 202 with a queued audit; poll GET /audits/{audit} until its status is \"done\", then read the measurements there or the written report from GET /reports/{report}.\n\nScores are pain scores: higher means more problems were found. A null category was not measured, which is not the same as zero.\n\nAuthenticate with a bearer token issued from Settings, API in the SiteAssay console. API access is on the Agency plan and above. A token carries the `read` ability, the `write` ability, or both; starting an audit needs `write` and spends one audit from the account's monthly allowance, and everything else needs either. Every response is scoped to the account that owns the token."},"security":[{"bearerToken":[]}],"paths":{"/audits":{"post":{"operationId":"startAudit","summary":"Start an audit","description":"Starts an audit for a URL, or for a prospect already held in the account. Requires the `write` ability. Do not start a second audit for the same site while one is queued or running: each call spends one audit from the monthly allowance.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartAudit"}}}},"responses":{"202":{"description":"The audit is queued. Poll its `links.self` until `status` is \"done\".","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Audit"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"get":{"operationId":"listAudits","summary":"List audits","description":"The account's audits, newest first.","parameters":[{"name":"prospect_id","in":"query","description":"Only audits of this prospect.","schema":{"type":"string","format":"ulid"}},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/AuditStatus"}},{"name":"per_page","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}}],"responses":{"200":{"description":"One page of audits.","content":{"application/json":{"schema":{"type":"object","required":["data","links","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Audit"}},"links":{"$ref":"#/components/schemas/PaginationLinks"},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/audits/{audit}":{"get":{"operationId":"getAudit","summary":"Get an audit","description":"One audit, with its score. `payload` stays null until the audit is done, so a running audit is never served half-built.","parameters":[{"$ref":"#/components/parameters/AuditId"}],"responses":{"200":{"description":"The audit.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Audit"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/audits/{audit}/sarif":{"get":{"operationId":"getAuditSarif","summary":"Get an audit's security findings as SARIF","description":"A SARIF 2.1.0 document, suitable for GitHub code scanning.","parameters":[{"$ref":"#/components/parameters/AuditId"}],"responses":{"200":{"description":"The SARIF log.","content":{"application/sarif+json":{"schema":{"type":"object","description":"A SARIF 2.1.0 log. See https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html","required":["version","runs"],"properties":{"$schema":{"type":"string"},"version":{"const":"2.1.0"},"runs":{"type":"array","items":{"type":"object"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/reports/{report}":{"get":{"operationId":"getReport","summary":"Get a report","description":"The written report for a finished audit: the executive summary, a plain-English explanation of each graded category, the prioritised action plan and a share link the agency can send to the business. Prefer it over the audit payload when writing for a person.","parameters":[{"name":"report","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"The report.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Report"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}}},"components":{"securitySchemes":{"bearerToken":{"type":"http","scheme":"bearer","description":"A token issued from Settings, API. The same token drives the REST API, the MCP server and webhooks."}},"parameters":{"AuditId":{"name":"audit","in":"path","required":true,"schema":{"type":"integer"}}},"responses":{"Unauthenticated":{"description":"No token, or one that has expired or been revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The token lacks the ability this call needs, the plan does not include API access, or the account is unverified or disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No such record in this account. A record that belongs to another account is also a 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ValidationFailed":{"description":"The request did not validate.","content":{"application/json":{"schema":{"type":"object","required":["message","errors"],"properties":{"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}}},"TooManyRequests":{"description":"Too many requests, or no audits left in this period. Wait for the number of seconds in the Retry-After header.","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"retry_after":{"type":"integer"}}}}}}},"schemas":{"StartAudit":{"type":"object","description":"Exactly one of `url` and `prospect_id`.","properties":{"url":{"type":"string","maxLength":255,"examples":["https://example.com"]},"prospect_id":{"type":"string","format":"ulid"},"name":{"type":["string","null"],"maxLength":255,"description":"The business name, when auditing by URL."}},"oneOf":[{"required":["url"]},{"required":["prospect_id"]}]},"AuditStatus":{"type":"string","enum":["queued","running","done","failed"]},"ToolStatus":{"type":"string","enum":["ok","empty","failed","missing","skipped"]},"Audit":{"type":"object","required":["id","object","domain","status","tools","payload","links"],"properties":{"id":{"type":"integer"},"object":{"const":"audit"},"prospect_id":{"type":["string","null"],"format":"ulid"},"domain":{"type":"string"},"status":{"$ref":"#/components/schemas/AuditStatus"},"payload_version":{"type":["string","null"],"description":"Which version of the payload schema this audit was written against."},"requested_at":{"type":["string","null"],"format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"tools":{"type":"object","description":"How each check in this audit ended, keyed by check name. A check that is absent from the payload is explained here.","additionalProperties":{"$ref":"#/components/schemas/ToolStatus"}},"payload":{"type":["object","null"],"description":"The measurements, keyed by check. Only schema-declared fields are included. Null until the audit is done.","additionalProperties":true},"score":{"oneOf":[{"$ref":"#/components/schemas/Score"},{"type":"null"}]},"links":{"type":"object","required":["self"],"properties":{"self":{"type":"string","format":"uri"}}}}},"Score":{"type":"object","required":["id","object","composite","direction","grade","categories"],"properties":{"id":{"type":"integer"},"object":{"const":"score"},"composite":{"type":["number","null"],"description":"The composite pain score. Higher is worse."},"direction":{"const":"higher_is_worse"},"grade":{"type":"string","enum":["A","B","C","D","F"]},"profile":{"type":["string","null"]},"categories":{"type":"object","description":"Each category's pain score. Null means not measured, which is not the same as zero.","properties":{"performance":{"type":["number","null"]},"accessibility":{"type":["number","null"]},"seo":{"type":["number","null"]},"security":{"type":["number","null"]},"tech":{"type":["number","null"]},"reputation":{"type":["number","null"]},"aeo":{"type":["number","null"]},"geo":{"type":["number","null"]},"cro":{"type":["number","null"]},"quality":{"type":["number","null"]},"gbp":{"type":["number","null"]}}},"top_issues":{"type":"array","items":[]},"created_at":{"type":["string","null"],"format":"date-time"}}},"Report":{"type":"object","required":["id","object","domain","share_url","summary","sections","issues","links"],"properties":{"id":{"type":"integer"},"object":{"const":"report"},"prospect_id":{"type":["string","null"],"format":"ulid"},"audit_id":{"type":["integer","null"]},"business":{"type":["string","null"]},"domain":{"type":"string"},"share_url":{"type":"string","format":"uri","description":"The public link to send to the business. It opens without an account."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the share link stops working. Null never expires."},"view_count":{"type":"integer"},"first_viewed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"},"summary":{"type":"object","properties":{"headline":{"type":"string"},"verdict":{"type":"string"},"score":{"type":["number","null"]},"direction":{"const":"higher_is_worse"},"grade":{"type":["string","null"]},"label":{"type":["string","null"]},"counts":{"type":"object"}}},"sections":{"type":"array","description":"One entry per graded category.","items":{"$ref":"#/components/schemas/ReportSection"}},"issues":{"type":"array","description":"The prioritised action plan, in the order the report shows it.","items":{"type":"object"}},"links":{"type":"object","required":["self"],"properties":{"self":{"type":"string","format":"uri"},"audit":{"type":["string","null"],"format":"uri"}}}}},"ReportSection":{"type":"object","properties":{"key":{"type":"string"},"number":{"type":["integer","string"]},"title":{"type":"string"},"score":{"type":["number","null"]},"grade":{"type":["string","null"]},"verdict":{"type":["string","null"]},"status":{"type":["string","null"]},"summary":{"type":["string","null"]},"impact":{"type":["string","null"]},"badge":{"type":["string","null"],"description":"Why a section may have no numbers: MEASURED, PARTIAL, UNAVAILABLE or NOT INCLUDED."},"metrics":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":[],"status":{"type":["string","null"]},"meaning":{"type":["string","null"]},"matters":{"type":["string","null"]},"impact":{"type":["string","null"]},"fix_hint":{"type":["string","null"]},"confidence":[]}}}}},"Error":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}},"PaginationLinks":{"type":"object","properties":{"first":{"type":["string","null"],"format":"uri"},"last":{"type":["string","null"],"format":"uri"},"prev":{"type":["string","null"],"format":"uri"},"next":{"type":["string","null"],"format":"uri"}}},"PaginationMeta":{"type":"object","properties":{"current_page":{"type":"integer"},"from":{"type":["integer","null"]},"last_page":{"type":"integer"},"path":{"type":"string","format":"uri"},"per_page":{"type":"integer"},"to":{"type":["integer","null"]},"total":{"type":"integer"}}}}},"servers":[{"url":"https://siteassay.com/api/v1"}],"externalDocs":{"description":"API, MCP server and webhooks","url":"https://siteassay.com/integrations"}}