API Fuzz Testing: Techniques, Tools and Schemathesis Example
Quick answer
API fuzz testing sends large numbers of unexpected, malformed or random inputs to an API to find crashes, 500 errors, schema violations and inputs the API should reject but accepts. Mutation fuzzers change valid requests; generation and schema-based fuzzers build inputs from a grammar or an OpenAPI spec. Run it in test environments and gate CI on new findings.
Reviewed by Rishi Gaurav
Part of the API Security & Auth Testing hub. Start with the pillar guide: API security testing: the complete guide.
API fuzz testing sends large numbers of unexpected, malformed or random inputs to an API and watches what breaks. A fuzzer looks for 500 errors, crashes and timeouts. It also flags responses that don't match the schema, leaked stack traces, and bad input the API should reject but accepts. It finds the defects nobody wrote a test for, because nobody thought of that input.
Fuzzing is one technique inside the wider practice covered in our API security testing guide. This article explains how the three families of fuzzers differ and which tools to use. It then walks through a worked example with Schemathesis against an OpenAPI spec, shows how to triage findings without drowning in noise, and covers how to run fuzzing in CI.
What is API fuzz testing?
Fuzzing started in the late 1980s as a way to crash Unix utilities with random input. Applied to APIs, the idea is the same but the target is the request: path and query parameters, headers, JSON or XML bodies, and the order of calls. A fuzzer:
- Learns the input shape from a spec, recorded traffic or a seed request.
- Generates or mutates inputs: wrong types, boundary numbers, huge strings, empty values, special characters, encodings, missing and extra fields.
- Sends them, often thousands per endpoint.
- Applies oracles, the rules that decide whether a response is a bug: a
5xxstatus, a timeout, a response that breaks the schema, a2xxfor input the schema forbids, or sensitive text in the body. - Reports a minimal reproducing request for each distinct failure.
Hand-written tests check the cases you thought of. Fuzzing checks the cases you didn't, which is why it finds parser bugs, unhandled exceptions, integer overflows and validation gaps. Those defects often become security issues: a crash is a denial-of-service, and an accepted bad value can be an injection.
Mutation vs generation vs schema-based fuzzing
All fuzzers produce inputs, but they get there differently. The choice decides how much setup you need and how deep the fuzzer reaches into your code.
| Approach | How inputs are made | Needs | Strength | Weakness |
|---|---|---|---|---|
| Mutation | Take valid requests and change bytes, values or fields | Seed requests (traffic, HAR, Postman collection) | Fast to start; realistic requests reach deep logic | Only explores near the seeds; misses whole operations nobody recorded |
| Generation (grammar) | Build inputs from a grammar or model of the protocol | A grammar or hand-written model | Can explore systematically, including call sequences | Writing the model is work |
| Schema-based | Generate inputs from an OpenAPI or GraphQL schema, valid and invalid | A reasonably accurate spec | Covers every documented operation; can also check responses against the schema | Only as good as the spec; undocumented behaviour is invisible |
Mutation-based fuzzing
A mutation fuzzer starts from known-good requests and corrupts them: flip a type, double a string, drop a required field, change 10 to -10 or 99999999999. Because the starting request was valid, the mutated one often gets past authentication and routing and hits business logic. Burp Suite's Intruder and ZAP's Fuzzer work this way, as does replaying a captured HAR file with mutations.
Generation-based fuzzing
A generation fuzzer builds inputs from a model. For REST APIs, the best-known example is Microsoft Research's RESTler. It compiles an OpenAPI spec into a grammar and infers producer-consumer dependencies (the id returned by POST /orders feeds GET /orders/{id}). It then explores call sequences, so it can find bugs that only appear after a particular series of calls.
Schema-based (property-based) fuzzing
Schema-based fuzzers treat the spec as both the input generator and the oracle. Schemathesis, built on the Hypothesis property-based testing library, generates values that match the schema (positive) and values that break it (negative). It sends them and checks that responses conform to the documented status codes, content types and response schemas. That makes it good at finding spec drift as well as crashes.
In practice, teams combine them: schema-based for broad coverage of every operation, stateful generation for workflows, and mutation for inputs you captured from real clients.
API fuzz testing tools
| Tool | Approach | Input | Licence | Best for |
|---|---|---|---|---|
| Schemathesis | Schema-based, property-based, stateful links | OpenAPI 2/3, GraphQL | Open source (MIT) | Fuzzing every documented operation and checking responses against the spec |
| RESTler | Generation, stateful sequences | OpenAPI | Open source (MIT) | Finding bugs that need a sequence of calls |
| ZAP | DAST active scan plus fuzzer | OpenAPI, GraphQL, SOAP, traffic | Open source (Apache 2.0) | Security-focused scanning with an OpenAPI import; manual fuzzing in the desktop UI |
| ffuf | Wordlist and mutation | URL, headers or body with a FUZZ marker | Open source (MIT) | Fast discovery of hidden paths, parameters and values |
| GitLab API fuzzing | Mutation from recorded or specified requests | OpenAPI, HAR, Postman collection, GraphQL | GitLab Ultimate | Teams already on GitLab Ultimate pipelines |
| CATS | Schema-based negative and boundary "fuzzers" | OpenAPI | Open source (Apache 2.0) | Large libraries of negative payloads without writing code |
Ready to shift left with your API testing?
Try our no-code API test automation platform free. Generate tests from OpenAPI, run in CI/CD, and scale quality.
For a wider comparison of scanners, see API security testing tools compared. If the question is which protocols web API fuzzing supports, the answer depends on the tool: most handle REST with JSON well. ZAP and GitLab also cover SOAP and GraphQL, and Schemathesis supports GraphQL schemas.
Example: fuzzing an OpenAPI API with Schemathesis
This walkthrough fuzzes a local API that publishes its spec at /openapi.json. The commands use Schemathesis 4.x; version 3.x used --base-url instead of --url and different report flags, so check st run --help on your version.
Step 1: install. Use pipx or uv so it doesn't clash with project dependencies:
pipx install schemathesis
# or run without installing:
uvx schemathesis --version
Step 2: run against the spec. Point it at the spec and the running service, pass a test account's token, and enable all checks:
export TOKEN="$(./scripts/get-test-token.sh)" # a dedicated test account
st run http://localhost:8080/openapi.json \
--url http://localhost:8080 \
-H "Authorization: Bearer ${TOKEN}" \
--checks all \
--max-examples 100 \
--report junit
--checks all turns on every response check: no server errors, documented status codes, content types, response schema conformance, and whether invalid data is rejected. --max-examples sets how many inputs each operation gets. The JUnit report lands in the report directory for CI to pick up.
Step 3: read the output. Each failure comes with the check that failed and a ready-to-run curl command. A typical result looks like this (trimmed):
POST /orders
1. Server error
[500] Internal Server Error
Reproduce with:
curl -X POST -H 'Authorization: [Filtered]' \
-H 'Content-Type: application/json' \
-d '{"productId": "0", "quantity": 1e309}' http://localhost:8080/orders
2. API accepted schema-violating request
[201] Created
Invalid component: quantity = 0 (minimum is 1)
Finding 1 is a crash: a number too large for the parser's float type reached code that didn't handle it. Finding 2 is a validation gap: the spec says minimum: 1, but the API created an order with quantity 0.
Step 4: keep it as a test. For repeatable runs in a Python codebase, use the pytest integration so fuzz cases live next to your other tests:
# test_fuzz.py
import schemathesis
schema = schemathesis.openapi.from_url("http://localhost:8080/openapi.json")
@schema.parametrize()
def test_api_conforms_to_spec(case):
case.call_and_validate()
Step 5: add stateful testing. If your spec declares OpenAPI links (for example, from the id in a POST /orders response to GET /orders/{id}), Schemathesis can chain calls. That finds bugs like a resource that can be created but not read, or deleted twice without error.
Fuzzing as part of a security run. In the Shift-Left API API security testing add-on (paid; included on Trial), the input campaign sends 19 values to each documented query, header, path, JSON and XML-body parameter. It is capped at 600 requests and 10 at a time. Results are minimised to one stored test per distinct defect, and failures that don't reproduce are dropped. It sits beside OWASP-mapped authorization and configuration checks. It doesn't fuzz multipart form parts, and it isn't a penetration test.
Triage: from thousands of requests to a few bugs
A fuzz run can produce hundreds of failures that come from a handful of root causes. Good triage turns them into a short list developers will fix.
- Deduplicate by root cause, not by request. Group failures by operation, status code and error signature (exception type, stack frame or error message). Fifty
500s from one uncheckedfloat()are one bug. - Minimise the input. Shrink the failing request to the smallest one that still fails. Schemathesis and Hypothesis shrink automatically; for other tools, remove fields until the failure goes away.
- Reproduce outside the fuzzer. Rerun the
curlcommand against a fresh environment. If it doesn't reproduce, look for state from earlier requests or a flaky dependency before filing. - Classify each finding.
| Class | Example | Typical severity |
|---|---|---|
| Crash or unhandled error | 500 on a huge number, NUL byte or deep JSON nesting | Medium to high (denial-of-service risk) |
| Accepted invalid input | 201 for quantity 0 when minimum: 1 | Low to high, depending on what the value controls |
| Information disclosure | Stack trace, SQL error or internal hostname in the body | Medium |
| Spec drift | Undocumented status code or response field | Low (but breaks clients) |
| Timeout or resource exhaustion | One request takes 30 seconds or spikes memory | Medium to high |
| Injection signal | Database error text after a quote character | High: confirm by hand |
Free ZIP: CSV pack + guide
OWASP API Top 10 Test-Case Pack
60 API security test cases mapped to the OWASP API Security Top 10 (2023): positive and negative cases for BOLA, broken auth, SSRF and misconfiguration.
Download Free- File with the reproducer. Include the minimal request, the response, the check that failed and the spec line it violates. Then add a regression test so the bug stays fixed.
Injection signals and auth-related findings deserve a manual look. Fuzzers find symptoms; a person judges exploitability. The OWASP API Security Top 10 guide helps map findings to risk categories.
Running API fuzz tests in CI
Fuzzing works best as two loops: a short, deterministic run on every pull request and a longer exploratory run on a schedule.
| Loop | Trigger | Budget | Fails the build on |
|---|---|---|---|
| PR fuzz | Pull request | 2–5 minutes, low --max-examples, fixed seed | Server errors and schema violations on changed operations |
| Deep fuzz | Nightly or weekly | 30–120 minutes, high examples, stateful | New unique findings (compared with a baseline) |
A minimal GitHub Actions job:
api-fuzz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start API
run: docker compose up -d --wait
- name: Fuzz with Schemathesis
run: |
pipx install schemathesis
st run http://localhost:8080/openapi.json \
--url http://localhost:8080 \
--checks all --max-examples 25 \
--report junit
- name: Publish results
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-report
path: schemathesis-report/
Tips that keep CI fuzzing useful:
- Fix the seed on PRs so a failure reproduces on rerun; let nightly runs explore.
- Baseline known findings and fail only on new ones, or the gate will be switched off within a week.
- Fuzz an ephemeral environment seeded with test data, then throw it away.
- Export SARIF or JUnit so findings show up in the pull request.
Fuzzing safely: scope, environments and limits
- Never fuzz production by default. Fuzzers create records, trigger alerts, lock accounts and can degrade service. Use staging or an ephemeral environment with production-like configuration.
- Use dedicated test accounts with the roles you want to test, and no access to real customer data.
- Exclude dangerous operations (
DELETEon shared data, payouts, emails to real addresses) or point them at sandboxes. - Rate-limit the fuzzer so you test your API, not your infrastructure's patience.
- Tell the people who watch alerts. A fuzz run looks like an attack, because in a sense it is one.
- Know what fuzzing misses. It rarely finds broken object-level authorization, business-logic abuse or multi-step flaws on its own. Those need two-user tests and targeted checks. Use the API security testing checklist to cover them.
API fuzz testing FAQ
What is API fuzz testing?
API fuzz testing is an automated technique that sends large numbers of invalid, unexpected or random inputs to API endpoints and watches for failures: server errors, crashes, timeouts, responses that break the schema, leaked internal details, or bad input the API accepts. It finds robustness and security defects that hand-written tests miss because nobody thought to try those inputs.
What is a fuzz testing example for a REST API?
Take POST /orders with a quantity field defined as an integer from 1 to 100. A fuzzer sends quantity values such as -1, 0, 2147483648, 1e309, an empty string, null, an array, a 10,000-character string and Unicode control characters. If any of those returns 500, hangs, or creates an order, the fuzzer reports it with the exact request to reproduce.
What are the best API fuzz testing tools?
For OpenAPI and GraphQL APIs, Schemathesis is the usual starting point because it generates inputs from the schema and checks responses against it. RESTler from Microsoft Research adds stateful sequences for REST APIs. ZAP combines an OpenAPI import with active scan rules, ffuf is a fast wordlist fuzzer for paths and parameters, and GitLab Ultimate includes web API fuzzing in its pipelines.
Is fuzz testing the same as penetration testing?
No. Fuzz testing is one automated technique that throws many inputs at an interface to see what breaks. Penetration testing is a broader, usually human-led assessment that chains weaknesses, tests business logic and authorization, and judges real-world impact. Fuzzing is a useful input to a pen test, not a replacement for one. See API penetration testing vs API security testing.
Is it safe to fuzz a production API?
Generally no. Fuzzers send high volumes of malformed and data-changing requests that can create junk records, trigger alerts, lock accounts or degrade service. Fuzz a staging or ephemeral environment with production-like configuration, use dedicated test accounts, and only run read-only, rate-limited checks against production with explicit approval.
Sources and further reading
- Schemathesis documentation: CLI, checks, stateful testing and pytest integration.
- RESTler on GitHub and the paper RESTler: Stateful REST API Fuzzing (ICSE 2019).
- ZAP API scan documentation: scanning an OpenAPI definition from Docker.
- ffuf on GitHub: usage and matchers.
- GitLab web API fuzz testing: supported inputs and configuration.
- OWASP: Fuzzing and the OWASP API Security Top 10 (2023).
- Hypothesis documentation: the property-based testing engine behind Schemathesis.
Ready to shift left with your API testing?
Import your OpenAPI spec and get a runnable test suite, in our cloud or on your own infrastructure.