API Test Cases: What a Real One Contains
Most API "tests" check that you got a 200 OK. A real API test case names the endpoint, the request, the preconditions, and one exact response — body and status.
"Got 200 OK" is not a test
Open most API test suites and you'll find the same thing over and over: send a request, check the status was 200, move on. It feels like testing. It mostly isn't. A 200 means the server answered — not that it answered correctly.
An API test case is a test case like any other. It has the same job a UI case has: state exactly what should happen, specifically enough that two people would agree on whether it passed. "Got 200 OK" fails that bar completely. It doesn't say what the response body should contain, what state should have changed, or what should happen when the request is wrong.
This is about what actually belongs in an API test case — the fields, the types of case that make a suite worth having, and the one field everyone under-writes. The angle throughout is the case, not the client. Which tool fires the request matters far less than whether the case knows what a correct answer looks like.
What an API test case actually contains
Strip an API case down and the same parts matter every time. Miss one and the case gets vague in a way that shows up later as an argument about whether it passed.
The endpoint and method. Which resource, and GET, POST, PATCH, or DELETE. Obvious, but it belongs written down, because the same path behaves differently by method.
The request. The full thing: path and query parameters, headers, the auth token or key, and the body. "Send a valid payload" is not a request; the specific payload is.
The preconditions. The state the system must be in before the call makes sense. A GET /orders/123 assumes order 123 exists and belongs to this user. Leave that implicit and the case fails at random depending on what data happens to be there.
The expected response. What should come back — the status code and the body — plus any state that should have changed on the server. This is the part that separates a real case from a smoke check, and it gets its own section next.
Written out, those four turn "test the orders endpoint" into a case someone can run and grade without asking you what you meant.
The expected result is the field everyone under-writes
Watch how people fill in the expected result for an API case. "Returns 200." "Response is correct." "Order is created." Every one is a placeholder wearing the costume of an assertion — the exact same failure as "login works" on a UI case.
Correct how? A real expected result names the status code, the shape and key values of the response body, and what changed on the server. "Returns 201, the body contains the new order id and a status of pending, and a row now exists in orders with that id" is an expected result. "Order is created" is a hope.
The reason this matters more for APIs, not less, is that the response is invisible. On a UI a tester might eyeball that something looks wrong even with a vague case. An API response is just JSON.
If the case doesn't say what the JSON should contain, a wrong-but-well-formed body sails straight through a status-code check. The endpoint can return 200 with completely wrong data and your test still goes green.
The types that make a real API suite
A suite of happy-path cases tells you the API works when everything goes right, which is the situation that needs testing least. Five types together make a suite that actually finds things.
Happy path. The request is valid, the state is right, and you assert the full correct response. The baseline everything else builds on.
Contract. Does the response match its agreed shape — the fields, types, and required keys the consumers depend on? A field quietly changing from a number to a string, or a key going missing, breaks clients without ever changing the status code.
Negative and error. Bad or missing auth, a malformed body, a missing required field, the wrong type. Here you assert the error: the right status (a 400 or 401, not a 500), a useful error body, and — crucially — that nothing was written.
Boundary. The edges: an empty collection, the maximum page size, a zero quantity, a string at its length limit. Bugs cluster at the boundaries, on APIs as much as anywhere.
State and idempotency. What happens on the second identical POST? Does a retried payment charge twice? Does deleting an already-deleted record error cleanly or blow up? These cases check behaviour across calls, which no single request can reveal.
A happy-path API suite passes right up until a real client sends something weird. The bugs were always in the cases you didn't write.
What a written-out API case looks like
Enough principle — here are two cases written the way this article argues for, against a "create order" endpoint.
Happy path — create an order with a valid cart
- Precondition: a logged-in user with one in-stock item in their cart.
- Request:
POST /orderswith the cart id and the user's auth token. - Expected response:
201 Created; the body contains anid,status: "pending", and atotalmatching the cart; and a new row exists inordersfor that user with statuspending.
Run the checks and it's unambiguous: right status, named body fields, and a stated state change. Two people grade it the same way.
Negative — create an order with an out-of-stock item
- Precondition: a logged-in user whose cart holds an item that is now out of stock.
- Request: the same
POST /orders. - Expected response:
409 Conflict; the body is an error that names the out-of-stock item; and — the check people forget — no order row was created.
The negative case asserts what a status check never would: the exact error status rather than a generic 500, a useful message, and that nothing was written.
Setup, teardown, and not colliding
API cases fail intermittently for a reason that has nothing to do with the API: they trip over each other's data. Two cases both use user [email protected], run in parallel, and one deletes what the other expected. It looks like flakiness; it's a data collision.
The fix is the same discipline as any good case: each case owns its data. Create what you need in setup, use it, and tear it down after — or generate unique data per run so no two cases ever reach for the same record. A case that assumes it's the only thing touching the database was never really isolated; it just hadn't collided yet.
There's an environment piece to this too. An API case quietly assumes a base URL, an environment, and a valid auth token that hasn't expired. Name those assumptions in the case or its setup, rather than leaving them to whatever the last person configured, so the case runs the same from a clean environment as it does on your laptop.
The case is the source; the script is one implementation
Here's the part that decides whether all this documentation is worth keeping. An automated API test — the code in your repo that fires the request — is an implementation of a case. On its own it can't tell you what requirement it protects or why it exists; it's a set of assertions with no stated intent.
Treat the documented case as the source of truth and the script as one way of executing it. The case says what the behaviour should be and which requirement it verifies; the automation carries it out. Change the intended behaviour and you change the case first, then the code follows.
Now a failing test points back to a specific case and expected result, and every check in the suite traces to something a person decided mattered — the same record a release ultimately rests on.
This is the job Tesbo is built for, and the boundary is worth stating plainly. Tesbo manages and drafts the documented cases, with a person approving each one before it enters the trusted record. It does not run, schedule, or execute your API tests — the framework that fires the request and the CI that runs it stay entirely yours. The case is ours to keep; the client is yours.
Write the case the endpoint deserves
An API test case is not a lower form of test that gets away with less. It needs the same things every case needs: a stated precondition, a full request, and one specific expected result that names the status, the body, and the state that changed.
Do that, cover the five types instead of just the happy path, and keep each case isolated and traceable, and your API suite stops being a wall of green 200s that proves nothing. It becomes a record of what the API is actually supposed to do — which is the only kind of suite worth running.
Questions people ask
What should an API test case include?
The endpoint and method, the full request (parameters, headers, auth, body), the preconditions the system must be in, and a specific expected response — the status code, the response body, and any state that should change on the server. That last part is what separates a real case from a status-code smoke check.
Why isn't checking for a 200 status code enough?
Because a 200 only means the server answered, not that it answered correctly. An endpoint can return 200 with wrong or missing data and a status-only check passes. You have to assert on the response body and the resulting state too — the status is the easiest thing for a broken endpoint to still get right.
What types of API test cases should a suite have?
Five: happy path (valid request, full correct response), contract (the response matches its agreed shape), negative and error (bad auth, malformed or missing fields, asserting the error and that nothing was written), boundary (empty collections, limits, edges), and state or idempotency (what a second identical call does). Happy paths alone find the least.
How do you write negative test cases for an API?
Send deliberately wrong requests — expired auth, a malformed body, a missing required field, an out-of-range value — and assert the specific error: the correct status (a 400 or 401, not a 500), a useful error body, and that no data was created or changed. The "nothing was written" check is the one people forget.
How do API test cases relate to automated tests?
The documented case is the source of truth; the automated test is one implementation of it. The case states the behaviour and the requirement it verifies; the script carries it out and is traceable back to it. When behaviour changes, you update the case first and the automation follows, so a failure always points at a specific expected result.
-1-900x600.png%3Fprefix%3Dprod&w=3840&q=75)