Negative API Testing

Negative API testing sends requests that the API contract says must be refused, and checks that each one is refused correctly: the right 4xx status code, an error body in the documented format, and no change to any data. It is the negative testing you already know, at a level where no form stops the user: any client can send anything to an endpoint.

Example: POST /api/v1/cart/items accepts a quantity from 1 to 10. A request with "quantity": 0 must get 422 with a body that names quantity, and a follow-up GET /api/v1/cart must show the cart unchanged. A 201, a 500, or a 200 with an error message in the body is a defect.

Why it matters

Mobile apps, partner systems and scripts call an API without going through its user interface, so the API must enforce every rule itself. When it trusts its input, it stores corrupt data, crashes with a 500, leaks stack traces, or lets a client set properties it should never control, such as a price. The OWASP API Security Top 10 2023 lists that last case under API3, Broken Object Property Level Authorization.

Error responses are an interface too: clients read the status and the error body to decide what to show and whether to retry, so a changed error format breaks them like a changed success response. For a tester, the contract gives an exact expected result for every rejection, so negative API tests are precise and easy to automate.

Classes of invalid request and the status each one earns

Design negative API tests like the invalid partitions of equivalence partitioning: one class per test and every other part of the request valid, so that a failure points to one rule. The codes come from RFC 9110 and the lesson on REST APIs; where HTTP leaves a choice, such as 400 or 422 for invalid values, the contract decides.

  • Malformed syntax: a truncated body, a trailing comma, an empty body declared as JSON → 400 Bad Request.
  • Missing fields: each required field left out in turn, then sent as null and as "", which an API may treat differently → 400 or 422 naming the field.
  • Wrong types: "2" instead of 2, 2.5 for an integer, an array instead of an object → 400 or 422, not a silent conversion.
  • Invalid values: an unknown enum value, the date 2026-02-30, a property the client must not set such as "price": 0.01 → 422 or 400, or ignored if the contract says so, but never applied.
  • Wrong media type: Content-Type: text/plain on a JSON body → 415 Unsupported Media Type.
  • Wrong method: PUT on a path that supports only GET and POST → 405 Method Not Allowed with an Allow header. A supported method that this caller may not use gets 403, as in the lesson on authorization testing.
  • Bad authentication: a missing, malformed or expired credential → 401 with WWW-Authenticate, as in the lesson on API authentication.
  • Unknown resource: GET /api/v1/cart/items/99999 → 404 Not Found.
Eight classes of invalid request to the cart API, the status its contract requires for each, and a wrong answer that shows a defect in the API’s validation.
Invalid request classExampleExpected statusA wrong answer to report
Malformed syntax{"productId": 42,400Pass500: the parser error is unhandledFail
Missing field{"quantity": 2}422pointer #/productIdPass500, or 201 with an empty value storedFail
Wrong type"quantity": "2"422Pass201: the string was silently convertedFail
Invalid value"quantity": 11422Pass201: the limit of 10 is checked only in the UIFail
Wrong media typeContent-Type: text/plain415Pass500, or an HTML error pageFail
Wrong methodPUT /api/v1/cart/items405with Allow: GET, POSTPass404 or 500, no Allow headerFail
Bad authenticationno Authorization header401with WWW-AuthenticatePass422 with field errors: the body was read firstFail
Unknown resourceGET /api/v1/cart/items/99999404Pass200 with an empty body, or 500Fail

Status codes follow RFC 9110. This contract uses 400 for malformed syntax and 422 for invalid values; another may use 400 for both. In every row the error body follows the documented format and no data changes.

Try it: Negative Test Generator Try it: API Test Data Generator

Boundary testing for APIs

Boundary value analysis works on an API as on a form, but the boundaries are written in the OpenAPI schema. The JSON Schema keywords minimum, maximum, minLength, maxLength, minItems and maxItems (and the exclusive variants) each give a boundary and its neighbours. Query parameters have them too: a page size limit from 1 to 100 gives 0, 1, 100 and 101. RFC 9110 defines 422 for request content, so many APIs answer an invalid query parameter with 400 even when they use 422 for body values, as in the example below; the contract decides.

The contract must also say what happens beyond a limit: reject the request, or clamp the value, for example treat limit=500 as 100. Both are testable; unspecified behaviour is a question for the team. Some limits are never written in the schema at all: OWASP API4:2023, Unrestricted Resource Consumption, names missing limits such as records per page and maximum upload size.

  • Large integers: RFC 8259 notes that implementations agree exactly on JSON integers only between -9007199254740991 and 9007199254740991 (2^53 - 1), so a JavaScript client can silently round a larger ID.
  • Body size: just under and just over the documented limit, expecting 413 Content Too Large (RFC 9110) above it, on an environment you are allowed to load.
quantity (body):  { "type": "integer", "minimum": 1, "maximum": 10 }
  0 -> 422      1 -> 201      10 -> 201      11 -> 422

limit (query):    integer, 1..100, default 20
  0 -> 400      1 -> 200      100 -> 200     101 -> 400 (reject)
                                             101 -> 200, at most 100 items (clamp)

Try it: Boundary Value Generator Try it: OpenAPI → Test Cases

The error response contract: RFC 9457 problem details

A negative test needs an expected error body, not only a status. RFC 9457, Problem Details for HTTP APIs, which replaced RFC 7807, defines one: the media type application/problem+json and a JSON object with five standard members. An API may add extension members, such as a list of field errors, and clients must ignore extensions they do not recognise. In the example below, as in the RFC, each field error has a pointer: a JSON Pointer (RFC 6901) to the field in the request body, written in its URI fragment form #/quantity.

Your team does not have to use RFC 9457, but every endpoint should follow one documented format. Include the errors that the application code does not produce itself, such as a 404 from the router or a 413 from a proxy: they are the most likely to arrive in another format, sometimes as an HTML page.

  • type: a URI for the kind of problem. Clients and tests branch on it, not on the wording of title. When it is absent, its value is about:blank.
  • title: a short summary of the type, the same for every occurrence except for translation.
  • status: if present, identical to the code in the status line, as RFC 9457 requires.
  • detail: this occurrence, written to help the client correct the request, not to debug the server.
  • instance: optional, a URI reference to this occurrence, useful for finding it in the logs.
  • No internals: no stack trace, SQL, file path or version number, and no password or token echoed back. RFC 9457 warns against such leaks, and OWASP API8:2023 counts them as security misconfiguration.
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/validation-error",
  "title": "The request contains invalid fields.",
  "status": 422,
  "detail": "quantity must be between 1 and 10.",
  "errors": [
    { "pointer": "#/quantity", "detail": "must be between 1 and 10" }
  ]
}

Try it: Mock Error Response Generator Try it: API Response Validator

Worked example: negative tests for adding a cart item

Contract: POST /api/v1/cart/items needs a Bearer token and a JSON body with productId (integer, at least 1) and quantity (integer, 1 to 10). The path supports GET and POST; unknown properties are rejected. Errors use application/problem+json: 400 for a malformed body, 422 for invalid values. Each test starts with an empty cart, and product 42 exists.

Each test breaks one rule, and afterwards GET /api/v1/cart must still return an empty cart: a correct status with a stored item is still a failed test.

  • NA-04 is one of four boundary tests for quantity: "quantity": 0 must also return 422, and its positive partners "quantity": 1 and "quantity": 10 must return 201.
  • NA-09 breaks two rules on purpose to test precedence: the team agreed that credentials are checked before the body, so anonymous callers never learn the validation rules.
  • If NA-05 returned 201 with a line price of 1.00 SAR, that is a critical defect: the client set the price.
ID     Request (one rule broken)                      Expected
NA-01  {"productId": 42, "quantity": 2,               400, problem+json
NA-02  {"quantity": 2}                                422, pointer #/productId
NA-03  {"productId": 42, "quantity": "2"}             422, pointer #/quantity
NA-04  {"productId": 42, "quantity": 11}              422, pointer #/quantity
NA-05  {"productId": 42, "quantity": 2, "price": 1}   422, pointer #/price
NA-06  valid body, Content-Type: text/plain           415
NA-07  PUT /api/v1/cart/items                         405, Allow: GET, POST
NA-08  valid body, no Authorization header            401, WWW-Authenticate: Bearer
NA-09  no Authorization header, {"quantity": 0}       401, not 422

Try it: REST API Tester Try it: API Request Builder

Common mistakes

  • Asserting only "status is 4xx": a 400 where the contract says 401 hides an authentication check that runs too late.
  • Checking the response but not the data: the API answered 422, yet the item was saved. Follow every rejected write with a read.
  • Breaking several rules in one request, so it is unclear which rule the API enforced, outside a deliberate precedence test.
  • Testing only the errors of the application code and forgetting those of the router, the framework and the proxy.
  • Accepting a 500 because "at least it was rejected": a 5xx for a client mistake means validation is missing.

The QA perspective

Ask for the error contract in refinement, before the endpoint is built: the status for each class, the body format, whether unknown properties are rejected or ignored, whether values beyond a limit are rejected or clamped, and the order in which credentials, method, media type and body are checked. Get the answers into the OpenAPI document, which developers, client teams and tests all read. Cases generated from it are a good start; you still choose the classes and boundaries that matter for the risk.

Negative API tests suit data-driven automation: one row per class or boundary value with the request, the expected status and the expected type, run on every change. A bug report needs the exact request (a cURL command is ideal), the full response, the contract rule it breaks and any side effect. A badly worded title is minor; corrupt data, a client-set price or a leaked stack trace is serious. Send invalid requests only to systems you own or have written permission to test.

Practice exercise

Judge six error responses from an appointments API

Your task

A clinic booking API has the contract below, and a colleague pasted the results of six negative tests. For each one, write the expected response, decide whether the test passed, and write a one-line bug title for each failure.

Then list the missing boundary tests for date and notes, with the expected status.

Material
Contract: POST /api/v1/appointments    (the path supports GET and POST)
Auth:     Bearer token, checked before the body
Body:     doctorId  integer >= 1, required
          date      "YYYY-MM-DD", from today to 60 days ahead, required
          notes     string, at most 500 characters, optional
Errors:   application/problem+json; malformed body 400; invalid values 422
Today:    2026-11-01; tests #1 to #5 send a valid token

#1{"doctorId": 7, "date": "2026-11-05", "notes": "Back pa      (body cut off)
    -> 500  Content-Type: text/html, an error page with a stack trace
#2  {"date": "2026-11-05"}
    -> 422  problem+json, errors: [{"pointer": "#/doctorId", "detail": "is required"}]
#3  {"doctorId": "7", "date": "2026-11-05"}
    -> 201  {"id": 5521, "doctorId": 7, "date": "2026-11-05"}
#4  {"doctorId": 7, "date": "2026-11-05", "notes": "<501 characters>"}
    -> 422  problem+json, {"status": 400, "title": "Bad Request", ...}
#5  DELETE /api/v1/appointments
    -> 404  problem+json, {"title": "Not Found"}
#6  no Authorization header, body {"date": "tomorrow"}
    -> 422  problem+json, errors for #/doctorId and #/date
Steps
  1. Find the rule each request breaks and the expected status and body.
  2. Compare the actual status line, Content-Type and body, including status.
  3. Mark each test passed or failed; write a bug title for each failure.
  4. Derive the missing boundary values from the contract.
Show the model answer
Model answer

#1 failed: truncated JSON is malformed syntax, so 400 with application/problem+json was expected; the HTML 500 with a stack trace leaks internals. Bug: "Truncated JSON on POST /api/v1/appointments returns 500 with a stack trace".

#2 passed: 422, the documented format and a pointer to the missing field. Confirm that nothing was created.

#3 failed: "7" is a string, so 422 with the pointer #/doctorId was expected; the API converted it and created appointment 5521, which must be cleaned up. Bug: "doctorId sent as a string is accepted and the appointment is created".

#4 failed, minor: 422 is right, but the status member says 400, and RFC 9457 requires the two to match. Bug: "Problem details for a too-long notes report status 400 in a 422 response".

#5 failed: the path supports GET and POST, so DELETE should get 405 with Allow: GET, POST. Bug: "DELETE /api/v1/appointments returns 404 instead of 405".

#6 failed: credentials are checked first, so 401 with WWW-Authenticate: Bearer was expected; the 422 reveals the validation rules to an anonymous caller. Bug: "Unauthenticated appointment request gets field errors instead of 401".

Missing boundaries: for date, 2026-10-31 → 422, 2026-11-01 → 201, 2026-12-31 (60 days ahead) → 201, 2027-01-01 → 422; for notes, 500 characters → 201, plus "" and null, which the contract should cover. Also doctorId 0 → 422, and a doctorId that does not exist, whose status (404 or 422) is a question for the team.

Frequently asked questions

Should an invalid value get 400 or 422?

RFC 9110 defines 400 for a request the server sees as a client error and 422 for content that is well-formed but cannot be processed. Many APIs use 400 for malformed syntax and 422 for invalid values, as this lesson does; others use 400 for both. Either is acceptable when the contract states it and every endpoint follows it. A 500 or a 200 never is.

Does every API have to use RFC 9457?

No. RFC 9457 is an IETF standards-track format that an API can choose to adopt, not a requirement of HTTP. An existing format is fine if it is documented and used consistently. The advantage of RFC 9457 is that generic tools and libraries recognise application/problem+json, so clients need less custom code.

How many negative tests does one field need?

Start with one test per invalid class that applies to the field (missing, null, wrong type, invalid format) and the boundary values around each limit. Then prioritise by risk: a price or a quantity deserves more tests than a free-text comment. Tools that generate cases from the OpenAPI document give you the full list quickly; you decide which rows to keep.

Is it safe to send invalid requests to an API?

Only to systems you own or have written permission to test, preferably in a test environment. The payload ideas in this lesson are non-destructive, but very large bodies or many requests can still affect availability, so agree limits with the team first.

References

  • RFC 9110: HTTP SemanticsIETF · 2022The meaning of HTTP methods, header fields, status codes and content negotiation, independent of the wire version.
  • RFC 9457: Problem Details for HTTP APIsIETF · 2023The application/problem+json error format (type, title, status, detail, instance) for machine-readable API errors.
  • OWASP API Security Top 10 (2023)OWASP Foundation · 2023The ten most common API weaknesses: broken object-level authorization, broken authentication, unrestricted resource consumption and more.
  • JSON Schema SpecificationJSON SchemaThe vocabulary for describing and validating the shape of JSON documents (types, required properties, formats).

Practise with Mutqan tools

Test REST endpoints with full control over method, headers and JSON body, then assert on status, headers and JSON paths.

API Testing

Generate negative test cases for form fields and APIs: empty, null, wrong type, too long, injection, invalid format.

Test Design

Generate request bodies and datasets for API testing from a schema or field list, including invalid variants.

Test Data

Generate a structured test-case matrix (positive, negative, auth, boundary) from an OpenAPI / Swagger specification.

Automation

Validate an API response against expectations: status, required headers, JSON schema and field rules.

API Testing

Generate structured error responses (validation, auth, not found, rate limit, server error) in popular formats.

Mocking

Go deeper

Your progress on this topic

Part of the QA Roadmap