← Recipes

阅读中文版本

Fuzz the API Against Its OpenAPI Schema

Drive Schemathesis from the OpenAPI document, skip destructive endpoints, and turn every unexpected 500 into a regression test.

~30 mintestingapifuzzing

Task

Generate malformed and edge-case requests from the API schema to find unhandled 500 responses before clients do.

When to use this

A hand-written suite covers valid requests only. A negative quantity or a Unicode boundary value reaches an unguarded path and returns a 500 with a stack trace.

Prerequisites

  • Schemathesis 3.x installed with pipx or available as a Docker image
  • An OpenAPI 3 document served at a stable URL
  • A disposable staging environment with test data

Steps

1. Install the CLI and confirm the version.

pipx install schemathesis && schemathesis --version

Expected output: A version string such as 3.39.0, confirming the CLI is on PATH.

2. Run every check against staging while excluding endpoints tagged destructive.

schemathesis run https://staging.example.com/openapi.json --checks all --exclude-tag destructive --max-examples 50

Expected output: A per-endpoint report; an unexpected 500 is printed with a ready-to-run curl command.

3. Reproduce the failure outside the fuzzer, then encode it as a regression test.

curl -s -o /dev/null -w '%{http_code}\n' -X POST https://staging.example.com/api/orders -H 'Content-Type: application/json' -d '{"quantity":-1}'

Expected output: 500 confirmed; after the fix the same request returns 422 and the test asserts that status.

4. Schedule a reproducible nightly run with a fixed seed and more examples.

schemathesis run "$SCHEMA_URL" --checks all --exclude-tag destructive --max-examples 200 --seed 20261021 --workers 4

Expected output: The same failures recur on every run, and the reported seed reproduces any new one locally.

5. Fail the pipeline on server errors while allowing expected 4xx responses.

- run: schemathesis run "$SCHEMA_URL" --checks all --exclude-tag destructive
  env:
    SCHEMA_URL: ${{ secrets.STAGING_SCHEMA_URL }}

Expected output: The job exits non-zero when a request returns 5xx, and passes when invalid input is rejected with 4xx.

Troubleshooting

Problem

The fuzzer writes garbage rows into staging

Solution

Run against an isolated environment, exclude destructive tags and paths, and reset the database after the run.

Problem

Many failures come from endpoints that are not implemented yet

Solution

Exclude those paths or tags explicitly and track them; do not disable checks globally, which hides real 500s.

Problem

The fuzzer reports failures for inputs that violate cross-field rules

Solution

Schemathesis cannot infer rules like startDate < endDate. Treat 4xx as acceptable, and cover cross-field validation with explicit tests.

Limitations

  • It only explores what the schema describes; business invariants still need targeted tests.
  • Generated data can violate constraints the schema does not express, producing false positives.
  • Destructive endpoints must be excluded or run only against an isolated, disposable environment.

Hand-written tests only cover known shapes

Every example test was written by someone who already knew what the endpoint expects. Fuzzing starts from the OpenAPI schema and generates values the author did not think about: negative amounts, extremely long strings, unusual Unicode, missing optional fields. The goal is not to prove the API is correct, but to find inputs that reach an unguarded path and produce a 500. A 500 is almost always a bug: the request was invalid, and the API should have said so with a 4xx.

Start from the contract you already publish

Schemathesis reads the same OpenAPI document your clients use, so the fuzz surface stays aligned with the documented API. It generates valid and invalid requests, checks response codes against the schema, and reports failures with a reproducible curl command. That command is the bridge between fuzzing and a normal regression test: paste it into a test, assert the correct status, and the finding is fixed permanently.

schemathesis run https://staging.example.com/openapi.json \
  --checks all --exclude-tag destructive --max-examples 50

Be careful with side effects

Fuzzing POST and DELETE endpoints creates and removes real data. Tag destructive endpoints and exclude them, or run the whole exercise against a disposable environment that is reset afterwards. Authentication is also part of the setup: provide a test token with limited scope, not a production credential, and never point the fuzzer at production.

Make runs reproducible

Random generation is valuable, but debugging needs determinism. Record the seed, the example count, and the schema version in the report. A nightly run with more examples and a fixed seed gives broad coverage while keeping failures repeatable. For pull requests, a smaller example count keeps the job under a few minutes and still catches regressions in changed endpoints.

Act on findings, not on counts

A long list of failures is only useful if it is triaged. Group by root cause: missing validation in one handler, an unhandled null, a database constraint leaking as a 500. Fix the handler and add a regression test per group. Keep the remaining known issues in an explicit exclusion list with an owner, so the nightly job stays green for real regressions and does not become background noise.