Skip to content

Concurrent changes: ETag and If-Match

Read a versioned resource, send the version back as If-Match, and resolve a 409 conflict cleanly.

5 minute readAPIUpdated: 10/25/2026

On this page

Before you start

For
Anyone who changes scenes, playlists, programs, roles, or displays through the API.
Prerequisite
an ak_… key with a write permission, in the example scene.update (class Content authoring)
Where
command line

This guide shows you how to change a versioned resource so that a concurrent change by another client is not lost: You read the tag from the ETag header, send it back as If-Match, and resolve a 409 conflict cleanly.

How the version check works

Five resources carry a version field: scenes, playlists, programs (schedules), roles, and displays. When you make a change, you send the value you read as the If-Match header. If someone else has written in the meantime, the API answers with 409 Conflict.OptimisticLock, and you read again.

The version is the ETag. It appears twice in the response: as the field version in the body and as the header ETag: "7", a strong tag, not a W/ one. Both carry the same value, and which one you read is your choice. The header is also exposed to browser clients on other origins.

The read route and the change route of the same resources send ETag: GET and PATCH on /v1/scenes/{id}, /v1/playlists/{id}, /v1/schedules/{id}, /v1/roles/{id}, and /v1/players/{id}, as well as POST /v1/scenes/{id}/versions/{vid}/revert. If a response carries no version in an exceptional case, the header is omitted instead of claiming a zero.

Steps

  1. Read the resource and note the version, from the header or from the body:

    curl -sS -D - -o /dev/null "$SZENERA_API/v1/scenes/scn_01KJPX8NH0SZENE00000000001" \
      -H "Authorization: Bearer $SZENERA_API_KEY" | grep -i '^etag:'
    
    ETag: "7"
    
  2. Send the change with If-Match and exactly this value:

    curl -sS -X PATCH "$SZENERA_API/v1/scenes/scn_01KJPX8NH0SZENE00000000001" \
      -H "Authorization: Bearer $SZENERA_API_KEY" \
      -H "Content-Type: application/json" \
      -H 'If-Match: "7"' \
      -d '{ "description": "Startbild im Schaufenster: Wortmarke, Wochenplan und Uhrzeit." }'
    

    The response is the scene with the new version (shortened here to four fields):

    { "id": "scn_01KJPX8NH0SZENE00000000001", "name": "Notdienst & Wetter", "version": 8, "updatedAt": "2026-07-06T08:30:00.000Z" }
    

    A patch with identical content leaves the version as it is.

  3. If 409 comes back, someone wrote between steps 1 and 2:

    {
      "code": "Conflict.OptimisticLock",
      "message": "Scene draft was modified concurrently",
      "traceId": "trc_…",
      "details": { "sceneId": "scn_01KJPX8NH0SZENE00000000001", "expectedVersion": 7 }
    }
    

    Read the resource again (step 1), apply your change to the fresh state, and send again with the new version. Do not repeat the patch blindly with the old version, because that ends in 409 again.

Options

If-Match accepts three spellings of the same value:

Header Effect
If-Match: "7" the standard form: the tag exactly as the response sent it
If-Match: W/"7" the weak form, also accepted
If-Match: 7 the bare number from the body, kept as is so that no running client breaks
If-Match: 0 or a non-numeric value 422 Validation.Failed (“If-Match must be a positive resource version”)
no If-Match on routes that enforce the header: 422 Validation.Failed (“If-Match header required for this operation”)
Displays a missing header ends in 409 Conflict.OptimisticLock (version comparison against 0), never in 422

Five operations enforce the header and list it as a required parameter in the reference: PATCH /v1/scenes/{id}, POST /v1/scenes/{id}/versions/{vid}/revert, PATCH /v1/playlists/{id}, PATCH /v1/schedules/{id}, and PATCH /v1/roles/{id}. PATCH /v1/players/{id} accepts it but does not require it: Without the header, the route checks against version 0 and so runs into the same conflict as a stale value. For programs, version is located under schedule.version, for roles under role.version, and for displays under player.version.

What details carries for a conflict depends on the resource. Scenes name sceneId and expectedVersion. Roles name resource, resourceId, expectedVersion, and actualVersion. Displays name playerId, expectedVersion, and actualVersion. Playlists and programs name entity, id, and expectedVersion.

Command line. szenera scene update <id> --version=7 … sends the version as If-Match. Without --version, the command reads it itself beforehand. --retry reads again once on a conflict and repeats. The Fassung (version) column in szenera scene list is the same value. Details are described under Commands for your organization.

Check the result

  • The response to the patch is 200 with the resource, and version has increased by one (or is unchanged if the patch changed nothing).
  • A second patch with the old If-Match answers 409 Conflict.OptimisticLock.

If something goes wrong

Symptom Meaning Solution
409 Conflict.OptimisticLock Another client made a change between your read and your write. Read again, apply the change again, and send with the new version.
422 Validation.Failed with “If-Match header required for this operation” One of the five routes above enforces the header. Send If-Match with the tag you read.
422 Validation.Failed with “If-Match must be a positive resource version” The value is not a number from 1 upward, for example a self-built tag made of several parts. Send the tag from the response: "7", W/"7", or 7.
409 Conflict.OptimisticLock on a display although nobody wrote The header was missing, so the route compares against version 0. Send If-Match with the tag you read.
You expect 412 There is no 412. The conflict is always 409. Branch on 409 and the code Conflict.OptimisticLock.
No ETag header in the response The route is not versioned, or the response carried no version in an exceptional case. Read version from the body. If that is missing too, the resource has no version.