HTTP gateway
POST /v1/gateway/http decides an HTTP call and, when the decision allows it, forwards the call to a registered route. Routes are registered in the app. The SDKs do not create routes and do not wrap this endpoint.
The response is the same decision body as POST /v1/events, plus gateway. gateway.upstream is null when the call was not forwarded. gateway.upstream.status is an integer or null. It is null when the upstream call failed before a response.
Request
Section titled “Request”curl -X POST https://api.vulnify.io/v1/gateway/http \ -H "Authorization: Bearer $VULNIFY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "routeId": "ROUTE_ID", "action": "READ_DATA", "method": "GET", "path": "/v1/customers?limit=50", "resource": "Customer Database" }'X-API-Key may replace the bearer header. Without routeId the gateway only decides.
| Name | In | Required | Type | Description |
|---|---|---|---|---|
Idempotency-Key | header | no | string | 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. |
Same action fields as POST /v1/events, plus the registered route and the path to append.
| Field | Type | Required | Description |
|---|---|---|---|
agent | string (max length 100) | no | Agent name as registered in Vulnify. |
agentId | string (uuid) | no | Agent id. Use this or `agent`. |
resource | string (max length 100) | no | Resource name as registered in Vulnify. |
resourceId | string (uuid) | no | Resource id. Use this or `resource`. |
destination | INTERNAL | EXTERNAL_EMAIL | EXTERNAL_API | no | |
recordsAffected | integer (0–100000000) | no | |
content | string (max length 100000) | no | Optional text scanned for sensitive data. Scanned in memory and never stored. |
action | READ_DATA | WRITE_DATA | DELETE_DATA | EXPORT_DATA | SEND_EMAIL | yes | |
routeId | string (uuid) | no | Registered HTTP route. Without it the gateway only decides. |
path | string (max length 1000) | no | Path and query appended to the route base URL. Must start with /. |
method | GET | POST | PUT | PATCH | DELETE | no | Defaults to GET. |
body | JSON value | no | JSON value forwarded upstream when method is not GET. |
path must start with /. method defaults to GET. body is forwarded upstream when method is not GET. The JSON body of this request must be at most 200 KB.
Response
Section titled “Response”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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | |
decision | ALLOW | REVIEW | BLOCK | yes | Decision recorded on the event. It does not change when a review is resolved. |
finalDecision | ALLOW | REVIEW | BLOCK | yes | 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 | ALLOW | REVIEW | BLOCK | yes | What full enforcement would have decided. Differs from `decision` in monitor mode. |
monitored | boolean | yes | |
riskLevel | LOW | MEDIUM | HIGH | CRITICAL | yes | |
riskScore | integer (0–100) | yes | |
reasons | array of string | yes | |
policy | object or null | yes | |
policy.id | string (uuid) | yes | |
policy.name | string | yes | |
dlpFindings | array of string | yes | Sensitive-data types found in `content`. |
lgpdCategories | array of IDENTIFICATION | CONTACT | LOCATION | FINANCIAL | HEALTH | COMPANY | CREDENTIALS | yes | LGPD categories derived from `dlpFindings`. |
review | object or null | yes | Null when the event has no human review. |
review.status | PENDING | APPROVED | DENIED | EXPIRED | yes | |
review.expiresAt | string or null (date-time) | yes | |
review.decidedAt | string or null (date-time) | yes | |
review.note | string or null | yes | |
quotaExceeded | boolean | yes | Plan quota is over the limit. The decision is still made. |
sandbox | boolean | yes | True when the event was created with a TEST key. |
gateway | object | no | |
gateway.protocol | HTTP | yes | |
gateway.routeId | string or null (uuid) | no | |
gateway.proxied | boolean | yes | |
gateway.credentialId | string or null (uuid) | no | |
gateway.upstream | object or null | no | Null when the call was not forwarded. `status` is null when the upstream call failed before a response. |
gateway.upstream.status | integer or null | yes | Upstream HTTP status, or null when the call failed before a response. |
gateway.upstream.truncatedBody | string | yes | Upstream body, truncated to 2 KB. Injected secrets are redacted. |
gateway.upstream.error | string or null | yes | |
gateway.upstream.latencyMs | integer | yes |
| Header | Description |
|---|---|
X-Request-Id | Request id for support and log correlation (a caller-sent plain id is kept). |
x-api-version | Machine API contract (v1). |
x-vulnify-version | API build (SemVer, see /changelog). |
Idempotent-Replay | "true" when the body is a stored replay |
gateway.upstream.truncatedBody is the upstream body truncated to 2 KB, with injected secrets redacted. gateway.credentialId is the credential used for that injection, or null.
Errors
Section titled “Errors”| Status | Meaning |
|---|---|
400 | Validation error (agent/agentId and resource/resourceId are required) |
401 | Missing, invalid, revoked or expired API key |
403 | Key bound to another agent, or caller IP not in the key allowlist |
404 | Unknown agent, resource or gateway route |
413 | JSON body larger than 200 KB |
429 | Rate limit exceeded |
A 413 is a client error. Do not treat it as an outage or as an allow. See Errors.
Credentials
Section titled “Credentials”A route can attach a credential from an API integration. The app injects that credential into the upstream call only after ALLOW, and only if the upstream host is in the integration’s allowed hosts. The agent never sees the credential. Gateway routes do not accept a Slack webhook or a ticket integration as that credential.
Allowed hosts are configured on the integration, up to 20, as host names such as api.example.com or *.example.com. Credential material the app can store for an HTTP integration includes an API key header (the form defaults the header name to X-Api-Key), a query parameter (default name api_key), an Authorization scheme (default Bearer), a username and secret, or an OAuth 2.0 client-credentials token URL whose host is one of the allowed hosts.
Calls through /v1/gateway/http and /v1/gateway/mcp appear in the app’s gateway call log, and from there in the audit log. A TEST key is marked as a test key on that log.

