Skip to main content

oas-validator

The oas-validator plugin validates incoming HTTP requests against an OpenAPI 3 specification before forwarding them upstream. It does not validate upstream responses.

OpenAPI 3.1 is supported in API7 Enterprise from version 3.9.8 and APISIX from version 3.17.0. This includes numeric exclusiveMinimum and exclusiveMaximum, conditional schemas, types that include null, const, patternProperties, prefixItems, and JSON Schema dynamic references.

Examples

Before proceeding, fetch the Swagger Petstore Open API spec, which will be used in the following examples.

export OPEN_API_SPEC=$(curl -s "https://petstore3.swagger.io/api/v3/openapi.json" | sed 's/"/\\"/g')

Validate Request Body

This example demonstrates validation of request body against a given specification.

Create the following route with the OAS validator plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
--data-binary @- <<EOF
{
"id": "body_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec": "${OPEN_API_SPEC}"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore3.swagger.io:443": 1
},
"scheme": "https",
"pass_host": "node"
}
}
EOF

Failed Validation

Send a request to the above route with a request body that does not satisfy the defined Open API Spec:

curl -i "http://127.0.0.1:9080/api/v3/pet" -X POST \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"invalid-body": "this is an invalid body"}'

You should see an HTTP/1.1 400 Bad Request response with the response body similar to the following:

{"message":"failed to validate request."}

Successful Validation

Send a request to the route with a valid request body:

curl -i "http://127.0.0.1:9080/api/v3/pet" -X POST \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": 1,
"name": "doggie",
"category": { "id": 1, "name": "Dogs" },
"photoUrls": ["string"],
"tags": [{ "id": 1, "name": "tag1" }],
"status": "available"
}'

You should see an HTTP/1.1 200 OK response with the response body similar to the following:

{
"id": 1,
"category": { "id": 1, "name": "Dogs" },
"name": "doggie",
"photoUrls": ["string"],
"tags": [{ "id": 1, "name": "tag1" }],
"status": "available"
}

Get Verbose Error Response

This example demonstrates getting verbose error response when the validation fails.

Create a route with the OAS Validator plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
--data-binary @- <<EOF
{
"id": "body_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec": "${OPEN_API_SPEC}",
"verbose_errors": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore3.swagger.io:443": 1
},
"scheme": "https",
"pass_host": "node"
}
}
EOF

Send a request to the route created above with an invalid request body:

curl -i "http://127.0.0.1:9080/api/v3/pet" -X POST \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"invalid-body": "this is an invalid body"}'

You should see an HTTP/1.1 400 Bad Request response with the response body similar to the following:

doesn't match schema #/components/schemas/Pet: Error at "/name": property "name" is missing
Schema:
{
"properties": {
"category": {
"$ref": "#/components/schemas/Category"
},
"id": {
"example": 10,
"format": "int64",
"type": "integer"
},
...
}

Value:
{
"invalid-body": "this is an invalid body"
}
| Error at "/photoUrls": property "photoUrls" is missing
Schema:
{
"properties": {
"category": {
"$ref": "#/components/schemas/Category"
},
...
}

Value:
{
"invalid-body": "this is an invalid body"
}

Monitor Violations Without Blocking Traffic

Use reject_if_not_match to control whether non-compliant requests are blocked or allowed through. This option is available in API7 Enterprise from version 3.9.6 and APISIX from version 3.17.0.

Reject Non-Compliant Requests

When reject_if_not_match is set to true (default), requests that fail OAS validation are blocked with a HTTP/1.1 400 Bad Request response.

Create a route with the OAS Validator plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
--data-binary @- <<EOF
{
"id": "body_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec": "${OPEN_API_SPEC}",
"reject_if_not_match": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore3.swagger.io:443": 1
},
"scheme": "https",
"pass_host": "node"
}
}
EOF

Send a request with an invalid body:

curl -i "http://127.0.0.1:9080/api/v3/pet" -X POST \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"invalid-body": "this is an invalid body"}'

You should see an HTTP/1.1 400 Bad Request response with the response body similar to the following:

{"message":"failed to validate request."}

Allow Non-Compliant Requests to Pass

When reject_if_not_match is set to false, non-compliant requests are forwarded to the upstream instead of being blocked, and validation errors are recorded in the error logs.

Update the route to set reject_if_not_match to false:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
--data-binary @- <<EOF
{
"id": "body_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec": "${OPEN_API_SPEC}",
"reject_if_not_match": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore3.swagger.io:443": 1
},
"scheme": "https",
"pass_host": "node"
}
}
EOF

Send a request with an invalid body:

curl -i "http://127.0.0.1:9080/api/v3/pet" -X POST \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"invalid-body": "this is an invalid body"}'

You should see an HTTP/1.1 500 Internal Server Error response since petstore3.swagger.io does not correctly handle an invalid body. However, you can see that the request got passed successfully to the upstream.

You should also see an error log capturing the method, URI, and validation error similar to this:

[error] error occurred while validating request [POST /api/v3/pet], err: ...

This lets you audit non-compliant traffic in your logs without breaking existing clients.

Validate Using Remote Spec URL

This example applies to API7 Enterprise from version 3.9.12 and APISIX from version 3.17.0.

When your OpenAPI specification is too large to embed inline (the spec field has a 2 MB size limit), or you want the spec to auto-refresh periodically, use spec_url to load it from a remote URL.

The plugin caches the compiled specification. After the cache expires, the stale entry continues serving requests while APISIX refreshes it in the background.

If the initial fetch or compilation fails and no cached specification is available, the plugin returns 500 Internal Server Error.

TLS certificate verification for spec_url is disabled by default for compatibility. Set ssl_verify to true and configure a trusted certificate chain in production.

Create a route that fetches the spec from a URL:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "url_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec_url": "https://petstore3.swagger.io/api/v3/openapi.json",
"timeout": 5000
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore3.swagger.io:443": 1
},
"scheme": "https",
"pass_host": "node"
}
}'

If the spec endpoint requires authentication, use the same configuration shape and replace the placeholder URL, backend, and token with values from your environment:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "url_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec_url": "https://internal-api.example.com/openapi.json",
"spec_url_request_headers": { "Authorization": "Bearer <token>" },
"timeout": 5000
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"internal-api.example.com:443": 1
},
"scheme": "https",
"pass_host": "node"
}
}'

To configure the cache TTL for the fetched spec (default is 3600 seconds), set plugin metadata:

curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/oas-validator" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"spec_url_ttl": 1800
}'