Load Testing: A Practical Guide for APIs
Load testing sends simulated traffic at an API to find where it slows or fails. See the test types, ramping vs arrival rate, thresholds and abort rules.

Load testing means putting a simulated workload on an API to see how it performs. A good load test has four parts: realistic requests, a plan for how many users arrive and when, pass or fail thresholds, and an abort rule that stops the run if the API is clearly failing. The sections below cover each part, starting with what the test is and ending with what it costs to run on Stresseur.
What load testing is, with an API example
Load testing is a form of software testing that puts a simulated workload on a system, whether an application, an API or a website, to see how it performs. Generally you are looking for how the system responds to normal and peak usage.
Take a shop API with three calls: log in, list products, place an order. A functional test sends each call once and checks the answer. A load test sends the same sequence from many virtual users at once, for several minutes, and watches what changes. Does the p95 latency of the order call climb? Do errors appear at a certain number of users? Does the login step slow down before anything else does?
Those questions are the point. One request that returns 200 in 80 ms says little about the same request when hundreds of users are in the middle of checkout. The test gives you three readings to track as load rises: latency (p95 is the usual one), error rate, and requests per second. Everything below is about producing those readings in a way you can trust.
Types of load test: smoke, load, stress, soak, spike and breakpoint
Different traffic patterns carry different risks, so teams use more than one test type. Without a profile file, Stresseur offers Smoke, Load, Stress, Spike, Soak and Breakpoint, the same six that the k6 test types guide describes, in its words:
- Smoke: validates that your script works and that the system performs adequately under minimal load.
- Load: assesses how the system performs under expected normal conditions.
- Stress: assesses how the system performs at its limits, when load exceeds the expected average.
- Soak: assesses reliability over extended periods.
- Spike: validates behavior and survival through sudden, short and massive increases in activity.
- Breakpoint: raises load gradually to find the capacity limits of the system.
Start with a smoke test. Before you run bigger tests, check that the script works and that the system holds up with a few users. Treat the names loosely: a stress test for one application is an average-load test for another, and there is no consensus on what these types are called.
One limit applies on Stresseur. Scheduled soak tests are not available.
Ramping virtual users vs arrival rate
A virtual user, or VU, is one simulated person looping through your flow. There are two ways to describe how much load to apply, and they behave differently when the API slows down.
With virtual users, the test is a closed model: a VU starts its next iteration only when its last one finishes. That couples the load to the response time. When the API gets slower, iterations take longer and fewer new ones start, so the request rate tapers off just when the system is under stress. k6 documents this problem in its open and closed models guide.
With arrival rate, the test is an open model, which decouples the start of new iterations from how long the last one took. The response time of the API no longer changes the load on it.
A practical rule follows from that. Use virtual users when you want to model a number of people at once, such as 200 logged-in shoppers pausing between clicks. Use arrival rate when you want to hold a request rate steady, such as a fixed number of orders per second, and see what the API does to its latency under that pressure. Stresseur profiles support both: ramping VUs, plus constant and ramping arrival rate. The four executors are constant-vus, ramping-vus, constant-arrival-rate and ramping-arrival-rate.
If you are still working out how many VUs to start with, the guide to choosing virtual users for an API walks through the arithmetic.
Shape the ramp
A ramp has three phases: climb to a target, hold it long enough to read stable numbers, then drop back to zero. Jumping straight to full load makes it hard to tell what broke the API and at what level. A gradual climb shows you the point where latency bends.
Here is a Stresseur profile that does exactly that. Load profiles live in api-tests/stresseur.load.yaml.
version: 1
profiles:
- name: load
flows: { storefront-browse: 70, admin-catalog-crud: 30 }
executor: ramping-vus
stages:
- { duration: 1m, target: 200 }
- { duration: 3m, target: 200 }
- { duration: 30s, target: 0 }
think_time: { min: 1s, max: 3s }
thresholds:
p95: 300ms
errors: 1%
steps: { Checkout: { p95: 500ms } }
abort: { errors: 20%, window: 30s }
The three stages climb to 200 virtual users over one minute, hold them for three minutes, and ramp down to zero over thirty seconds. The think_time line makes each virtual user pause between one and three seconds, as a person would, so the 200 users do not behave like 200 loops hammering the API as fast as they can. The thresholds and the abort rule at the bottom are the subject of the next section.
For a breakpoint test, change the shape: keep climbing instead of holding, and read where the thresholds first fail.
Thresholds and abort rules
A load test without a pass or fail condition produces a chart and an argument. Thresholds turn it into a result.
Thresholds are the pass or fail criteria you define for your test metrics. If the system does not meet a threshold, the test finishes with a failed status. The k6 documentation gives two typical examples: less than 1% of requests return an error, and 95% of requests have a response time below 200 ms. See its thresholds guide for the full syntax.
The profile above sets the same kinds of limits in three places: p95 under 300 ms, errors under 1%, and a tighter rule for the Checkout step, p95 under 500 ms. A step-level threshold matters because an average over the whole flow hides one slow call behind many fast ones.
An abort rule is different. A threshold judges the run. An abort rule ends it early. In k6 you set abortOnFail to true to stop a test as soon as a threshold is crossed. In the Stresseur profile, abort: { errors: 20%, window: 30s } sets the abort rule at 20% errors over a 30-second window. That protects a staging environment, and your bill, from a test that is plainly failing. Set the abort limit well above the threshold: the threshold decides pass or fail, the abort rule only prevents waste.
Load test flows, not single endpoints
Real users do not call one endpoint. They log in, take a token, read a list, create something, and read it back. A load test that hits one URL in a loop measures a path no user takes, and it skips the step where state passes from one call to the next.
This is where recorded flows help. In Stresseur any recorded flow can also run as a load test. The profile can mix several flows by weight, as in storefront-browse: 70, admin-catalog-crud: 30, so the load resembles your real traffic split. The same flow file runs in CI on every pull request as a test and runs under load as a capacity test, so there is no second script to keep in step with the first. The load testing landing page shows this in action.
The post on multi-step flow load tests covers the mechanics, and testing multi-step API flows with auth tokens covers the token handling that every logged-in flow needs.
Read the results
A finished test gives you a lot of numbers. Read them in this order.
First, check whether a threshold failed, and which one. That is the verdict, and it tells you where to look. Second, compare the load you applied with the throughput you got. In a ramp with virtual users, requests per second should rise as users rise. When the users keep climbing and the request rate flattens, the API has reached its ceiling, and anything beyond that point is queueing. Third, look at p95 instead of the average. The average hides the slow tail, and the tail is what your unluckiest users experience.
Then go step by step. Stresseur streams virtual users, req/s, p95, errors and per-step latency while the test runs. A flow that is slow overall almost always has one step that is slower than the rest, and that step is where the work is.
Common mistakes
Most failed load tests fail for avoidable reasons.
- Starting at full load. You learn that the API breaks, not where or why. Ramp instead.
- No think time. Real users pause. Without pauses a small number of virtual users produces far more load than the same number of people would.
- Testing one endpoint. Real traffic chains requests. Test the flow.
- No pass or fail rule. Without thresholds, every result is a matter of opinion.
- Comparing runs on different environments. A staging box and a preview environment have different resources. Compare like with like, such as a pull request run against the last run on the default branch.
- Skipping the smoke test. A broken script wastes a long test. Run a few users first.
Run it safely: verified domains, environments and regions
A load test can look like an attack. Stresseur sends load only to domains your workspace has verified, and secrets stay encrypted in the workspace’s environments.
You verify a domain in one of two ways: add a TXT record stresseur-verify=<token> at _stresseur.<domain>, or serve the token from https://<domain>/.well-known/stresseur-verify.txt.
Aim the test at a place that can take it. Add an environment on the repository’s Environments page: staging, or a pull request preview whose URL contains {number}. Secrets are stored encrypted. A preview environment per pull request means the load test can run against exactly the change under review. In the app you then pick a flow, a profile and regions under Load tests, and results stream live.
The regions are Frankfurt, Virginia, San Jose and Singapore. Load generators start in the regions you pick, and the results stream live: virtual users, req/s, p95, errors and per-step latency. Picking a region near your real users gives you latency readings closer to what they see. Picking several shows whether the API behaves differently for traffic that comes from far away.
The AI test engineer can start one from a pull request: tagging @stresseur with “load” starts a load test on the preview with the Load profile and compares it with the last one on the default branch.
Plan limits and virtual-user hours
VUh means virtual-user hours. The pricing page lists every limit; the ones that matter for load testing are these.
- Free: load tests up to 50 VUs for 10 minutes in 1 region, with 50 VUh a month.
- Team: load tests up to 1,000 VUs for 1 hour in all regions, with 5,000 VUh a month.
- Launch pack: a $499 one-off pack for a 2-hour window of load tests up to 5,000 VUs, outside the plan’s included VUh.
Overage is $0.10 per VUh on Team. It stays off until an Owner turns it on and always stops at a hard spend cap. New workspaces get a 14-day Team trial without a card, with Team limits and 500 VUh. The load test docs list the limits in a table.
The 200-user profile above is over the Free plan’s 50-VU limit, so it needs Team. If you are choosing between tools first, API load testing tools for small teams compares the options, and k6 vs Loadmill looks at two of them side by side.
Where to go next
Load testing comes down to a few decisions: which flows to run, whether to hold users or a request rate, how to ramp, and what counts as a pass. Start with a smoke test on one flow, add a ramp with thresholds, and put an abort rule on it. If you already record flows for your tests, the Stresseur load testing page shows how the same flow runs under load, and the load test docs have the profile reference.
Frequently asked questions
What are the main types of load testing?
The usual six are smoke, load, stress, soak, spike and breakpoint tests. Smoke tests check the script under minimal load, and breakpoint tests raise load gradually to find the capacity limits.
What is the difference between a load test and a stress test?
A load test assesses how the system performs under expected normal conditions. A stress test assesses how it performs at its limits, when load exceeds the expected average.
Should I use virtual users or arrival rate?
Use virtual users to model a number of people at once, and arrival rate to hold a steady request rate. In the open model that arrival rate uses, the response time of the system no longer influences the load on it.
What is a threshold in a load test?
A threshold is a pass or fail criterion for a test metric. If the system does not meet it, the test finishes with a failed status.
Can Stresseur load test any website?
No. Load goes only to domains the workspace has verified. You verify a domain with a TXT record or a file served at a well-known path.