{"openapi":"3.0.0","info":{"title":"AccessioAI Public API v1","version":"1.0.0","description":"Programmatic access to accessibility scans, alt-text, embed scripts, and usage quotas. Authenticate with the `X-API-Key` header (service-account key issued from the dashboard). Every response includes a `disclaimer` and `scanSchemaVersion`; agent clients that cache results MUST revalidate when `scanSchemaVersion` changes."},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}}},"security":[{"ApiKeyAuth":[]}],"paths":{"/v1/alt-text/{id}/approve":{"post":{"summary":"Approve a pending alt-text suggestion.","description":"Transitions the record to APPROVED (or EDITED when `approvedText` differs from the generated text). Only PENDING records can be approved; non-PENDING records return 409. The state change and audit row commit atomically in a single transaction. Requires the `alt-text:approve` scope.","tags":["AltText"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"approvedText":{"type":"string","minLength":1,"maxLength":2000},"longDescription":{"type":"string","minLength":1,"maxLength":10000}}}}}},"responses":{"200":{"description":"Approved record."},"400":{"description":"Invalid id","body":null,"or JSON.":null},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant alt-text:approve."},"404":{"description":"Not found or not owned by the authenticated org."},"409":{"description":"Record is not in PENDING state."},"413":{"description":"Payload too large."},"415":{"description":"Unsupported media type."},"429":{"description":"Rate limit exceeded."}}}},"/v1/alt-text/{id}/reject":{"post":{"summary":"Reject a pending alt-text suggestion.","description":"Transitions the record to REJECTED with a reason. Only PENDING records can be rejected; non-PENDING records return 409. The state change and audit row commit atomically in a single transaction. Requires the `alt-text:approve` scope.","tags":["AltText"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["reason"],"properties":{"reason":{"type":"string","enum":["DECORATIVE","ALREADY_MEANINGFUL","NEEDS_LONG_DESC","BRAND_SPECIFIC","OTHER"]}}}}}},"responses":{"200":{"description":"Rejected record."},"400":{"description":"Invalid id","body":null,"or JSON.":null},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant alt-text:approve."},"404":{"description":"Not found or not owned by the authenticated org."},"409":{"description":"Record is not in PENDING state."},"413":{"description":"Payload too large."},"415":{"description":"Unsupported media type."},"429":{"description":"Rate limit exceeded."}}}},"/v1/alt-text/{id}":{"get":{"summary":"Get a single alt-text suggestion by id.","description":"Returns the alt-text record if it belongs to the authenticated organization, otherwise 404. Requires the `alt-text:read` scope.","tags":["AltText"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Alt-text record."},"400":{"description":"Malformed id."},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant alt-text:read."},"404":{"description":"Not found or not owned by the authenticated org."},"429":{"description":"Rate limit exceeded."}}}},"/v1/alt-text":{"get":{"summary":"List generated alt-text suggestions for the organization.","description":"Newest-first, cursor paginated. Optional filters by status and embedScriptId. Requires the `alt-text:read` scope.","tags":["AltText"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"type":"string","enum":["PENDING","APPROVED","EDITED","REJECTED"]}},{"name":"embedScriptId","in":"query","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Alt-text suggestions list."},"400":{"description":"Invalid query."},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant alt-text:read."},"429":{"description":"Rate limit exceeded."}}}},"/v1/embed-scripts":{"get":{"summary":"List embed scripts for the authenticated organization.","description":"Returns embed-script metadata — website URL, status, whitelabel labels, trial/expiry timestamps. Does NOT return the script token or its hash. Requires the `embed:read` scope.","tags":["EmbedScripts"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"type":"string","maxLength":20}}],"responses":{"200":{"description":"Embed script list."},"400":{"description":"Invalid query or cursor."},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant embed:read."},"429":{"description":"Rate limit exceeded."}}}},"/v1/quota":{"get":{"summary":"Current-month usage and plan limits for the organization.","description":"Returns the cached quota snapshot (currentUsage / quotaLimit / tier / isOverQuota) for the authenticated organization. BigInt counters are serialized as strings to avoid JS precision loss. Requires the `quota:read` scope.","tags":["Quota"],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Quota snapshot."},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant quota:read."},"429":{"description":"Rate limit exceeded."}}}},"/v1/scans/{id}/issues":{"get":{"summary":"List WCAG issues for a scan session.","description":"Returns issues ordered by priority (highest first), then newest. Optional severity / status filters. Raw HTML is never returned in this endpoint. Requires the `scans:read` scope.","tags":["Scans"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"severity","in":"query","schema":{"type":"string","enum":["CRITICAL","SERIOUS","MODERATE","MINOR"]}},{"name":"status","in":"query","schema":{"type":"string","enum":["OPEN","CONFIRMED","FIXED","WONT_FIX","FALSE_POSITIVE","SUPPRESSED"]}}],"responses":{"200":{"description":"Issues list."},"400":{"description":"Invalid id or query parameters."},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant scans:read."},"404":{"description":"Scan session not found in this organization."},"429":{"description":"Rate limit exceeded."}}}},"/v1/scans/{id}":{"get":{"summary":"Get a single scan session by id.","description":"Returns the scan session if it belongs to the authenticated organization, otherwise 404. Requires the `scans:read` scope.","tags":["Scans"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Scan session."},"400":{"description":"Malformed id."},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant scans:read."},"404":{"description":"Not found or not owned by the authenticated org."},"429":{"description":"Rate limit exceeded."}}}},"/v1/scans/{id}/run":{"post":{"summary":"Queue a re-run of an existing scan session.","description":"Records the intent to re-scan on both sides of the Node/JVM boundary (single shared requestId) and returns 202. Supply an `Idempotency-Key` header to make retries safe; the cached response replays on duplicate keys within 24h. Requires the `scans:write` scope.","tags":["Scans"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,128}$"}}],"responses":{"202":{"description":"Run accepted."},"400":{"description":"Malformed id or idempotency key."},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant scans:write."},"404":{"description":"Scan session not found in this organization."},"429":{"description":"Rate limit exceeded."},"502":{"description":"Upstream accessio-service rejected or was unreachable."}}}},"/v1/scans":{"get":{"summary":"List accessibility scan sessions for the authenticated organization.","description":"Returns scan sessions newest-first. Paginate with cursor (last id from previous page). Rate limited to 60 requests / minute per API key. Requires the `scans:read` scope.","tags":["Scans"],"security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Scan sessions list."},"400":{"description":"Invalid query parameters or cursor."},"401":{"description":"Missing or invalid API key."},"403":{"description":"API key does not grant scans:read."},"429":{"description":"Rate limit exceeded."}}}}},"tags":[]}