Validating Requests Against OpenAPI at the Gateway
An OpenAPI document that only generates documentation is a description of what the API was supposed to be. Loading the same document into the gateway as an enforcement policy makes it a description of what the API is, because a request that does not match it never arrives. This page covers how to get from a specification in a repository to a rule in a proxy, without the two drifting apart and without the first day of enforcement being an outage.
Prerequisite concepts
This assumes the gateway-versus-service split in request validation and schema enforcement, and it uses the contract-publishing mechanics described in consumer-driven contract testing at the gateway.
From document to enforced policy
What to compile out of the document
Not everything in an OpenAPI document is enforceable, and trying to enforce all of it produces false rejections. Compile the parts that describe the request and are decidable from the message.
# openapi.yaml — the enforceable parts, marked
paths:
/v2/orders:
post:
operationId: createOrder
parameters:
- name: x-idempotency-key # enforceable: required header, pattern
in: header
required: true
schema: { type: string, minLength: 16, maxLength: 64 }
requestBody:
required: true
content:
application/json: # enforceable: accepted content types
schema:
type: object
required: [customerId, items]
additionalProperties: false
properties:
customerId: { type: string, format: uuid }
items:
type: array
minItems: 1
maxItems: 100
items: { $ref: '#/components/schemas/LineItem' }
responses:
'201': { description: created } # NOT enforced on the request path
'409': { description: duplicate }
The response section is documentation for consumers, not a gateway rule. Security schemes are usually better expressed as the gateway’s own auth configuration than derived from the document, because the document says which scheme applies and not how to validate it.
Handling the parts that do not compile cleanly
format deserves specific caution: in JSON Schema it is an annotation rather than an assertion, so whether format: email rejects anything at all depends on the validator and its configuration. If a format matters, express it as a pattern as well.
Keeping the document and the routes in agreement
The document describes operations; the gateway has routes. Nothing forces the two to match, and they diverge the first time somebody adds a route without touching the specification. The fix is a CI check that compares the sets in both directions.
# Fail the build when routes and operations disagree
compiled=$(oas-compile openapi.yaml --list-operations | sort)
routes=$(gw-config list-routes --json | jq -r '.[].name' | sort)
comm -3 <(echo "$compiled") <(echo "$routes") | tee /tmp/drift
[ -s /tmp/drift ] && { echo "route/spec drift detected"; exit 1; }
An operation with no route is dead documentation. A route with no operation is an unenforced endpoint, which is the more dangerous of the two, because it is precisely the endpoint nobody reviewed.
Decision matrix
| Question | Answer |
|---|---|
| Where does the document live | with the service, versioned alongside its code |
| When is it compiled | in CI, on every change |
| How does the gateway get it | as a versioned artefact, at deploy |
| What about responses | documented, not enforced on the request path |
| What about security schemes | declared in the document, configured in the gateway |
| What happens on drift | build fails for an unenforced route, warns for a dead operation |
Gotchas and failure signals
A document written for humans over-specifies. Descriptions promising “must be a valid ISO country code” mean nothing until they are an enum or a pattern. Enforcement exposes exactly how much of the specification was prose.
Shared component schemas propagate changes further than expected. Tightening one $ref used by nine operations tightens all nine at once, so review by compiled output rather than by document diff.
Validator dialects differ, particularly around nullable, format and how additionalProperties interacts with allOf. Pin the version and test the constructs you actually use.
A document too large to compile quickly slows every deploy. Split by service rather than maintaining one document for the whole platform.
Validation
- Document lives with the service and is versioned with it
- Compilation runs in CI and fails on constructs the validator cannot enforce
- The gateway loads a versioned artefact rather than fetching at runtime
- Route and operation sets compared in both directions, failing on unenforced routes
- Validator version pinned, with a test for each construct in use
- Report-only mode used before enforcement on any existing route
FAQ
Should the gateway fetch the OpenAPI document at runtime?
No. Compile it in CI into a versioned artefact and load that at deploy. Fetching at runtime puts a dependency on the request path, introduces a cache with its own staleness question, and creates a way for a document change to reach production without a deploy or a review. The specification should be an artefact with the same discipline as any other the gateway depends on.
Which parts of an OpenAPI document actually enforce anything?
Types, required fields, enumerations, numeric bounds, string lengths and patterns, array bounds, additionalProperties and the accepted content types. Descriptions, examples, response definitions, links and tags carry no enforcement meaning. In between sit discriminated unions, recursive references and the format keyword, whose behaviour depends on the validator — which is why the validator version should be pinned and the constructs tested.
What happens when routes and documented operations disagree?
Two different problems. An operation with no route is dead documentation that misleads consumers. A route with no operation is an endpoint serving traffic with nothing enforcing its shape, which is the one worth failing a build over — it is precisely the endpoint that nobody reviewed. Compare both sets in CI, on every pull request rather than nightly.
Does format: email actually validate anything?
It depends entirely on the validator and its configuration, because in JSON Schema format is an annotation rather than an assertion. Some validators check it, some ignore it, and some check it differently. If a format matters to your service, express it as a pattern as well so the rule holds regardless of dialect.
Parent: Request Validation & Schema Enforcement
Related
- Request Validation & Schema Enforcement — which validation belongs at the edge in the first place.
- JSON Schema Validation: Kong vs Envoy — where the compiled schema actually executes.
- Consumer-Driven Contract Testing at the Gateway — the same publishing discipline applied to consumer expectations.