Skip to content

A sync session

This page walks through one complete sync round between a client and a server. Each step links to the page that specifies it.

sequenceDiagram
  autonumber
  participant C as Client
  participant S as Server
  C->>S: GET /lve/v1/info
  S-->>C: protocol, versions, auth, features, license
  Note over C: check protocol = lve-sync and 1 in versions,<br/>verify the licence (if any)
  alt username and password
    C->>S: POST /lve/v1/auth/token {username, password, device}
  else pairing code from a QR code
    C->>S: POST /lve/v1/auth/pair {code, device}
  end
  S-->>C: token, user, inventory
  Note over C: store the token securely,<br/>forget any old cursor,<br/>queue every local record for push
  C->>S: first sync round (below)
  1. Discovery: the client reads GET /lve/v1/info without a token and refuses the server unless protocol is lve-sync and versions contains 1. It checks the organisation licence if the server sends one.
  2. Authentication: the client signs in with a username and password or with a pairing code and receives a bearer token bound to its device id.
  3. The client drops any pull cursor it had (it belongs to another server or to an earlier connection) and puts every record it holds into its outbox, so that data created before the connection reaches the server. Records the server already has with the same or a newer version are answered with ignored and cost nothing else.
sequenceDiagram
  autonumber
  participant C as Client
  participant S as Server
  C->>S: GET /lve/v1/info
  S-->>C: license (may have changed)
  loop until the outbox is empty
    loop every attachment the next 500 outbox entries reference
      C->>S: HEAD /lve/v1/attachments/{sha256}
      alt 404 Not Found
        C->>S: PUT /lve/v1/attachments/{sha256} (bytes)
        S-->>C: 201 Created
      end
    end
    C->>S: POST /lve/v1/sync/push {device_id, changes[<=500]}
    S-->>C: results[] (applied, ignored, rejected)
    Note over C: drop applied and ignored entries,<br/>mark rejected entries
  end
  loop while has_more
    C->>S: GET /lve/v1/sync/pull?cursor=...&limit=500
    S-->>C: changes[], cursor, has_more
    Note over C: merge the page, then store the cursor
  end
  loop every referenced attachment missing locally
    C->>S: GET /lve/v1/attachments/{sha256}
    S-->>C: bytes (verify the SHA-256)
  end
  opt server numbering, fewer than 5 numbers left
    C->>S: POST /lve/v1/number-ranges/delivery_note/reserve {count: 25}
    S-->>C: numbers[]
  end
  1. Licence refresh. The client reads GET /lve/v1/info again on every round, because the organisation licence can be renewed or withdrawn at any time. If the licence no longer covers server sync, the round stops here and local changes wait. See Organisation licence.
  2. Push. The client takes up to 500 entries from its outbox, oldest first. It uploads every attachment those changes reference that the server does not have yet, then pushes the changes. It removes entries answered applied or ignored and marks entries answered rejected. It repeats until the outbox holds only rejected entries. See Push and Attachments.
  3. Pull. Starting from its stored cursor (none on the first round), the client pulls pages of changes and merges each page with the merge rules. It stores the returned cursor after every page and pulls again while has_more is true. See Pull.
  4. Attachment download. The client downloads every attachment that a record it holds references and that it does not have yet, and keeps only bytes whose SHA-256 matches.
  5. Number reservation. If delivery notes are numbered by the server and the client holds fewer than 5 reserved numbers, it reserves 25 more. See Number ranges.

Push comes before pull so that the client's own changes are in the server's log before it reads the log; the pull then also returns them, which is harmless.

The reference app runs one round at a time; a round requested while another runs waits for it.

Trigger Delay
App start, and connecting to a server immediately
A local change (outbox grew) 1.5 seconds after the last change, so several edits cause one round
Timer while the app runs every 60 seconds
The network becomes available immediately

A server should expect this traffic pattern: a short info, push and pull sequence per device about once a minute, more while users work.

Outcome Client state
All steps succeeded up to date, with the time of the round
A transport error (no network, timeout, DNS, TLS) offline; the next trigger retries
401 on any authenticated request signed out: the user has to sign in again; local data and the outbox stay
The licence or in-app plan does not cover server sync paused: nothing is sent or received until the plan is back
Any other error failed, with the server's error.message; the next trigger retries

Nothing in a failed round is lost: unsent changes stay in the outbox, and the cursor only moves after a page was merged.