Message structure
Every message is a single line of text and contains exactly one JSON object. The controlling program sends a request, the display sends exactly one response.
→ {"v":1,"id":17,"cmd":"session.open","customer":{"masked":"B. L."}}
│ │ │ │
│ │ │ └─ data of the command
│ │ └────────────────────── command
│ └────────────────────────────── freely chosen request identifier
└───────────────────────────────────── version of the protocol
← {"v":1,"id":17,"ok":true,"result":{"state":"active"}}
Connection#
The connection is an ordinary TCP connection. The display accepts it, the controlling program opens it.
By default the display listens on 127.0.0.1, that is, on the same computer
only. If it sits on another computer on the library's local network it must
require authentication; the interface has no business on the internet at all
(see operation).
The default port is 7207. A display that uses a different one announces it in its identification file, which also states the address at which it can be reached.
Two connection models#
A display must handle both:
- One connection per request – connect, send, read the response, close. Stateless and robust; a controlling program needs little more than a socket. The display closes the connection once it has sent the response.
- Persistent connection – several requests one after another over the same
connection. The display answers them in the order received; anyone wanting
several requests in flight at once matches them up using the
idfield.
The display may close an idle persistent connection after a while (ten minutes is recommended). The controlling program must expect this and reconnect. A dropped connection does not end a running session: what is on the screen stays there.
Line and character set#
- Every message ends with a line feed (
LF, 0x0A). A carriage return before it must be tolerated by the other side, but not produced. - The character set is UTF-8 without a byte order mark. A BOM sent regardless is discarded by the display.
- A line should not exceed 8 KiB; a display must process at least 8 KiB.
No checksum, no sequence number#
Anyone who knows the circulation interface will miss both. There is neither here, and that is deliberate:
- The checksum in SIP2 dates from the days of the serial line, where a
single flipped character passed unnoticed. TCP already secures the path and
restores the order. If a line arrives incomplete it is not valid JSON – the
display answers
400 bad_requestinstead of processing half a message. - The sequence number in SIP2 serves to request a lost response again. Nothing can be lost here without being noticed: every request receives exactly one response, and if the connection drops the controlling program knows at once.
The id field is therefore expressly not a sequence number: it only matches
responses to requests when several are in flight. It need be neither
gap-free nor ascending, and the display does not remember it.
If a controlling program repeats a request because no response arrived, the
display may carry it out a second time. That is harmless here: every command
describes a state, not a movement – session.update sets the state of the
session, it does not increment anything.
The request#
| Field | Required | Content |
|---|---|---|
v |
yes | version of the protocol, currently 1 |
cmd |
yes | command, lower case, full stop as separator |
id |
no | number or string; the response returns it unchanged |
All other fields belong to the command in question.
The response#
{"v":1,"id":17,"ok":true,"result":{"state":"active"}}
{"v":1,"id":18,"ok":false,"error":{"code":404,"name":"unknown_command","text":"…"}}
ok says whether the request was carried out. result is absent when there is
nothing to report.
error.text is a diagnostic message for the log – not intended for display,
and in no defined language. Anyone wanting to tell the staff something evaluates
code and name.
Error codes#
| Code | name |
Occasion |
|---|---|---|
| 400 | bad_request |
line unreadable, required field missing, value unusable |
| 401 | unauthorized |
authentication required or failed |
| 404 | unknown_command |
command unknown |
| 404 | unknown_key |
key unknown to config.get/config.set |
| 409 | wrong_state |
command not meaningful in the current state |
| 500 | internal |
internal error of the display |
| 501 | not_supported |
command known but not present on this device |
A controlling program must treat an unknown code like the next lower hundred.
How data is written#
| Kind | Notation | Example |
|---|---|---|
| Date | ISO 8601, YYYY-MM-DD |
"2026-08-29" |
| Point in time | ISO 8601 with time zone | "2026-08-29T17:30:00+02:00" |
| Time of day | HH:MM local time, 24 hours |
"15:00" |
| Weekday | ISO 8601: 1 Monday to 7 Sunday |
2 |
| Duration | whole seconds as a number | 10 |
| Amount | decimal string, full stop as separator | "-3.50" |
| Currency | ISO 4217 | "EUR" |
| Language | BCP 47 | "de", "de-AT", "uk" |
| Boolean | genuine JSON true/false |
|
| Identifiers | string, even when it looks like a number | "10245" |
Identifiers are always transmitted as strings, even when they consist of
digits alone. In some libraries patron numbers and barcode numbers begin with
zeros or contain letters. Written as a JSON number both would be lost: "00123"
would become 123, and "A4711" could not be represented at all.
Ready-made text as an exception#
For every displayable field x a field x_text carrying ready-made wording may
be added. If it is present the display shows that text unchanged and does not
word anything itself.
This is the bridge for existing programs and for libraries with wording of their
own – not the regular route. Anyone using x_text gives up the display's choice
of language and its authority over the design.
Extensibility#
So that the protocol can grow without breaking existing integrations, five rules apply:
- Unknown fields are silently ignored. Never an error. Sending new data disturbs no older display.
- Unknown commands yield
404 unknown_command, known but absent ones501 not_supported. Either lets the controlling program fall back to a simpler route. - Manufacturer-specific things carry a prefix:
x-<manufacturer>.for commands and fields, e.g.x-ibtc.gate_alarm. Such names can never collide with the standard, now or later. - Capabilities instead of a version number: what a device can do is asked
with
hello, not read from a version number. - Data without effect is allowed. A display that does not show covers still
answers
okto a request containing one. It has done nothing wrong, and the controlling program need do nothing differently.