409 Conflict

Conflict is a normal sync outcome, not an unknown error.

Persistly returns the canonical server save so the client can recover intentionally. Games should design this path instead of pretending every sync is accepted.

Response

HTTP 409 Conflict

The status field tells the SDK exactly what happened, and details.reason explains why.

HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "status": "conflict",
  "save": {
    "saveId": "sv_01HXYZ",
    "externalUserId": "auth0|123",
    "metadata": {
      "characterName": "Ayla",
      "slot": 2
    },
    "state": {
      "gold": 140,
      "level": 3
    },
    "version": 5,
    "createdAt": "2026-04-09T10:00:00Z",
    "updatedAt": "2026-04-09T10:06:00Z"
  },
  "details": {
    "reason": "base_version_mismatch"
  }
}

Recovery Rules

Build your conflict path explicitly.

HTTP 409 means the submitted baseVersion is stale.

Persistly returns the current canonical save in the same response.

Clients should replace their canonical local snapshot with the returned save before deciding whether to replay local edits.

Do not silently discard the canonical response or treat conflict as a transport failure.

SDK Pattern

Minimal conflict handling

This is the shape your client logic should follow even if you wrap it inside engine-specific abstractions.

const result = await client.syncSave(saveId, {
  baseVersion: local.version,
  metadata: local.metadata,
  state: nextState
});

if (result.status === PersistlySyncStatus.Accepted) {
  local = result.save;
}

if (result.status === PersistlySyncStatus.Conflict) {
  local = result.save;
  // decide whether to reapply local unsynced changes or ask the player
}