{"openapi":"3.0.0","info":{"title":"Vulnify API","version":"0.3.0","description":"Public machine contract for AI agents. Send an API key as `Authorization: Bearer vln_live_...` or `X-API-Key` (the same secret; not a JWT). POST /v1/events decides an action; GET /v1/events/{id} returns that same body, including finalDecision, quotaExceeded and sandbox. Idempotent replays include finalDecision too. GET /v1/policies, POST /v1/policies/apply and POST /v1/policies/test manage policies as code. POST /public/contact accepts marketing-site leads. Webhook deliveries are under webhooks (OpenAPI 3.1 shape) and components.schemas. Contract v1."},"servers":[{"url":"https://api.vulnify.io"}],"tags":[{"name":"Machine API","description":"Called by agents with an API key (Bearer vln_live_... / vln_test_... or X-API-Key). Contract v1."}],"paths":{"/v1/policies":{"get":{"operationId":"PoliciesAsCodeController_list","parameters":[],"responses":{"200":{"description":"Policies sorted by name","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["apiVersion","policies"],"properties":{"apiVersion":{"type":"string","enum":["vulnify.io/v1"]},"policies":{"type":"array","description":"Organization policies sorted by name.","items":{"$ref":"#/components/schemas/PolicyRecord"}}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"401":{"description":"Missing or invalid credentials","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"403":{"description":"Caller IP is not in the API key allowlist.","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"429":{"description":"60 requests per minute per API key.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","enum":["rate_limited"]}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}}},"tags":["Machine API"],"summary":"List organization policies for policies as code","description":"Returns apiVersion vulnify.io/v1 and policies sorted by name in Unicode code point order. Each item is a PolicySpec plus id and updatedAt. Any non-revoked API key of the organization may call it.","security":[{"apiKey":[]},{"apiKeyBearer":[]}]}},"/v1/policies/apply":{"post":{"operationId":"PoliciesAsCodeController_apply","parameters":[],"responses":{"200":{"description":"Change plan. dryRun true writes nothing.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["dryRun","changes"],"properties":{"dryRun":{"type":"boolean"},"changes":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","op"],"properties":{"name":{"type":"string"},"op":{"type":"string","enum":["create","update","delete","unchanged"]}}}}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"400":{"description":"Invalid policy spec, duplicate name, or unknown action, decision, mode or role.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error","fields"],"properties":{"error":{"type":"string","enum":["validation_error"]},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Paths such as policies[0].name or cases[0].input.agent, each with a message."}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"401":{"description":"Missing or invalid credentials","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"403":{"description":"The key is not an org-wide LIVE key. A source IP outside the key allowlist is also forbidden.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error","message"],"properties":{"error":{"type":"string","enum":["forbidden"]},"message":{"type":"string","enum":["policies:apply requires an org-wide LIVE key"]}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"413":{"description":"JSON body larger than 200 KB","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"429":{"description":"60 requests per minute per API key.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","enum":["rate_limited"]}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["policies"],"properties":{"policies":{"type":"array","items":{"$ref":"#/components/schemas/PolicySpec"}},"prune":{"type":"boolean","default":false,"description":"Delete organization policies whose names are missing from policies."},"dryRun":{"type":"boolean","default":false,"description":"Return the change plan and write nothing."}}}}}},"tags":["Machine API"],"summary":"Create, update and optionally delete policies in one transaction","description":"Matching key is the policy name. prune deletes organization policies missing from the payload. dryRun returns the plan and writes nothing. Requires an org-wide LIVE API key, otherwise 403 with error forbidden and message \"policies:apply requires an org-wide LIVE key\". Each create, update and delete appends an audit entry with metadata.source policies-as-code and the key prefix, and invalidates the decision cache.","security":[{"apiKey":[]},{"apiKeyBearer":[]}]}},"/v1/policies/test":{"post":{"operationId":"PoliciesAsCodeController_test","parameters":[],"responses":{"200":{"description":"One result per case. Nothing is stored.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["results"],"properties":{"results":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","decision","matchedPolicy","riskScore","riskLevel","reasons","pass"],"properties":{"name":{"type":"string"},"decision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"]},"matchedPolicy":{"type":"string","nullable":true},"riskScore":{"type":"integer","minimum":0,"maximum":100},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"reasons":{"type":"array","items":{"type":"string"}},"pass":{"type":"boolean","nullable":true,"description":"Null when expect was omitted."}}}}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"400":{"description":"Invalid policy spec, duplicate name, or unknown action, decision, mode or role.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error","fields"],"properties":{"error":{"type":"string","enum":["validation_error"]},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Paths such as policies[0].name or cases[0].input.agent, each with a message."}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"401":{"description":"Missing or invalid credentials","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"403":{"description":"Caller IP is not in the API key allowlist.","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"413":{"description":"JSON body larger than 200 KB","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"429":{"description":"60 requests per minute per API key.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","enum":["rate_limited"]}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["cases"],"properties":{"policies":{"type":"array","description":"When set, these policies are evaluated instead of the stored ones. Omitted uses the enabled stored policies.","items":{"$ref":"#/components/schemas/PolicySpec"}},"cases":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/PolicyTestCase"}}}}}}},"tags":["Machine API"],"summary":"Evaluate policy test cases without recording an event","description":"Uses the decision path (permissions, risk, policies, monitor mode). When policies is omitted, the enabled stored policies are used. Writes no security event and no audit entry. pass is null when expect is omitted. At most 200 cases. input.metadata is accepted and not used. 60 requests per minute per API key.","security":[{"apiKey":[]},{"apiKeyBearer":[]}]}},"/v1/events":{"post":{"operationId":"IngestController_ingest","summary":"Decide an agent action before it runs","parameters":[{"name":"Idempotency-Key","in":"header","description":"Retries with the same key (per organization and endpoint, 24 hours) return the stored decision instead of deciding again. The replay always includes finalDecision, derived from the stored decision and the current review.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["action"],"description":"agent or agentId is required unless the API key is bound to an agent. resource or resourceId is required.","properties":{"agent":{"type":"string","maxLength":100,"description":"Agent name as registered in Vulnify."},"agentId":{"type":"string","format":"uuid","description":"Agent id. Use this or `agent`."},"resource":{"type":"string","maxLength":100,"description":"Resource name as registered in Vulnify."},"resourceId":{"type":"string","format":"uuid","description":"Resource id. Use this or `resource`."},"destination":{"type":"string","enum":["INTERNAL","EXTERNAL_EMAIL","EXTERNAL_API"]},"recordsAffected":{"type":"integer","minimum":0,"maximum":100000000},"content":{"type":"string","maxLength":100000,"description":"Optional text scanned for sensitive data. Scanned in memory and never stored."},"action":{"type":"string","enum":["READ_DATA","WRITE_DATA","DELETE_DATA","EXPORT_DATA","SEND_EMAIL"]}}}}}},"responses":{"200":{"description":"Decision: { id, decision, finalDecision, evaluatedDecision, monitored, riskLevel, riskScore, reasons, policy, dlpFindings, lgpdCategories, review, quotaExceeded, sandbox }. decision is the stored outcome and does not change when a review is resolved. finalDecision is the effective outcome (ALLOW after approval, BLOCK after denial or expiry, REVIEW while pending) and is always present, including an idempotent replay. quotaExceeded flags a plan overrun without blocking; sandbox is true for TEST keys.","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}},"Idempotent-Replay":{"description":"\"true\" when the body is a stored replay","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"required":["id","decision","finalDecision","evaluatedDecision","monitored","riskLevel","riskScore","reasons","policy","dlpFindings","lgpdCategories","review","quotaExceeded","sandbox"],"properties":{"id":{"type":"string","format":"uuid"},"decision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Decision recorded on the event. It does not change when a review is resolved."},"finalDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Effective outcome. Equals `decision` when there is no review. REVIEW while a review is pending. ALLOW after approval, BLOCK after denial or expiry. Always present, including an idempotent replay of a response stored before this field existed: the replay derives it from the stored decision and the current review."},"evaluatedDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"What full enforcement would have decided. Differs from `decision` in monitor mode."},"monitored":{"type":"boolean"},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"riskScore":{"type":"integer","minimum":0,"maximum":100},"reasons":{"type":"array","items":{"type":"string"}},"policy":{"nullable":true,"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"dlpFindings":{"type":"array","items":{"type":"string"},"description":"Sensitive-data types found in `content`."},"lgpdCategories":{"type":"array","items":{"type":"string","enum":["IDENTIFICATION","CONTACT","LOCATION","FINANCIAL","HEALTH","COMPANY","CREDENTIALS"]},"description":"LGPD categories derived from `dlpFindings`."},"review":{"nullable":true,"description":"Null when the event has no human review.","type":"object","required":["status","expiresAt","decidedAt","note"],"properties":{"status":{"type":"string","enum":["PENDING","APPROVED","DENIED","EXPIRED"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"decidedAt":{"type":"string","format":"date-time","nullable":true},"note":{"type":"string","nullable":true}}},"quotaExceeded":{"type":"boolean","description":"Plan quota is over the limit. The decision is still made."},"sandbox":{"type":"boolean","description":"True when the event was created with a TEST key."}}}}}},"400":{"description":"Validation error (agent/agentId and resource/resourceId are required)","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"401":{"description":"Missing, invalid, revoked or expired API key","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"403":{"description":"Key bound to another agent, or caller IP not in the key allowlist","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"404":{"description":"Unknown agent, resource or gateway route","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"413":{"description":"JSON body larger than 200 KB","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}}},"security":[{"apiKey":[]},{"apiKeyBearer":[]}],"tags":["Machine API"]}},"/v1/events/{id}":{"get":{"operationId":"IngestController_status","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The same decision body as POST /v1/events, including quotaExceeded, sandbox and finalDecision","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"required":["id","decision","finalDecision","evaluatedDecision","monitored","riskLevel","riskScore","reasons","policy","dlpFindings","lgpdCategories","review","quotaExceeded","sandbox"],"properties":{"id":{"type":"string","format":"uuid"},"decision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Decision recorded on the event. It does not change when a review is resolved."},"finalDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Effective outcome. Equals `decision` when there is no review. REVIEW while a review is pending. ALLOW after approval, BLOCK after denial or expiry. Always present, including an idempotent replay of a response stored before this field existed: the replay derives it from the stored decision and the current review."},"evaluatedDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"What full enforcement would have decided. Differs from `decision` in monitor mode."},"monitored":{"type":"boolean"},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"riskScore":{"type":"integer","minimum":0,"maximum":100},"reasons":{"type":"array","items":{"type":"string"}},"policy":{"nullable":true,"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"dlpFindings":{"type":"array","items":{"type":"string"},"description":"Sensitive-data types found in `content`."},"lgpdCategories":{"type":"array","items":{"type":"string","enum":["IDENTIFICATION","CONTACT","LOCATION","FINANCIAL","HEALTH","COMPANY","CREDENTIALS"]},"description":"LGPD categories derived from `dlpFindings`."},"review":{"nullable":true,"description":"Null when the event has no human review.","type":"object","required":["status","expiresAt","decidedAt","note"],"properties":{"status":{"type":"string","enum":["PENDING","APPROVED","DENIED","EXPIRED"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"decidedAt":{"type":"string","format":"date-time","nullable":true},"note":{"type":"string","nullable":true}}},"quotaExceeded":{"type":"boolean","description":"Plan quota is over the limit. The decision is still made."},"sandbox":{"type":"boolean","description":"True when the event was created with a TEST key."}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"400":{"description":"Validation error, or agent/resource was not identified","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"401":{"description":"Missing, invalid, revoked or expired API key","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"403":{"description":"API key is bound to another agent, or the caller IP is not in the key allowlist","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"404":{"description":"Unknown agent, resource, event or gateway route","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}}},"tags":["Machine API"],"summary":"Read a decision and its review status (poll a REVIEW)","security":[{"apiKey":[]},{"apiKeyBearer":[]}]}},"/v1/gateway/http":{"post":{"operationId":"GatewayIngestController_http","summary":"Decide an HTTP call and, when ALLOWed, forward it to a registered route","parameters":[{"name":"Idempotency-Key","in":"header","description":"Retries with the same key (per organization and endpoint, 24 hours) return the stored decision instead of deciding again. The replay always includes finalDecision, derived from the stored decision and the current review.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["action"],"description":"Same action fields as POST /v1/events, plus the registered route and the path to append.","properties":{"agent":{"type":"string","maxLength":100,"description":"Agent name as registered in Vulnify."},"agentId":{"type":"string","format":"uuid","description":"Agent id. Use this or `agent`."},"resource":{"type":"string","maxLength":100,"description":"Resource name as registered in Vulnify."},"resourceId":{"type":"string","format":"uuid","description":"Resource id. Use this or `resource`."},"destination":{"type":"string","enum":["INTERNAL","EXTERNAL_EMAIL","EXTERNAL_API"]},"recordsAffected":{"type":"integer","minimum":0,"maximum":100000000},"content":{"type":"string","maxLength":100000,"description":"Optional text scanned for sensitive data. Scanned in memory and never stored."},"action":{"type":"string","enum":["READ_DATA","WRITE_DATA","DELETE_DATA","EXPORT_DATA","SEND_EMAIL"]},"routeId":{"type":"string","format":"uuid","description":"Registered HTTP route. Without it the gateway only decides."},"path":{"type":"string","maxLength":1000,"description":"Path and query appended to the route base URL. Must start with /."},"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"],"description":"Defaults to GET."},"body":{"description":"JSON value forwarded upstream when method is not GET."}}}}}},"responses":{"200":{"description":"Decision: { id, decision, finalDecision, evaluatedDecision, monitored, riskLevel, riskScore, reasons, policy, dlpFindings, lgpdCategories, review, quotaExceeded, sandbox }. decision is the stored outcome and does not change when a review is resolved. finalDecision is the effective outcome (ALLOW after approval, BLOCK after denial or expiry, REVIEW while pending) and is always present, including an idempotent replay. quotaExceeded flags a plan overrun without blocking; sandbox is true for TEST keys.","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}},"Idempotent-Replay":{"description":"\"true\" when the body is a stored replay","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"required":["id","decision","finalDecision","evaluatedDecision","monitored","riskLevel","riskScore","reasons","policy","dlpFindings","lgpdCategories","review","quotaExceeded","sandbox"],"properties":{"id":{"type":"string","format":"uuid"},"decision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Decision recorded on the event. It does not change when a review is resolved."},"finalDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Effective outcome. Equals `decision` when there is no review. REVIEW while a review is pending. ALLOW after approval, BLOCK after denial or expiry. Always present, including an idempotent replay of a response stored before this field existed: the replay derives it from the stored decision and the current review."},"evaluatedDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"What full enforcement would have decided. Differs from `decision` in monitor mode."},"monitored":{"type":"boolean"},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"riskScore":{"type":"integer","minimum":0,"maximum":100},"reasons":{"type":"array","items":{"type":"string"}},"policy":{"nullable":true,"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"dlpFindings":{"type":"array","items":{"type":"string"},"description":"Sensitive-data types found in `content`."},"lgpdCategories":{"type":"array","items":{"type":"string","enum":["IDENTIFICATION","CONTACT","LOCATION","FINANCIAL","HEALTH","COMPANY","CREDENTIALS"]},"description":"LGPD categories derived from `dlpFindings`."},"review":{"nullable":true,"description":"Null when the event has no human review.","type":"object","required":["status","expiresAt","decidedAt","note"],"properties":{"status":{"type":"string","enum":["PENDING","APPROVED","DENIED","EXPIRED"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"decidedAt":{"type":"string","format":"date-time","nullable":true},"note":{"type":"string","nullable":true}}},"quotaExceeded":{"type":"boolean","description":"Plan quota is over the limit. The decision is still made."},"sandbox":{"type":"boolean","description":"True when the event was created with a TEST key."},"gateway":{"type":"object","required":["protocol","proxied"],"properties":{"protocol":{"type":"string","enum":["HTTP"]},"routeId":{"type":"string","format":"uuid","nullable":true},"proxied":{"type":"boolean"},"credentialId":{"type":"string","format":"uuid","nullable":true},"upstream":{"nullable":true,"description":"Null when the call was not forwarded. `status` is null when the upstream call failed before a response.","type":"object","required":["status","truncatedBody","error","latencyMs"],"properties":{"status":{"type":"integer","nullable":true,"description":"Upstream HTTP status, or null when the call failed before a response."},"truncatedBody":{"type":"string","description":"Upstream body, truncated to 2 KB. Injected secrets are redacted."},"error":{"type":"string","nullable":true},"latencyMs":{"type":"integer"}}}}}}}}}},"400":{"description":"Validation error (agent/agentId and resource/resourceId are required)","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"401":{"description":"Missing, invalid, revoked or expired API key","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"403":{"description":"Key bound to another agent, or caller IP not in the key allowlist","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"404":{"description":"Unknown agent, resource or gateway route","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"413":{"description":"JSON body larger than 200 KB","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}}},"security":[{"apiKey":[]},{"apiKeyBearer":[]}],"tags":["Machine API"]}},"/v1/gateway/mcp":{"post":{"operationId":"GatewayIngestController_mcp","summary":"Decide an MCP tool call (the tool name is mapped to an action)","parameters":[{"name":"Idempotency-Key","in":"header","description":"Retries with the same key (per organization and endpoint, 24 hours) return the stored decision instead of deciding again. The replay always includes finalDecision, derived from the stored decision and the current review.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["tool"],"properties":{"agent":{"type":"string","maxLength":100,"description":"Agent name as registered in Vulnify."},"agentId":{"type":"string","format":"uuid","description":"Agent id. Use this or `agent`."},"resource":{"type":"string","maxLength":100,"description":"Resource name as registered in Vulnify."},"resourceId":{"type":"string","format":"uuid","description":"Resource id. Use this or `resource`."},"destination":{"type":"string","enum":["INTERNAL","EXTERNAL_EMAIL","EXTERNAL_API"]},"recordsAffected":{"type":"integer","minimum":0,"maximum":100000000},"content":{"type":"string","maxLength":100000,"description":"Optional text scanned for sensitive data. Scanned in memory and never stored."},"routeId":{"type":"string","format":"uuid","description":"Registered MCP route (supplies a default agent)."},"tool":{"type":"string","minLength":1,"maxLength":200,"description":"MCP tool name. Mapped to an action by substring."},"arguments":{"type":"object","additionalProperties":true,"description":"Tool arguments. Not executed by Vulnify."}}}}}},"responses":{"200":{"description":"Decision: { id, decision, finalDecision, evaluatedDecision, monitored, riskLevel, riskScore, reasons, policy, dlpFindings, lgpdCategories, review, quotaExceeded, sandbox }. decision is the stored outcome and does not change when a review is resolved. finalDecision is the effective outcome (ALLOW after approval, BLOCK after denial or expiry, REVIEW while pending) and is always present, including an idempotent replay. quotaExceeded flags a plan overrun without blocking; sandbox is true for TEST keys.","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}},"Idempotent-Replay":{"description":"\"true\" when the body is a stored replay","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"required":["id","decision","finalDecision","evaluatedDecision","monitored","riskLevel","riskScore","reasons","policy","dlpFindings","lgpdCategories","review","quotaExceeded","sandbox"],"properties":{"id":{"type":"string","format":"uuid"},"decision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Decision recorded on the event. It does not change when a review is resolved."},"finalDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Effective outcome. Equals `decision` when there is no review. REVIEW while a review is pending. ALLOW after approval, BLOCK after denial or expiry. Always present, including an idempotent replay of a response stored before this field existed: the replay derives it from the stored decision and the current review."},"evaluatedDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"What full enforcement would have decided. Differs from `decision` in monitor mode."},"monitored":{"type":"boolean"},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"riskScore":{"type":"integer","minimum":0,"maximum":100},"reasons":{"type":"array","items":{"type":"string"}},"policy":{"nullable":true,"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"dlpFindings":{"type":"array","items":{"type":"string"},"description":"Sensitive-data types found in `content`."},"lgpdCategories":{"type":"array","items":{"type":"string","enum":["IDENTIFICATION","CONTACT","LOCATION","FINANCIAL","HEALTH","COMPANY","CREDENTIALS"]},"description":"LGPD categories derived from `dlpFindings`."},"review":{"nullable":true,"description":"Null when the event has no human review.","type":"object","required":["status","expiresAt","decidedAt","note"],"properties":{"status":{"type":"string","enum":["PENDING","APPROVED","DENIED","EXPIRED"]},"expiresAt":{"type":"string","format":"date-time","nullable":true},"decidedAt":{"type":"string","format":"date-time","nullable":true},"note":{"type":"string","nullable":true}}},"quotaExceeded":{"type":"boolean","description":"Plan quota is over the limit. The decision is still made."},"sandbox":{"type":"boolean","description":"True when the event was created with a TEST key."},"gateway":{"type":"object","required":["protocol","tool","mappedAction","allowed"],"properties":{"protocol":{"type":"string","enum":["MCP"]},"routeId":{"type":"string","format":"uuid","nullable":true},"tool":{"type":"string"},"mappedAction":{"type":"string","enum":["READ_DATA","WRITE_DATA","DELETE_DATA","EXPORT_DATA","SEND_EMAIL"]},"allowed":{"type":"boolean"}}}}}}}},"400":{"description":"Validation error (agent/agentId and resource/resourceId are required)","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"401":{"description":"Missing, invalid, revoked or expired API key","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"403":{"description":"Key bound to another agent, or caller IP not in the key allowlist","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"404":{"description":"Unknown agent, resource or gateway route","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"413":{"description":"JSON body larger than 200 KB","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"429":{"description":"Rate limit exceeded","headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}}},"security":[{"apiKey":[]},{"apiKeyBearer":[]}],"tags":["Machine API"]}},"/public/contact":{"post":{"operationId":"ContactController_submit","summary":"Submit the public contact form","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["name","email","message","locale","website"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"email":{"type":"string","format":"email"},"company":{"type":"string","maxLength":160},"message":{"type":"string","minLength":10,"maxLength":5000},"locale":{"type":"string","enum":["en","pt-BR"]},"website":{"type":"string","description":"Honeypot. Must be empty. A non-empty value answers 202 and is not stored or emailed."}}}}}},"responses":{"202":{"description":"Accepted. Also returned, with nothing stored, when website is not empty.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["ok"],"properties":{"ok":{"type":"boolean","enum":[true]}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"400":{"description":"The body failed validation.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error","fields"],"properties":{"error":{"type":"string","enum":["validation_error"]},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Field name to required, invalid, too_short, too_long or unknown."}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"429":{"description":"More than 5 requests from this IP in the last hour.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","enum":["rate_limited"]}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}},"503":{"description":"CONTACT_NOTIFY_TO is unset.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","enum":["unavailable"]}}}}},"headers":{"X-Request-Id":{"description":"Request id for support and log correlation (a caller-sent plain id is kept).","schema":{"type":"string"}},"x-api-version":{"description":"Machine API contract (v1).","schema":{"type":"string"}},"x-vulnify-version":{"description":"API build (SemVer, see /changelog).","schema":{"type":"string"}}}}},"tags":["Public"],"description":"Body: { name (1-120), email, company? (<=160), message (10-5000), locale: \"en\" | \"pt-BR\", website (honeypot, must be empty) }. 202 { ok: true }, also when website is not empty and then nothing is stored. 400 { error: \"validation_error\", fields }. 429 { error: \"rate_limited\" } at 5/hour per IP. 503 { error: \"unavailable\" } when CONTACT_NOTIFY_TO is unset. CORS allows https://www.vulnify.io and https://vulnify.io on every response, including errors.","security":[]}}},"webhooks":{"decision":{"post":{"summary":"Decision alert","description":"Sent when a live decision is BLOCK, REVIEW, CRITICAL (CRITICAL can be combined with BLOCK or REVIEW). One delivery per endpoint. Sandbox events are not sent.\n\nHMAC-SHA256. The key is the endpoint secret as UTF-8: the whole whsec_ value, prefix included, not base64-decoded. The signed message is the unix timestamp in seconds, a dot, and the raw body (`t.` + raw body). x-vulnify-signature is `t={unix seconds},v1={hex}`. Reject the delivery when abs(now - t) is greater than 300 seconds. The raw body is canonical JSON (object keys sorted). Verify those exact bytes.","parameters":[{"name":"Content-Type","in":"header","required":true,"description":"JSON body.","schema":{"type":"string","enum":["application/json"]}},{"name":"User-Agent","in":"header","required":true,"description":"Sender identity.","schema":{"type":"string","enum":["Vulnify-Webhooks/1.0"]}},{"name":"X-Vulnify-Signature","in":"header","required":true,"description":"HMAC-SHA256. The key is the endpoint secret as UTF-8: the whole whsec_ value, prefix included, not base64-decoded. The signed message is the unix timestamp in seconds, a dot, and the raw body (`t.` + raw body). x-vulnify-signature is `t={unix seconds},v1={hex}`. Reject the delivery when abs(now - t) is greater than 300 seconds. The raw body is canonical JSON (object keys sorted). Verify those exact bytes.","schema":{"type":"string","pattern":"^t=[0-9]+,v1=[0-9a-f]{64}$"}},{"name":"X-Vulnify-Attempt","in":"header","required":true,"description":"This attempt, starting at 1. The same delivery id is retried with a higher number.","schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"X-Vulnify-Event","in":"header","required":true,"description":"Primary event type. Same as body.type.","schema":{"type":"string","enum":["BLOCK","REVIEW","CRITICAL"]}},{"name":"X-Vulnify-Delivery","in":"header","required":true,"description":"Delivery id. Same as body.id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDecisionDelivery"}}}},"responses":{"200":{"description":"Any 2xx acknowledges the delivery. 408, 429 and 5xx are retried. Any other 4xx stops retries."}}}},"anomaly":{"post":{"summary":"Anomaly alert","description":"Sent when a scan opens an anomaly (VOLUME_SPIKE, NEW_ACTION, NEW_EXTERNAL_DEST, RATE_SPIKE). One delivery per endpoint.\n\nHMAC-SHA256. The key is the endpoint secret as UTF-8: the whole whsec_ value, prefix included, not base64-decoded. The signed message is the unix timestamp in seconds, a dot, and the raw body (`t.` + raw body). x-vulnify-signature is `t={unix seconds},v1={hex}`. Reject the delivery when abs(now - t) is greater than 300 seconds. The raw body is canonical JSON (object keys sorted). Verify those exact bytes.","parameters":[{"name":"Content-Type","in":"header","required":true,"description":"JSON body.","schema":{"type":"string","enum":["application/json"]}},{"name":"User-Agent","in":"header","required":true,"description":"Sender identity.","schema":{"type":"string","enum":["Vulnify-Webhooks/1.0"]}},{"name":"X-Vulnify-Signature","in":"header","required":true,"description":"HMAC-SHA256. The key is the endpoint secret as UTF-8: the whole whsec_ value, prefix included, not base64-decoded. The signed message is the unix timestamp in seconds, a dot, and the raw body (`t.` + raw body). x-vulnify-signature is `t={unix seconds},v1={hex}`. Reject the delivery when abs(now - t) is greater than 300 seconds. The raw body is canonical JSON (object keys sorted). Verify those exact bytes.","schema":{"type":"string","pattern":"^t=[0-9]+,v1=[0-9a-f]{64}$"}},{"name":"X-Vulnify-Attempt","in":"header","required":true,"description":"This attempt, starting at 1. The same delivery id is retried with a higher number.","schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"X-Vulnify-Event","in":"header","required":true,"description":"Primary event type. Same as body.type.","schema":{"type":"string","enum":["ANOMALY"]}},{"name":"X-Vulnify-Delivery","in":"header","required":true,"description":"Delivery id. Same as body.id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookAnomalyDelivery"}}}},"responses":{"200":{"description":"Any 2xx acknowledges the delivery. 408, 429 and 5xx are retried. Any other 4xx stops retries."}}}},"test":{"post":{"summary":"Test delivery","description":"Sent only when an administrator requests a test event. It is not a security decision.\n\nHMAC-SHA256. The key is the endpoint secret as UTF-8: the whole whsec_ value, prefix included, not base64-decoded. The signed message is the unix timestamp in seconds, a dot, and the raw body (`t.` + raw body). x-vulnify-signature is `t={unix seconds},v1={hex}`. Reject the delivery when abs(now - t) is greater than 300 seconds. The raw body is canonical JSON (object keys sorted). Verify those exact bytes.","parameters":[{"name":"Content-Type","in":"header","required":true,"description":"JSON body.","schema":{"type":"string","enum":["application/json"]}},{"name":"User-Agent","in":"header","required":true,"description":"Sender identity.","schema":{"type":"string","enum":["Vulnify-Webhooks/1.0"]}},{"name":"X-Vulnify-Signature","in":"header","required":true,"description":"HMAC-SHA256. The key is the endpoint secret as UTF-8: the whole whsec_ value, prefix included, not base64-decoded. The signed message is the unix timestamp in seconds, a dot, and the raw body (`t.` + raw body). x-vulnify-signature is `t={unix seconds},v1={hex}`. Reject the delivery when abs(now - t) is greater than 300 seconds. The raw body is canonical JSON (object keys sorted). Verify those exact bytes.","schema":{"type":"string","pattern":"^t=[0-9]+,v1=[0-9a-f]{64}$"}},{"name":"X-Vulnify-Attempt","in":"header","required":true,"description":"This attempt, starting at 1. The same delivery id is retried with a higher number.","schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"X-Vulnify-Event","in":"header","required":true,"description":"Primary event type. Same as body.type.","schema":{"type":"string","enum":["TEST"]}},{"name":"X-Vulnify-Delivery","in":"header","required":true,"description":"Delivery id. Same as body.id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestDelivery"}}}},"responses":{"200":{"description":"Any 2xx acknowledges the delivery. 408, 429 and 5xx are retried. Any other 4xx stops retries."}}}}},"components":{"securitySchemes":{"apiKeyBearer":{"type":"http","scheme":"bearer","description":"API key as a Bearer token: `Authorization: Bearer vln_live_...` or `vln_test_...`. The machine API guard accepts this or the X-API-Key header. This is not a JWT."},"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key (`vln_live_...` or `vln_test_...`). Equivalent to Authorization: Bearer with the same key."}},"schemas":{"PolicyRecord":{"allOf":[{"$ref":"#/components/schemas/PolicySpec"},{"type":"object","required":["id","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"updatedAt":{"type":"string","format":"date-time"}}}]},"PolicySpec":{"type":"object","additionalProperties":false,"required":["name","action","decision"],"description":"One policy. name is unique per organization. action is the action family (for example EXPORT), not an event action such as EXPORT_DATA. Omitted enabled defaults to true. Omitted mode defaults to ENFORCE. Omitted resource, null and ANY match every classification.","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":500},"enabled":{"type":"boolean","default":true},"action":{"type":"string","enum":["ANY","READ","WRITE","DELETE","EXPORT"]},"resource":{"description":"Resource classification. ANY and null match every classification.","nullable":true,"enum":["ANY","PUBLIC","INTERNAL","SENSITIVE","CUSTOMER_PII","FINANCIAL","EMPLOYEE",null]},"condition":{"$ref":"#/components/schemas/PolicyCondition"},"decision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"]},"mode":{"type":"string","enum":["ENFORCE","MONITOR"],"default":"ENFORCE"},"approverRoles":{"type":"array","uniqueItems":true,"items":{"type":"string","enum":["OWNER","ADMIN","MEMBER"]},"description":"Roles that may approve a REVIEW raised by this policy. Empty means owners and admins."}}},"PolicyCondition":{"type":"object","additionalProperties":false,"description":"Every field that is set must match. minRecords means recordsAffected is greater than or equal to the value. maxRecords means recordsAffected is less than or equal to the value. Groups may nest up to 32 levels, with at most 20000 conditions in one group. minRecords and maxRecords are integers from 0 through 9007199254740991. agentIds holds at most 8000 ids. allOf matches when every nested condition matches. anyOf matches when at least one does. An empty allOf matches. An empty anyOf does not. dlpTypes and lgpdCategories are accepted here and are not part of the policies-as-code YAML schema.","properties":{"action":{"type":"string","minLength":1,"maxLength":64,"description":"Event action (READ_DATA, WRITE_DATA, DELETE_DATA, EXPORT_DATA, SEND_EMAIL) or an action family. Matched against the event action and its family."},"destination":{"type":"string","enum":["EXTERNAL","INTERNAL"],"description":"EXTERNAL matches an external destination. INTERNAL matches an internal one."},"destinationContains":{"type":"string","maxLength":100,"description":"Case-insensitive substring of the destination."},"containsSensitiveData":{"type":"boolean"},"minRecords":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxRecords":{"type":"integer","minimum":0,"maximum":9007199254740991},"minRiskScore":{"type":"integer","minimum":0,"maximum":100},"outsideBusinessHours":{"type":"boolean","description":"Monday to Friday 09:00-18:00 in the organization time zone, inverted."},"agentIds":{"type":"array","maxItems":8000,"items":{"type":"string"}},"dlpTypes":{"type":"array","items":{"type":"string","enum":["CPF","CNPJ","RG","CNH","PIX_KEY","PHONE_BR","CEP","CREDIT_CARD","EMAIL","HEALTH_DATA","API_KEY","PRIVATE_KEY"]}},"lgpdCategories":{"type":"array","items":{"type":"string","enum":["IDENTIFICATION","CONTACT","LOCATION","FINANCIAL","HEALTH","COMPANY","CREDENTIALS"]}},"allOf":{"type":"array","maxItems":20000,"items":{"$ref":"#/components/schemas/PolicyCondition"}},"anyOf":{"type":"array","maxItems":20000,"items":{"$ref":"#/components/schemas/PolicyCondition"}}}},"PolicyTestCase":{"type":"object","additionalProperties":false,"required":["name","input"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"input":{"$ref":"#/components/schemas/PolicyTestInput"},"expect":{"$ref":"#/components/schemas/PolicyTestExpect"}}},"PolicyTestInput":{"type":"object","additionalProperties":false,"required":["agent","action","resource"],"description":"action is an event action. resource is the resource name registered in the organization, not the policy resource classification. metadata is accepted and not used.","properties":{"agent":{"type":"string","minLength":1,"maxLength":100,"description":"Agent name."},"action":{"type":"string","enum":["READ_DATA","WRITE_DATA","DELETE_DATA","EXPORT_DATA","SEND_EMAIL"]},"resource":{"type":"string","minLength":1,"maxLength":100,"description":"Resource name."},"destination":{"type":"string","enum":["INTERNAL","EXTERNAL_EMAIL","EXTERNAL_API"]},"recordsAffected":{"type":"integer","minimum":0,"maximum":100000000},"containsSensitiveData":{"type":"boolean"},"metadata":{"type":"object","additionalProperties":true,"description":"Accepted and not used by the policy engine."}}},"PolicyTestExpect":{"type":"object","additionalProperties":false,"required":["decision"],"properties":{"decision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"]},"policy":{"type":"string","minLength":1,"maxLength":120,"description":"Expected matched policy name."}}},"WebhookDecisionData":{"type":"object","additionalProperties":false,"required":["id","agent","action","resource","riskScore","riskLevel","decision","finalDecision"],"properties":{"id":{"type":"string","format":"uuid","description":"Security event id. Same as the envelope eventId."},"agent":{"type":"string","description":"Agent name."},"action":{"type":"string","enum":["READ_DATA","WRITE_DATA","DELETE_DATA","EXPORT_DATA","SEND_EMAIL"]},"resource":{"type":"string","description":"Resource name."},"riskScore":{"type":"integer","minimum":0,"maximum":100},"riskLevel":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"decision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Decision recorded on the event. It does not change when a review is resolved."},"finalDecision":{"type":"string","enum":["ALLOW","REVIEW","BLOCK"],"description":"Effective outcome at send time. REVIEW while a review is pending, ALLOW after approval, BLOCK after denial or expiry. Equals decision when the event has no review."}}},"WebhookAnomalyData":{"type":"object","additionalProperties":false,"required":["id","kind","severity","message","agentId","messageCode","messageParams"],"properties":{"id":{"type":"string","format":"uuid","description":"Anomaly id. Same as the envelope eventId."},"kind":{"type":"string","enum":["VOLUME_SPIKE","NEW_ACTION","NEW_EXTERNAL_DEST","RATE_SPIKE"]},"severity":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"]},"message":{"type":"string","description":"English fallback. Render messageCode and messageParams for other languages."},"agentId":{"type":"string","format":"uuid"},"messageCode":{"type":"string","enum":["anomaly.volume_spike","anomaly.new_action","anomaly.new_external_destination","anomaly.rate_spike"]},"messageParams":{"type":"object","description":"Parameters for messageCode. Values are strings, numbers, or null.","additionalProperties":{"nullable":true,"anyOf":[{"type":"string"},{"type":"number"}]}}}},"WebhookTestData":{"type":"object","additionalProperties":false,"required":["message","webhookId","organizationId"],"properties":{"message":{"type":"string","enum":["Test event from Vulnify"]},"webhookId":{"type":"string","format":"uuid","description":"Webhook endpoint id."},"organizationId":{"type":"string","format":"uuid"}}},"WebhookDecisionDelivery":{"type":"object","additionalProperties":false,"required":["id","type","types","eventId","createdAt","data"],"properties":{"id":{"type":"string","format":"uuid","description":"Delivery id. The same value is sent on every retry. Dedupe on it."},"type":{"type":"string","enum":["BLOCK","REVIEW","CRITICAL"],"description":"Primary type. The first entry of types, and the X-Vulnify-Event header."},"types":{"type":"array","minItems":1,"uniqueItems":true,"description":"Every decision type this event matched, in this order: BLOCK if the decision is BLOCK, REVIEW if it is REVIEW, CRITICAL if the risk level is CRITICAL. A delivery is one of those, or BLOCK plus CRITICAL, or REVIEW plus CRITICAL.","items":{"type":"string","enum":["BLOCK","REVIEW","CRITICAL"]}},"eventId":{"type":"string","format":"uuid","description":"Security event id. Same as data.id."},"createdAt":{"type":"string","format":"date-time"},"data":{"$ref":"#/components/schemas/WebhookDecisionData"}}},"WebhookAnomalyDelivery":{"type":"object","additionalProperties":false,"required":["id","type","types","eventId","createdAt","data"],"properties":{"id":{"type":"string","format":"uuid","description":"Delivery id. The same value is sent on every retry. Dedupe on it."},"type":{"type":"string","enum":["ANOMALY"],"description":"Primary type. The first entry of types, and the X-Vulnify-Event header."},"types":{"type":"array","minItems":1,"maxItems":1,"items":{"type":"string","enum":["ANOMALY"]}},"eventId":{"type":"string","format":"uuid","description":"Anomaly id. Same as data.id."},"createdAt":{"type":"string","format":"date-time"},"data":{"$ref":"#/components/schemas/WebhookAnomalyData"}}},"WebhookTestDelivery":{"type":"object","additionalProperties":false,"required":["id","type","types","eventId","createdAt","data"],"properties":{"id":{"type":"string","format":"uuid","description":"Delivery id. The same value is sent on every retry. Dedupe on it."},"type":{"type":"string","enum":["TEST"],"description":"Primary type. The first entry of types, and the X-Vulnify-Event header."},"types":{"type":"array","minItems":1,"maxItems":1,"items":{"type":"string","enum":["TEST"]}},"eventId":{"type":"string","pattern":"^test-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$","description":"test- followed by a UUID"},"createdAt":{"type":"string","format":"date-time"},"data":{"$ref":"#/components/schemas/WebhookTestData"}}},"WebhookDelivery":{"description":"Body of every webhook the API sends.","oneOf":[{"$ref":"#/components/schemas/WebhookDecisionDelivery"},{"$ref":"#/components/schemas/WebhookAnomalyDelivery"},{"$ref":"#/components/schemas/WebhookTestDelivery"}]}}}}