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 support was introduced in API7 Enterprise 3.9.8 and APISIX 3.17.0.
Examples
The examples use version 1.0.27 of the official Swagger Petstore image. The fixture serves both the OpenAPI document and the API. Its manifest digest is pinned so the validation results remain reproducible.
Start Petstore
Start the fixture for the deployment type used by the gateway:
- Docker
- Kubernetes
Set GATEWAY_CONTAINER to the running APISIX or API7 Gateway container, then place the gateway and Petstore on a dedicated network:
export GATEWAY_CONTAINER=replace-with-gateway-container-name
docker network inspect gateway-petstore-net > /dev/null 2>&1 || \
docker network create gateway-petstore-net
if ! docker inspect "$GATEWAY_CONTAINER" \
--format '{{json .NetworkSettings.Networks}}' \
| grep -q '"gateway-petstore-net"'; then
docker network connect gateway-petstore-net "$GATEWAY_CONTAINER"
fi
Create a Compose file for the pinned Petstore fixture on the shared network:
services:
petstore:
image: swaggerapi/petstore3:1.0.27@sha256:749432676c31cd05256f0d9e878302164debb94daa1c1223e193747e1b34e7d1
platform: linux/amd64
networks:
- gateway-petstore-net
ports:
- "18080:8080"
networks:
gateway-petstore-net:
external: true
Start or reconcile the fixture with a fixed Compose project name so the command can be rerun across these guides:
docker compose -p api7-docs-petstore \
-f docker-compose.petstore.yaml up -d
Once the container has started, confirm that the OpenAPI document is available:
curl -fsS "http://127.0.0.1:18080/api/v3/openapi.json" > /dev/null
The fixture image is published for linux/amd64. Create it in the same namespace as the gateway. The manifest and commands use aic, the Ingress Controller example namespace; replace every occurrence with the gateway namespace for another deployment, typically api7 for API7 Gateway.
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: aic
name: petstore
spec:
replicas: 1
selector:
matchLabels:
app: petstore
template:
metadata:
labels:
app: petstore
spec:
nodeSelector:
kubernetes.io/arch: amd64
containers:
- name: petstore
image: swaggerapi/petstore3:1.0.27@sha256:749432676c31cd05256f0d9e878302164debb94daa1c1223e193747e1b34e7d1
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: petstore
spec:
selector:
app: petstore
ports:
- port: 8080
targetPort: 8080
Apply the manifest:
kubectl apply -f petstore.yaml
Wait for the fixture to become ready:
kubectl rollout status -n aic deployment/petstore
The Admin API examples load the OpenAPI document inline. If Petstore runs on Kubernetes, make the document available locally in another terminal:
kubectl port-forward -n aic service/petstore 18080:8080
For the Admin API tabs, fetch and escape the document for the JSON request body:
export OPEN_API_SPEC="$(curl -fsS "http://127.0.0.1:18080/api/v3/openapi.json" | jq -Rs .)"
The ADC and Ingress Controller examples use spec_url so the gateway retrieves the same document directly from the fixture.
Validate Request Body
The following example demonstrates how to validate a request body against an OpenAPI specification.
Create the following route with the OAS validator plugin:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"id": "body_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec": ${OPEN_API_SPEC}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore:8080": 1
},
"scheme": "http",
"pass_host": "node"
}
}
EOF
services:
- name: petstore
labels:
docs-example: oas-validator
routes:
- uris:
- /*
name: body_validation
plugins:
oas-validator:
spec_url: http://petstore:8080/api/v3/openapi.json
upstream:
type: roundrobin
scheme: http
pass_host: node
nodes:
- host: petstore
port: 8080
weight: 1
ADC reconciles services as desired state. Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: petstore-external-domain
spec:
type: ExternalName
externalName: petstore.aic.svc.cluster.local
ports:
- port: 8080
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: oas-validator-plugin-config
spec:
plugins:
- name: oas-validator
config:
spec_url: http://petstore:8080/api/v3/openapi.json
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-validation
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: oas-validator-plugin-config
backendRefs:
- name: petstore-external-domain
port: 8080
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: petstore-external-domain
spec:
targetRefs:
- group: ""
kind: Service
name: petstore-external-domain
passHost: node
scheme: http
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: petstore-external-domain
spec:
ingressClassName: apisix
scheme: http
passHost: node
externalNodes:
- type: Domain
name: petstore.aic.svc.cluster.local
port: 8080
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-validation
spec:
ingressClassName: apisix
http:
- name: body-validation
match:
paths:
- /*
upstreams:
- name: petstore-external-domain
plugins:
- name: oas-validator
config:
spec_url: http://petstore:8080/api/v3/openapi.json
Apply the configuration:
kubectl apply -f oas-validator-ic.yaml
Failed Validation
Send a request to the route with a body that does not satisfy the OpenAPI specification:
curl -i "http://127.0.0.1:9080/api/v3/pet" -X POST \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"name": "doggie"}'
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
The following example demonstrates how to return a verbose error response when validation fails.
Create a route with the OAS Validator plugin:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"id": "body_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec": ${OPEN_API_SPEC},
"verbose_errors": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore:8080": 1
},
"scheme": "http",
"pass_host": "node"
}
}
EOF
services:
- name: petstore
labels:
docs-example: oas-validator
routes:
- uris:
- /*
name: body_validation
plugins:
oas-validator:
spec_url: http://petstore:8080/api/v3/openapi.json
verbose_errors: true
upstream:
type: roundrobin
scheme: http
pass_host: node
nodes:
- host: petstore
port: 8080
weight: 1
ADC reconciles services as desired state. Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: petstore-external-domain
spec:
type: ExternalName
externalName: petstore.aic.svc.cluster.local
ports:
- port: 8080
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: oas-validator-plugin-config
spec:
plugins:
- name: oas-validator
config:
spec_url: http://petstore:8080/api/v3/openapi.json
verbose_errors: true
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-validation
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: oas-validator-plugin-config
backendRefs:
- name: petstore-external-domain
port: 8080
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: petstore-external-domain
spec:
targetRefs:
- group: ""
kind: Service
name: petstore-external-domain
passHost: node
scheme: http
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: petstore-external-domain
spec:
ingressClassName: apisix
scheme: http
passHost: node
externalNodes:
- type: Domain
name: petstore.aic.svc.cluster.local
port: 8080
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-validation
spec:
ingressClassName: apisix
http:
- name: body-validation
match:
paths:
- /*
upstreams:
- name: petstore-external-domain
plugins:
- name: oas-validator
config:
spec_url: http://petstore:8080/api/v3/openapi.json
verbose_errors: true
Apply the configuration:
kubectl apply -f oas-validator-ic.yaml
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 '{"name": "doggie"}'
You should see an HTTP/1.1 400 Bad Request response with the response body similar to the following:
{
"message": "failed to validate request. body: property \"photoUrls\" is required"
}
Allow Non-Compliant Requests to Pass
By default, requests that fail OpenAPI validation are rejected. Set reject_if_not_match to false to log the validation error and forward the request to the upstream instead. This option was introduced in API7 Enterprise 3.9.6 and APISIX 3.17.0.
Update the route to set reject_if_not_match to false:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d @- <<EOF
{
"id": "body_validation",
"uri": "/*",
"plugins": {
"oas-validator": {
"spec": ${OPEN_API_SPEC},
"reject_if_not_match": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore:8080": 1
},
"scheme": "http",
"pass_host": "node"
}
}
EOF
services:
- name: petstore
labels:
docs-example: oas-validator
routes:
- uris:
- /*
name: body_validation
plugins:
oas-validator:
spec_url: http://petstore:8080/api/v3/openapi.json
reject_if_not_match: false
upstream:
type: roundrobin
scheme: http
pass_host: node
nodes:
- host: petstore
port: 8080
weight: 1
ADC reconciles services as desired state. Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: petstore-external-domain
spec:
type: ExternalName
externalName: petstore.aic.svc.cluster.local
ports:
- port: 8080
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: oas-validator-plugin-config
spec:
plugins:
- name: oas-validator
config:
spec_url: http://petstore:8080/api/v3/openapi.json
reject_if_not_match: false
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-validation
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: oas-validator-plugin-config
backendRefs:
- name: petstore-external-domain
port: 8080
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: petstore-external-domain
spec:
ingressClassName: apisix
scheme: http
passHost: node
externalNodes:
- type: Domain
name: petstore.aic.svc.cluster.local
port: 8080
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-validation
spec:
ingressClassName: apisix
http:
- name: body-validation
match:
paths:
- /*
upstreams:
- name: petstore-external-domain
plugins:
- name: oas-validator
config:
spec_url: http://petstore:8080/api/v3/openapi.json
reject_if_not_match: false
Apply the configuration:
kubectl apply -f oas-validator-ic.yaml
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 '{"id": 101, "photoUrls": []}'
The OpenAPI document requires name, but Petstore accepts the deserialized object without it. You should receive an HTTP/1.1 200 OK response with a body similar to:
{"id":101,"photoUrls":[],"tags":[]}
This confirms that the plugin forwarded the non-compliant request instead of rejecting it.
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 while leaving the upstream to decide whether to accept or reject it.
Validate Using Remote Spec URL
The following example demonstrates how to load an OpenAPI specification from a remote URL. Remote specification support was introduced in API7 Enterprise 3.9.12 and APISIX 3.17.0.
Use spec_url when an OpenAPI specification is too large to embed inline or should refresh automatically. The inline spec field has a 2 MB size limit.
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:
- Admin API
- ADC
- Ingress Controller
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": "http://petstore:8080/api/v3/openapi.json",
"timeout": 5000
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"petstore:8080": 1
},
"scheme": "http",
"pass_host": "node"
}
}'
services:
- name: petstore
labels:
docs-example: oas-validator
routes:
- name: url-validation
uris:
- /*
plugins:
oas-validator:
spec_url: http://petstore:8080/api/v3/openapi.json
timeout: 5000
upstream:
type: roundrobin
nodes:
- host: petstore
port: 8080
weight: 1
scheme: http
pass_host: node
ADC reconciles services as desired state. Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: petstore-external-domain
spec:
type: ExternalName
externalName: petstore.aic.svc.cluster.local
ports:
- port: 8080
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: oas-validator-plugin-config
spec:
plugins:
- name: oas-validator
config:
spec_url: http://petstore:8080/api/v3/openapi.json
timeout: 5000
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: url-validation
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: oas-validator-plugin-config
backendRefs:
- name: petstore-external-domain
port: 8080
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: petstore-external-domain
spec:
targetRefs:
- group: ""
kind: Service
name: petstore-external-domain
passHost: node
scheme: http
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: petstore-external-domain
spec:
ingressClassName: apisix
scheme: http
passHost: node
externalNodes:
- type: Domain
name: petstore.aic.svc.cluster.local
port: 8080
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: url-validation
spec:
ingressClassName: apisix
http:
- name: url-validation
match:
paths:
- /*
upstreams:
- name: petstore-external-domain
plugins:
- name: oas-validator
enable: true
config:
spec_url: http://petstore:8080/api/v3/openapi.json
timeout: 5000
Apply the configuration:
kubectl apply -f oas-validator-ic.yaml
If the spec endpoint requires authentication, use the same configuration shape and replace the placeholder URL, backend, and token with values from your environment:
- Admin API
- ADC
- Ingress Controller
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"
}
}'
services:
- name: internal-api
labels:
docs-example: oas-validator
routes:
- name: url-validation
uris:
- /*
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:
- host: internal-api.example.com
port: 443
weight: 1
scheme: https
pass_host: node
ADC reconciles services as desired state. Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=oas-validator
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: internal-api-external-domain
spec:
type: ExternalName
externalName: internal-api.example.com
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: oas-validator-plugin-config
spec:
plugins:
- name: oas-validator
config:
spec_url: https://internal-api.example.com/openapi.json
spec_url_request_headers:
Authorization: Bearer <token>
timeout: 5000
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: url-validation
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: oas-validator-plugin-config
backendRefs:
- name: internal-api-external-domain
port: 443
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: internal-api-external-domain
spec:
targetRefs:
- group: ""
kind: Service
name: internal-api-external-domain
passHost: node
scheme: https
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: internal-api-external-domain
spec:
ingressClassName: apisix
scheme: https
passHost: node
externalNodes:
- type: Domain
name: internal-api.example.com
port: 443
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: url-validation
spec:
ingressClassName: apisix
http:
- name: url-validation
match:
paths:
- /*
upstreams:
- name: internal-api-external-domain
plugins:
- name: oas-validator
enable: true
config:
spec_url: https://internal-api.example.com/openapi.json
spec_url_request_headers:
Authorization: Bearer <token>
timeout: 5000
Apply the configuration:
kubectl apply -f oas-validator-ic.yaml
To configure the cache TTL for the fetched spec (default is 3600 seconds), set plugin metadata:
- Admin API
- ADC
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
}'
ADC manages plugin metadata as a global collection and cannot isolate one entry with a label selector. Use the Admin API tab for this single-entry update. To manage plugin metadata with ADC, dump the complete collection, update the oas-validator entry in that file, review adc diff, and then synchronize the complete collection.