Sharing & servers
Sharing runs on a server.
Tessera needs no account to work — that is the default, and it stays the default. Sharing is what needs one. We operate servers, so for most people that is a sign-up and nothing else. The protocol is published and the server is a program you can run, so if you would rather own the machine your documents sit on, that path is supported rather than merely tolerated.
How it fits together
Three pieces, and you decide who runs the middle one.
1 · The application
Runs on your machine, stores pages as files, and works entirely offline. It has no account of its own. Until you type a server address into Settings, it opens no network connection at all.
2 · A server
Holds the accounts, orders the changes people make, and stores the documents published to it along with any media in them. This is the piece that has to exist somewhere, and the only one where there is a decision to make.
Ours by default. We run it, patch it and back it up, and you never
touch a configuration file. Or yours — the same program, on your own
hardware, with the app none the wiser. Any server implementing tessera/1
works, because the specification is what the client is written against rather than any
particular deployment.
3 · This website
Publishes the installer, these pages, and the sign-in form. It stores no documents of its own and puts no token in your browser: signing in here hands the credentials straight to the server and keeps nothing but an opaque session identifier in a cookie. If you use a server we do not run, this site never learns that it exists.
Accounts
What signing in actually does.
The same thing whether you sign in here or in the app, and against our server or your own — there is one account system and it belongs to whichever server you use.
You register an email address and a password with a server. The server hashes the password with Argon2id and returns two tokens: a short-lived access token, good for fifteen minutes, and a refresh token that rotates every time it is used.
The application keeps only the refresh token, encrypted at rest with the Windows data-protection API and bound to your user account on that machine. Your password is never stored, in any form, anywhere on your disk. The access token is never written down either — it lives in memory and expires.
Sign out and the whole session is revoked server-side immediately, not fifteen minutes later. Present a refresh token twice and the server assumes it was stolen and ends the session — which is the correct response, because it cannot tell a replay from a theft.
One server at a time
A token issued by one server is never offered to another. Changing the configured server in the app clears the stored credential rather than carrying it over.
Signing in on this website
Your browser never receives a token. The form posts to this site, this site talks to the server, and what comes back to you is an opaque session identifier in an httpOnly cookie. There is no token in local storage for a script to find — there is no script.
No email is ever sent
The server has no mail capability at all: no verification, no password-reset link, no notifications and no marketing. The upside is that an address given to it is used for signing in and nothing else. The downside is real — a forgotten password cannot be reset by a link, and recovering an account means asking whoever runs the server. That gap is being closed, and it is said here rather than discovered later.
Who may join
The server operator decides: registration can be open, invite-only, or closed. A document invite grants access to a document, never an account — so an invite-only server does not quietly acquire new users because somebody shared a link.
Be clear about what a server operator can see. A server stores the documents published to it in a form it can read. The protocol says so rather than implying otherwise. Where the operator is us, access is limited to what running the service requires — but that is an organisational guarantee, not a cryptographic one, and the difference matters. End-to-end encryption is designed for in the message envelope and is not built. If your work needs a stronger answer than a promise, run the server yourself.
Live
The server we operate
This is the server that accounts created here live on. The panel is read live, once a minute — a real request to that server, not a hand-written status somebody forgot to update.
api.studioheino.com
reachable- Software
- tessera-server 0.1.0
- Protocol
- tessera/1
- Registration
- open
- Transport
- TLS 1.3
- Capabilities
- assets.blobs, auth.password, doc.locks, doc.sync, history.server, invites.link, presence.cursors, transient.stream
Limits it advertises
| People per document | 32 |
|---|---|
| Largest attachment | 100 MB |
| Largest document | 64 MB |
| Largest single change | 256 KB |
| Changes per second | 40 |
| Selection lock expiry | 15s of inactivity |
Everything published to it is readable by whoever administers it — us. That is equally true of a server you run yourself, and it is said out loud here rather than left to be inferred from the protocol specification.
Conformance check
Point this at a server and see what Tessera would see.
This reads the server's public discovery document — the same unauthenticated request the
application makes before it has any credentials — and reports whether the app will work
against it. Nothing is sent but a GET for
/.well-known/tessera, and no credentials are involved.
Private, loopback and link-local addresses are refused, and redirects are not followed. Both restrictions exist so this form cannot be used to probe the network this site runs on.
Self-hosting
Running your own.
A supported alternative, not a workaround. The server is a Go program and a Postgres database; on a machine with Docker it is one command, and it refuses to start rather than coming up with an unsafe placeholder for anything it needs. The app cannot tell which kind of server it is talking to, and no feature is withheld from one you run.
cp .env.example .env
openssl rand -base64 48 # -> TESSERA_JWT_KEYS=k1:...
openssl rand -base64 24 # -> DB_PASSWORD
docker compose up -d --build
curl http://127.0.0.1:8080/.well-known/tessera
Then put the address into Tessera's settings and register the first account. Out of the box
both ports bind to 127.0.0.1, so nothing is reachable from the network until you
decide it should be.
Before you expose it
-
TLS is not optional. The protocol requires HTTPS and WSS, and the
application refuses plain
httpto any host that is not loopback. Put a TLS-terminating proxy in front, or give the server a certificate directly. - Raise your proxy's read timeout. The default of about a minute cuts every idle WebSocket and puts clients into a reconnect loop that looks exactly like a bad network.
-
Leave
TESSERA_TRUST_PROXYoff until something you control is settingX-Forwarded-For. A trusted header is a header anyone can write, and every rate limit and log line is downstream of it. -
Docker writes its own firewall rules. A host-level deny does not undo a
container published on
0.0.0.0; the bind address in the compose file is the real control.
A server you run is a server whose users' documents you can read, and — if other people use it — that makes you responsible for their data under whatever law applies to you. The privacy policy says what that means in practice.
Protocol
Written so somebody else can implement it.
tessera/1 is specified independently of the application's source: a server author
never needs to know what a shape or a connector is. Deltas are applied structurally, so the
document format can grow without a server release, and a server that has never heard of a
block type still stores it byte for byte.
| Capability | What it gives you |
|---|---|
| doc.sync | Documents, changes and catch-up after a disconnect. Required. |
| doc.locks | Lock arbitration, which is how conflicts are prevented rather than merged. Required. |
| presence.cursors | Seeing where other people are pointing. |
| transient.stream | Live previews while somebody drags a shape or draws. |
| history.server | Browsing a shared document's history on the server. |
| assets.blobs | Images, video and 3D scenes in shared documents, stored by content hash. |
| invites.link | Share links — hashed at rest, expiring, revocable, and never able to grant ownership. |