Reliable REST API testing is more than sending a request and checking for a 200 response. This guide gives you a repeatable workflow for testing requests, headers, authentication, response bodies, validation rules, failure cases, and regression behavior with curl, API clients, and automated integration tests.
Overview
A useful API test answers a specific question. Can an authorized user retrieve the expected resource? Does the endpoint reject malformed input? Is the response shaped correctly? Does a failed request return a safe, predictable error? Writing down these questions before testing prevents a common problem: collecting ad hoc examples without building meaningful coverage.
Start with a small request contract for each endpoint. Record the HTTP method, URL path, required headers, authentication method, query or path parameters, request body, expected status codes, and important response fields. This contract becomes the shared reference for manual checks, API collections, and automated tests.
REST API testing usually works best in layers:
- Manual checks: Quickly explore a new endpoint or investigate a failure.
- Collection-based checks: Organize repeatable requests and environment variables in an API client.
- Automated integration tests: Run assertions in a test suite as part of development or continuous integration.
| Method | Best for | Limitation |
|---|---|---|
| Manual curl request | Fast exploration, debugging, and reproducible examples | Easy to forget or run inconsistently |
| API client collection | Organized requests, shared variables, and team review | May remain separate from the application test suite |
| Automated integration test | Regression coverage and repeatable release checks | Requires test data, setup, and maintenance |
Checklist by scenario
1. Test a basic endpoint with curl
Begin with the smallest request that should succeed. The following example uses placeholder values and asks the server to return JSON:
curl --request GET \
--url "https://api.example.test/v1/widgets/42" \
--header "Accept: application/json"
Check the status code, response headers, body format, required fields, and value types. If the endpoint is expected to be cacheable, inspect the relevant cache headers; if it changes data, confirm that the response communicates the result clearly. Save the command or export it from your API client so another developer can reproduce the check.
2. Test a JSON request body
For a create or update operation, send the content type explicitly and keep the body easy to inspect:
curl --request POST \
--url "https://api.example.test/v1/widgets" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{"name":"Example widget","enabled":true}'
Verify both the success response and the stored result. A successful request should not only return an expected status; it should also preserve the intended data, apply documented defaults, and avoid returning sensitive values unnecessarily.
3. Test authentication and authorization
Keep credentials out of committed request files, shell history where practical, screenshots, and bug reports. Use environment variables or your API client's secret-value support:
export API_TOKEN="replace-with-a-local-token"
curl --request GET \
--url "https://api.example.test/v1/widgets" \
--header "Accept: application/json" \
--header "Authorization: Bearer ${API_TOKEN}"
Test authentication as separate cases rather than treating it as one checkbox:
- A valid credential can access the permitted resource.
- A missing credential receives the expected unauthenticated response.
- An invalid, expired, or malformed credential is rejected.
- A valid user cannot access a resource outside their permissions.
- Credentials are not echoed in response bodies, logs, or error messages.
Do not use a privileged token to test ordinary user behavior. Different test identities make authorization defects easier to detect.
4. Test validation and error cases
For every required field, create at least one valid and one invalid input. Try missing fields, incorrect types, empty strings, unexpected values, excessively long values, invalid identifiers, and malformed JSON where applicable. Also test invalid query parameters and nonexistent resource IDs.
Record the expected status code and the useful parts of the error response. A good test checks that the server rejects the input, returns a stable error shape, identifies the relevant field when appropriate, and does not expose implementation details such as stack traces or internal database messages.
5. Add assertions to an API client or test suite
An assertion should verify behavior, not merely that a request completed. Typical assertions include:
- The status code belongs to the expected outcome.
- The response has the expected content type.
- Required JSON properties exist and have the correct types.
- An identifier in the response matches the created or requested resource.
- A list respects pagination, filtering, and sorting parameters.
- An error response contains a predictable code or message category.
Keep assertions focused. One test that checks twenty unrelated details can be difficult to diagnose when it fails. Prefer small cases with names that describe the behavior, such as “rejects a request without a name” or “does not return another user’s widget.”
6. Build a reusable integration-test workflow
- Prepare isolated test data or create it through the API.
- Send the request under test.
- Assert the response status, structure, and important values.
- Verify side effects through a supported API or test database interface.
- Clean up data when the environment requires it.
- Capture the request context and response details when the test fails.
Prefer stable identifiers and controlled fixtures over depending on records that happen to exist in a shared environment. If your project uses containers for repeatable local services, a documented setup can make API tests easier to run consistently; see the Docker for Local Development guide for related workflow ideas.
What to double-check
- URLs: Confirm the base URL, API version, path parameters, trailing-slash behavior, and URL encoding of special characters.
- Headers: Check
Accept,Content-Type, authorization, correlation, and conditional-request headers where relevant. - Authentication: Make sure the test uses the intended identity and that secrets are stored outside source control.
- Status codes: Test successful, invalid, unauthenticated, unauthorized, missing, conflict, and server-error paths where the endpoint supports them.
- JSON shape: Confirm nullability, arrays, nested objects, field naming, numeric representation, and optional fields.
- Pagination: Check page size limits, empty results, next or previous links, stable ordering, and behavior beyond the final page.
- Idempotency: Repeat requests that are expected to be safe or idempotent and verify that they do not create unintended duplicates.
- Time and locale: Verify timestamp format, timezone assumptions, decimal values, and text encoding.
- Observability: Include a request identifier or timestamp in a bug report when available, while removing credentials and personal data.
For a structured debugging process, pair API checks with a broader web application troubleshooting workflow. If a response contains SQL-related data, validate the expected query behavior separately rather than assuming a correctly shaped JSON response proves the underlying result is correct.
Common mistakes
Checking only for a 2xx response
A passing status code does not prove that the body, side effects, authorization boundary, or error handling is correct. Assert the parts of the contract that matter to the caller.
Using production data casually
Testing with real records can create privacy, safety, and repeatability problems. Use synthetic or isolated data whenever possible, and ensure that destructive requests are clearly separated from exploratory requests.
Hard-coding tokens and environment details
Replace secrets with variables and document which values a tester must provide. Keep development, test, staging, and production base URLs distinct to reduce accidental writes to the wrong environment.
Testing only the happy path
Most useful API defects appear at boundaries: missing input, duplicate requests, permission changes, empty collections, stale identifiers, and malformed payloads. Treat negative cases as first-class tests.
Writing assertions that are too fragile
A test should not fail because an unordered list changed order unless order is part of the contract. Avoid comparing entire response bodies when only a few fields are guaranteed to remain stable.
Reporting a failure without reproduction details
A useful bug report includes the environment, method, sanitized URL, headers that affect behavior, request body, expected result, actual result, status code, response body, and reproduction steps. Remove tokens, personal data, and unnecessary internal details before sharing it.
When to revisit
Review your REST API testing checklist whenever an endpoint, authentication flow, response schema, data model, or deployment environment changes. Revisit it before a release that adds a new resource, changes permissions, introduces pagination, or modifies validation rules. Tool interfaces and test-library syntax can also change, so refresh copied commands when your team updates its API client, shell workflow, or testing framework.
For an efficient review, take these actions:
- Read the current endpoint contract and list changed inputs, outputs, and permissions.
- Run one successful request and one representative failure for each changed behavior.
- Run authentication and authorization cases with more than one test identity.
- Run the automated integration suite against a controlled environment.
- Review failures for test-data collisions, stale fixtures, and environment-specific assumptions.
- Update the request examples, collection variables, assertions, and bug-report template.
Keep this checklist close to the endpoint documentation rather than treating it as a one-time exercise. A small, repeatable set of meaningful API tests is easier to maintain and more valuable than a large collection of requests that nobody can confidently rerun.