Skip to content

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.

QA Workflow Assistant12 min read
  • 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

FieldExample
IDAPI-ORD-014
TitleBuyer can list only their own orders with pagination
ActorBuyer JWT
RequestGET /v1/orders?limit=20&cursor=
Expect status200
Expect bodyitems[] each has buyerId = actor; nextCursor null or string
Side effectsNone

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/json

Authentication 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 DELETE204/404 per contract—assert the chosen rule so clients can rely on it.

Validation and schema cases

ScenarioExpect
Unknown JSON fieldIgnore or 400—match documented policy
Wrong types (name: 123)400/422
Enum out of range400/422
Unicode in allowed text fieldsAccepted if product supports i18n
Null vs omitted optional fieldBehavior 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.

ScenarioExpect
Valid file under size limit201/200; stored object retrievable with matching checksum
File over size limit413 or documented gateway error, not a hung connection
Wrong content-type declared vs actual bytesRejected or sniffed per documented policy
Concurrent upload of same logical fileLast-write-wins or versioned, per contract—assert the one you ship
Download requires correct auth scope403 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/exports202 with a jobId and status URL. Expect: immediate response; job appears pending on first poll.

Polling reflects real progress

Expect: status transitions pendingprocessingcompleted/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

HeaderCaseExpect
ETagUnchanged resource re-requested with If-None-Match304 with no body
ETagResource changed, stale If-None-Match sent200 with new body and new ETag
Cache-ControlSensitive endpoint (billing, PII)no-store present; not left to default
Last-ModifiedConditional GET with If-Modified-SinceHonored 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.

CaseExpect
Provider adds a new optional fieldExisting consumer contracts still pass unmodified
Provider removes a field a contract asserts onContract test fails before deploy, not after
Provider changes a field's typeContract test fails with a clear diff, not a runtime cast error downstream
Consumer adds a new expectationProvider 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.

  1. Buyer creates order201, totals computed server-side (client-sent price ignored or validated).
  2. Buyer gets own order200.
  3. Other buyer gets same ID404/403 (prefer not leaking existence if that is policy).
  4. Cancel shipped order → business rule error, not 500.
  5. 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
  • code values are stable across releases (grep-able in test assertions)
  • Unhandled server errors still return the shape at 500, not a stack trace
  • Client-facing message can change wording without breaking tests keyed on code

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.

StrategyProsWatch for
Dedicated tenant per test runNo cross-test pollutionSlower seed/teardown
Shared tenant, namespaced resourcesFastNaming collisions under parallel runs
Ephemeral environment per PRFull isolationInfra 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

ApproachBest for
Manual (Insomnia/Postman)New endpoint discovery, weird gateway behavior
Automated contractSchema drift, required fields
Automated workflowMulti-step authz + lifecycle
Chaos/perf adjacentTimeouts, 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/orders authorized
  • 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:

CaseExpect
/v1 still serves documented behavior during overlap windowNo silent breaking change
Deprecated field present with warning header (if you emit one)Clients can migrate intentionally
Removed field absent after cutover dateDocumented 400/422 or ignored per policy
Accept header for unsupported versionStable 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.