Skip to content
Version v3.2.0

Optimistic Concurrency ​

Mutating endpoints in the Management API v2 and User Store API v2 that expose a versioned resource use HTTP optimistic concurrency control as defined by RFC 9110 §13.

The contract follows the standard ETag / If-Match exchange — no proprietary extensions — so any RFC 9110-compliant HTTP client can participate without a ProAuth-specific SDK.

Where this applies

Concurrency control is enforced for every resource whose DTO derives from ConcurrencyAwareDto. In v2 this includes (non-exhaustive): Tenant, ClientApp, IdpInstance, User, Group, ProAuthUser, ProAuthRole, ProAuthRoleAssignment, ClaimRule, Subscription, CustomView, and most other long-lived configuration entities. v1 endpoints do not enforce concurrency.

At a glance ​

HTTP methodServer emits ETag?Client must send If-Match?Conflict response
GETYes (on aware resources)Non/a
POST (create)Yes — ETag of the newly created resourceNon/a
PUT (replace)Yes — new ETag of the updated resourceYes412 Precondition Failed
PATCHYes — new ETag of the updated resourceYes412 Precondition Failed
DELETEn/aYes412 Precondition Failed

Missing the If-Match header on a write to an aware resource yields 428 Precondition Required. A malformed or syntactically inconsistent If-Match value yields 400 Bad Request.

ETag format ​

ProAuth emits strong, double-quoted ETags whose tag value is the lowercase 32-character hexadecimal form (the .NET Guid "N" format) of the entity's internal concurrency token:

http
ETag: "8b1f3c2d4e5f6789a0b1c2d3e4f50617"

Weak ETags (W/"…") are never emitted by ProAuth and must not be sent on If-Match.

Read–then–write workflow ​

  1. GET the resource and capture the ETag response header.
  2. Mutate the local copy.
  3. Send the mutation back with If-Match set to the captured ETag.
  4. On 200/201/204, capture the new ETag from the response and use it for the next mutation.
  5. On 412 Precondition Failed, refetch the resource and reconcile.
http
GET /api/management/v2/Tenant/3a7e1f5a-1b2c-4d5e-9f80-aa11bb22cc33 HTTP/1.1
Authorization: Bearer …

HTTP/1.1 200 OK
ETag: "8b1f3c2d4e5f6789a0b1c2d3e4f50617"
Content-Type: application/json

{ "id": "3a7e1f5a-…", "name": "Acme", … }
http
PUT /api/management/v2/Tenant/3a7e1f5a-1b2c-4d5e-9f80-aa11bb22cc33 HTTP/1.1
Authorization: Bearer …
If-Match: "8b1f3c2d4e5f6789a0b1c2d3e4f50617"
Content-Type: application/json

{ "id": "3a7e1f5a-…", "name": "Acme Holdings", "subscriptionId": "…" }

A successful response carries the new token to use for the next write:

http
HTTP/1.1 200 OK
ETag: "f60a3b8c5d2e1f9087564738291a0b1c"

Error responses ​

412 Precondition Failed — Conflict ​

The request reached the server but its If-Match does not match the current server-side token. Another client has updated the resource since the token was issued. Refetch, reconcile, and retry.

http
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.13",
  "title": "Precondition Failed",
  "status": 412,
  "detail": "The supplied If-Match precondition does not match the current resource version."
}

428 Precondition Required — Missing If-Match ​

The endpoint requires If-Match and none was supplied.

http
HTTP/1.1 428 Precondition Required
Content-Type: application/problem+json

{
  "type": "https://tools.ietf.org/html/rfc6585#section-3",
  "title": "Precondition Required",
  "status": 428,
  "detail": "If-Match is required for this operation."
}

400 Bad Request — Malformed precondition ​

Sent when:

  • If-Match is present but not a syntactically valid entity-tag,
  • the request body's embedded concurrency token contradicts the If-Match header (for DTOs that carry their own ConcurrencyToken field).

Body-level concurrency token ​

Update*Command and Patch*Command DTOs derive from ConcurrencyAwareDto, which exposes an optional ConcurrencyToken property. When present the value must equal the If-Match header tag; the server uses the header as the authoritative precondition and treats body/header divergence as a malformed request (400 Bad Request).

Most clients should rely solely on the If-Match header and leave the body's ConcurrencyToken unset.

OpenAPI surface ​

Concurrency-aware operations are advertised in the OpenAPI document:

  • Every concurrency-protected PUT, PATCH, and DELETE declares an If-Match header parameter as required.
  • Every GET, POST, PUT, and PATCH of an aware resource declares an ETag response header.

Generated SDKs are expected to surface the header on the operation signature or to forward it transparently.

Client SDK behaviour ​

The first-party .NET clients (ProAuth.Clients.Management.V2 and ProAuth.Clients.UserStore.V2) currently expose the If-Match parameter on each mutating operation. Use the ETag from the resource version you read as the If-Match value when submitting a change. If the server reports a conflict, read the current resource and review the changes before trying again.

v1 compatibility ​

The v1 endpoints (/api/management/v1/…, /api/userstore/v1/…) do not participate in optimistic concurrency. Last-writer-wins semantics still apply there; migrate to v2 to gain conflict detection.