Guard concurrent writes

When your system and people in the Circularo application may touch the same document, an unconditional write can silently overwrite their change. Document reads return the current version as an ETag, and writes accept it back in If-Match — three requests in total.

Before you begin

  • A document to work with. The example uses one created beforehand.

Step 1: Read the version from the ETag header

Document reads return the current version in the ETag response header:

GET /documents/aQ7xKvN2mRt5ZpB8cWdE
Authorization: Bearer YOUR_API_KEY
HTTP/2 200
ETag: "4"

{
    "id": "aQ7xKvN2mRt5ZpB8cWdE",
    "title": "Supplier contract",
    "trashed": false,
    ...
}

Keep the ETag value — the next steps use it to guard a write.

Step 2: Attempt a write guarded by a wrong version

Pass a version in the If-Match header to make a write conditional. If it does not match the document's current version — typically because somebody changed the document after you read it — the write is rejected instead of overwriting their change:

DELETE /documents/aQ7xKvN2mRt5ZpB8cWdE
Authorization: Bearer YOUR_API_KEY
If-Match: "999"
HTTP/2 412

{
    "status": 412,
    "type": "RESOURCE:PRECONDITION_FAILED",
    "message": "The resource was modified and no longer matches the version supplied in the If-Match header.",
    "requestId": "c58e2f7b9a044d168c3e5b7f2a94d0e1"
}

The 412 says the document moved on since the version you supplied. Nothing changed on the server, so you are free to read the document again and decide what to do with the change you found.

Step 3: Retry with the current version

With the version from the ETag you read earlier, the same delete succeeds:

DELETE /documents/aQ7xKvN2mRt5ZpB8cWdE
Authorization: Bearer YOUR_API_KEY
If-Match: "4"
HTTP/2 204

If-Match is optional everywhere: omit it and writes apply unconditionally, last write wins. Use it when several actors may touch the same document.

Complete example

A conditional write that re-reads and retries once when it loses the race, as a Node.js script. Error handling is minimal for brevity — in production, inspect the error envelope of non-2xx responses.

JavaScript
const BASE_URL = "https://sandbox.circularo.com/api/v1/public"; // your instance's base URL
const API_KEY = "YOUR_API_KEY";
const AUTH_HEADER = { "Authorization": `Bearer ${API_KEY}` };
const documentId = "aQ7xKvN2mRt5ZpB8cWdE"; // the document to delete

const currentVersion = async () => {
    const res = await fetch(`${BASE_URL}/documents/${documentId}`, { headers: AUTH_HEADER });
    if (!res.ok) throw new Error(`Read failed: ${res.status}`); // error handling kept minimal for brevity
    return res.headers.get("ETag");
};

let version = await currentVersion();
let res = await fetch(`${BASE_URL}/documents/${documentId}`, {
    method: "DELETE",
    headers: { ...AUTH_HEADER, "If-Match": version }
});

if (res.status === 412) {
    // somebody changed the document in the meantime - look at it again before insisting
    version = await currentVersion();
    res = await fetch(`${BASE_URL}/documents/${documentId}`, {
        method: "DELETE",
        headers: { ...AUTH_HEADER, "If-Match": version }
    });
}

console.log(`Delete finished with ${res.status}`);

Next steps

  • See a 412 next to the other error classes — Handle errors.

  • Guard the writes that change a document's content — Annotate and seal a document.

  • Which writes accept If-Match is documented per endpoint in the API reference.