Skip to main content

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:

RepositoryWhat it is
tavrn-nodeThe node (server, Rust); generates openapi.json
tavrn-webThe web client and the desktop app (Linux and Windows)
tavrn-jsJavaScript packages: @tavrn/sdk, @tavrn/e2ee, @tavrn/bot
tavrn-cryptoThe encryption core (Rust, also as WebAssembly @tavrn/crypto)
DocumentContents
PROTOCOL.mdThe 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.mdHow 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.mdEnd-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.mdVoice, 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.mdThe shared whiteboard built on encrypted ephemeral events
APPLICATIONS.mdApplications 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.mdMessage search (search.v1): the node index for plaintext and the client-side index for encrypted messages and files
FILES.mdFiles channels: a shared folder tree per channel, external storage, end-to-end encryption (files.e2ee) and deletion (node-local)
ENTITLEMENTS.mdEntitlements (per-user and per-room limits), admin API tokens and attachment retention (node-local)
MODERATION.mdReports 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.mdNotices, legal pages, terms and data rights an operator can publish
permissions.mdThe permission bits (normative positions), the channel-scoped bits and the layered evaluation of roles, category and channel overrides
openapi.jsonEvery route in detail (OpenAPI 3.1), generated by the reference node
testvectors/Reference inputs and outputs every implementation must reproduce
CHANGELOG.mdChanges 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.

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 capability content.signed1;
  • VOICE.md: voice, screenshare, soundboard;
  • APPLICATIONS.md: apps.v1, commands.v1 and components.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:

  1. Fields are never removed or reinterpreted. New fields are optional.
  2. Unknown fields, event types and message types are ignored or shown as placeholders.
  3. Breaking changes create /api/v2 with 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.

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).

FileCoversShape
permissions.jsonPermission evaluation (permissions.md): owner, roles, category and channel overrides, timeouts, bots and scopes, system channelslist of { name, input, expected }; expected is the effective 64-bit mask as a decimal string
no_bots.jsonThe effective “no bots” flag of a channel (APPLICATIONS.md, permissions.md)list of { name, input, expected } with a boolean result
encryption.jsonFormats 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 sessionsobject per section; binary values base64url without padding
federation.jsonFEDERATION.md §10: JSON canonicalization (JCS), plaintext signatures, safety numbers, payloads that must be refusedobject per section; binary values base64url without padding
polls.jsonPROTOCOL.md §7g: the hash of a poll (JCS without ctx), an encrypted vote record and a plaintext signed vote of a date pollobject per section; binary values base64url without padding
components.jsonAPPLICATIONS.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 objectvalidation: list of { name, input, expected }; other sections objects, binary values base64url without padding
upload_categories.jsonPROTOCOL.md §7a: the upload category and the executable flag of a file from its name, its declared type and its first bytescases: 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.jsonFEDERATION.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.

  • Git submodule (used by tavrn-node and tavrn-js while the repositories are private): add this repository as a submodule and pin it to a tag; Rust code can embed the files with include_str!.

  • npm (once published): npm install @tavrn/protocol. The package contains openapi.json, testvectors/*.json, permissions.md and 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.

There is no build. The checks need Node.js 20 or newer and no dependencies:

Terminal window
npm test # every JSON file parses, openapi.json is OpenAPI 3.1, vector shapes, package exports,
# no hidden <placeholder> in the Markdown prose
scripts/check-placeholders.mjs [files] # the placeholder check alone, with file:line:column per finding
scripts/check-spdx.sh # SPDX headers of scripts and tests
scripts/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.

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 in permissions.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.