offline-protocol-service command (module offline_protocol_sdk.local_api), from v0.28.0. Install the package from PyPI first; see Python and Linux gateways.
Guarantees for every client
- The server owns the engine. It runs the loop, drains inbound messages, starts and stops. No client can call
process(),receive_message()orstop(). - A connection speaks for one application. The first request is
hellowith anapp_id. Every message that connection sends is stamped with it, and an inbound message stamped with it reaches only connections that declared it. - The socket is the authorization boundary. Whoever can open the Unix socket, or holds the launch token on TCP, is one of the operator’s applications. The server does not tell one local process from another.
- Events are the engine’s JSON, unchanged. A client that handles
message_receivedfrom the embedded SDK handles the same object here.
Start the service
The service runs over the built-in file stores, so a host with no secret service needs only two directories and a key:config.json holds the ProtocolConfig fields by name, with enum values spelled as in the interface definition:
--listen and --peer drive the peer-stream transport when the configuration enables it (--peer takes host:port or off1...@host:port). --gateway HOST:PORT names the gateway daemon when the configuration enables reticulum (default localhost:4242). --keyring uses the platform keyring instead of the file stores. Run offline-protocol-service --help for the full list.
The server never binds a non-loopback address. To reach the API across a network, put a reverse proxy with its own authentication in front of it.
GET /health on either carrier returns the server name and version, the API version and the carrier. There is no other HTTP path: a single call is a connection that sends hello, one request, and closes.
Talk to it
Every request is a JSON-RPC 2.0 object with anid. Every event is a notification whose method is event and whose params is the event object.
websockets package the SDK already depends on.
- Sending. Call the engine’s methods by name with the parameters the interface definition gives them, for example
send_messagewithrecipient,contentandpriority. It returns the message ID. Themessage_sentandmessage_deliveredevents for that ID come back to the sending connection only. Watch formessage_undeliverableas well. - Subscribing. After
helloa connection receives every event routed to its application.subscribewithtypesnarrows that;subscribewith"all"resets it. - Held messages. An inbound message for an application with no connected client is held, up to 256 per application ID, and delivered after the next
hellounder that ID. Delivery is at least once, so deduplicate onmessage_id. - Documents and services. The replicated document store is reachable as
data.*methods and mesh services asservices.*. A write carries a tagged value; a read returns plain JSON. With the data layer off, everydata.*call answersDataDisabled. - Errors. A failed call is a JSON-RPC error whose
data.variantis the engine’sProtocolErrorvariant. Switch on the variant, not the number. A method beforehelloisInvalidState; a refused token, space or method isPermissionDenied.
Policy file
With no policy, any well-formed application ID is accepted.--policy policy.json adds rules per application ID:
Once
spaces or denied names any application, an ID that no section names is refused at hello.
Application IDs on the wire
The ID a client declares is stamped on each message it sends, in place of the configuredapp_id, and message_received and file_received report it on the receiving node. It changes only the stamp, never the storage namespace or the MLS session. The ID is cleartext and unsigned on the wire: route on it, never authorize on it. The same per-send ID is available without the service through SendMessageOptions and MediaSendOptions in Python and Rust, and the appId option of sendMessage and sendMedia in React Native.
Reference
- Local API guide and specification chapter, with every method, event and error.
- Python client example over the Unix socket or TCP.
- Node client example for Node 22 or later with no dependencies, over TCP with the token file.

