Authentication
A client signs in once per server and then sends a bearer token with every request. There are two ways to sign in, and both return the same response.
Device identity
Section titled “Device identity”Both sign-in requests carry a device object:
| Field | Type | Required | Meaning |
|---|---|---|---|
id |
string, at most 64 characters | yes | Stable identifier of the app installation. The same value is sent as device_id with every push. |
name |
string, at most 255 characters | yes | Name of the device, for example "Erika's iPhone". The reference server stores it as the token name, so administrators can see which device a token belongs to. |
model |
string or null, at most 255 characters | no | Hardware model, for example iPhone or iPad. |
The reference app creates a random UUID at first launch and uses it as id (upper case); its first 8 hexadecimal digits are the device's HLC node id. name is the device name the system reports (on current iOS versions usually just the model name, such as iPhone), model the device model (iPhone, iPad).
Credentials
Section titled “Credentials”POST /lve/v1/auth/token HTTP/1.1Content-Type: application/jsonAccept-Language: en
{ "username": "erika", "password": "correct horse battery staple", "device": { "id": "3F2A9C1B-7E4D-4B2A-9C1E-5D8F0A2B4C6E", "name": "Erika's iPhone", "model": "iPhone" }}username may be the username or the email address of the user; the reference server compares both case-insensitively after trimming spaces. username and password have at most 255 characters each.
Pairing
Section titled “Pairing”Pairing signs a device in without typing a password. An administrator creates a single-use code on the server for a user and shows it as a QR code. The device scans the code (or the user types it) and exchanges it for a token.
The pairing link
Section titled “The pairing link”The QR code contains a URL:
lve://pair?server=<percent-encoded base URL>&code=<code>Example:
lve://pair?server=https%3A%2F%2Flager.example.com&code=K7P2-9QXM| Part | Rule |
|---|---|
| scheme | lve, compared case-insensitively |
| host | pair, compared case-insensitively |
server |
The base URL the device connects to, percent-encoded (RFC 3986). It must be reachable from the devices, not only from the server itself. Readers apply the same normalisation as for typed URLs (add https:// when the scheme is missing, drop trailing slashes). |
code |
The pairing code, percent-encoded. Must not be blank. |
The reference app reads such links from its own scanner, from the system camera (the app registers the lve URL scheme) and from text pasted into the connect screen. It then calls GET /lve/v1/info on the server named in the link, checks the plan and pairs.
POST /lve/v1/auth/pair HTTP/1.1Content-Type: application/json
{ "code": "K7P2-9QXM", "device": { "id": "3F2A9C1B-7E4D-4B2A-9C1E-5D8F0A2B4C6E", "name": "Erika's iPhone", "model": "iPhone" } }A server decides the code format. Requirements:
- A code works exactly once. Two devices sending the same code at the same time MUST NOT both get in.
- A code expires. The reference server uses 24 hours by default (
lve:pair --hours=Nchanges it). codeis at most 32 characters.- Servers SHOULD ignore case, spaces and dashes when comparing, so a code can be typed from a screen.
- Servers SHOULD store only a keyed hash of each code, so a leaked database does not reveal valid codes.
The reference server creates eight characters from the alphabet 23456789ABCDEFGHJKMNPQRSTWXYZ (no 0/O, 1/I/L, U/V), shown as two groups of four, K7P2-9QXM, about 39 bits. It stores HMAC-SHA256(APP_KEY, normalised code), removes everything but 0-9 and A-Z after upper-casing before it compares, and marks a code used with a conditional update so that only one of two concurrent requests succeeds. An unknown, used or expired code is answered with 401 and invalid_pairing_code; the three cases are not told apart.
The sign-in response
Section titled “The sign-in response”{ "token": "7|kM2pQ9xV3rT8wZ1yB6nH4jL0sD5fG7aC2eR9tY1u3f9a1c07", "user": { "id": "42", "name": "Erika Mustermann" }, "inventory": { "id": "B2D7E4A1-9C3F-4E8B-A6D2-1F5C7E9B3A40", "name": "Lager Nord" }}| Field | Meaning |
|---|---|
token |
Bearer token for this device. Opaque to the client. Store it in secure storage (the reference app uses the iOS keychain). |
user.id |
User id, always a string |
user.name |
Display name of the user |
inventory.id |
UUID of the inventory the server holds. It never changes while devices are connected; a different id means a different data set. |
inventory.name |
Display name of the inventory |
After a successful sign-in the client:
- stores the token,
- drops any pull cursor it had,
- queues every record it holds for push (see A sync session),
- starts a sync round.
The reference app shows user.name and inventory.name in its settings. It does not yet compare inventory.id with an earlier connection; a client SHOULD warn before merging its local data into a server whose inventory id differs from the one it was connected to before.
Tokens
Section titled “Tokens”- A token identifies one user on one device. The reference server issues Laravel Sanctum personal access tokens (
<id>|<random>) that do not expire. - When the same user signs in again with the same device id, the server replaces the previous token of that device, which stops working. Reinstalled or re-paired devices therefore do not accumulate tokens.
- Clients send the token as
Authorization: Bearer <token>on every request exceptinfoand the sign-in calls.
401 handling
Section titled “401 handling”Any authenticated request may answer 401 with unauthenticated (token missing, unknown, revoked or replaced). The client then:
- stops the sync round,
- keeps all local data and the outbox,
- tells the user to sign in again.
The reference app shows the state "signed out" and keeps the server connection settings, so signing in again continues where it stopped.
Sign-in calls answer 401 with invalid_credentials or invalid_pairing_code. The reference app shows "invalid pairing code" for any 401 from the pairing call.
Signing out
Section titled “Signing out”DELETE /lve/v1/auth/token HTTP/1.1Authorization: Bearer 7|kM2pQ9xV3rT8wZ1yB6nH4jL0sD5fG7aC2eR9tY1u3f9a1c07Answers 204 without body and revokes the token of this request. Other devices of the same user stay signed in. The client deletes the token, the cursor and any reserved numbers whatever the answer is; local data stays on the device.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
400 |
malformed_request |
Body is not valid JSON |
401 |
invalid_credentials |
Unknown user or wrong password |
401 |
invalid_pairing_code |
Unknown, used or expired code |
401 |
unauthenticated |
DELETE /auth/token without a valid token |
422 |
validation_failed |
A field is missing or too long; message names the first problem |
429 |
too_many_requests |
More than 10 sign-in requests per minute from one address (reference server) |
Account management
Section titled “Account management”Creating users, resetting passwords and listing or revoking device tokens are server administration and not part of the protocol. The reference server has console commands for users and pairing codes; see Reference server.