Skip to content

Transport basics

A server is addressed by a base URL, which may contain a path: https://lager.example.com or https://example.com/inventory. All endpoints are below <base URL>/lve/v1/.

The reference app normalises what the user types: it trims spaces, adds https:// when no scheme is given, removes trailing slashes and accepts only http and https URLs with a host. lager.example.com:8443 becomes https://lager.example.com:8443.

Endpoint Auth Purpose
GET /lve/v1/info no Discovery
POST /lve/v1/auth/token no Sign in with credentials
POST /lve/v1/auth/pair no Sign in with a pairing code
DELETE /lve/v1/auth/token yes Sign out
POST /lve/v1/sync/push yes Push changes
GET /lve/v1/sync/pull yes Pull changes
HEAD, GET, PUT /lve/v1/attachments/{sha256} yes Attachments
POST /lve/v1/number-ranges/{key}/reserve yes Number ranges

Servers reachable over the internet MUST use HTTPS with a certificate the client's platform trusts. Tokens, passwords and inventory data travel in every request.

Plain HTTP is acceptable only inside a trusted local network. The iOS reference app allows plain HTTP only to hosts that App Transport Security treats as local (for example .local names and addresses in the local network) and asks the user for local network access the first time. Everything else needs HTTPS.

  • Request and response bodies are JSON in UTF-8, Content-Type: application/json, except attachment bodies, which are raw bytes.
  • Clients send Accept: application/json.
  • Clients send Authorization: Bearer <token> on every endpoint except info and the two sign-in calls.
  • A request body declared as JSON that does not parse is answered with 400 and malformed_request.
  • JSON objects in payloads stay objects. A server written in a language whose JSON decoder turns {} into an empty array (PHP with associative decoding, for example) MUST decode payloads so that empty objects survive, because the payload is relayed to other clients unchanged. The reference server decodes pushed changes into objects for this reason.

Clients send Accept-Language with de or en. The reference app sends de when the device's preferred language is German and en otherwise.

Servers answer error.message and the message of rejected changes in that language and name it in the Content-Language response header. The reference server supports de and en, uses German when the header is missing or names neither, and localises every error below /lve, including 404 and 405 for unknown addresses.

Messages are for people. Clients MUST decide by error.code or by the push reason, never by message.

Any endpoint may answer 429 with too_many_requests. Clients wait at least the number of seconds in Retry-After (when present) before trying again.

The reference server limits only the two sign-in endpoints: together 10 requests per minute per client IP address. Their responses carry X-RateLimit-Limit and X-RateLimit-Remaining; a 429 also carries Retry-After.

The reference app waits at most 15 seconds for a response to start and 120 seconds for a whole request (also for attachment uploads). On a timeout or a transport error it marks the device offline and retries with the next sync trigger, typically within a minute.

Every request in the protocol can be repeated safely:

Request Why a repeat is harmless
push Changes that were stored are answered ignored the second time
pull Pulling from the same cursor returns the same changes; merging them again changes nothing
attachment upload Content-addressed; a second upload is answered 200
number reservation A repeat reserves new numbers; the numbers of the lost response stay unused (a gap)
sign-in A repeat issues a new token and revokes the previous one of the same device

A server SHOULD process a push request even if the client disconnects, and MUST NOT leave a change half stored.

The path prefix carries the major version. A server that supports version 2 in the future serves it below /lve/v2/ and lists both versions in info.versions. Clients pick the highest version both sides support.