Resource library

QA How-To

HTTP 200 vs 201: What Testers Need to Know

Learn HTTP status 200 vs 201 semantics, headers, bodies, REST API test cases, automation examples, edge cases, and interview-ready explanations for QA.

25 min read | 3,710 words

TL;DR

HTTP 200 means the request succeeded, while HTTP 201 means the request succeeded and created one or more resources before the response completed. Testers should verify not only the code, but also resource identity, Location, representation, persistence, duplicate behavior, and the endpoint's documented contract.

Key Takeaways

  • Use 200 when a request succeeds and the response represents a completed result, not specifically a newly created resource.
  • Use 201 when the request creates a resource before the response is sent, and validate the new resource identity.
  • A strong 201 test checks the Location header or another documented identifier, the response body, and persisted server state.
  • POST does not automatically require 201, because actions, searches, and synchronous calculations can correctly return 200.
  • PUT can return 201 when it creates the target resource and 200 or 204 when it updates an existing one.
  • Test retries and duplicate submissions so that create semantics do not hide duplicated resources.
  • Treat status, headers, representation, persistence, authorization, and side effects as one response contract.

The http status 200 vs 201 distinction is about the result of a successful request. 200 OK says the request succeeded and the response carries the result. 201 Created says the request succeeded and caused one or more resources to be created before the server sent the response. A tester should never decide between them from the HTTP method alone.

That difference shapes contract tests, negative tests, retry tests, and interview answers. A create endpoint that returns 200 may be intentionally modeling an action, or it may be hiding an imprecise contract. A 201 response can also be wrong if nothing durable was created. This guide shows how to evaluate the complete behavior, with current Playwright APIs and practical test oracles.

TL;DR

Question 200 OK 201 Created
What does it communicate? The request succeeded The request succeeded and created resource state
Is creation required? No Yes
Typical methods GET, POST actions, PATCH, PUT POST create, PUT create
Body allowed? Yes Yes, or an empty body when the resource is identified elsewhere
Important headers Content-Type, cache validators as applicable Location for the primary created resource, plus representation metadata
Main test oracle Correct completed result Correct resource identity and persisted creation

Method plus code is only the starting point. The endpoint description, response headers, schema, new state, and side effects form the actual contract.

1. HTTP Status 200 vs 201 in Precise Terms

200 OK is the general successful-response status. Its meaning depends on the request method. For GET, the selected resource representation appears in the response. For HEAD, the server sends the headers that a corresponding GET would send, without the response content. For POST, 200 can carry the result of an action. For a synchronous validation, calculation, search, command, or RPC-style operation, that can be exactly right.

201 Created is narrower. The request must have been fulfilled and resulted in one or more new resources by the time the response is sent. The primary resource is usually identified by a Location header or by the target URI when the client chose it. A response representation can also contain an identifier or links. Creation is the key fact, not the presence of JSON.

Both codes are successful 2xx responses, but they are not interchangeable labels for a green request. A client may use 201 to start a resource-specific workflow, read Location, store the new identifier, or issue a follow-up GET. Monitoring may count creations separately. Contract consumers may explicitly allow one code and reject the other.

Testers should turn the semantic difference into an observable claim. For 200, ask what operation completed and what result should be represented. For 201, ask what was created, where it can be retrieved, whether the caller can access it, and whether the same business action accidentally created more than one resource. This is the foundation of correct http status 200 vs 201 validation.

2. Compare 200 OK and 201 Created Across Methods

The HTTP method gives context, but it does not mechanically select a response. A successful GET normally returns 200 because it retrieves a representation. A POST that creates /orders/8472 normally returns 201. A POST to /reports:generate-preview that calculates a preview without creating a report resource can return 200 with the preview.

Request pattern Likely response Why What QA verifies
GET /users/42 200 Existing representation returned Schema, selected user, caching metadata
POST /users 201 New user resource created New ID, Location, persisted user, defaults
POST /tax-calculations as a pure calculation 200 Completed result, no durable resource Formula inputs, output schema, no saved object
PUT /profiles/42 when profile exists 200 or 204 Existing resource updated Final state and documented representation behavior
PUT /profiles/42 when profile does not exist 201 Client-addressed resource created Creation at target URI and persisted state
PATCH /tickets/42 200 or 204 Existing resource changed Patch semantics, final representation, concurrency
POST /jobs for asynchronous processing 202 Work accepted, not completed Operation URL, lifecycle, eventual outcome

The final row matters because teams sometimes use 201 merely to say that a job record was queued. If the API genuinely creates an operation resource before responding, 201 can describe that resource. If it only accepts work that may later be rejected or never complete, 202 is usually the clearer signal. The resource model and documentation decide the assertion.

Do not encode a blanket rule such as every POST must return 201. That rule creates false defects and misses semantic defects. Build a method, operation, outcome matrix from the OpenAPI description and product behavior. Then test each branch.

3. Decide Whether the Endpoint Truly Created a Resource

A resource is an addressable concept in the API, not necessarily one database row. An order, uploaded document, saved search, workflow operation, or reservation can be a resource. The test question is whether a new server-visible identity now exists because of this request. If that identity can be retrieved, linked, updated, canceled, or observed, 201 is a strong candidate.

Start with three checks. First, did the operation create new state rather than only compute or return existing state? Second, was creation completed before the response? Third, can the created result be identified through the request URI, Location, a response link, or a documented identifier? If any answer is unclear, the API contract needs refinement before the test can be authoritative.

A command can change state without creating a resource. POST /accounts/42/lock may complete an action and return 200 with the updated account, or 204 without content. A bulk import can create many records. It may return 201 with a representation that describes the created resources, but one Location generally identifies the primary resource, such as an import batch. A login endpoint can create a server session yet return 200 because its public operation is authentication and token delivery. Status semantics should describe the exposed resource model, not internal table activity.

Confirm persistence through a supported API, database read in a component test, or controlled event ledger. A 201 followed by a GET that consistently returns 404 is a contract failure unless documented eventual visibility applies. If creation is asynchronous, require an operation model and a bounded eventual assertion. The status code alone is not proof.

For deeper state and retry coverage, use the API idempotency testing guide to distinguish one logical creation from duplicate transport attempts.

4. Validate Location, Identifiers, and Representation

A 201 response should make the primary created resource discoverable. Location is the standard response header for a URI reference to that resource. The URI can be absolute or relative under modern HTTP syntax, but client and organizational standards may impose a stricter rule. Test what the published contract promises. Do not assume the header must always exist if the request target already identifies the created resource, as with PUT /profiles/42, or if another well-defined mechanism identifies it.

When Location exists, parse it as a URI reference rather than concatenating strings blindly. Resolve a relative value against the response URL, verify the scheme and host against allowlisted expectations, and follow it with the same authorized test principal when appropriate. The resulting GET should identify the same resource as the response body. Prevent open redirects or cross-tenant disclosure from hiding behind a formally valid header.

Validate consistency among all identity sources: Location, body id, body self link, request-generated key, and follow-up representation. If Location ends in /orders/8472 but the body says id: 8473, the response is unusable even though it is 201. If the create response omits the body, Location or the target URI becomes more important. If both are absent and the client cannot identify what was created, file a contract problem.

For 200, focus on the representation of the completed operation. Verify Content-Type, schema, nullable fields, business values, and any validators such as ETag. A 200 response with an empty body can be legal in some contracts, but 204 is often more expressive when there is intentionally no content. Do not switch assertions without examining client compatibility and published behavior.

5. Build a Runnable Playwright API Test Harness

Playwright Test provides a supported request fixture and APIResponse methods, so browser installation is not required for API-only tests. The following example targets a documented test service. It fails unless API_BASE_URL and API_TOKEN point to an approved nonproduction environment. Install Playwright Test with npm install -D @playwright/test, then run npx playwright test.

// tests/orders.spec.ts
import { test, expect } from '@playwright/test';

const baseURL = process.env.API_BASE_URL;
const token = process.env.API_TOKEN;

if (!baseURL || !token) {
  throw new Error('Set API_BASE_URL and API_TOKEN for the test environment');
}

function authHeaders() {
  return {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  };
}

test('POST /orders returns 201 and an addressable order', async ({ request }) => {
  const customerReference = `qa-${crypto.randomUUID()}`;
  const createResponse = await request.post(`${baseURL}/v1/orders`, {
    headers: authHeaders(),
    data: {
      customerReference,
      items: [{ sku: 'QA-BOOK', quantity: 1 }],
    },
  });

  expect(createResponse.status()).toBe(201);
  const location = createResponse.headers()['location'];
  expect(location).toBeTruthy();

  const created = await createResponse.json();
  expect(created).toMatchObject({ customerReference, status: 'pending' });
  expect(created.id).toEqual(expect.any(String));

  const resourceURL = new URL(location, baseURL).toString();
  const getResponse = await request.get(resourceURL, { headers: authHeaders() });
  expect(getResponse.status()).toBe(200);
  await expect(getResponse.json()).resolves.toMatchObject({
    id: created.id,
    customerReference,
  });
});

All used methods are current Playwright Test APIs: request.post, request.get, status, headers, and json. crypto.randomUUID() is available in supported Node.js runtimes. The sample assumes a test SKU and response fields defined by the example contract. Change domain fields to match your API, but keep the identity and persistence assertions.

A safe suite creates unique references, targets an allowlisted environment, and cleans up using supported APIs. Never compensate for an ambiguous response by reading production databases or logging bearer tokens.

6. Test 200 Results Without Treating Them as Weak Success

A 200 test needs just as much domain evidence as a 201 test. Suppose POST /v1/shipping/quotes calculates available options but does not save a quote resource. The expected result is 200 with a representation of the completed calculation. Assert inputs are reflected correctly, monetary values follow currency rules, unavailable services are absent, and repeated requests do not create hidden customer state.

import { test, expect } from '@playwright/test';

test('quote calculation returns 200 without creating an order', async ({ request }) => {
  const response = await request.post(`${baseURL}/v1/shipping/quotes`, {
    headers: authHeaders(),
    data: {
      destinationPostalCode: '10001',
      parcels: [{ weightGrams: 1200 }],
    },
  });

  expect(response.status()).toBe(200);
  expect(response.headers()['location']).toBeUndefined();

  const result = await response.json();
  expect(result.options).toEqual(
    expect.arrayContaining([
      expect.objectContaining({ service: expect.any(String) }),
    ]),
  );
});

Do not claim that absence of Location proves no resource was created. That assertion is supporting evidence, not a complete oracle. Use audit endpoints, a unique correlation field, or a component-level repository query to show that the quote operation did not persist an order. Also verify that 200 is not being used for application failures such as { success: false, error: ... }. Transport success with a failure envelope often breaks caches, observability, client retries, and generated SDK expectations.

For GET responses, test conditional behavior when the contract supports it. An initial 200 can include ETag; a later conditional GET might return 304, not 200. For actions, test both successful result and domain conflict paths. The purpose is not to maximize 200 assertions. It is to prove that 200 accurately represents the completed outcome.

7. Test 201 Creation as a State Transition

Treat creation as a before, request, response, after sequence. Before the request, a lookup by the unique business reference should find nothing. The create call should return 201. Afterward, exactly one matching resource should exist, carry server defaults, belong to the correct tenant, and be visible through its documented URL. This approach detects false 201 responses and duplicate records.

Validate server-generated fields by type and invariant, not fragile exact values. IDs should be nonempty and unique across independent creates. Creation timestamps should fall within a captured time window, allowing reasonable clock behavior. Default status should match the state model. The caller must not be able to inject protected fields such as owner, approval state, price, or audit identity.

Test two independent valid create requests. They should normally produce different resource identities even when ordinary fields match, unless a domain uniqueness rule deliberately merges or rejects them. Then test the same logical request retried with an idempotency key. The result should follow the documented replay contract and create one resource. A service that returns a fresh 201 and fresh ID for every network retry can cause expensive duplicate orders.

Creation and visibility may cross storage boundaries. If the create response commits to immediate retrieval, the follow-up GET should be reliable. If read replicas are eventually consistent, the contract should say so, and the test should poll with a deadline and useful diagnostics rather than sleep for a fixed interval. The resource still must have been created before a 201 was sent. Eventual projection does not excuse losing the authoritative write.

Also test rollback. Validation failure, authorization failure, conflict, and dependency failure must leave no partial resource unless a documented failed-operation resource is part of the model.

8. Cover PUT Upsert, POST Actions, and Async Edge Cases

PUT is the best interview edge case. Because the client addresses the target URI, the same endpoint can return 201 when no current representation existed and creation succeeds, then return 200 or 204 when a later PUT replaces that resource. Your test must arrange both starting states. If the API always returns 200, check whether its contract intentionally treats PUT as an update-only operation or loses the creation signal.

POST is flexible. A create to a collection often returns 201. A POST action can return 200 with a result, 204 with no content, or 202 when accepted for later processing. A batch endpoint can have partial outcomes that need a response model more detailed than a single success code. Never infer all item results from the outer 200 or 201. Verify per-item status, atomicity, and rollback rules.

Asynchronous creation needs special care. If POST /exports immediately creates an export job resource, 201 plus a job Location may be correct even though the file is not ready. The created resource is the job, not the final file. If no job exists yet and the system merely accepted a command, 202 plus a monitor URL is clearer. Test the exact lifecycle: accepted, running, succeeded or failed, retention, and access control.

A 200 response can also return an existing resource after a deduplicated create attempt. Some APIs replay the original 201, some return 200, and some use a conflict response. There is no safe universal assertion without the idempotency contract. Document the status set in OpenAPI and prove that all variants point to one resource.

Review API error handling and negative testing when success branches interact with validation, conflict, or dependency errors.

9. Design the HTTP Status 200 vs 201 Test Matrix

A compact matrix prevents status-only coverage. For each endpoint, capture precondition, request variation, expected status, required headers, body schema, authoritative state, side effects, and cleanup. Include at least these rows for a create-capable endpoint:

  1. Valid new request -> 201, correct identity, one persisted resource.
  2. Valid action with no new public resource -> 200, correct result, no hidden creation.
  3. Missing required field -> documented 400 or 422, no partial resource.
  4. Unauthenticated caller -> 401, no creation.
  5. Authenticated but forbidden caller -> 403, no creation.
  6. Unique-field conflict -> documented 409, original resource unchanged.
  7. Same idempotency key and same payload -> documented replay, one resource.
  8. Same idempotency key and different payload -> conflict, original unchanged.
  9. Dependency fails before commit -> failure response, no misleading 201.
  10. Response is lost after commit -> retry resolves to the original resource.

Add content negotiation. If the client requests an unsupported response format, verify 406 or the documented behavior. If it sends an unsupported request media type, verify 415 before business creation. Confirm schema-generated clients accept exactly the statuses the server emits. OpenAPI can define separate 200 and 201 schemas, headers, and examples. A catch-all 2XX assertion may hide a breaking response.

Use a coverage table in test design reviews rather than copying status assertions into every test. Pair-based selection can reduce combinations, but never remove high-risk identity, authorization, and duplicate cases. For broader request design, the REST Assured given when then guide shows how to keep Java API tests readable.

10. Verify Contracts, Schemas, and Consumer Expectations

OpenAPI should list 200 and 201 only where each is possible. A 201 response can define a Location header and a resource schema. A 200 response can define the action result. Avoid documenting a generic success response that leaves clients guessing. Generated clients frequently branch on declared statuses, so an undocumented switch from 200 to 201 can be breaking even though both are 2xx.

Contract tests should compare more than JSON shape. Assert status, required headers, media type, field formats, nullable behavior, and links. Check that examples agree with schemas. If the server returns no body for 201, the schema should not require one. If the body is returned, a client should not have to scrape an ID from free text.

Consumer-driven contract tests are valuable when clients have different assumptions. A mobile client may accept any 2xx while a workflow service requires 201 and Location. The provider team needs to know both. Still, consumer expectations cannot redefine HTTP semantics. If a client demands 201 from an operation that creates nothing, fix the shared model rather than encoding misinformation.

Monitor production by operation and exact status class without storing sensitive payloads. A sudden shift from 201 to 200 may reveal a deployment regression. A rise in 201 paired with falling persistence could signal rollback or replication faults. Metrics do not replace tests, but they reveal behavior under real concurrency and dependency conditions.

Keep assertions deliberate. expect(status).toBeGreaterThanOrEqual(200) is useful for a broad health probe, not a resource contract test. Exact codes communicate product meaning and catch accidental framework defaults.

11. Review Security, Caching, and Observability

A 201 response can leak resource identifiers through Location, logs, analytics, or error reports. Verify that identifiers are safe to expose, tenant boundaries are enforced on the follow-up URL, and the response never builds Location from an untrusted Host header. If identifiers are guessable, authorization must still protect every retrieval. Security must not depend on obscurity.

Created responses and 200 responses have different caching contexts, but neither should be cached casually when personalized or sensitive. Check explicit cache controls for authentication, payment, profile, and token operations. A shared cache must not replay one user's 200 result or 201 representation to another user. GET 200 responses can use normal validators when appropriate. Create responses often need conservative controls.

Trace the request through persistence and downstream events using a sanitized correlation ID. Do not confuse a trace ID with the resource identity or idempotency key. Logs should answer whether a 201 corresponded to one commit, but should not store tokens, full personal data, or reusable credentials. Metrics should distinguish endpoint and status while avoiding unbounded resource IDs as labels.

Test audit events. A creation should usually produce one appropriate audit record attributed to the caller. A 200 calculation may need access telemetry but should not claim a resource was created. A replayed idempotent request should not emit duplicate business events merely because it produced another HTTP response.

These nonfunctional checks make the semantic distinction operationally trustworthy. Status correctness is useful only when clients can safely locate the created resource and operators can reconcile what happened.

Interview Questions and Answers

Q: What is the core difference between HTTP 200 and 201?

200 indicates successful completion and represents the result in the context of the request method. 201 adds a specific claim: the request completed and created one or more resources before the response. I verify the code together with resource identity and state.

Q: Must every successful POST return 201?

No. POST can invoke an action, calculation, search, login, or other processing that creates no public resource, and 200 may be correct. If processing is only accepted for later completion, 202 may be correct. The resource model determines the status.

Q: Can PUT return 201?

Yes. When PUT creates the resource at the client-selected target URI, 201 is appropriate. A later PUT that replaces an existing representation commonly returns 200 with a representation or 204 without one. Tests need both initial states.

Q: Is Location mandatory for every 201?

The response should identify the primary created resource through Location or the effective request URI, depending on the operation. Location is the clearest pattern for POST collection creates. I test the documented contract and ensure body IDs and links agree with it.

Q: How would you test a 201 response?

I assert 201, validate Location and the response schema, capture the ID, retrieve the resource, and verify its persisted business fields and ownership. I also prove only one resource and one side effect exist. Negative and retry paths must not create partial or duplicate resources.

Q: Can a 201 response have no body?

Yes, if the created resource is still clearly identified, commonly through Location or the target URI. The API contract should document the empty response. A representation is often convenient, but it is not what makes the response a 201.

Q: Why is accepting any 2xx weak in a contract test?

Different 2xx codes describe different lifecycle outcomes. A broad assertion can accept 200 when creation should be communicated, 201 when nothing exists, or 202 when work is incomplete. Exact assertions protect consumer behavior and domain meaning.

Q: What if a create request returns 200?

I first check the published contract and resource model. It may intentionally return an action result or an existing deduplicated resource. If a new addressable resource was created and the contract promises creation semantics, I report the mismatch and its client impact.

Common Mistakes

  • Assuming POST always means 201 and GET always means 200 without reading the operation contract.
  • Checking only the numeric code and never proving resource creation or completed result behavior.
  • Requiring a JSON body for 201 while ignoring valid Location-based identification.
  • Accepting mismatched IDs in Location, response body, and the follow-up GET.
  • Returning 200 with an embedded failure object for validation or authorization errors.
  • Using 201 for work that is merely queued and has not created the represented resource.
  • Following Location automatically without validating its host, tenant access, and authorization.
  • Skipping retries, concurrency, idempotency-key reuse, and duplicate-state assertions.
  • Treating a switch between 200 and 201 as harmless to generated clients and consumer contracts.
  • Testing persisted rows but ignoring messages, charges, inventory changes, audit events, or notifications.

Conclusion

The http status 200 vs 201 choice is a statement about outcome, not a style preference. Use and test 200 for a successfully completed result. Use and test 201 when the request completed creation of an identifiable resource. Method, status, headers, body, persistence, and side effects must tell one consistent story.

Choose one create-capable endpoint in your suite and expand its assertion today. Verify Location or target identity, retrieve the new state, check authorization and defaults, and prove retries do not create duplicates. That is the difference between checking a green number and testing an API contract.

Interview Questions and Answers

Explain HTTP 200 vs 201 in one interview answer.

200 means the request completed successfully in the context of its method. 201 means successful completion also created one or more resources before the response. I validate 201 by checking identity, Location when specified, persistence, and side effects.

Does every POST request need to return 201?

No. POST is a processing method and can represent actions or calculations that create no addressable resource. Those can return 200, while deferred processing may return 202. A create operation normally uses 201.

What would you assert for a 201 response?

I assert the exact code, required headers, media type, schema, stable ID, and consistency between Location and body links. Then I retrieve authoritative state and verify ownership, defaults, one resource, and expected downstream effects. I also repeat the logical request to verify the documented duplicate behavior.

When can PUT return 201?

PUT can return 201 when the target resource did not exist and the request creates it at the client-selected URI. If it already existed, successful replacement commonly returns 200 or 204. I arrange both preconditions in the test suite.

Can 201 Created be returned for asynchronous work?

It can be used if the request has already created an operation or job resource, even if that job later produces another result. If the server only accepted work and has not created the represented resource, 202 is clearer. Tests must identify which resource the response describes.

How do retries affect 201 testing?

A lost response can cause the client to repeat a create request. I use a stable idempotency key, retry the logical operation, and verify one resource and one downstream effect. The replay status must match the documented contract.

Why not accept any successful status in an API test?

Successful statuses carry different lifecycle semantics that clients act on. Accepting any 2xx can hide a false creation claim, incomplete processing, or missing response content. Exact codes plus state assertions make the test meaningful.

How do you report a 200 response from a documented create endpoint?

I show the contract expectation, actual status, and evidence that a new resource exists. I explain consumer impact such as missing Location handling or generated-client mismatch. I also confirm that idempotency or an action-style model does not intentionally explain the behavior. This evidence separates a status defect from a documentation defect.

Frequently Asked Questions

What is the difference between HTTP status 200 and 201?

200 OK indicates that the request succeeded, with meaning refined by the request method. 201 Created indicates that the request succeeded and created one or more resources before the response was sent. Testers should verify the resulting state and identity, not only the number.

Should POST return 200 or 201?

A POST that creates a new addressable resource commonly returns 201. A POST that performs a synchronous action or calculation without creating such a resource can return 200. The documented resource model decides the expected code.

Does a 201 response require a Location header?

A 201 response should identify the primary created resource through Location or the effective request URI. Location is especially useful for collection POST requests. Tests should follow the documented rule and verify consistency with body IDs and links.

Can HTTP 201 have a response body?

Yes. A 201 response can include a representation of the created resource, and many APIs do so to avoid an immediate GET. It can also be empty when the resource is identified clearly elsewhere and the contract documents that behavior.

Can PUT return both 200 and 201?

Yes. PUT can return 201 when it creates the resource at the target URI. When it updates an existing resource, it commonly returns 200 with a representation or 204 without response content.

How do you test a 201 Created API response?

Assert the exact status, validate Location and schema, compare all returned identifiers, retrieve the resource, and verify persisted fields, defaults, tenant ownership, and side effects. Add duplicate, retry, authorization, validation, and rollback cases.

Is any 2xx assertion enough for API automation?

It may be enough for a simple availability probe, but it is too weak for a contract test. 200, 201, 202, and 204 express different outcomes, so exact assertions catch lifecycle and consumer compatibility defects.

Related Guides