Clams Server
Your Bitcoin books, deployed.
install:
$ curl -sSL https://clams.tech/install.sh | sh
> powershell -c "irm clams.tech/install.ps1 | iex"
start:
$ clams setup
Self-host anywhere.
Run Clams as an HTTP server. Deploy on your infrastructure, expose API and CLI access, maintain full control.
Built for teams who want control
- Developers integrating Bitcoin accounting into their stack
- Exchanges needing dedicated instances
- Miners running continuous reconciliation
- Accountants serving multiple clients from one server
- CFOs connecting to internal ERP and BI tools
- Node operators running always-on infrastructure
- Anyone who wants to self-host their Bitcoin books
Resources
- Auth quickstart — plain markdown that explains how the auth layer works.
- Auth API reference — full Swagger/OpenAPI spec for the auth server.
- REST API reference — full Swagger/OpenAPI spec for the Clams Server API.
Self-hosting
This guide describes how to run and configure clams server (the Clams Accounting HTTP API server) when you already have the clams binary.
What You Need
clams server is a stateful process backed by local LMDB storage. A typical production deployment is:
clams serverlistening on plain HTTP behind a TLS-terminating reverse proxy- A persistent data directory for LMDB state and instance secrets
- A single
clams serverprocess per data root (multiple processes against the same data root is not a validated deployment shape) - Network access from
clams serverto:https://auth.clams.techfor login/setup, JWKS validation, invite emails, and support feedback forwarding (Auth API docs)https://rates.clams.techfor background rates syncing
Commands
The binary has two operational subcommands:
clams server setup: one-time interactive setup (OAuth provisioning + instance config)clams server run: runs the HTTP server
Run clams server --help / clams server setup --help / clams server run --help to see the full CLI, including which flags map to which environment variables.
Quick Start (Local)
This starts an instance bound to localhost with a local data directory:
export CLAMS_DATA_ROOT="$HOME/.local/share/clams/backend"
export CLAMS_SERVER_BIND="127.0.0.1:8080"
export CLAMS_SERVER_ENV="dev"
# One-time setup (opens a browser, or prints a URL to open manually).
clams server setup
# Run the server.
clams server run
Check:
curl -fsS http://127.0.0.1:8080/v1/health
Recommended Production Shape
1) Pick a persistent data root
Set CLAMS_DATA_ROOT to a durable, private filesystem location and run the process as a dedicated OS user.
clams server stores both application state and secrets under the data root. Back up and protect this directory.
2) Bind addresses
- Public HTTP bind:
CLAMS_SERVER_BIND- Recommended behind a reverse proxy:
127.0.0.1:8080 - Recommended inside containers:
0.0.0.0:8080
- Recommended behind a reverse proxy:
- Internal metrics bind (optional):
CLAMS_SERVER_INTERNAL_BIND- Recommended:
127.0.0.1:9090and only scrape locally/VPN
- Recommended:
clams server does not terminate TLS itself; use a reverse proxy / load balancer for HTTPS.
3) Run setup once
Setup provisions a service OAuth client via the auth service, persists instance configuration, and initializes the backend metadata store. See the authentication quickstart for details on token flows and validation.
CLAMS_DATA_ROOT=/var/lib/clams/backend \
CLAMS_SERVER_ENV=prod \
clams server setup
Notes:
- The user you log in as becomes the instance admin (stored in
<data_root>/instance.toml) --access-modeonly applies to setup (it is persisted). It does not affect run directly- To rotate existing credentials, run
clams server setup --rotate. This can change the stored instance admin depending on who logs in
4) Run the server under a process manager
CLAMS_DATA_ROOT=/var/lib/clams/backend \
CLAMS_SERVER_BIND=127.0.0.1:8080 \
CLAMS_SERVER_INTERNAL_BIND=127.0.0.1:9090 \
CLAMS_SERVER_ENV=prod \
CLAMS_SERVER_PUBLIC_BASE_URL=https://api.example.com \
clams server run
When run under a process manager, send SIGTERM to stop the process (Ctrl-C also works when running in a terminal).
Example: systemd (Linux)
This example assumes:
- binary at
/usr/local/bin/clams - data root at
/var/lib/clams/backend - the service runs as a dedicated
clamsuser/group
Environment file (example): /etc/clams-server.env
CLAMS_DATA_ROOT=/var/lib/clams/backend
CLAMS_SERVER_BIND=127.0.0.1:8080
CLAMS_SERVER_INTERNAL_BIND=127.0.0.1:9090
CLAMS_SERVER_ENV=prod
CLAMS_SERVER_PUBLIC_BASE_URL=https://api.example.com
RUST_LOG=info
Unit file (example): /etc/systemd/system/clams-server.service
[Unit]
Description=clams-server
After=network-online.target
Wants=network-online.target
[Service]
User=clams
Group=clams
EnvironmentFile=/etc/clams-server.env
ExecStart=/usr/local/bin/clams server run
Restart=on-failure
RestartSec=2
# Minimal hardening; adjust for your environment.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/clams/backend
[Install]
WantedBy=multi-user.target
Run clams server setup once before enabling the service (same environment file, but run interactively so you can complete the browser login).
Core Configuration
The primary configuration surface is environment variables (CLI flags are equivalent; see --help for the exact mapping).
Required (practically)
CLAMS_DATA_ROOT |
Where all state and secrets live. Defaults to an OS-specific location if unset:
|
Network
CLAMS_SERVER_BIND |
Public HTTP listener address. Default: 127.0.0.1:8080 |
CLAMS_SERVER_INTERNAL_BIND |
When set, exposes GET /metrics on a separate listener. Default: unset |
CLAMS_SERVER_TRUST_PROXY_HEADERS |
Enable only when a trusted proxy terminates inbound traffic and you need accurate client IP extraction for abuse controls and logging. When enabled, clams server trusts Fly-Client-IP and the first entry in X-Forwarded-For for client IP extraction. Default: false |
Public Base URL (recommended for invites/feedback)
CLAMS_SERVER_PUBLIC_BASE_URL |
Used when generating workspace invite acceptance links and in support feedback payloads. Must be an origin-only URL (scheme + host [+ port], no path/query/fragment). Validation depends on CLAMS_SERVER_ENV:
|
CLAMS_SERVER_ENV |
dev or prod. Default: prod |
Access Control (Instance Access Mode)
Access mode controls who can create workspaces on the instance:
open: any authenticated user can create workspacesinvite-only: users must be invited before they can create workspaces
This is configured during setup:
clams server setup --access-mode invite-only
# or
clams server setup --access-mode open
The chosen mode is persisted to <data_root>/instance.toml.
In invite-only mode, the common operational workflow is:
- Instance admin runs setup and creates the first workspace
- Admin invites users via
POST /v1/workspaces/{id}/invites - Invited users accept the invite
- Users with existing workspace membership can create their own workspaces
Limits and Abuse Controls
These are runtime configuration (not persisted in instance.toml):
Workspace/Profile limits
CLAMS_SERVER_MAX_WORKSPACES_PER_SUBJECT |
Maximum number of workspaces a subject (user) can create on this instance. Default: 1 |
CLAMS_SERVER_MAX_PROFILES_PER_WORKSPACE |
Default maximum number of profiles allowed within a workspace. Default: 5 |
CLAMS_SERVER_WORKSPACE_PROFILE_LIMITS |
Comma-separated WORKSPACE_ID=LIMIT overrides. Example: <workspace_id_1>=10,<workspace_id_2>=2 |
Workspace creation rate limits
CLAMS_SERVER_WORKSPACE_CREATE_PER_IP_HOURLY |
Per-client-IP hourly budget for POST /v1/workspaces. Default: 1 |
CLAMS_SERVER_WORKSPACE_CREATE_PER_IP_DAILY |
Per-client-IP daily budget for POST /v1/workspaces. Default: 3 |
CLAMS_SERVER_WORKSPACE_CREATE_GLOBAL_HOURLY |
Process-global hourly budget for POST /v1/workspaces. Default: 10 |
CLAMS_SERVER_WORKSPACE_CREATE_GLOBAL_DAILY |
Process-global daily budget for POST /v1/workspaces. Default: 50 |
Invite issuance rate limits
CLAMS_SERVER_INVITES_PER_WORKSPACE_DAILY |
Per-workspace daily budget for POST /v1/workspaces/{id}/invites. Default: 10 |
CLAMS_SERVER_INVITES_PER_RECIPIENT_DAILY |
Per-recipient-email daily budget for POST /v1/workspaces/{id}/invites. Default: 3 |
CLAMS_SERVER_INVITES_GLOBAL_DAILY |
Process-global daily budget for POST /v1/workspaces/{id}/invites. Default: 30 |
These limits are enforced in-memory per process. If you run multiple processes with independent data roots, limits are not shared.
Data Root Layout
The data root contains all persistent state and instance secrets.
Important files/directories:
<data_root>/service_oauth.toml |
service OAuth client credentials (secret) |
<data_root>/instance.toml |
instance admin + access mode |
<data_root>/meta/ |
shared backend metadata LMDB environment |
<data_root>/settings/ |
shared per-subject settings LMDB environment |
<data_root>/rates/ |
local rates cache LMDB environment (synced periodically) |
<data_root>/workspaces/<workspace_id>/<profile_id>/lmdb/ |
per-profile LMDB state |
<data_root>/workspaces/<workspace_id>/<profile_id>/archive/ |
per-profile durable archive store |
<data_root>/workspaces/<workspace_id>/<profile_id>/journals/ |
per-profile journal snapshots |
HTTP Endpoints (Ops-Relevant)
GET /v1/health(public): returns 200 when the process is runningGET /metrics(internal): Prometheus text format (only whenCLAMS_SERVER_INTERNAL_BINDis set)
The API surface is served under /v1. See the full references:
- REST API reference — interactive OpenAPI docs for the Clams Server API
- Auth API reference — interactive OpenAPI docs for the Clams Auth API
- Authentication quickstart — obtaining tokens, refresh flows, and offline validation
Logging
Logs are emitted to stdout by default and can be controlled with RUST_LOG (for example RUST_LOG=info or RUST_LOG=debug).
Optional rolling file logging (in addition to stdout):
CLAMS_SERVER_LOG_TO_DISK |
When true, write logs to rolling files under CLAMS_SERVER_LOG_DIR. Default: false |
CLAMS_SERVER_LOG_DIR |
Directory used for rolling log files. If it cannot be created, file logging is disabled and logs continue to go to stdout only. Default: <data_root>/logs/clams-server |
CLAMS_SERVER_LOG_RETENTION_MINUTES |
Deletes old log files once they are older than this age. Default: 60 |
CLAMS_SERVER_LOG_MAX_BYTES |
If set, keeps the total size of the log directory under this cap by deleting the oldest log files first. Default: unset |
Optional diagnostics:
CLAMS_SERVER_LOG_SUMMARY |
Enables periodic summary logs for some internal subsystems (currently includes offline auth cache hit/miss counters). Default: false |
CORS
clams server disables CORS by default. To enable it, configure:
CLAMS_SERVER_CORS_ORIGINS |
Comma-separated allowlist of allowed origins (scheme + host [+ port]). * is ignored. If the list is empty/invalid after parsing, CORS remains disabled. |
CLAMS_SERVER_CORS_ALLOW_CREDENTIALS |
Set to true only when you need browsers to send credentials (for example cookies via fetch(..., { credentials: "include" })). Default: false |
Example:
CLAMS_SERVER_CORS_ORIGINS=https://app.example.com,https://admin.example.com \
CLAMS_SERVER_CORS_ALLOW_CREDENTIALS=true \
clams server run
HTTP Hardening and Rate Limiting
clams server applies request-level hardening (timeouts, concurrency limits, load shedding) and per-client request rate limiting.
Common controls
CLAMS_SERVER_CONCURRENCY_LIMIT |
Global concurrency limit across the entire HTTP surface (all routes). Default: 128 |
CLAMS_SERVER_LOAD_SHED |
When true, rejects requests quickly when the server is overloaded instead of queueing work. Default: true |
CLAMS_SERVER_TIMEOUT_SECS |
Global per-request timeout (returns 504 on timeout). Default: 30 |
CLAMS_SERVER_RSS_SOFT_LIMIT_MB |
Best-effort memory load shedding: rejects requests (returns 503) when process RSS exceeds this threshold. Units are MiB. Default: unset |
Per-client request rate limiting
CLAMS_SERVER_RATE_LIMIT_RPM |
Sustained requests-per-minute budget per client. Default: 60 |
CLAMS_SERVER_RATE_LIMIT_BURST |
Additional burst capacity above the sustained rate. Default: 60 |
CLAMS_SERVER_RATE_LIMIT_TABLE_MAX_KEYS |
Maximum number of distinct client keys kept in memory for rate limiting. Default: 50000 |
CLAMS_SERVER_RATE_LIMIT_TRUST_X_FORWARDED_FOR |
When true, rate limiting keys requests by the real client IP from trusted proxy headers instead of the TCP peer IP. Enable only behind a trusted proxy that strips spoofed forwarding headers. Default: false |
Additional controls
LMDB max map size caps (bytes; default unset = allow growth up to 64 GiB):
CLAMS_SERVER_META_LMDB_MAX_MAP_SIZE_BYTES |
Max size for <data_root>/meta/ |
CLAMS_SERVER_SETTINGS_LMDB_MAX_MAP_SIZE_BYTES |
Max size for <data_root>/settings/ |
CLAMS_SERVER_PROFILE_LMDB_MAX_MAP_SIZE_BYTES |
Max size for <data_root>/workspaces/.../<profile_id>/lmdb/ |
CLAMS_SERVER_ARCHIVE_LMDB_MAX_MAP_SIZE_BYTES |
Max size for <data_root>/workspaces/.../<profile_id>/archive/ |
Values must be between 4 MiB and 64 GiB. If you see LMDB "database full"/"map full" errors, increase the relevant max map size and restart clams server.
Offline auth controls (offline JWT validation + in-memory caching):
CLAMS_AUTH_JWKS_RELOAD_COOLDOWN_SECONDS |
Minimum time between JWKS reload attempts. Default: 60 |
CLAMS_SERVER_AUTH_CACHE_MAX_TOKENS |
Maximum number of validated bearer tokens to cache in memory (bounded by token expiry). Default: 10000 |
Onchain outbound restrictions (SSRF / filesystem hardening):
CLAMS_ALLOW_PRIVATE_NETWORK_TARGETS |
When false, rejects onchain URLs that target private/loopback network addresses. Set to true if your Electrum/Esplora/Bitcoin Core endpoints are only reachable on private networks. Default: false |
CLAMS_ALLOW_RPC_COOKIE_FILE |
When false, rejects Bitcoin Core RPC configs that use a cookie file path. Set to true only when you trust all API users. Default: false |
Notification stream caps (Server-Sent Events):
CLAMS_SERVER_NOTIFICATIONS_MAX_STREAMS_GLOBAL |
Max concurrent /v1/notifications/stream connections across the process. Default: 64 |
CLAMS_SERVER_NOTIFICATIONS_MAX_STREAMS_PER_SUBJECT |
Max concurrent /v1/notifications/stream connections per subject. Default: 4 |
CLAMS_SERVER_NOTIFICATIONS_IDLE_TIMEOUT_SECS |
Closes an idle SSE stream when no events arrive within this window. Default: 300 |
Backups and Restores
Backups should include the entire data root directory.
Recommended approach:
- Stop
clams server - Copy/snapshot the data root directory
- Start
clams server
Restore by replacing the data root directory and restarting the process.
Troubleshooting
"setup required" on startup
clams server run requires a configured data root. Run:
clams server setup
clams server run
"invalid public base url"
Either:
- remove
CLAMS_SERVER_PUBLIC_BASE_URL(invites/feedback will be degraded), or - set a valid origin-only URL, and ensure:
- in
prod: HTTPS + non-loopback host - in
dev:CLAMS_SERVER_ENV=devif you wanthttp://localhost:...
- in
Clients appear to share one IP (rate limiting / abuse controls)
When running behind a reverse proxy, configure client IP handling carefully:
CLAMS_SERVER_TRUST_PROXY_HEADERS=truefor general client IP extractionCLAMS_SERVER_RATE_LIMIT_TRUST_X_FORWARDED_FOR=trueif you want per-client request rate limiting by real client IP
Enable only when the proxy is trusted and strips spoofed forwarding headers.
Notes
This guide is platform-agnostic. Run clams server the same way on bare metal, VMs, containers, or PaaS: provide a persistent data root, configure environment variables, run setup once, then run the server under a process manager.
Clams is free for personal use and businesses under $1M annual revenue. Enterprise license required above $1M.