{"openapi":"3.1.0","info":{"title":"vendor-evidence-drift","version":"0.1.0","description":"Monitor vendor security, privacy, subprocessor, certification and SLA evidence, and map changes to controls and risk."},"servers":[{"url":"https://web-production-e514b.up.railway.app"}],"paths":{"/health":{"get":{"summary":"Liveness and deployed version","operationId":"getHealth","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/":{"get":{"summary":"Service index — endpoints, auth and error format","operationId":"get/","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/checks":{"post":{"summary":"Compare two evidence snapshots and return drift, controls and risk","description":"Send a previous and a current snapshot of one vendor's published compliance evidence — certifications, subprocessors, policies, SLA commitments, data-residency statements, pen-test and insurance documents. Returns every drift finding with a stable code, a fixed severity, the controls it bears on and a recommended action, plus a risk score whose derivation is printed line by line. Expiry arithmetic is in whole UTC days and expiresAt is the last day covered. Omit \"previous\" for a first-ever baseline. Send one \"check\" or up to 50 in \"checks\" — billed one evidence_check per check. This service compares only what you supply: it never fetches a vendor document, never verifies that a certificate is genuine, and makes no claim about any real vendor's actual posture.","operationId":"postV1Checks","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"reports":{"type":"array","items":{"type":"object","properties":{"vendorId":{"type":"string"},"baseline":{"type":"boolean","description":"True when no previous snapshot was supplied."},"windowDays":{"type":["integer","null"],"description":"Whole UTC days between the two observations."},"evaluatedAt":{"type":"string","description":"The evaluation instant. Pin it and the whole report is reproducible."},"findings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"Stable. Branch on this, never on \"detail\". Full list at GET /v1/drift-types."},"kind":{"type":"string","enum":["drift","posture"],"description":"\"drift\" needs both snapshots; \"posture\" is computed from the current one and the evaluation instant, so it appears on a baseline too."},"signal":{"type":"string","enum":["certification","subprocessor","policy","sla","residency","attestation"]},"severity":{"type":"string","enum":["info","low","medium","high","critical"],"description":"Fixed per code. Never varies by instance."},"points":{"type":"integer","description":"Contribution to the risk score. Derived from severity alone."},"material":{"type":"boolean","description":"True for high and above — the findings that set reviewRequired."},"subject":{"type":"string","description":"The pairing key the finding is about."},"detail":{"type":"string","description":"Plain language. Prose, and it will be reworded — do not match on it."},"recommendedAction":{"type":"string"},"controls":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"framework":{"type":"string"},"name":{"type":"string"}}},"description":"Editorial mapping, not an official crosswalk."},"data":{"type":"object","description":"Machine-readable specifics: previousValue, currentValue, gapDays, added, removed, deltaMinor, deltaBasisPoints."}}},"description":"Every finding, worst first. Never truncated to the worst one."},"counts":{"type":"object","description":"Findings per severity, plus a total."},"materialChanges":{"type":"integer"},"reviewRequired":{"type":"boolean"},"risk":{"type":"object","description":"score (0-100, capped), band, rawPoints, a per-signal breakdown and a line-by-line derivation you can reproduce by hand from the published severity table."},"expiries":{"type":"array","items":{"type":"object"},"description":"Every dated item in the current snapshot with daysUntilExpiry and a status, soonest first — whether or not it drifted."},"evidence":{"type":"object","description":"What was actually compared: item counts per collection, per snapshot."},"warnings":{"type":"array","items":{"type":"string"},"description":"What this check could NOT see. A missing scope array or contentHash disables the two quietest detections in the catalogue, and a clean result computed without them is not the same as a clean result."}}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"check":{"type":"object","required":["vendorId","current"],"properties":{"vendorId":{"type":"string","description":"Your identifier for the vendor. Echoed back; nothing is stored against it."},"vendorName":{"type":"string"},"previous":{"type":"object","required":["capturedAt"],"description":"The earlier observation. Omit it for a first-ever check: the response then carries baseline:true and current-state findings only.","properties":{"capturedAt":{"type":"string","format":"date","description":"When you observed this posture. Not the evaluation instant."},"certifications":{"type":"array","maxItems":50,"items":{"type":"object","required":["framework","issuedAt","expiresAt"],"properties":{"id":{"type":"string","description":"Pairing key across snapshots. Defaults to \"framework\". Supply one only when a vendor holds two certifications under the same framework."},"framework":{"type":"string","enum":["soc2_type1","soc2_type2","iso_27001","iso_27017","iso_27018","iso_27701","iso_22301","pci_dss","hitrust_csf","fedramp_moderate","fedramp_high","csa_star","cyber_essentials_plus","tisax","other"]},"name":{"type":"string","description":"Required when framework is \"other\"."},"auditor":{"type":"string","description":"Omit on either side and an auditor change cannot be detected; the response says so in warnings."},"issuedAt":{"type":"string","format":"date","description":"First day covered. Used to tell a late renewal from an on-time one."},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. A certification expiring today is valid today."},"scope":{"type":"array","maxItems":60,"items":{"type":"string"},"description":"Trust services criteria, Annex A domains, PCI levels. The field this API exists for: a SOC 2 that quietly drops \"availability\" changes nothing on its cover page."},"exceptions":{"type":"array","maxItems":100,"items":{"type":"string"},"description":"Qualifications noted in the report. Send [] to mean \"none noted\" — an absent array means \"not observed\" and suppresses the comparison."},"opinion":{"type":"string","enum":["unqualified","qualified","adverse","disclaimer","not_applicable"]}}}},"subprocessors":{"type":"array","maxItems":500,"items":{"type":"object","required":["name","country"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2. Required — the jurisdiction is the risk."},"purpose":{"type":"string"},"processesPersonalData":{"type":"boolean","description":"false to true on an existing subprocessor is a material widening."}}}},"policies":{"type":"array","maxItems":100,"items":{"type":"object","required":["name","version","effectiveAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"version":{"type":"string"},"effectiveAt":{"type":"string","format":"date"},"contentHash":{"type":"string","description":"Any stable digest of the document as you retrieved it. The only way to see a content change made under an unchanged version number."}}}},"slas":{"type":"array","maxItems":50,"items":{"type":"object","required":["metric"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"metric\"."},"metric":{"type":"string","enum":["uptime","support_response","incident_notification","breach_notification","rto","rpo"]},"uptimeBasisPoints":{"type":"integer","minimum":0,"maximum":10000,"description":"Required for \"uptime\" and rejected on every other metric. 99.95% is 9995 — integer basis points, never a float."},"targetMinutes":{"type":"integer","minimum":0,"description":"Required for every metric except \"uptime\". Whole minutes; four hours is 240. Lower is better, which is how \"weaker\" is decided."},"description":{"type":"string"}}}},"residency":{"type":"array","maxItems":50,"items":{"type":"object","required":["dataCategory","countries"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased dataCategory."},"dataCategory":{"type":"string","description":"What the statement covers — \"customer_content\", \"backups\", \"support_data\"."},"countries":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"},"description":"ISO 3166-1 alpha-2 codes."}}}},"attestations":{"type":"array","maxItems":100,"items":{"type":"object","required":["kind","issuedAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"kind\"."},"kind":{"type":"string","enum":["pen_test","insurance","questionnaire","bridge_letter","other"]},"name":{"type":"string"},"provider":{"type":"string","description":"Testing firm, insurer, or questionnaire standard."},"issuedAt":{"type":"string","format":"date"},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. Omit for evidence that does not expire."},"coverageMinor":{"type":"integer","minimum":0,"description":"Insurance only. INTEGER minor units — 5000000.5 cents is a 400, not a rounding. Requires \"currency\"."},"currency":{"type":"string","description":"ISO-4217. Required with coverageMinor. Limits in different currencies are not compared; the response says so."},"openFindings":{"type":"integer","minimum":0,"description":"Pen tests only."},"version":{"type":"string"}}}}}},"current":{"type":"object","required":["capturedAt"],"description":"One observation of a vendor's published evidence. Every collection is optional; an omitted collection is treated as NOT OBSERVED, never as observed-and-empty.","properties":{"capturedAt":{"type":"string","format":"date","description":"When you observed this posture. Not the evaluation instant."},"certifications":{"type":"array","maxItems":50,"items":{"type":"object","required":["framework","issuedAt","expiresAt"],"properties":{"id":{"type":"string","description":"Pairing key across snapshots. Defaults to \"framework\". Supply one only when a vendor holds two certifications under the same framework."},"framework":{"type":"string","enum":["soc2_type1","soc2_type2","iso_27001","iso_27017","iso_27018","iso_27701","iso_22301","pci_dss","hitrust_csf","fedramp_moderate","fedramp_high","csa_star","cyber_essentials_plus","tisax","other"]},"name":{"type":"string","description":"Required when framework is \"other\"."},"auditor":{"type":"string","description":"Omit on either side and an auditor change cannot be detected; the response says so in warnings."},"issuedAt":{"type":"string","format":"date","description":"First day covered. Used to tell a late renewal from an on-time one."},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. A certification expiring today is valid today."},"scope":{"type":"array","maxItems":60,"items":{"type":"string"},"description":"Trust services criteria, Annex A domains, PCI levels. The field this API exists for: a SOC 2 that quietly drops \"availability\" changes nothing on its cover page."},"exceptions":{"type":"array","maxItems":100,"items":{"type":"string"},"description":"Qualifications noted in the report. Send [] to mean \"none noted\" — an absent array means \"not observed\" and suppresses the comparison."},"opinion":{"type":"string","enum":["unqualified","qualified","adverse","disclaimer","not_applicable"]}}}},"subprocessors":{"type":"array","maxItems":500,"items":{"type":"object","required":["name","country"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2. Required — the jurisdiction is the risk."},"purpose":{"type":"string"},"processesPersonalData":{"type":"boolean","description":"false to true on an existing subprocessor is a material widening."}}}},"policies":{"type":"array","maxItems":100,"items":{"type":"object","required":["name","version","effectiveAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"version":{"type":"string"},"effectiveAt":{"type":"string","format":"date"},"contentHash":{"type":"string","description":"Any stable digest of the document as you retrieved it. The only way to see a content change made under an unchanged version number."}}}},"slas":{"type":"array","maxItems":50,"items":{"type":"object","required":["metric"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"metric\"."},"metric":{"type":"string","enum":["uptime","support_response","incident_notification","breach_notification","rto","rpo"]},"uptimeBasisPoints":{"type":"integer","minimum":0,"maximum":10000,"description":"Required for \"uptime\" and rejected on every other metric. 99.95% is 9995 — integer basis points, never a float."},"targetMinutes":{"type":"integer","minimum":0,"description":"Required for every metric except \"uptime\". Whole minutes; four hours is 240. Lower is better, which is how \"weaker\" is decided."},"description":{"type":"string"}}}},"residency":{"type":"array","maxItems":50,"items":{"type":"object","required":["dataCategory","countries"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased dataCategory."},"dataCategory":{"type":"string","description":"What the statement covers — \"customer_content\", \"backups\", \"support_data\"."},"countries":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"},"description":"ISO 3166-1 alpha-2 codes."}}}},"attestations":{"type":"array","maxItems":100,"items":{"type":"object","required":["kind","issuedAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"kind\"."},"kind":{"type":"string","enum":["pen_test","insurance","questionnaire","bridge_letter","other"]},"name":{"type":"string"},"provider":{"type":"string","description":"Testing firm, insurer, or questionnaire standard."},"issuedAt":{"type":"string","format":"date"},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. Omit for evidence that does not expire."},"coverageMinor":{"type":"integer","minimum":0,"description":"Insurance only. INTEGER minor units — 5000000.5 cents is a 400, not a rounding. Requires \"currency\"."},"currency":{"type":"string","description":"ISO-4217. Required with coverageMinor. Limits in different currencies are not compared; the response says so."},"openFindings":{"type":"integer","minimum":0,"description":"Pen tests only."},"version":{"type":"string"}}}}}},"metadata":{"type":"object","description":"Echoed nowhere; accepted so your queue can round-trip a payload unchanged."}}},"checks":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","required":["vendorId","current"],"properties":{"vendorId":{"type":"string","description":"Your identifier for the vendor. Echoed back; nothing is stored against it."},"vendorName":{"type":"string"},"previous":{"type":"object","required":["capturedAt"],"description":"The earlier observation. Omit it for a first-ever check: the response then carries baseline:true and current-state findings only.","properties":{"capturedAt":{"type":"string","format":"date","description":"When you observed this posture. Not the evaluation instant."},"certifications":{"type":"array","maxItems":50,"items":{"type":"object","required":["framework","issuedAt","expiresAt"],"properties":{"id":{"type":"string","description":"Pairing key across snapshots. Defaults to \"framework\". Supply one only when a vendor holds two certifications under the same framework."},"framework":{"type":"string","enum":["soc2_type1","soc2_type2","iso_27001","iso_27017","iso_27018","iso_27701","iso_22301","pci_dss","hitrust_csf","fedramp_moderate","fedramp_high","csa_star","cyber_essentials_plus","tisax","other"]},"name":{"type":"string","description":"Required when framework is \"other\"."},"auditor":{"type":"string","description":"Omit on either side and an auditor change cannot be detected; the response says so in warnings."},"issuedAt":{"type":"string","format":"date","description":"First day covered. Used to tell a late renewal from an on-time one."},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. A certification expiring today is valid today."},"scope":{"type":"array","maxItems":60,"items":{"type":"string"},"description":"Trust services criteria, Annex A domains, PCI levels. The field this API exists for: a SOC 2 that quietly drops \"availability\" changes nothing on its cover page."},"exceptions":{"type":"array","maxItems":100,"items":{"type":"string"},"description":"Qualifications noted in the report. Send [] to mean \"none noted\" — an absent array means \"not observed\" and suppresses the comparison."},"opinion":{"type":"string","enum":["unqualified","qualified","adverse","disclaimer","not_applicable"]}}}},"subprocessors":{"type":"array","maxItems":500,"items":{"type":"object","required":["name","country"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2. Required — the jurisdiction is the risk."},"purpose":{"type":"string"},"processesPersonalData":{"type":"boolean","description":"false to true on an existing subprocessor is a material widening."}}}},"policies":{"type":"array","maxItems":100,"items":{"type":"object","required":["name","version","effectiveAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"version":{"type":"string"},"effectiveAt":{"type":"string","format":"date"},"contentHash":{"type":"string","description":"Any stable digest of the document as you retrieved it. The only way to see a content change made under an unchanged version number."}}}},"slas":{"type":"array","maxItems":50,"items":{"type":"object","required":["metric"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"metric\"."},"metric":{"type":"string","enum":["uptime","support_response","incident_notification","breach_notification","rto","rpo"]},"uptimeBasisPoints":{"type":"integer","minimum":0,"maximum":10000,"description":"Required for \"uptime\" and rejected on every other metric. 99.95% is 9995 — integer basis points, never a float."},"targetMinutes":{"type":"integer","minimum":0,"description":"Required for every metric except \"uptime\". Whole minutes; four hours is 240. Lower is better, which is how \"weaker\" is decided."},"description":{"type":"string"}}}},"residency":{"type":"array","maxItems":50,"items":{"type":"object","required":["dataCategory","countries"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased dataCategory."},"dataCategory":{"type":"string","description":"What the statement covers — \"customer_content\", \"backups\", \"support_data\"."},"countries":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"},"description":"ISO 3166-1 alpha-2 codes."}}}},"attestations":{"type":"array","maxItems":100,"items":{"type":"object","required":["kind","issuedAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"kind\"."},"kind":{"type":"string","enum":["pen_test","insurance","questionnaire","bridge_letter","other"]},"name":{"type":"string"},"provider":{"type":"string","description":"Testing firm, insurer, or questionnaire standard."},"issuedAt":{"type":"string","format":"date"},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. Omit for evidence that does not expire."},"coverageMinor":{"type":"integer","minimum":0,"description":"Insurance only. INTEGER minor units — 5000000.5 cents is a 400, not a rounding. Requires \"currency\"."},"currency":{"type":"string","description":"ISO-4217. Required with coverageMinor. Limits in different currencies are not compared; the response says so."},"openFindings":{"type":"integer","minimum":0,"description":"Pen tests only."},"version":{"type":"string"}}}}}},"current":{"type":"object","required":["capturedAt"],"description":"One observation of a vendor's published evidence. Every collection is optional; an omitted collection is treated as NOT OBSERVED, never as observed-and-empty.","properties":{"capturedAt":{"type":"string","format":"date","description":"When you observed this posture. Not the evaluation instant."},"certifications":{"type":"array","maxItems":50,"items":{"type":"object","required":["framework","issuedAt","expiresAt"],"properties":{"id":{"type":"string","description":"Pairing key across snapshots. Defaults to \"framework\". Supply one only when a vendor holds two certifications under the same framework."},"framework":{"type":"string","enum":["soc2_type1","soc2_type2","iso_27001","iso_27017","iso_27018","iso_27701","iso_22301","pci_dss","hitrust_csf","fedramp_moderate","fedramp_high","csa_star","cyber_essentials_plus","tisax","other"]},"name":{"type":"string","description":"Required when framework is \"other\"."},"auditor":{"type":"string","description":"Omit on either side and an auditor change cannot be detected; the response says so in warnings."},"issuedAt":{"type":"string","format":"date","description":"First day covered. Used to tell a late renewal from an on-time one."},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. A certification expiring today is valid today."},"scope":{"type":"array","maxItems":60,"items":{"type":"string"},"description":"Trust services criteria, Annex A domains, PCI levels. The field this API exists for: a SOC 2 that quietly drops \"availability\" changes nothing on its cover page."},"exceptions":{"type":"array","maxItems":100,"items":{"type":"string"},"description":"Qualifications noted in the report. Send [] to mean \"none noted\" — an absent array means \"not observed\" and suppresses the comparison."},"opinion":{"type":"string","enum":["unqualified","qualified","adverse","disclaimer","not_applicable"]}}}},"subprocessors":{"type":"array","maxItems":500,"items":{"type":"object","required":["name","country"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2. Required — the jurisdiction is the risk."},"purpose":{"type":"string"},"processesPersonalData":{"type":"boolean","description":"false to true on an existing subprocessor is a material widening."}}}},"policies":{"type":"array","maxItems":100,"items":{"type":"object","required":["name","version","effectiveAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"version":{"type":"string"},"effectiveAt":{"type":"string","format":"date"},"contentHash":{"type":"string","description":"Any stable digest of the document as you retrieved it. The only way to see a content change made under an unchanged version number."}}}},"slas":{"type":"array","maxItems":50,"items":{"type":"object","required":["metric"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"metric\"."},"metric":{"type":"string","enum":["uptime","support_response","incident_notification","breach_notification","rto","rpo"]},"uptimeBasisPoints":{"type":"integer","minimum":0,"maximum":10000,"description":"Required for \"uptime\" and rejected on every other metric. 99.95% is 9995 — integer basis points, never a float."},"targetMinutes":{"type":"integer","minimum":0,"description":"Required for every metric except \"uptime\". Whole minutes; four hours is 240. Lower is better, which is how \"weaker\" is decided."},"description":{"type":"string"}}}},"residency":{"type":"array","maxItems":50,"items":{"type":"object","required":["dataCategory","countries"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased dataCategory."},"dataCategory":{"type":"string","description":"What the statement covers — \"customer_content\", \"backups\", \"support_data\"."},"countries":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"},"description":"ISO 3166-1 alpha-2 codes."}}}},"attestations":{"type":"array","maxItems":100,"items":{"type":"object","required":["kind","issuedAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"kind\"."},"kind":{"type":"string","enum":["pen_test","insurance","questionnaire","bridge_letter","other"]},"name":{"type":"string"},"provider":{"type":"string","description":"Testing firm, insurer, or questionnaire standard."},"issuedAt":{"type":"string","format":"date"},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. Omit for evidence that does not expire."},"coverageMinor":{"type":"integer","minimum":0,"description":"Insurance only. INTEGER minor units — 5000000.5 cents is a 400, not a rounding. Requires \"currency\"."},"currency":{"type":"string","description":"ISO-4217. Required with coverageMinor. Limits in different currencies are not compared; the response says so."},"openFindings":{"type":"integer","minimum":0,"description":"Pen tests only."},"version":{"type":"string"}}}}}},"metadata":{"type":"object","description":"Echoed nowhere; accepted so your queue can round-trip a payload unchanged."}}}}}},"example":{"check":{"vendorId":"vnd-northwind","vendorName":"Northwind Software","previous":{"capturedAt":"2026-02-01","certifications":[{"framework":"soc2_type2","auditor":"Prior & Co","issuedAt":"2025-02-15","expiresAt":"2026-02-14","scope":["security","availability","confidentiality"],"exceptions":[],"opinion":"unqualified"}],"subprocessors":[{"name":"Cloudmail","country":"US","purpose":"transactional email","processesPersonalData":true},{"name":"Metricly","country":"IE","purpose":"product analytics","processesPersonalData":false}],"policies":[{"name":"Information Security Policy","version":"4.1","effectiveAt":"2025-03-01","contentHash":"sha256:9f2c"}],"slas":[{"metric":"uptime","uptimeBasisPoints":9995},{"metric":"support_response","targetMinutes":240}],"residency":[{"dataCategory":"customer_content","countries":["IE","US"]}],"attestations":[{"kind":"insurance","provider":"Meridian","issuedAt":"2025-06-01","expiresAt":"2026-05-31","coverageMinor":1000000000,"currency":"USD"}]},"current":{"capturedAt":"2026-08-01","certifications":[{"framework":"soc2_type2","auditor":"Nextgen Assurance","issuedAt":"2026-03-20","expiresAt":"2027-03-19","scope":["security","confidentiality"],"exceptions":["Access review evidence incomplete for two of twelve months"],"opinion":"unqualified"}],"subprocessors":[{"name":"Cloudmail","country":"US","purpose":"transactional email","processesPersonalData":true},{"name":"Metricly","country":"IE","purpose":"product analytics","processesPersonalData":false},{"name":"Supportly","country":"BR","purpose":"customer support tooling","processesPersonalData":true}],"policies":[{"name":"Information Security Policy","version":"4.1","effectiveAt":"2025-03-01","contentHash":"sha256:44e1"}],"slas":[{"metric":"uptime","uptimeBasisPoints":9990},{"metric":"support_response","targetMinutes":480}],"residency":[{"dataCategory":"customer_content","countries":["BR","IE","US"]}],"attestations":[{"kind":"insurance","provider":"Meridian","issuedAt":"2026-06-01","expiresAt":"2027-05-31","coverageMinor":500000000,"currency":"USD"}]}}}}}},"x-quota-cost":0}},"/v1/demo/check":{"post":{"summary":"Public demo — compare one pair of snapshots without a key","description":"The real engine, capped at one check and 40 evidence items across both snapshots. Nothing is stored, nothing is metered, no key is needed.","operationId":"postV1DemoCheck","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"report":{"type":"object","properties":{"vendorId":{"type":"string"},"baseline":{"type":"boolean","description":"True when no previous snapshot was supplied."},"windowDays":{"type":["integer","null"],"description":"Whole UTC days between the two observations."},"evaluatedAt":{"type":"string","description":"The evaluation instant. Pin it and the whole report is reproducible."},"findings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"Stable. Branch on this, never on \"detail\". Full list at GET /v1/drift-types."},"kind":{"type":"string","enum":["drift","posture"],"description":"\"drift\" needs both snapshots; \"posture\" is computed from the current one and the evaluation instant, so it appears on a baseline too."},"signal":{"type":"string","enum":["certification","subprocessor","policy","sla","residency","attestation"]},"severity":{"type":"string","enum":["info","low","medium","high","critical"],"description":"Fixed per code. Never varies by instance."},"points":{"type":"integer","description":"Contribution to the risk score. Derived from severity alone."},"material":{"type":"boolean","description":"True for high and above — the findings that set reviewRequired."},"subject":{"type":"string","description":"The pairing key the finding is about."},"detail":{"type":"string","description":"Plain language. Prose, and it will be reworded — do not match on it."},"recommendedAction":{"type":"string"},"controls":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"framework":{"type":"string"},"name":{"type":"string"}}},"description":"Editorial mapping, not an official crosswalk."},"data":{"type":"object","description":"Machine-readable specifics: previousValue, currentValue, gapDays, added, removed, deltaMinor, deltaBasisPoints."}}},"description":"Every finding, worst first. Never truncated to the worst one."},"counts":{"type":"object","description":"Findings per severity, plus a total."},"materialChanges":{"type":"integer"},"reviewRequired":{"type":"boolean"},"risk":{"type":"object","description":"score (0-100, capped), band, rawPoints, a per-signal breakdown and a line-by-line derivation you can reproduce by hand from the published severity table."},"expiries":{"type":"array","items":{"type":"object"},"description":"Every dated item in the current snapshot with daysUntilExpiry and a status, soonest first — whether or not it drifted."},"evidence":{"type":"object","description":"What was actually compared: item counts per collection, per snapshot."},"warnings":{"type":"array","items":{"type":"string"},"description":"What this check could NOT see. A missing scope array or contentHash disables the two quietest detections in the catalogue, and a clean result computed without them is not the same as a clean result."}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["check"],"properties":{"check":{"type":"object","required":["vendorId","current"],"properties":{"vendorId":{"type":"string","description":"Your identifier for the vendor. Echoed back; nothing is stored against it."},"vendorName":{"type":"string"},"previous":{"type":"object","required":["capturedAt"],"description":"The earlier observation. Omit it for a first-ever check: the response then carries baseline:true and current-state findings only.","properties":{"capturedAt":{"type":"string","format":"date","description":"When you observed this posture. Not the evaluation instant."},"certifications":{"type":"array","maxItems":50,"items":{"type":"object","required":["framework","issuedAt","expiresAt"],"properties":{"id":{"type":"string","description":"Pairing key across snapshots. Defaults to \"framework\". Supply one only when a vendor holds two certifications under the same framework."},"framework":{"type":"string","enum":["soc2_type1","soc2_type2","iso_27001","iso_27017","iso_27018","iso_27701","iso_22301","pci_dss","hitrust_csf","fedramp_moderate","fedramp_high","csa_star","cyber_essentials_plus","tisax","other"]},"name":{"type":"string","description":"Required when framework is \"other\"."},"auditor":{"type":"string","description":"Omit on either side and an auditor change cannot be detected; the response says so in warnings."},"issuedAt":{"type":"string","format":"date","description":"First day covered. Used to tell a late renewal from an on-time one."},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. A certification expiring today is valid today."},"scope":{"type":"array","maxItems":60,"items":{"type":"string"},"description":"Trust services criteria, Annex A domains, PCI levels. The field this API exists for: a SOC 2 that quietly drops \"availability\" changes nothing on its cover page."},"exceptions":{"type":"array","maxItems":100,"items":{"type":"string"},"description":"Qualifications noted in the report. Send [] to mean \"none noted\" — an absent array means \"not observed\" and suppresses the comparison."},"opinion":{"type":"string","enum":["unqualified","qualified","adverse","disclaimer","not_applicable"]}}}},"subprocessors":{"type":"array","maxItems":500,"items":{"type":"object","required":["name","country"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2. Required — the jurisdiction is the risk."},"purpose":{"type":"string"},"processesPersonalData":{"type":"boolean","description":"false to true on an existing subprocessor is a material widening."}}}},"policies":{"type":"array","maxItems":100,"items":{"type":"object","required":["name","version","effectiveAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"version":{"type":"string"},"effectiveAt":{"type":"string","format":"date"},"contentHash":{"type":"string","description":"Any stable digest of the document as you retrieved it. The only way to see a content change made under an unchanged version number."}}}},"slas":{"type":"array","maxItems":50,"items":{"type":"object","required":["metric"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"metric\"."},"metric":{"type":"string","enum":["uptime","support_response","incident_notification","breach_notification","rto","rpo"]},"uptimeBasisPoints":{"type":"integer","minimum":0,"maximum":10000,"description":"Required for \"uptime\" and rejected on every other metric. 99.95% is 9995 — integer basis points, never a float."},"targetMinutes":{"type":"integer","minimum":0,"description":"Required for every metric except \"uptime\". Whole minutes; four hours is 240. Lower is better, which is how \"weaker\" is decided."},"description":{"type":"string"}}}},"residency":{"type":"array","maxItems":50,"items":{"type":"object","required":["dataCategory","countries"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased dataCategory."},"dataCategory":{"type":"string","description":"What the statement covers — \"customer_content\", \"backups\", \"support_data\"."},"countries":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"},"description":"ISO 3166-1 alpha-2 codes."}}}},"attestations":{"type":"array","maxItems":100,"items":{"type":"object","required":["kind","issuedAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"kind\"."},"kind":{"type":"string","enum":["pen_test","insurance","questionnaire","bridge_letter","other"]},"name":{"type":"string"},"provider":{"type":"string","description":"Testing firm, insurer, or questionnaire standard."},"issuedAt":{"type":"string","format":"date"},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. Omit for evidence that does not expire."},"coverageMinor":{"type":"integer","minimum":0,"description":"Insurance only. INTEGER minor units — 5000000.5 cents is a 400, not a rounding. Requires \"currency\"."},"currency":{"type":"string","description":"ISO-4217. Required with coverageMinor. Limits in different currencies are not compared; the response says so."},"openFindings":{"type":"integer","minimum":0,"description":"Pen tests only."},"version":{"type":"string"}}}}}},"current":{"type":"object","required":["capturedAt"],"description":"One observation of a vendor's published evidence. Every collection is optional; an omitted collection is treated as NOT OBSERVED, never as observed-and-empty.","properties":{"capturedAt":{"type":"string","format":"date","description":"When you observed this posture. Not the evaluation instant."},"certifications":{"type":"array","maxItems":50,"items":{"type":"object","required":["framework","issuedAt","expiresAt"],"properties":{"id":{"type":"string","description":"Pairing key across snapshots. Defaults to \"framework\". Supply one only when a vendor holds two certifications under the same framework."},"framework":{"type":"string","enum":["soc2_type1","soc2_type2","iso_27001","iso_27017","iso_27018","iso_27701","iso_22301","pci_dss","hitrust_csf","fedramp_moderate","fedramp_high","csa_star","cyber_essentials_plus","tisax","other"]},"name":{"type":"string","description":"Required when framework is \"other\"."},"auditor":{"type":"string","description":"Omit on either side and an auditor change cannot be detected; the response says so in warnings."},"issuedAt":{"type":"string","format":"date","description":"First day covered. Used to tell a late renewal from an on-time one."},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. A certification expiring today is valid today."},"scope":{"type":"array","maxItems":60,"items":{"type":"string"},"description":"Trust services criteria, Annex A domains, PCI levels. The field this API exists for: a SOC 2 that quietly drops \"availability\" changes nothing on its cover page."},"exceptions":{"type":"array","maxItems":100,"items":{"type":"string"},"description":"Qualifications noted in the report. Send [] to mean \"none noted\" — an absent array means \"not observed\" and suppresses the comparison."},"opinion":{"type":"string","enum":["unqualified","qualified","adverse","disclaimer","not_applicable"]}}}},"subprocessors":{"type":"array","maxItems":500,"items":{"type":"object","required":["name","country"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2. Required — the jurisdiction is the risk."},"purpose":{"type":"string"},"processesPersonalData":{"type":"boolean","description":"false to true on an existing subprocessor is a material widening."}}}},"policies":{"type":"array","maxItems":100,"items":{"type":"object","required":["name","version","effectiveAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased name."},"name":{"type":"string"},"version":{"type":"string"},"effectiveAt":{"type":"string","format":"date"},"contentHash":{"type":"string","description":"Any stable digest of the document as you retrieved it. The only way to see a content change made under an unchanged version number."}}}},"slas":{"type":"array","maxItems":50,"items":{"type":"object","required":["metric"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"metric\"."},"metric":{"type":"string","enum":["uptime","support_response","incident_notification","breach_notification","rto","rpo"]},"uptimeBasisPoints":{"type":"integer","minimum":0,"maximum":10000,"description":"Required for \"uptime\" and rejected on every other metric. 99.95% is 9995 — integer basis points, never a float."},"targetMinutes":{"type":"integer","minimum":0,"description":"Required for every metric except \"uptime\". Whole minutes; four hours is 240. Lower is better, which is how \"weaker\" is decided."},"description":{"type":"string"}}}},"residency":{"type":"array","maxItems":50,"items":{"type":"object","required":["dataCategory","countries"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to the lower-cased dataCategory."},"dataCategory":{"type":"string","description":"What the statement covers — \"customer_content\", \"backups\", \"support_data\"."},"countries":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"},"description":"ISO 3166-1 alpha-2 codes."}}}},"attestations":{"type":"array","maxItems":100,"items":{"type":"object","required":["kind","issuedAt"],"properties":{"id":{"type":"string","description":"Pairing key. Defaults to \"kind\"."},"kind":{"type":"string","enum":["pen_test","insurance","questionnaire","bridge_letter","other"]},"name":{"type":"string"},"provider":{"type":"string","description":"Testing firm, insurer, or questionnaire standard."},"issuedAt":{"type":"string","format":"date"},"expiresAt":{"type":"string","format":"date","description":"LAST DAY COVERED, inclusive. Omit for evidence that does not expire."},"coverageMinor":{"type":"integer","minimum":0,"description":"Insurance only. INTEGER minor units — 5000000.5 cents is a 400, not a rounding. Requires \"currency\"."},"currency":{"type":"string","description":"ISO-4217. Required with coverageMinor. Limits in different currencies are not compared; the response says so."},"openFindings":{"type":"integer","minimum":0,"description":"Pen tests only."},"version":{"type":"string"}}}}}},"metadata":{"type":"object","description":"Echoed nowhere; accepted so your queue can round-trip a payload unchanged."}}}}},"example":{"check":{"vendorId":"vnd-northwind","vendorName":"Northwind Software","previous":{"capturedAt":"2026-02-01","certifications":[{"framework":"soc2_type2","auditor":"Prior & Co","issuedAt":"2025-02-15","expiresAt":"2026-02-14","scope":["security","availability","confidentiality"],"exceptions":[],"opinion":"unqualified"}],"subprocessors":[{"name":"Cloudmail","country":"US","purpose":"transactional email","processesPersonalData":true},{"name":"Metricly","country":"IE","purpose":"product analytics","processesPersonalData":false}],"policies":[{"name":"Information Security Policy","version":"4.1","effectiveAt":"2025-03-01","contentHash":"sha256:9f2c"}],"slas":[{"metric":"uptime","uptimeBasisPoints":9995},{"metric":"support_response","targetMinutes":240}],"residency":[{"dataCategory":"customer_content","countries":["IE","US"]}],"attestations":[{"kind":"insurance","provider":"Meridian","issuedAt":"2025-06-01","expiresAt":"2026-05-31","coverageMinor":1000000000,"currency":"USD"}]},"current":{"capturedAt":"2026-08-01","certifications":[{"framework":"soc2_type2","auditor":"Nextgen Assurance","issuedAt":"2026-03-20","expiresAt":"2027-03-19","scope":["security","confidentiality"],"exceptions":["Access review evidence incomplete for two of twelve months"],"opinion":"unqualified"}],"subprocessors":[{"name":"Cloudmail","country":"US","purpose":"transactional email","processesPersonalData":true},{"name":"Metricly","country":"IE","purpose":"product analytics","processesPersonalData":false},{"name":"Supportly","country":"BR","purpose":"customer support tooling","processesPersonalData":true}],"policies":[{"name":"Information Security Policy","version":"4.1","effectiveAt":"2025-03-01","contentHash":"sha256:44e1"}],"slas":[{"metric":"uptime","uptimeBasisPoints":9990},{"metric":"support_response","targetMinutes":480}],"residency":[{"dataCategory":"customer_content","countries":["BR","IE","US"]}],"attestations":[{"kind":"insurance","provider":"Meridian","issuedAt":"2026-06-01","expiresAt":"2027-05-31","coverageMinor":500000000,"currency":"USD"}]}}}}}}}},"/v1/drift-types":{"get":{"summary":"Every drift code, severity, control mapping and scoring constant","description":"The catalogue the engine emits from — not a copy of it, the same object. Branch on these codes rather than on the human-readable detail, which is prose and will be reworded. Includes the severity-to-points table and the band boundaries, so a risk score can be reproduced by hand.","operationId":"getV1DriftTypes","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"driftTypes":{"type":"object","description":"Keyed by code: kind, signal, severity, meaning, recommendedAction and control ids."},"bySignal":{"type":"object","description":"The same codes grouped by evidence family."},"controls":{"type":"object","description":"The control identifiers findings are mapped to, with framework and name."},"scoring":{"type":"object","description":"Severity points, band boundaries, the material threshold and the two age windows."}}}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout":{"post":{"summary":"Start a hosted Square checkout for a paid tier","description":"Returns a Square-hosted payment URL for the requested tier. No API key is needed — a prospect has not got one yet. The email Square collects becomes the account, so a key issued later for the same address inherits the paid quota. Enterprise returns a contact route rather than a payment link.","operationId":"postV1Checkout","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"checkoutUrl":{"type":"string","format":"uri","description":"Square-hosted payment page. Redirect the buyer here."},"tier":{"type":"string"},"sku":{"type":"string"},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tier"],"properties":{"tier":{"type":"string","description":"developer | growth | scale | enterprise"}}},"example":{"tier":"developer"}}}}}},"/api/billing/webhook":{"post":{"summary":"Square billing events, forwarded by the shared hub","description":"Machine-to-machine only. The shared Square billing hub POSTs signed events here; the signature is verified over the exact request bytes. Not callable by API consumers — documented so the integration is auditable.","operationId":"postApiBillingWebhook","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/keys":{"post":{"summary":"Create a free sandbox API key","description":"Issues a product-scoped key with a free monthly quota, no card required. The key works only on this API. Keep the email — a paid plan bought with the same address raises this key's quota rather than issuing a new one. The key is shown once and is not recoverable.","operationId":"postV1Keys","security":[],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"Shown once. Only a peppered hash is stored, so it cannot be recovered later."},"accountId":{"type":"string"},"product":{"type":"string","description":"The key works only on this API."},"quotaPerPeriod":{"type":"integer"},"plan":{"type":"string"},"warning":{"type":"string"},"usage":{"type":"string"},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}},"example":{"email":"you@example.com"}}}}}},"/v1/usage":{"get":{"summary":"Your consumption and remaining allowance for this period","description":"Reports what this API key has consumed in the current quota period, the ceiling being enforced against it, and when that ceiling resets. Free, and it consumes no quota, so it still answers once you are getting 429 quota_exceeded — which is the point: compare \"used\" against \"ceiling\" and read \"period.resetsAt\" to tell a period you have simply exhausted from a plan that is no longer active, which shows as a \"status\" of canceled and a ceiling of 0. \"used\" counts billing units (see \"unit\"), not HTTP requests: a single request can consume several. All money is in integer minor units (cents), never a decimal. The period reported here is the quota period — the calendar month in UTC, resetting at 00:00 UTC on the 1st. Your invoice window is anchored to the date you subscribed and is a different window; the amounts here are what this period’s usage has accrued so far, not a bill.","operationId":"getV1Usage","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"type":"string","description":"The API this key is bound to. A key works only on one."},"tier":{"type":["string","null"],"description":"The plan tier you are billed on, e.g. \"developer\". Null on a free sandbox key with no paid plan."},"status":{"type":"string","enum":["sandbox","active","past_due","canceled"],"description":"State of your plan. \"sandbox\" means no paid plan; \"canceled\" explains a ceiling of 0."},"unit":{"type":"string","description":"The billing unit this API meters. \"used\", \"included\", \"ceiling\" and \"remaining\" are all counted in these, not in requests."},"period":{"type":"object","description":"The quota period: the calendar month in UTC.","properties":{"start":{"type":"string","format":"date-time","description":"00:00 UTC on the 1st of the current month."},"resetsAt":{"type":"string","format":"date-time","description":"When \"used\" returns to 0 — 00:00 UTC on the 1st of next month."}}},"included":{"type":"integer","description":"Units your flat monthly fee already covers, before overage pricing applies. 0 without a paid plan."},"used":{"type":"integer","description":"Units consumed so far this period."},"ceiling":{"type":["integer","null"],"description":"The hard limit enforced against this key: requests are refused beyond it. Null means unlimited."},"remaining":{"type":["integer","null"],"description":"ceiling minus used, never below 0. Null when the ceiling is unlimited."},"overageSoFarMinor":{"type":"integer","description":"What this period’s usage beyond \"included\" has accrued, in minor units, before your spend cap is applied. 0 without a paid plan."},"spendCapMinor":{"type":"integer","description":"The most the overage part of one period can ever cost you, in minor units. Your flat base fee is separate and is never capped away."},"requestId":{"type":"string","description":"Quote this when reporting a problem."}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"x-quota-cost":0}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Your API key. Create one free at POST /v1/keys. Also accepted as the x-api-key header."}},"responses":{"Error":{"description":"Deterministic error. Branch on `code`; quote `requestId` when reporting a problem.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","requestId"],"properties":{"code":{"type":"string","enum":["invalid_api_key","missing_api_key","quota_exceeded","rate_limited","invalid_request","not_found","method_not_allowed","payload_too_large","conflict","internal_error"]},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true},"requestId":{"type":"string","format":"uuid"}}}}}}}}}}}