Errors
BLOCK and REVIEW are HTTP 200. The JSON decision tells you not to run the action. guard() then raises VulnifyBlockedError. That exception is not an HTTP failure.
The machine contract uses these statuses. The tables on each endpoint page are generated from the spec.
| Status | Meaning |
|---|---|
| 400 | Validation error. For a decision or gateway call, agent or agentId and resource or resourceId are required unless the key is bound to an agent (the agent half). GET /v1/events/{id} uses 400 for a validation error or when the agent or resource was not identified. |
| 401 | Missing, invalid, revoked, or expired API key. |
| 403 | The key is bound to another agent, or the caller IP is not in the key allowlist. POST /v1/policies/apply also returns 403 when the key is not an org-wide LIVE key. |
| 404 | Unknown agent, resource, gateway route, or, on GET, unknown event. |
| 413 | JSON body larger than 200 KB. Returned by POST /v1/events, POST /v1/gateway/http, POST /v1/gateway/mcp, POST /v1/policies/apply, and POST /v1/policies/test. GET routes do not return 413. |
| 429 | Rate limit exceeded. |
A 413 is a client error. The body was rejected before Vulnify evaluated the action. Do not run the action, and do not treat it as an outage.
What the SDKs raise
Section titled “What the SDKs raise”@vulnify/sdk 0.2.2 and vulnify 0.2.1 raise on every HTTP 4xx except 408 and 429. That includes 400, 401, 403, 404, and 413. failMode: 'open' / fail_mode="open" does not turn those responses into ALLOW. They are not retried. The Node.js fix shipped in 0.2.1 (0.2.0 was not published). The Python fix shipped in 0.2.0.
Node.js throws Error with Vulnify request rejected (STATUS): …. Python raises VulnifyError with the same shape. Python joins a validation message array into one string.
408 is not a status in the OpenAPI document. The SDKs still treat a 408, if one is returned, as transient, along with 429.
Unreachable API
Section titled “Unreachable API”For Node.js check() and Python check(), timeouts, network errors, HTTP 408, HTTP 429, and HTTP 5xx are retried. The default is 2 retries, so 3 attempts, all with the same Idempotency-Key. Python waits a short backoff between attempts.
After those attempts, check() returns a local decision and does not throw:
failMode / fail_mode |
decision |
degraded |
id |
|---|---|---|---|
closed (default) |
BLOCK |
true |
null |
open |
ALLOW |
true |
null |
reasons[0] starts with Vulnify unavailable and includes the fail mode. degraded: true means Vulnify did not make this decision. A 413 never takes this path.
Node.js getEvent throws on any non-OK status, including 429 and 5xx. It does not retry and it does not apply failMode.
Python get_event and wait_for_review retry the same transient failures as check(), then raise VulnifyError. They do not apply fail_mode.
cURL has no fallback. If the request fails, do not run the action.

