Protocol overview
tavrn is a self-hostable, federated chat platform: rooms with text, voice and video channels, end-to-end encryption with MLS, bots and applications, and nodes that talk to each other so a user of one node can join rooms on any other. This repository is the contract between all of them. It specifies what a node must do, what clients and bots may expect, and how nodes federate, independent of any implementation.
The reference implementations live in their own repositories:
| Repository | What it is |
|---|---|
tavrn-node | The node (server, Rust); generates openapi.json |
tavrn-web | The web client and the desktop app (Linux and Windows) |
tavrn-js | JavaScript packages: @tavrn/sdk, @tavrn/e2ee, @tavrn/bot |
tavrn-crypto | The encryption core (Rust, also as WebAssembly @tavrn/crypto) |
Documents
Section titled “Documents”| Document | Contents |
|---|---|
PROTOCOL.md | The core: identities and addresses, discovery (/.well-known/tavrn), capabilities, API conventions, tokens and authentication, date of birth and age gates (age.v1), realtime events and who receives them, presence, profiles, notifications and web push, the message envelope, uploads, threads and forums, polls and date polls (polls.v1), message retention, DMs, permissions overview |
FEDERATION.md | How nodes find, authenticate and trust each other: node keys, federation tokens, inbox and outbox, remote rooms, friends and DMs across nodes, directory, signing in with an account of another node (also from native apps, connect.app), conformance, identity verification |
ENCRYPTION.md | End-to-end encryption (e2ee.mls1): master secret and key file, devices and their share keys, MLS groups, content and file encryption, backups, history for new members (history.shares2) and its restore after a key reset (history.restore), secret storage, identity reset and rotation |
VOICE.md | Voice, video and screen sharing (voice, screenshare, soundboard): limits, the channel status, join flow, quality presets, screen publishers of native apps, presence and the session start, moderation and moved participations, the encrypted media frames, soundboard, connectivity, calls in DMs |
WHITEBOARD.md | The shared whiteboard built on encrypted ephemeral events |
APPLICATIONS.md | Applications and bots (apps.v1), slash commands (commands.v1), message components: buttons, select menus and modals (components.v1), webhooks and event subscriptions (webhooks.v1), rich presence, single sign-on (sso.oidc) |
SEARCH.md | Message search (search.v1): the node index for plaintext and the client-side index for encrypted messages and files |
FILES.md | Files channels: a shared folder tree per channel, external storage, end-to-end encryption (files.e2ee) and deletion (node-local) |
ENTITLEMENTS.md | Entitlements (per-user and per-room limits), admin API tokens and attachment retention (node-local) |
MODERATION.md | Reports with evidence (reports.v1), forwarding them to the reported user’s home node (reports.v2), auto-moderation of unencrypted conversations (automod.v1), the protected welcome channel entries, room ownership transfer, age gates (node-local) |
LEGAL.md | Notices, legal pages, terms and data rights an operator can publish |
permissions.md | The permission bits (normative positions), the channel-scoped bits and the layered evaluation of roles, category and channel overrides |
openapi.json | Every route in detail (OpenAPI 3.1), generated by the reference node |
testvectors/ | Reference inputs and outputs every implementation must reproduce |
CHANGELOG.md | Changes implementations must follow, newest first |
The key words MUST, SHOULD and MAY are used as in RFC 2119. Documents marked “node-local” describe behaviour that never crosses node boundaries; other nodes do not have to understand it.
Versioning and capabilities
Section titled “Versioning and capabilities”tavrn does not compare version numbers between nodes and clients. Both sides announce a list of
capability strings (nodes in GET /api/v1/node and the discovery document, clients in the WebSocket hello and
the Tavrn-Client header), and a feature is used only when both sides list it (PROTOCOL.md §3, which has the
table of every capability and when nodes announce it). Third-party extensions use reverse-domain capabilities such
as org.example.feature.
The capabilities defined today, by document:
PROTOCOL.md:content.v1,reactions,attachments,pins,emojis,gifs,unfurl,push.webpush,threads,forum,announcement,threads.client_ids,age.v1,polls.v1;ENCRYPTION.md:e2ee.mls1,history.shares2,history.restore;FEDERATION.md:federation,federation.v1,connect.app, and the client capabilitycontent.signed1;VOICE.md:voice,screenshare,soundboard;APPLICATIONS.md:apps.v1,commands.v1andcomponents.v1(also announced by clients),webhooks.v1,sso.oidc;SEARCH.md:search.v1;FILES.md:files.e2ee;MODERATION.md:reports.v1,reports.v2,automod.v1.
The compatibility rules of PROTOCOL.md §3 bind every change to this repository:
- Fields are never removed or reinterpreted. New fields are optional.
- Unknown fields, event types and message types are ignored or shown as placeholders.
- Breaking changes create
/api/v2with at least 12 months of parallel operation.
New behaviour that an implementation must opt into gets a new capability (or a new version of one, such as
content.v1 → content.v2), never a silent change of an existing one.
This repository is tagged per protocol version (vMAJOR.MINOR.PATCH, the same version as the npm package):
- patch: clarifications, typos, new or corrected test vectors that do not change the specified behaviour;
- minor: compatible extensions (new optional fields, routes, event types, capabilities, permission bits);
- major: changes that need a new API version (
/api/v2).
Implementations pin a tag. The current version is in package.json (0.2.1); changes merged since
the last tag are listed under “Unreleased” in CHANGELOG.md and become the next minor version.
Test vectors
Section titled “Test vectors”The files in testvectors/ are the executable part of the specification. Every implementation runs them in
its own test suite; an implementation that disagrees with a vector is wrong (or the vector is, which is a bug
report here).
| File | Covers | Shape |
|---|---|---|
permissions.json | Permission evaluation (permissions.md): owner, roles, category and channel overrides, timeouts, bots and scopes, system channels | list of { name, input, expected }; expected is the effective 64-bit mask as a decimal string |
no_bots.json | The effective “no bots” flag of a channel (APPLICATIONS.md, permissions.md) | list of { name, input, expected } with a boolean result |
encryption.json | Formats of ENCRYPTION.md and FILES.md §8: key file, local storage key, device attestation, device share key, device login proof, content (also with the signed context) and file encryption, the chunks and entry records of encrypted files channels, history shares (formats 1 and 2), backups, whiteboard scenes, secret storage, identity rotation, the webhook inbox, the sealed unlock material of app sessions | object per section; binary values base64url without padding |
federation.json | FEDERATION.md §10: JSON canonicalization (JCS), plaintext signatures, safety numbers, payloads that must be refused | object per section; binary values base64url without padding |
polls.json | PROTOCOL.md §7g: the hash of a poll (JCS without ctx), an encrypted vote record and a plaintext signed vote of a date poll | object per section; binary values base64url without padding |
components.json | APPLICATIONS.md §5.10: validation of components and modals (valid cases and the error code and meta.path of invalid ones), an encrypted component.values and modal.values content object | validation: list of { name, input, expected }; other sections objects, binary values base64url without padding |
upload_categories.json | PROTOCOL.md §7a: the upload category and the executable flag of a file from its name, its declared type and its first bytes | cases: list of { name, file_name, mime, head_hex, category, executable }; head_hex is the hex of the first bytes or null (classified without content) |
federation_node.json | FEDERATION.md §1.1/§1.4 and §2.3/§7.3: discovery documents (one valid, invalid ones with the reason) and node and listing tokens (a fixed signing key, the exact token bytes, the body hash) | object per section; binary values base64url without padding |
The reference implementations use them as follows: the node evaluates permissions.json and no_bots.json
in its unit tests; the crypto core checks encryption.json and federation.json in Rust and through its
WebAssembly build; @tavrn/sdk runs permissions.json, no_bots.json and the JCS cases of
federation.json, the validation cases of components.json and upload_categories.json; @tavrn/e2ee runs the identity, webhook inbox, files channel, local storage key, signed context, device share key,
history share (both formats) and sealed unlock cases of encryption.json and the poll votes of polls.json and the encrypted values of components.json; the node checks the device login
proof, runs the validation cases of components.json, upload_categories.json and federation_node.json. A
new implementation should do the same: load the file, run every case, and compare exactly.
Using this repository from code
Section titled “Using this repository from code”-
Git submodule (used by
tavrn-nodeandtavrn-jswhile the repositories are private): add this repository as a submodule and pin it to a tag; Rust code can embed the files withinclude_str!. -
npm (once published):
npm install @tavrn/protocol. The package containsopenapi.json,testvectors/*.json,permissions.mdand the license files, and exports them:import openapi from "@tavrn/protocol/openapi.json" with { type: "json" };import permissionCases from "@tavrn/protocol/testvectors/permissions.json" with { type: "json" };SDKs generate their types from
@tavrn/protocol/openapi.json.
Working on this repository
Section titled “Working on this repository”There is no build. The checks need Node.js 20 or newer and no dependencies:
npm test # every JSON file parses, openapi.json is OpenAPI 3.1, vector shapes, package exports, # no hidden <placeholder> in the Markdown prosescripts/check-placeholders.mjs [files] # the placeholder check alone, with file:line:column per findingscripts/check-spdx.sh # SPDX headers of scripts and testsscripts/publish-check.sh # no private files, domains or secrets (needs the private pattern list, docs/GITHUB.md)openapi.json is generated by the reference node; change the node’s route documentation and regenerate it
there instead of editing the file by hand. Changes to the prose go through pull requests like code: describe
the problem in an issue first when the change is normative.
Contributions are welcome. Please read the organization’s
contributing guide and
code of conduct. A one-time
contributor license agreement is signed through a bot on your first pull request. Report security
problems privately as described in the security policy,
never in a public issue. Repository settings and CI: docs/GITHUB.md.
License
Section titled “License”The repository carries two licenses (LICENSE.md):
- Apache-2.0 (
LICENSE-APACHE,NOTICE) for the machine-readable parts:openapi.json, the test vectors, the permission bit tables inpermissions.md, and the repository tooling. Implementations can copy them into their code under a code license. - CC-BY-4.0 (
LICENSE-CC-BY-4.0) for the specification prose (the Markdown documents). Attribute “the tavrn project”, link the specification and say whether you changed it. Code samples inside the prose may also be used under Apache-2.0.
The name “tavrn” and the logo are not covered by either license; see TRADEMARK.md.
This page is generated from tavrn-protocol/README.md (CC-BY-4.0) when the website is built; its links were adapted for this site.