Schemathesis CLI Tutorial
Estimated time: 15-20 minutes
This tutorial walks you through a complete API testing workflow with Schemathesis using a booking API. You'll see how property-based testing automatically finds bugs that manual testing typically misses.
If you're new to Schemathesis, check the Quick Start Guide first.
Prerequisites
-
Git - to clone the example API repository
-
Docker Compose - Install Docker Desktop which includes Docker Compose
-
uv - Python package manager that allows running Schemathesis without installation using
uvx -
curl (optional) - for reproducing API failures manually
Verify your setup:
git --version
docker compose version
uv --version
curl --version # optional
API under test
We'll test a booking API that handles hotel reservations - creating bookings and retrieving guest information.
The API lives in the Schemathesis repository:
git clone https://github.com/schemathesis/schemathesis.git
cd schemathesis/examples/booking
docker compose up -d --build
Verify the API is running
Open http://localhost:8080/docs - you should see the interactive API documentation.
Authentication token
The API requires bearer token authentication. Use: secret-token
First Test Run
Run Schemathesis against the API:
uvx schemathesis run http://127.0.0.1:8080/openapi.json \
--header 'Authorization: Bearer secret-token' \
--output-sanitize false
Key findings from the output:
Bug discovered
❌ Server error: 1
❌ Undocumented HTTP status code: 1
Reproduce with:
curl -X POST -H 'Authorization: Bearer secret-token' \
-H 'Content-Type: application/json' \
-d '{"guest_name": "00", "nights": 1, "room_type": ""}' \
http://127.0.0.1:8080/bookings
Run the provided curl command to reproduce this failure. When you do, read the response body — it shows the server error in detail. Notice that Hypothesis has already reduced the input to the simplest case that still triggers the failure: room_type: "" is all it takes. That's your starting point for diagnosis. Look up room_type in the schema: it accepts any string with no constraints. Your code assumes only specific room types are submitted, but the schema never enforces that.
Reporting
Schemathesis can export test results in multiple formats for integration with existing tools and CI/CD pipelines. Let's generate a JUnit report that can be imported into Jenkins, GitLab CI, or other test management systems:
uvx schemathesis run http://127.0.0.1:8080/openapi.json \
--header 'Authorization: Bearer secret-token' \
--output-sanitize false \
--report junit
This creates a junit.xml file in the schemathesis-report directory containing structured test results. The JUnit format includes:
- Test case details and execution times
- Failure descriptions with reproduction steps
Import it into any JUnit-compatible reporting tool to track failures over time — see CI/CD Integration for ready-made pipeline examples.
You can customize the output location using --report-junit-path or change the report directory with --report-dir. Other available formats include VCR cassettes (--report vcr) and HAR files (--report har) for detailed HTTP traffic analysis.
Fixing the bug
Root cause
The schema allows any string for room_type, but our pricing logic only handles specific values.
Fix: Constrain room types in the schema
class BookingRequest(BaseModel):
room_type: str # Any string allowed!
from enum import Enum
class RoomType(str, Enum):
standard = "standard"
deluxe = "deluxe"
suite = "suite"
class BookingRequest(BaseModel):
room_type: RoomType # Only valid values
Don't forget to restart:
docker compose restart
Re-running the tests
Now let's verify our fix by re-running the tests. Focus on the operation you just fixed:
uvx schemathesis run http://127.0.0.1:8080/openapi.json \
--header 'Authorization: Bearer secret-token' \
--output-sanitize false \
--include-operation-id create_booking_bookings_post
Operation ID explained
FastAPI generates operation IDs automatically. You can find them in the OpenAPI schema or API docs.
The --include-operation-id option targets only the specific operation we fixed, making the test run faster during development. You can also filter by HTTP method (--include-method POST) or path patterns (--include-path /bookings).
Generating more test cases
Want to find more bugs?
By default, Schemathesis stops at the first failure per operation and runs a limited number of test cases.
Let's be more thorough:
uvx schemathesis run http://127.0.0.1:8080/openapi.json \
--header 'Authorization: Bearer secret-token' \
--output-sanitize false \
--max-examples 500 \
--continue-on-failure
--max-examples 500 generates more test cases per operation, increasing the chance of finding edge cases that smaller test runs might miss.
--continue-on-failure prevents Schemathesis from stopping at the first failure, allowing it to discover multiple issues on the same API operation in a single run.
These options are particularly valuable when:
- Preparing for production releases
- Testing complex validation logic
The trade-off is longer execution time, but you'll get more chances to find bugs.
Configuration File
Tired of long command lines?
Instead of repeating long commands, save your settings once and reuse them across your team.
Create schemathesis.toml in your project:
# Core settings from our previous commands
headers = { Authorization = "Bearer ${API_TOKEN}" }
continue-on-failure = true
[output.sanitization]
enabled = false
[generation]
max-examples = 500
[reports.junit]
enabled = true
Environment variables
export API_TOKEN=secret-token
Now run with just:
uvx schemathesis run http://127.0.0.1:8080/openapi.json
Schemathesis automatically loads schemathesis.toml from the current directory or project root. You can specify a custom configuration file:
uvx schemathesis --config-file path/to/config.toml run http://...
Configuration precedence
CLI options override config file settings, so you can still adjust settings temporarily.
Continuous Fuzzing
st run exits when it's done. st fuzz is designed for the opposite: it keeps running, continuously generating test cases, until it finds a failure, a time limit is reached, or you stop it.
Run a short fuzzing session:
uvx schemathesis fuzz http://127.0.0.1:8080/openapi.json --max-time 30
st fuzz stops on the first failure, when --max-time expires, or when you press Ctrl+C.
For details, see Continuous Fuzzing.
What's next?
Continue learning:
- Triaging Failures — when your API has many failures, a systematic workflow for working through them
- Using Schemathesis with Docker — run without installing Python, with hooks and coverage support
- CLI Reference - All available CLI options
- Configuration Reference - Complete configuration reference