API Security & Auth Testing

API Fuzz Testing: Techniques, Tools and Schemathesis Example

Smeet Gohel13 min read

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

Share:
API fuzz testing diagram showing an OpenAPI spec feeding a fuzzer that sends mutated requests to an API and sorts responses into crashes, schema violations and accepted bad input

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:

  1. Learns the input shape from a spec, recorded traffic or a seed request.
  2. Generates or mutates inputs: wrong types, boundary numbers, huge strings, empty values, special characters, encodings, missing and extra fields.
  3. Sends them, often thousands per endpoint.
  4. Applies oracles, the rules that decide whether a response is a bug: a 5xx status, a timeout, a response that breaks the schema, a 2xx for input the schema forbids, or sensitive text in the body.
  5. 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.

ApproachHow inputs are madeNeedsStrengthWeakness
MutationTake valid requests and change bytes, values or fieldsSeed requests (traffic, HAR, Postman collection)Fast to start; realistic requests reach deep logicOnly explores near the seeds; misses whole operations nobody recorded
Generation (grammar)Build inputs from a grammar or model of the protocolA grammar or hand-written modelCan explore systematically, including call sequencesWriting the model is work
Schema-basedGenerate inputs from an OpenAPI or GraphQL schema, valid and invalidA reasonably accurate specCovers every documented operation; can also check responses against the schemaOnly 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

ToolApproachInputLicenceBest for
SchemathesisSchema-based, property-based, stateful linksOpenAPI 2/3, GraphQLOpen source (MIT)Fuzzing every documented operation and checking responses against the spec
RESTlerGeneration, stateful sequencesOpenAPIOpen source (MIT)Finding bugs that need a sequence of calls
ZAPDAST active scan plus fuzzerOpenAPI, GraphQL, SOAP, trafficOpen source (Apache 2.0)Security-focused scanning with an OpenAPI import; manual fuzzing in the desktop UI
ffufWordlist and mutationURL, headers or body with a FUZZ markerOpen source (MIT)Fast discovery of hidden paths, parameters and values
GitLab API fuzzingMutation from recorded or specified requestsOpenAPI, HAR, Postman collection, GraphQLGitLab UltimateTeams already on GitLab Ultimate pipelines
CATSSchema-based negative and boundary "fuzzers"OpenAPIOpen 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.

  1. 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 unchecked float() are one bug.
  2. 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.
  3. Reproduce outside the fuzzer. Rerun the curl command against a fresh environment. If it doesn't reproduce, look for state from earlier requests or a flaky dependency before filing.
  4. Classify each finding.
ClassExampleTypical severity
Crash or unhandled error500 on a huge number, NUL byte or deep JSON nestingMedium to high (denial-of-service risk)
Accepted invalid input201 for quantity 0 when minimum: 1Low to high, depending on what the value controls
Information disclosureStack trace, SQL error or internal hostname in the bodyMedium
Spec driftUndocumented status code or response fieldLow (but breaks clients)
Timeout or resource exhaustionOne request takes 30 seconds or spikes memoryMedium to high
Injection signalDatabase error text after a quote characterHigh: 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
  1. 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.

LoopTriggerBudgetFails the build on
PR fuzzPull request2–5 minutes, low --max-examples, fixed seedServer errors and schema violations on changed operations
Deep fuzzNightly or weekly30–120 minutes, high examples, statefulNew 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 (DELETE on 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

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.