tamis WIP#
Lightweight reverse proxy for atproto PDS record writes
(com.atproto.repo.createRecord / putRecord / deleteRecord).
tamis resolves each user's PDS from their DID document, keeps a per-user login
session in a supervised OTP actor, and transparently retries once on
ExpiredToken.
Onboarding & admin UI#
Users self-serve through a small web UI (server-rendered with Lustre):
- Log in at
/with atproto OAuth (handle or DID). OAuth only proves who you are — DPoP/PKCE/PAR are handled byatproto_client; tamis is a public client and serves its client-metadata at/oauth-client-metadata.json. - Submit an app-password, which tamis verifies against your PDS and stores; it then proxies your record writes using it.
The single admin (the DID in ADMIN_DID) additionally gets an allow-list panel to
authorize/remove DIDs and can update their own app-password the same way. The
allow-list and app-passwords persist to the JSON file at TAMIS_STATE.
Configure#
All configuration is via the environment. For convenience a .env file is loaded
at startup (path is the first CLI argument, default .env); real environment
variables take precedence over it, and a missing file is ignored.
TAMIS_PUBLIC_URL must be the URL the PDS's authorization server uses to reach
tamis. In production that's a public https:// URL (the auth server fetches
/oauth-client-metadata.json from it). For local development, set it to an
http loopback origin (e.g. http://127.0.0.1:3000) and tamis automatically uses
atproto's "localhost" OAuth client — no hosted metadata or tunnel needed. Use
127.0.0.1 (not localhost) and drive the browser flow through the same origin
so the redirect and session cookie line up; the auth server must permit localhost
dev clients (Bluesky's does).
| Env var | Default | Meaning |
|---|---|---|
ADMIN_DID |
— (required) | DID granted admin rights in the UI |
TAMIS_PUBLIC_URL |
— (required) | public base URL; derives Oauth client_id/redirect |
TAMIS_SECRET |
— (required) | secret for signing session cookies |
TAMIS_ADDR |
127.0.0.1:3000 |
listen address |
TAMIS_STATE |
tamis-state.json |
persisted allow-list + app-passwords |
TAMIS_PLC_URL |
https://plc.directory |
PLC directory for did:plc lookup (write-proxying) |
TAMIS_OAUTH_RESOLVER |
https://slingshot.microcosm.blue |
handle/DID → PDS resolver for OAuth login |
TAMIS_OAUTH_SCOPE |
atproto |
OAuth scope requested at login |
TAMIS_ALLOW_HTTP |
unset (https-only) | set to allow plain-http upstreams (dev) |
Develop#
nix develop # gleam + erlang + rebar3
gleam test
gleam format
ADMIN_DID=did:plc:... TAMIS_PUBLIC_URL=https://... TAMIS_SECRET=... gleam run
gleam run # or put the vars in a .env file
gleam run -- prod.env # ...or point at a specific env file
nix run .#e2e # full local PDS+PLC end-to-end suite (Linux only)
License#
MIT — see LICENSE.