One endpoint. Optimistic concurrency.

POST /v1/sync pushes new events, pulls anything newer than the cursor, and returns the current signed tree head. Per-request HTTP auth via Ed25519 over canonical request bytes.

Request shape

POST /v1/sync
Authorization: fd0-http-request-v1 pk=<base64> nonce=<base64> ts=<unix> sig=<base64>
Content-Type: application/cbor

{
  scope        : tstr,
  cursor       : uint,         ; last seq the client has
  push         : [* ScopeEvent],
  pull         : bool,          ; default true
  discover_memberships : bool,  ; optional
}

Response shape

200 OK
Content-Type: application/cbor

{
  accepted     : [* event_id],
  pulled       : [* ScopeEvent],
  sth          : SignedTreeHead,
  witness      : Cosign,         ; if witness configured
}

Optimistic concurrency

A scope-event push is rejected with 409 divergence if prev_hash ≠ scope.tip_hash. The response includes the current tip. The client pulls, applies, re-signs against the new tip, and retries. Auto-retry up to N (default 3) before surfacing the conflict.

Idempotency

event_id is content-derived. The server enforces UNIQUE(event_id); replays are accepted as no-ops rather than rejected. This makes fd0 sync safe to run as often as you like — at intervals, on unlock, on network reconnect.