Schemathesis: OpenAPI and GraphQL Fuzzing Guide
Schemathesis takes your OpenAPI or GraphQL schema and automatically generates hundreds of test cases from it — including edge cases your manual test suite never thought of. It finds server crashes, unhandled exceptions, and schema violations that sit invisible in hand-written tests. This guide covers everything from basic setup to stateful multi-step fuzzing.
What Schemathesis Does Differently
Most API testing tools test what you explicitly tell them to test. Schemathesis tests what your schema says is possible. If your OpenAPI spec says a field accepts a string, Schemathesis sends empty strings, strings with null bytes, 100,000-character strings, and Unicode edge cases — and checks whether your API either returns a valid response or a documented error code.
The critical insight: your schema is a contract with your users. If your spec says the endpoint accepts a particular input, it must handle it gracefully. Schemathesis verifies this automatically.
Installation
pip install schemathesis
# Or with all optional features
pip install schemathesis[all]Verify installation:
st --versionBasic Fuzzing Run
If your API is running locally with an OpenAPI spec at /api/openapi.json:
st run http://localhost:8080/api/openapi.jsonAgainst a remote API:
st run https://api.example.com/openapi.json \
--header "Authorization: Bearer $API_TOKEN"Schemathesis will:
- Parse the schema
- Generate test cases for every endpoint and method
- Check each response for server errors (5xx), schema violations, and malformed responses
- Report failures with full request/response details for reproduction
Understanding the Output
A typical Schemathesis run shows:
POST /api/users .......F.......
FAILED POST /api/users
ResponseSchemaConformance: Response violates schema
Response: {"id": 123, "email": null}
Expected: {"id": "integer", "email": "string"}
Request: {"name": "", "email": "test@example.com"}The failure tells you:
- Which endpoint failed
- What kind of failure (schema violation, server error, etc.)
- The exact request that triggered it
- The exact response that was wrong
This is immediately actionable — no guessing, no investigation needed.
Filtering Tests
Run only specific endpoints:
# Only GET endpoints
st run http://localhost:8080/openapi.json --method GET
<span class="hljs-comment"># Only specific path
st run http://localhost:8080/openapi.json --endpoint /api/users
<span class="hljs-comment"># Specific path pattern
st run http://localhost:8080/openapi.json --endpoint <span class="hljs-string">"/api/users/{user_id}"
<span class="hljs-comment"># Exclude certain endpoints
st run http://localhost:8080/openapi.json --exclude-endpoint /api/healthAuthentication
Pass static auth headers:
st run http://localhost:8080/openapi.json \
--header "Authorization: Bearer $TOKEN"For APIs with authentication endpoints, use auth in a YAML configuration:
# schemathesis.yml
schema: http://localhost:8080/openapi.json
checks:
- not_a_server_error
- response_schema_conformance
auth:
type: bearer
token: "${API_TOKEN}"st run --config schemathesis.ymlGraphQL Fuzzing
Schemathesis supports GraphQL introspection-based fuzzing:
st run http://localhost:8080/graphql --app graphqlFor GraphQL with authentication:
st run http://localhost:8080/graphql \
--app graphql \
--header "Authorization: Bearer $TOKEN"Schemathesis will introspect your schema, discover all queries and mutations, and generate test inputs for each. It catches:
- Queries that return 500 instead of null for missing data
- Mutations that accept invalid inputs without error
- Types that don't match the schema definition
Python API for Custom Scenarios
For complex authentication flows or stateful testing, use the Python API:
import schemathesis
from schemathesis import Case
schema = schemathesis.from_uri("http://localhost:8080/openapi.json")
@schema.parametrize()
def test_api(case: Case):
# Add auth before each request
response = case.call(headers={"Authorization": "Bearer test-token"})
case.validate_response(response)Run with pytest:
pytest test_api.py -vThis integrates Schemathesis into your standard pytest suite, enabling coverage tracking and parallel execution.
Stateful Testing
Schemathesis can chain requests — creating resources with POST, then accessing them with GET, then deleting with DELETE — to find bugs in stateful flows:
st run http://localhost:8080/openapi.json --stateful=linksThe --stateful=links flag uses OpenAPI links definitions to chain related endpoints. When your spec defines that the POST /users response contains a link to GET /users/{id}, Schemathesis will automatically create a user and then fetch it with a generated ID.
For explicit stateful scenarios in Python:
import schemathesis
from schemathesis.stateful import Stateful
schema = schemathesis.from_uri("http://localhost:8080/openapi.json")
@schema.parametrize(stateful=Stateful.links)
def test_stateful(case):
response = case.call_and_validate()Custom Checks
Define your own assertions beyond schema conformance:
import schemathesis
from schemathesis import Case, Response
schema = schemathesis.from_uri("http://localhost:8080/openapi.json")
@schemathesis.check
def no_debug_info(response: Response, case: Case) -> None:
"""Ensure no debug information leaks in responses."""
body = response.text
assert "stack trace" not in body.lower(), "Stack trace leaked in response"
assert "sql" not in body.lower(), "SQL query leaked in response"
assert "password" not in body.lower(), "Password leaked in response"
@schema.parametrize()
def test_api(case: Case):
response = case.call()
case.validate_response(response, checks=[no_debug_info])CI/CD Integration
GitHub Actions:
name: API Fuzzing
on: [push, pull_request]
jobs:
fuzz:
runs-on: ubuntu-latest
services:
api:
image: your-api:latest
ports:
- 8080:8080
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Schemathesis
run: pip install schemathesis
- name: Wait for API
run: |
until curl -f http://localhost:8080/health; do sleep 2; done
- name: Run Schemathesis
run: |
st run http://localhost:8080/openapi.json \
--checks all \
--hypothesis-max-examples 100 \
--junit-xml=results.xml
- name: Upload results
uses: actions/upload-artifact@v3
if: always()
with:
name: schemathesis-results
path: results.xmlControlling Test Generation
Schemathesis uses Hypothesis internally for property-based testing. You can control the test generation:
# More thorough testing (more examples)
st run http://localhost:8080/openapi.json \
--hypothesis-max-examples 500
<span class="hljs-comment"># Faster CI runs (fewer examples)
st run http://localhost:8080/openapi.json \
--hypothesis-max-examples 50
<span class="hljs-comment"># Set timeout
st run http://localhost:8080/openapi.json \
--hypothesis-deadline 5000Common Bugs Schemathesis Finds
Server errors on edge case inputs: Your API returns 200 for normal inputs but 500 when the string contains a null byte. Manual tests never send null bytes. Schemathesis does.
Schema mismatches: Your spec says a field is required, but your code sometimes omits it in responses. Schemathesis catches this.
Type coercion bugs: Your API accepts a string where you expected a number and crashes instead of returning 400. Schemathesis sends strings, integers, booleans, and nulls for every field.
Missing error handling: An endpoint crashes on empty arrays when your spec says arrays are valid. Schemathesis sends empty arrays for every array field.
Reproducing Failures
When Schemathesis finds a bug, it outputs a seed for reproduction:
Falsifying example:
case = Case(path='/api/users', method='POST', body={'name': '', 'email': ''})
You can reproduce this failure with:
--hypothesis-seed=12345Save the seed to always reproduce the same test run:
st run http://localhost:8080/openapi.json --hypothesis-seed=12345What to Do With Schemathesis Results
- Server errors (5xx): Always bugs. Your API must never return 5xx for valid inputs (inputs that match your schema). Fix the error handling.
- Schema violations: Your code doesn't match your spec. Either fix the code or fix the spec — they must agree.
- Slow responses: Not a Schemathesis check by default, but you can add custom timing checks.
- Security findings: Schemathesis has security-focused checks (path traversal, SQL injection patterns). Enable with
--checks security.
Schemathesis finds a category of bugs — schema-invalid inputs causing unexpected behavior — that manual API testing systematically misses. Running it as part of CI adds a fuzzing layer that continuously verifies your API handles everything it claims to handle.