Skip to main content
Resources that can be edited from multiple channels (API + NT24 web app) carry an integer version that bumps on every modification. The API surfaces it as an ETag header; writes that modify the resource must include If-Match with the last-seen ETag. Mismatch = 409 Conflict with the current resource.

Read → ETag

curl -v "https://api.novatrade24.com/v1/partners/${SELLER_ID}/orders/${ORDER_UUID}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"
Response headers include:
ETag: "5"
Body also carries "version": 5 — same value, two surfaces.

Write → If-Match

curl -X PATCH "https://api.novatrade24.com/v1/partners/${SELLER_ID}/orders/${ORDER_UUID}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'If-Match: "5"' \
  -H "Content-Type: application/json" \
  -d '{ "responsibleContact": { ... } }'
Server stateHeaderResult
version=5If-Match: "5"200 OK, version bumps to 6
version=7 (someone edited)If-Match: "5"409 Conflict with current resource in body
No If-Match header400 Bad Request — header required on versioned resources

Conflict resolution

409 responses carry the full current resource in the body. You don’t need a separate GET to reconcile.
{
  "type": "https://api.novatrade24.com/problems/version-mismatch",
  "title": "Version Mismatch",
  "status": 409,
  "detail": "If-Match version 5 does not match current version 7.",
  "traceId": "req_...",
  "currentResource": {
    "uuid": "o1p2q3r4-...",
    "version": 7,
    "...": "..."
  }
}
Merge your intended change with the current state and retry with the new ETag:
async function updateWithRetry(orderId: string, changes: Partial<Order>) {
  for (let attempt = 0; attempt < 3; attempt++) {
    const current = await getOrder(orderId);
    const merged = { ...current, ...changes };

    const r = await patchOrder(orderId, merged, current.version);
    if (r.status === 409) {
      // Another channel edited. Loop with the fresh version.
      continue;
    }
    return r;
  }
  throw new Error('Too many concurrent conflicts');
}

Which resources are versioned

Any resource that NT24 staff can also edit in the web app:
  • Buyer (TradePartner)
  • Order
  • Invoice (sub-resource on order)
  • Payment (sub-resource on order)
  • Transport (pickup/delivery milestones are append-only; see note)
Append-only resources (documents, self-service invitations) don’t have versions. Their mutations create new records or set status fields exclusively, so concurrency conflicts don’t arise.

Transport milestones — immutable once set

Pickup and delivery confirmations are not optimistically concurrent. They are immutable once set:
  • POST /transport/pickup twice → second call returns 409 with milestone-already-set problem type.
  • To reverse, use the explicit POST /transport/pickup/revoke endpoint.
  • Same for delivery.
This is stricter than optimistic concurrency because pickup/delivery have legal consequences (VAT applicability) and should not be “fixed up” via a silent merge.

Under the hood

Versions are backed by Hibernate’s @Version column — platform-wide on all audited entities. Envers captures every revision for audit trail (who/when); @Version prevents concurrent overwrites at commit time.

Next

Error reference

409 version-mismatch detail.

Idempotency

Safe retries on writes.