Skip to main content

Validate API requests at the gateway before they reach your backends

How to use Zerq's workflow builder and validate node to enforce JSON Schema validation at the API gateway layer, before invalid requests reach your backends.

  • api-gateway
  • workflow-builder
  • validation
  • security
  • platform-engineering
Zerq team

Every backend service you operate has to trust that the requests it receives are valid. In practice, most teams handle this with a combination of client-side checks, per-service validation logic, and occasional request logging that only surfaces problems after something breaks. The result: validation is scattered across a dozen services, each returning a different error format, and malformed requests burn compute and generate noise before they are finally rejected.

API gateway request validation fixes this by catching bad requests at the entry point — before anything touches your backend infrastructure. In Zerq, this is handled by the validate node in the workflow builder. The node validates incoming request payloads against a JSON Schema and routes the workflow differently for valid and invalid inputs. This guide walks through building a real validation workflow: from opening the builder to testing the first rejected request and reading it in the logs.

Why existing approaches fall short

Most API gateways treat request validation as a second-class feature. AWS API Gateway and Azure APIM both support model-based request validation, but the schema language is constrained by what their built-in validators accept, error responses come back in fixed formats you cannot override, and the validation configuration lives in a separate definition layer disconnected from your routing logic. If you need RFC 7807 problem details in your error response body, you are writing a Lambda function or an APIM policy XML block.

Kong handles this through a request-validator plugin that must be enabled per route. The plugin works, but the configuration syntax is separate from your routing rules, error output is fixed by the plugin, and you are adding another item to an already long plugin maintenance surface. MuleSoft's approach requires writing DataWeave validation expressions inside API policies — a custom language that few platform engineers know well.

In Zerq, validation is just another node in the same workflow graph that handles routing, transformation, and proxying. The schema lives in the node configuration. The error response is a response node you wire up exactly as you would any other response. No plugins, no separate validators, no translation layer between your routing config and your validation rules.

How the validate node handles API request validation

The validate node takes two inputs: a JSON Schema in its config.schema field, and a value to validate in inputs.value. For API request flows, the value is the incoming request body from the http_trigger. The node routes to one of two outputs: valid when the schema check passes, and invalid when it fails.

Here is a validate node configuration for a payment transfer endpoint that requires an amount, a currency from a fixed list, and a recipient account:

{
  "id": "n_validate_payment",
  "type": "validate_node",
  "inputs": {
    "value": "{{ $json['http_trigger'].request.body }}"
  },
  "config": {
    "schema": {
      "type": "object",
      "required": ["amount", "currency", "recipient_account"],
      "properties": {
        "amount": {
          "type": "number",
          "minimum": 0.01,
          "description": "Payment amount in base currency units"
        },
        "currency": {
          "type": "string",
          "enum": ["GBP", "EUR", "USD"],
          "description": "ISO 4217 currency code"
        },
        "recipient_account": {
          "type": "string",
          "minLength": 1,
          "description": "Target account identifier"
        },
        "reference": {
          "type": "string",
          "maxLength": 140,
          "description": "Optional payment reference, max 140 chars"
        }
      }
    }
  }
}

The valid output connects to your backend call — a proxy_node to forward the request to an upstream service, or an http_request_node for a direct outbound call. The invalid output connects to a response_node that returns a 422 with a structured error body:

{
  "id": "n_response_invalid",
  "type": "response_node",
  "config": {
    "status": 422,
    "headers": {
      "Content-Type": "application/problem+json"
    },
    "body": {
      "type": "https://zerq.dev/errors/validation-failed",
      "title": "Validation failed",
      "status": 422,
      "detail": "Request body failed schema validation. Check required fields: amount, currency, recipient_account."
    }
  }
}

Every consumer calling this endpoint now receives the same error format on a bad request — regardless of which required field is missing or which value is out of range. The backend never sees the request.

Step-by-step: building a payment validation workflow

The following assumes you have a collection and a proxy already set up in Zerq. If not, the platform capabilities overview covers creating both from scratch.

  1. Open your collection in the management UI and click on the proxy you want to add validation to.
  2. Click Edit Workflow to open the workflow builder. A new workflow starts with http_trigger wired to a response_node.
  3. Click Add node in the toolbar and select Validate from the Logic and Routing category. Place the node between the trigger and the existing response node.
  4. Drag the edge from http_trigger to the validate node's input handle. Delete the direct edge from http_trigger to the original response node if it is still there.
  5. Click the validate node to open its configuration panel on the right. In the inputs.value field, enter {{ $json['http_trigger'].request.body }}.
  6. In the schema section, paste your JSON Schema. The payment schema above is a working starting point for any endpoint that accepts a structured object.
  7. Add a Response Node for invalid requests. Set its status to 422, add the Content-Type: application/problem+json header, and write the error body. This node handles only the invalid branch.
  8. Add a Proxy Node (or HTTP Request Node) to forward valid requests to your backend. Configure the upstream URL and any required headers.
  9. Wire the output handles: the valid output of the validate node goes to the proxy node; the invalid output goes to the 422 response node. The proxy node connects to a final 200 response node.
  10. Click Validate in the toolbar to confirm the graph is correctly connected — no disconnected nodes, no missing required fields, exactly one trigger entry point.
  11. Click Save, then Enable Workflow.

To test before enabling in production, click Execute Workflow, set the method to POST, and send a body with the currency field missing. The validate node should route to the invalid branch and the execution panel should show the 422 response node output. Repeat with a valid body to confirm the valid branch reaches the proxy node.

Validating path parameters and query strings

The validate node is not limited to request bodies. The same node can validate path parameters or query strings using expressions that reference the http_trigger's request context:

{
  "id": "n_validate_query",
  "type": "validate_node",
  "inputs": {
    "value": "{{ $json['http_trigger'].request.query }}"
  },
  "config": {
    "schema": {
      "type": "object",
      "required": ["from_date", "to_date"],
      "properties": {
        "from_date": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "description": "Start date in YYYY-MM-DD format"
        },
        "to_date": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "description": "End date in YYYY-MM-DD format"
        },
        "page_size": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "description": "Results per page, 1–100"
        }
      }
    }
  }
}

Chaining validate nodes for different aspects of a request — body schema on one node, query parameter rules on another — is straightforward: wire them in sequence, with each node's valid output leading to the next, and each invalid output going to an appropriate error response.

What request logs show after validation failures

After your first real traffic reaches the workflow, open Logs in the sidebar and filter by status code 422. Each entry captures the full request payload, the response body, the client identity, and how long the request took.

A rejected payment request looks like this in the log detail view:

FieldValue
Status code422
MethodPOST
Path/payments/transfers
Client IDacme-payments-client
Profile IDprod-partner-profile
Request body{"amount": 50.00, "recipient_account": "GB29NWBK60161331926819"}
Response body{"type": "https://zerq.dev/errors/validation-failed", "title": "Validation failed", "status": 422}
Latency4ms

Two details matter here. The latency is 4ms — the backend was never called. The request body shows exactly what the partner sent, including the missing currency field. That is actionable for the platform team and for the partner trying to fix their integration.

Zerq's observability layer lets you filter by client ID to see whether a specific partner is sending a pattern of invalid requests, and the request body is stored in full so you can understand the exact shape of the bad input — not just that it failed. You can also sort by latency to confirm that validation rejections are adding near-zero overhead compared to successful backend-proxied requests.

Combining validation with access profile controls

The validate node runs inside the workflow, which executes after the gateway has already performed authentication and profile resolution. A request that reaches the validate node has already proven its identity and been matched to an access profile.

Access profiles in Zerq independently control which HTTP methods a client is permitted to call and which source IPs are allowed. These checks run before the workflow starts. Combining them with the validate node gives you layered control:

  • Wrong HTTP method → 405 Method Not Allowed (profile method restriction, before workflow)
  • Request from non-permitted IP → 403 Forbidden (profile IP restriction, before workflow)
  • Failed authentication → 401 Unauthorized (gateway auth, before workflow)
  • Missing required field → 422 Unprocessable Entity (validate node, inside workflow)
  • Backend unavailable → mapped error via http_request_node error branch

Each layer handles exactly one concern. The validate node does not need to think about auth — the gateway already enforced it. The profile restriction does not need to think about payload structure — the workflow handles that.

What changes in audit logs when you update the schema

Every modification to the workflow configuration — including edits to the validation schema — is recorded in Zerq's audit logs. When a platform engineer changes the required fields or adds a new enum value to the currency list, the audit log records:

FieldValue
Actor ID[email protected]
Actor typeuser
ActionUPDATE
Resource typeproxy
Resource IDpayments-transfers-proxy
Timestamp2026-08-17T09:14:22Z
Request body(full updated workflow definition, including the new schema)

Compliance teams with the Auditor role in Zerq can query this trail to answer "what was the validation schema for the payment endpoint on date X?" without having admin access to change anything. The full request body in the audit record captures the complete workflow definition, so the exact schema in effect at any point in time is retrievable.

This is directly relevant for regulated industries where the controls applied to API inputs must be documented and auditable. The Zerq architecture stores all audit data in the customer's own MongoDB instance — nothing is written to any external system.

What this looks like in practice

A payments team operating an open banking API had validation logic split across two backend microservices. Each service returned a different error body when required fields were missing: one returned 400 Bad Request with a plain text message, the other returned 400 with a proprietary JSON error object. Third-party PISP partners integrating against the API were receiving inconsistent responses and raising support tickets on every integration cycle.

After adding a validate node to each affected proxy in Zerq, every invalid request now gets the same 422 application/problem+json response regardless of which endpoint was called. The backend microservices no longer receive malformed payloads at all — they only see requests that have passed schema validation at the gateway. The team can open the Zerq request logs and see, for any partner and any time window, exactly which requests failed validation and what payload was sent.

The before state: validation failures were often caught by the backend, logged in service-specific formats, and difficult to aggregate across endpoints. The after state: all validation failures are visible in one place, with a consistent response format and a complete request log showing what was sent.

Closing

The validate node gives platform engineers a single place to define and enforce input requirements for every API endpoint, independent of what backend services do internally. Invalid requests are rejected early, with a consistent error format that every consumer sees. Every rejection is logged with the full context of who sent it, what they sent, and how long rejection took. The workflow builder means there is no plugin system to maintain separately, no external validator service to operate, and no custom policy language to learn — validation rules live in the same graph as routing and transformation logic.


Zerq is an enterprise API gateway built for regulated industries — one platform for API management, AI agent access, compliance audit, and developer portal, running entirely in your own infrastructure. See how it works or request a demo to walk through your specific requirements.