Skip to main content
REST API

Conditional Writes (If-Match / 412)

RequestValidateHard

What you will learn

  • Use ETag + If-Match to prevent lost updates under concurrent edits (optimistic concurrency)
  • Map the status codes: 428 (precondition required), 412 (precondition failed), 200 (current ETag)
  • Always re-read the ETag after a successful write before the next conditional update

Description

Prevent lost updates with optimistic concurrency: a PUT must carry the current ETag via If-Match or it is rejected.

Test Scenario

// Read the current ETag, then prove stale writes are blocked.
const read = await request.get('/api/v1/etag?id=resource1');
const etag = read.headers()['etag'];

const noHeader = await request.put('/api/v1/etag?id=resource1&strict=1', { data: { name: 'x' } });
expect(noHeader.status()).toBe(428); // If-Match is mandatory in strict mode

const stale = await request.put('/api/v1/etag?id=resource1&strict=1', {
  headers: { 'If-Match': '"deadbeef"' },
  data: { name: 'x' },
});
expect(stale.status()).toBe(412); // someone else changed it first

const ok = await request.put('/api/v1/etag?id=resource1&strict=1', {
  headers: { 'If-Match': etag },
  data: { name: 'x' },
});
expect(ok.status()).toBe(200);
Endpoint/api/v1/etag?strict=1

Testing Tips

  • โ€ขGET the resource and capture its ETag response header
  • โ€ขIn strict mode (?strict=1) a PUT with no If-Match โ†’ 428 Precondition Required
  • โ€ขA PUT with a STALE If-Match โ†’ 412 Precondition Failed; only the current ETag succeeds
// Playwright API Testing - REST
import { test, expect } from '@playwright/test';
test('REST API request', async ({ request }) => {
const response = await request.get('/api/v1/resource');
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
const data = await response.json();
expect(data).toBeDefined();
});

Challenge ID: rest-conditional-write

2 frameworks available

API Playground

Send requests and inspect responses

Endpoint: /api/v1/etag?strict=1

Press Enter to send