API Load Testing: 8 Tools and How to Load Test an API

API load testing sends many concurrent requests to your API endpoints and measures response time, throughput and error rate as the traffic grows. Tools for it include Grafana k6, Apache JMeter, Gatling, Locust, Artillery and Vegeta, all open source, plus Postman and ReadyAPI if your API tests already live there. A good API load test starts work at a fixed arrival rate, checks every response, and fails on a written budget.
A load test can check a few responses, but full functional coverage comes first. Qodex is an AI QA agent that writes and runs functional and security tests for your API from a chat.
API load testing tools compared
Facts and prices were read on each vendor's own pages on 23 October 2026, and each tool's section links its source. A dash means the vendor publishes no price.
| Tool | Tests written in | Fixed arrival rate | Licence and paid plans | Best for |
|---|---|---|---|---|
| Grafana k6 | JavaScript, TypeScript | Yes, arrival-rate executors | AGPL-3.0; Grafana Cloud from $19/month | Developers, CI budgets |
| Apache JMeter | GUI test plans, Groovy | Yes, throughput timers | Apache 2.0, free | REST plus JDBC, JMS, LDAP |
| Gatling | Java, Kotlin, Scala, JS, TS | Yes, open-model injection | Apache 2.0 core; Basic €99/month | JVM teams |
| Locust | Python | Caps each user's rate | MIT, free | Python teams |
| Artillery | YAML, JavaScript, TypeScript | Yes, arrivalRate phases | MPL-2.0; Team $199/month | Node.js teams |
| Vegeta | Command line, Go library | Yes, its core design | MIT, free | Quick constant-rate checks |
| Postman | Postman collections | Virtual-user load profiles | Cloud runs $0.04 per VU hour | Teams on Postman |
| ReadyAPI | Low-code UI, reused functional tests | Not on the page we read | - | SOAP and REST teams on SmartBear |
The third column says whether a tool can start work on a fixed schedule; the section on request rate vs virtual users explains why that matters. Rate control comes from JMeter's Constant and Precise Throughput Timers, Gatling's open-model injection, Locust's constant_throughput wait time and Artillery's arrivalRate phases.
The 8 API load testing tools
1. Grafana k6

k6 is Grafana Labs' open-source load tester, scripted in JavaScript or TypeScript. Its README lists HTTP, WebSockets and gRPC. Its executors include constant-arrival-rate and ramping-arrival-rate, which fix the rate of new iterations instead of the user count. A failed threshold ends the run with exit code 99, so CI fails without extra parsing. The worked example below uses k6.
- Version and licence: v2.3.0 (21 September 2026), AGPL-3.0.
- Price: free locally. Grafana Cloud gives 500 virtual user hours a month free; Pro is $19 a month plus from $0.15 per extra virtual user hour.
2. Apache JMeter

JMeter tests "SOAP / REST Webservices" alongside JDBC, LDAP, JMS, FTP and TCP, which suits an API whose slow part is a database or a queue you also want to hit directly. Test plans are built in a GUI and run headless from the command line. Version 5.6.3 needs Java 8 or later. Our step-by-step JMeter API testing guide builds a REST test plan through to a CI gate.
- Watch out: in distributed mode, "each runs the full test plan" (remote testing docs), so divide the users per engine yourself.
3. Gatling

Gatling writes API load tests as code in Java, Kotlin or Scala, or in JavaScript and TypeScript with its newer SDK (docs). The free core covers HTTP/1.1, HTTP/2, WebSocket and Server-Sent Events. Its gRPC and GraphQL components are licensed separately and, in the free edition, are "capped at 5 concurrent users and 5 minute tests" (README), which matters if your API is gRPC.
- Price (pricing): Community Edition free; Basic €99 a month; Team €396 a month; Enterprise on request.
4. Locust

Locust defines each simulated API client as a Python class, so request signing, token refresh or data generation use ordinary Python. HTTP is built in, and the docs include REST, gRPC and MQTT examples. Its README says it runs load tests "distributed over multiple machines." Version 2.47.0 came out on 21 October 2026, under the MIT licence.
5. Artillery

Artillery scripts are YAML, JavaScript or TypeScript (script reference), and it tests HTTP, WebSocket, Socket.io and gRPC. Distributed runs launch from your own AWS or Azure account or from Artillery's cloud runners.
- Price (pricing): Free $0 with 30 reports a month and tests up to 30 minutes; Team $199 a month; Business $499 a month.
6. Vegeta

Vegeta is "a versatile HTTP load testing tool built out of a need to drill HTTP services with a constant request rate." You give it a list of targets, and it sends requests at a fixed rate, 50 per second by default. It speaks HTTP, HTTP/2 and HTTPS and also works as a Go library. Version 12.13.0, MIT licence. Use it for quick constant-rate checks of single endpoints.
7. Postman

Postman's performance testing reuses a collection as the load test: "The pre-request and post-response scripts and pm.test() assertions you already wrote run under load the same way they do in a functional run." Local runs are on every plan. Cloud runs are on paid plans, need at least 10 virtual users, and are distributed across Postman's infrastructure.
- Price (pricing): "Unlimited local performance testing" on all plans; cloud runs at $0.04 per virtual user hour.
8. ReadyAPI

SmartBear's ReadyAPI performance testing builds load tests "in a low-code UI" and lets you "Reuse functional test cases across REST and SOAP APIs". Tests run locally or on distributed agents, including cloud machines such as Amazon EC2. SmartBear publishes no price on its pricing page.
Request rate vs virtual users
Tools generate API load in one of two ways:
- Virtual users (a closed model). A fixed number of simulated clients each send a request, wait for the answer, pause, and repeat. When the API slows down, each user sends fewer requests, so the load drops exactly when the API struggles.
- Arrival rate (an open model). New work starts on a schedule, for example 50 iterations per second, whether earlier ones have finished or not. That matches public API traffic, where clients do not wait for each other. In k6 an iteration can send several requests: the example below sends two per iteration, so 50 iterations a second made about 100 requests a second.
For a public API, the open model is the closer match. The two are linked by Little's law: users needed = iteration rate × time per iteration. The example below runs 50 iterations per second, each taking about 0.06 seconds, so it needs about 50 × 0.06 = 3 users at a time. The run used at most 4 of the 20 it pre-allocated.
Types of API load tests
| Test | What you do | What you learn |
|---|---|---|
| Baseline | One or a few users | Normal response time for each endpoint |
| Load | Expected peak rate, held for a set time (say 10 to 30 minutes) | Whether you meet your budget at peak |
| Stress | Raise the rate in steps until errors rise | Capacity and how the API fails |
| Spike | Jump to several times normal in seconds | Whether autoscaling and queues absorb a burst |
| Soak | Moderate rate for hours | Memory leaks and exhausted connection pools |
| Breakpoint | Small steps, each held, until failure | The exact rate where the API breaks |
Hold times and step sizes here are starting points, not rules; size them from your own traffic. For the differences between these tests in general, see what load testing is.
How to load test an API, step by step
- Write the budget first. Pick a target rate from production peaks, then the limits: say p95 under 300 ms and under 1% errors. These become thresholds.
- Choose the endpoints. Start with the busiest, the slowest, the ones that write to the database, and login, search and payment flows.
- Script real flows, not single calls. Create, then read, then update, with production-like proportions of reads to writes and varied test data.
- Handle authentication like real clients. Fetch a token once per virtual user, reuse it, and refresh it before it expires on a long run. Keep secrets in environment variables, never in the script.
- Plan the cleanup. A test that creates orders leaves orders behind. Use a disposable environment, or tag test data so a job can delete it.
- Run against a production-like environment and watch the servers: CPU, memory, database connections and queue depth.
- Read percentiles per endpoint. Name dynamic URLs (as the example does with
GET /orders/:id) so each order ID does not become its own metric. - Repeat in CI with a short version of the test, so a regression fails the build.
A worked API load test with k6
This example is complete and runs on your own machine with Node.js and k6; we ran it with Node.js 26 and k6 v2.3.0. Save the API as server.js. It waits 20 to 40 ms of simulated work per request and, to stand in for a real capacity limit, returns 503 once 50 requests are in flight.
// server.js: a small API to load test on your own machine
const http = require('node:http');
const CAPACITY = 50; // simulated limit: past 50 requests in flight, answer 503
let inFlight = 0;
const orders = new Map();
function send(res, status, body) {
res.writeHead(status, { 'content-type': 'application/json' });
res.end(JSON.stringify(body));
}
http.createServer((req, res) => {
if (inFlight >= CAPACITY) return send(res, 503, { error: 'overloaded' });
inFlight++;
res.on('close', () => inFlight--);
let raw = '';
req.on('data', (chunk) => (raw += chunk));
req.on('end', () => {
// 20 to 40 ms of simulated work per request
setTimeout(() => {
if (req.method === 'GET' && req.url === '/products') {
return send(res, 200, [{ id: 1, name: 'Keyboard', price: 49 }]);
}
if (req.method === 'POST' && req.url === '/orders') {
let order;
try { order = JSON.parse(raw); } catch { return send(res, 400, { error: 'invalid JSON' }); }
if (!Number.isInteger(order.productId) || !Number.isInteger(order.qty) || order.qty < 1) {
return send(res, 422, { error: 'productId and qty must be positive integers' });
}
const id = String(orders.size + 1);
orders.set(id, { id, ...order });
return send(res, 201, orders.get(id));
}
const match = req.url.match(/^\/orders\/(\d+)$/);
if (req.method === 'GET' && match && orders.has(match[1])) {
return send(res, 200, orders.get(match[1]));
}
send(res, 404, { error: 'not found' });
}, 20 + Math.random() * 20);
});
}).listen(3333, '127.0.0.1', () => console.log('API listening on http://127.0.0.1:3333'));
Save the test as api-test.js. Each iteration creates an order, reads it back, and checks the status codes and the saved quantity. The constant-arrival-rate executor starts 50 iterations a second for 30 seconds, and the latency budget can be changed per run with an environment variable.
// api-test.js: a fixed request rate against two endpoints, with checks and a budget
import http from 'k6/http';
import { check } from 'k6';
const BASE = 'http://127.0.0.1:3333';
const P95_MS = __ENV.P95_MS || '200'; // the latency budget, overridable per run
export const options = {
scenarios: {
orders: {
executor: 'constant-arrival-rate', // start iterations at a fixed rate
rate: 50, // 50 iterations per second
timeUnit: '1s',
duration: '30s',
preAllocatedVUs: 20,
maxVUs: 100,
},
},
thresholds: {
http_req_duration: [`p(95)<${P95_MS}`],
http_req_failed: ['rate<0.01'],
checks: ['rate>0.99'],
},
};
export default function () {
const headers = { 'content-type': 'application/json' };
const created = http.post(`${BASE}/orders`, JSON.stringify({ productId: 1, qty: 2 }), { headers });
check(created, { 'order created (201)': (r) => r.status === 201 });
const id = created.json('id');
const read = http.get(`${BASE}/orders/${id}`, { tags: { name: 'GET /orders/:id' } });
check(read, {
'order readable (200)': (r) => r.status === 200,
'quantity saved': (r) => r.json('qty') === 2,
});
}
Start the API with node server.js, then run k6 run api-test.js; echo "exit code: $?" in a second terminal. The end of our log:
█ THRESHOLDS
checks
✓ 'rate>0.99' rate=100.00%
http_req_duration
✓ 'p(95)<200' p(95)=39.5ms
http_req_failed
✓ 'rate<0.01' rate=0.00%
✓ order created (201)
✓ order readable (200)
✓ quantity saved
http_reqs......................: 3000 99.867489/s
iterations.....................: 1500 49.933745/s
vus............................: 3 min=2 max=4
running (0m30.0s), 000/020 VUs, 1500 complete and 0 interrupted iterations
exit code: 0
1,500 iterations made 3,000 requests at about 100 per second, every check passed, and the 95th percentile was 39.5 ms against a 200 ms budget.
Make the build fail on a slow API
The same script becomes a CI gate. Restart the API, then run it with a budget the API cannot meet: k6 run -e P95_MS=25 api-test.js; echo "exit code: $?". From our log:
http_req_duration
✗ 'p(95)<25' p(95)=39.44ms
time="2026-10-23T19:21:23-07:00" level=error msg="thresholds on metrics 'http_req_duration' have been crossed"
exit code: 99
The echo in these commands only prints the code for you to see. In CI, run k6 run api-test.js on its own: a step whose command exits with a non-zero code fails unless you configure the step to ignore errors. Set the budget to your real target, keep the CI run short, and save the long runs for nightly or pre-release jobs.
Problems API load tests find
- N+1 queries. An endpoint that runs one database query per item in a list slows down as the data grows. Batch the queries or load related rows in one join.
- Missing indexes. Filters and sorts on unindexed columns turn into full table scans under load. Check the query plan for the slowest endpoint.
- Exhausted connection pools. When every database connection is busy, requests queue and time out. The test shows it as latency that jumps at a fixed concurrency.
- No caching. Data that rarely changes, fetched from the database on every request, wastes capacity. Cache it in memory or with HTTP cache headers.
- Slow work in the request path. Sending email or building a report inside the request holds the connection open. Move that work to a background queue.
To understand where request time goes in the first place, read what API latency is.
Conclusion
Pick the tool your team will maintain. k6 or Gatling suit tests as code, Locust suits Python, JMeter suits protocol breadth, and Vegeta suits quick constant-rate checks. Postman or ReadyAPI fit if your functional tests already live there. Then drive the API at a fixed arrival rate, check every response, and let thresholds fail the build. The performance testing tools comparison covers the wider field, including NeoLoad, LoadRunner and BlazeMeter.
Frequently Asked Questions
What is API load testing?
It is a performance test that sends many concurrent requests to API endpoints and measures response time, throughput and error rate. The goal is to confirm the API meets its targets at expected traffic and to find the rate at which it starts to fail.
What is the best tool for API load testing?
It depends on how your team writes code. k6 suits JavaScript developers and CI gates, Gatling suits JVM teams, Locust suits Python teams, and JMeter suits teams who want a GUI and protocols such as JDBC. All four are free and open source.
How do you load test a REST API?
Write a script that calls the REST endpoints in a realistic order, set a target request rate and thresholds for latency and errors, and run it against a production-like environment. The k6 example on this page does exactly that and fails with exit code 99 when a threshold is missed.
Can you load test an API with Postman?
Yes. Postman's performance testing turns a collection into a load test and reruns your pm.test() assertions under load. Local runs are unlimited on every plan, and cloud runs cost $0.04 per virtual user hour (read 23 October 2026).
What is the difference between API load testing and API stress testing?
An API load test holds the request rate you expect and checks your budget. An API stress test keeps increasing the rate until errors or latency break the budget, to find capacity and see how the API fails and recovers.
How do you load test a GraphQL API?
Send POST requests with query payloads to the GraphQL endpoint using any HTTP load tool. Test both cheap queries and expensive nested ones, because the cost of a GraphQL request depends on the query. See GraphQL vs REST for how the two differ.





