Blog
API Test Cases: Practical Examples for REST API Testing
Practical API test cases for REST endpoints covering auth, validation, pagination, idempotency, and contract checks for QA and SDET teams.
- api-testing
- rest
- test-cases
- automation
UI tests prove journeys. API test cases prove the contracts those journeys depend on—status codes, payloads, authz, and failure modes that never appear as a pretty button state. For service-heavy products, API coverage often finds broken authorization and silent data corruption earlier than click-paths.
This guide focuses on REST-style HTTP APIs with JSON. It assumes you already know how to write QA test cases and want endpoint-level examples you can drop into Postman, REST Client, Playwright request, or pytest.
What makes a strong API test case
An API case should specify:
- Method and path (including path params)
- Auth scheme and actor (anonymous, user, admin, service)
- Headers and content type
- Request body or query
- Expected status
- Expected body constraints (fields, types, error code)
- Side effects (DB row, event emitted, email enqueued)—when observable in test env
“Returns 200” alone is rarely enough.
Baseline template for API cases
| Field | Example |
|---|---|
| ID | API-ORD-014 |
| Title | Buyer can list only their own orders with pagination |
| Actor | Buyer JWT |
| Request | GET /v1/orders?limit=20&cursor= |
| Expect status | 200 |
| Expect body | items[] each has buyerId = actor; nextCursor null or string |
| Side effects | None |
Store raw examples beside the case:
GET /v1/orders?limit=20 HTTP/1.1
Host: api.staging.example.com
Authorization: Bearer {{buyer_token}}
Accept: application/jsonAuthentication and authorization cases
Anonymous access denied on protected routes
Expect: 401 (or 403 if your gateway differs—document it). Body has stable error code. No partial PII.
Expired token rejected
Expect: 401; refresh path documented separately; retries with expired token never succeed.
Valid user forbidden from admin resource
Expect: 403; admin-only fields absent; timing should not leak existence beyond product policy.
Scoped token cannot exceed scope
Example: read-only token on DELETE /v1/orders/{id} → 403/401 per design.
These are classic negative test cases at the protocol layer—keep them next to UI negatives so authorization is tested twice only when risk warrants it.
Resource lifecycle examples
Create with valid payload
POST /v1/projects with required name → 201, Location or id returned, subsequent GET shows resource.
Create with missing required field
→ 400/422 with field-level error; no resource persisted.
Idempotent create with Idempotency-Key
Replay same key + body → same resource ID; no duplicates. Different body with same key → documented conflict behavior (409 or first-wins).
Update with stale If-Match / version
→ 412/409; client must refresh. Critical for collaborative editors.
Delete is idempotent or clearly second-delete
Second DELETE → 204/404 per contract—assert the chosen rule so clients can rely on it.
Validation and schema cases
| Scenario | Expect |
|---|---|
| Unknown JSON field | Ignore or 400—match documented policy |
Wrong types (name: 123) | 400/422 |
| Enum out of range | 400/422 |
| Unicode in allowed text fields | Accepted if product supports i18n |
| Null vs omitted optional field | Behavior matches OpenAPI |
Where you maintain OpenAPI/Swagger, add contract tests that fail CI when responses drift. Manual API cases still matter for authorization and workflows the schema cannot express.
Pagination, filtering, and sorting
Default page size honored
Unset limit → documented default; never unbounded dumps in production-like configs.
Cursor stability
Insertions during iteration should follow documented consistency model (or snapshot). Capture actual product behavior—do not invent database theory in the case.
Filter injection / unexpected operators
?status=open' OR '1'='1 → safe handling, not SQL errors.
Sort allowlist
?sort=passwordHash → rejected if not allowlisted.
File upload and download endpoints
Binary payloads need cases that JSON-only tables miss.
| Scenario | Expect |
|---|---|
| Valid file under size limit | 201/200; stored object retrievable with matching checksum |
| File over size limit | 413 or documented gateway error, not a hung connection |
| Wrong content-type declared vs actual bytes | Rejected or sniffed per documented policy |
| Concurrent upload of same logical file | Last-write-wins or versioned, per contract—assert the one you ship |
| Download requires correct auth scope | 403 for a token without read access, even if the object ID is guessed correctly |
POST /v1/attachments HTTP/1.1
Host: api.staging.example.com
Authorization: Bearer {{uploader_token}}
Content-Type: multipart/form-data; boundary=----test
------test
Content-Disposition: form-data; name="file"; filename="sample.pdf"
Content-Type: application/pdf
(binary content)
------test--Verify the response body's size and contentType match the actual bytes stored, not just what the client claimed in headers.
Long-running and asynchronous operations
Some endpoints cannot return a final result synchronously. Test the whole lifecycle, not just the kickoff call.
Job accepted and pollable
POST /v1/exports → 202 with a jobId and status URL. Expect: immediate response; job appears pending on first poll.
Polling reflects real progress
Expect: status transitions pending → processing → completed/failed without skipping straight to a stale cached state; polling too fast does not create duplicate jobs.
Completed job exposes a retrievable result
Expect: result URL or payload available after completion; expired result links fail predictably rather than serving corrupt partial data.
Failed job explains why
Expect: failed status includes an error code/reason a client can act on or display, not just a bare boolean.
Caching and conditional requests
| Header | Case | Expect |
|---|---|---|
ETag | Unchanged resource re-requested with If-None-Match | 304 with no body |
ETag | Resource changed, stale If-None-Match sent | 200 with new body and new ETag |
Cache-Control | Sensitive endpoint (billing, PII) | no-store present; not left to default |
Last-Modified | Conditional GET with If-Modified-Since | Honored consistently with ETag behavior |
Caching bugs are easy to miss manually because a single fresh request always looks correct. Script the paired request sequence instead of testing each call in isolation.
Content negotiation and localization
Accept-Language changes error message language
Expect: Error message field respects Accept-Language when the product supports localization; error code field stays stable across languages so client logic never branches on translated text.
Unsupported Accept header handled cleanly
Accept: application/xml on a JSON-only API → 406 or documented default, not a raw exception.
Rate limits and abuse
Burst over limit returns 429
Include Retry-After if you expose it. Automated tests should use dedicated clients so shared CI tokens are not banned.
Body size over limit
Large payload → 413 or gateway error mapped cleanly.
Consumer-driven contract cases
When multiple internal teams consume the same API, a schema check alone does not guarantee a safe deploy—a field a consumer actually depends on can be technically "optional" in the spec yet load-bearing in practice.
| Case | Expect |
|---|---|
| Provider adds a new optional field | Existing consumer contracts still pass unmodified |
| Provider removes a field a contract asserts on | Contract test fails before deploy, not after |
| Provider changes a field's type | Contract test fails with a clear diff, not a runtime cast error downstream |
| Consumer adds a new expectation | Provider CI runs the updated contract before the consumer merges |
Contract tests do not replace the authorization and workflow cases in this guide—they catch a different class of break, specifically the one where both sides pass their own suite and still fail together in production.
Practical scenario: order service slice
Assume POST /v1/orders, GET /v1/orders/{id}, POST /v1/orders/{id}/cancel.
- Buyer creates order →
201, totals computed server-side (client-sent price ignored or validated). - Buyer gets own order →
200. - Other buyer gets same ID →
404/403(prefer not leaking existence if that is policy). - Cancel shipped order → business rule error, not
500. - Double cancel → stable response, single cancel side effect.
Payment confirmation hooks belong with the payment testing checklist; keep API cases focused on your service boundary.
Designing a sane error taxonomy for tests to assert against
Cases that assert only an HTTP status code miss regressions where the status stays correct but the reason changes. A small, stable error shape makes assertions durable:
{
"error": {
"code": "ORDER_ALREADY_CANCELLED",
"message": "This order was already cancelled.",
"requestId": "req_8f2c1a"
}
}Cases to write once this shape exists:
- Every documented error path returns this shape, not a bare string or HTML page
codevalues are stable across releases (grep-able in test assertions)- Unhandled server errors still return the shape at
500, not a stack trace - Client-facing
messagecan change wording without breaking tests keyed oncode
Teams that skip this step end up with brittle suites that assert on exact English sentences, then fail every time a copywriter fixes a typo.
Multi-tenant and environment strategy
API suites often run against shared staging, which creates cross-test interference if tenancy is not isolated.
| Strategy | Pros | Watch for |
|---|---|---|
| Dedicated tenant per test run | No cross-test pollution | Slower seed/teardown |
| Shared tenant, namespaced resources | Fast | Naming collisions under parallel runs |
| Ephemeral environment per PR | Full isolation | Infra cost and setup time |
Whichever you choose, seed and tear down through the API itself rather than direct database writes when possible—doing so exercises the same validation paths your users hit and catches drift between the API and the database schema.
Manual exploration vs automated API suites
| Approach | Best for |
|---|---|
| Manual (Insomnia/Postman) | New endpoint discovery, weird gateway behavior |
| Automated contract | Schema drift, required fields |
| Automated workflow | Multi-step authz + lifecycle |
| Chaos/perf adjacent | Timeouts, partial failures—separate suites |
Do not force every exploratory note into regression automation on day one. Promote cases that failed once in production or block releases.
Data setup patterns
Prefer API-level fixtures:
# seed buyer + product, capture IDs
POST /v1/test-support/seed-order-context
Authorization: Bearer {{test_admin}}Avoid UI login just to obtain a token if your stack offers a test token endpoint in non-prod. Document that endpoint’s lockdown so it never ships publicly.
Observability expectations in test cases
When staging exposes request IDs:
Expect: Error responses include requestId / correlationId consistent with logs. This turns failing API cases into actionable developer handoffs.
Mapping API cases back to stories
Acceptance criteria often hide transport details. Rewrite “user sees order history” into:
GET /v1/ordersauthorized- empty list vs populated list
- pagination when > page size
For story-to-suite flow, see From Jira story to structured QA workflow. Coverage depth choices still apply—Basic vs Deep—even when the interface is HTTP.
Versioning and deprecation checks
APIs rarely die cleanly. Add a small pack whenever you introduce /v2 or sunset a field:
| Case | Expect |
|---|---|
/v1 still serves documented behavior during overlap window | No silent breaking change |
| Deprecated field present with warning header (if you emit one) | Clients can migrate intentionally |
| Removed field absent after cutover date | Documented 400/422 or ignored per policy |
| Accept header for unsupported version | Stable error, not HTML gateway page |
Put cutover dates in suite notes. Calendar-driven failures are still failures.
Partner and public API extras
If external integrators consume your API, add:
- Sample Postman/Insomnia collection reviewed each quarter
- Sandbox keys that cannot affect production data
- Explicit tests for pagination defaults partners rely on
- Breaking-change checklist signed by API owners before release
Internal-only services can skip partner packaging but should not skip authz matrices.
FAQ
Should QA own contract tests or developers?
Joint ownership works best: developers publish OpenAPI; QA adds authz and workflow cases schema tests miss. Argue about gaps, not turf.
Is 200 with an error object acceptable?
Only if that is the documented legacy contract. Prefer standard HTTP semantics for new APIs and test what you document.
How do we test webhooks?
Separate suite: signed payloads, retries, replay attacks, and destination failures. Link from API docs but do not bury them inside CRUD cases.
What about GraphQL?
Many ideas transfer (authz, validation), but field-level selection and error arrays need their own patterns. Do not paste REST tables blindly into GraphQL projects.
How do we test file uploads without storing real user files in test fixtures?
Generate synthetic files at run time (small PDFs, images with known checksums) instead of committing real documents to the repo. Verify checksums server-side so the test proves storage integrity, not just a 200 status.
Should async job polling be tested with real wait times?
Use a short-lived test job configuration in non-prod so suites do not sleep for minutes. Keep one slow, realistic-duration case in a nightly job if timing itself is part of the contract.
Tooling and plan limits for assisted drafting are on Pricing. Environment and integration setup: Docs.
Final checklist
- Authn and authz cases exist for each sensitive route class
- Validation covers missing fields, types, and enums
- Idempotency and concurrency rules asserted where claimed
- Pagination/filter/sort abuse cases exist for list endpoints
- Error shapes are stable and documented
- Side effects are verified, not assumed from status alone
- Rate limit tests use isolated credentials
- OpenAPI drift checked in CI when applicable
- Tokens and seeds are non-production and rotatable
- Failures include correlation IDs when the platform supports them
- File upload/download endpoints verify actual bytes, not just declared metadata
- Async jobs are tested through pending/completed/failed transitions
- Caching headers are covered with paired conditional requests
- Error responses use a stable code separate from translatable message text
CTA — turn service stories into endpoint suites
When a backend story lists endpoints and error codes, draft structured API cases in QA Workflow Assistant from that acceptance pack, then add authz matrices and idempotency checks by hand. Keep gateway quirks in suite notes, not buried in titles. Start environment wiring from Docs.
Related articles
Regression Test Cases: A Practical Guide
A practical guide to regression test cases—how to select, maintain, prioritize, and automate a suite that protects critical journeys without freezing delivery.
August 10, 2026 · 12 min read
Negative Test Cases: Examples Every QA Engineer Should Use
Learn how to design negative test cases with practical examples for validation, authorization, state conflicts, and API failures that catch real defects.
August 8, 2026 · 12 min read
Password Reset Test Cases (Complete Checklist)
Password reset test cases and a complete checklist for request, email token, expiry, reuse, and post-reset session handling in modern apps.
August 7, 2026 · 13 min read