Clams Server

Your Bitcoin books, deployed.

install:

View install script
$ 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.

  • REST API for programmatic access
  • Accessible via CLI for terminal workflows
  • Deploy anywhere: cloud, VPS, bare metal
  • Connect multiple clients to a single instance
  • Role-based access for team members and clients

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

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 server listening on plain HTTP behind a TLS-terminating reverse proxy
  • A persistent data directory for LMDB state and instance secrets
  • A single clams server process per data root (multiple processes against the same data root is not a validated deployment shape)
  • Network access from clams server to:
    • https://auth.clams.tech for login/setup, JWKS validation, invite emails, and support feedback forwarding (Auth API docs)
    • https://rates.clams.tech for 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
  • Internal metrics bind (optional): CLAMS_SERVER_INTERNAL_BIND
    • Recommended: 127.0.0.1:9090 and only scrape locally/VPN

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-mode only 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 clams user/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:
  • Windows: %APPDATA%\Clams\backend
  • macOS: $HOME/Library/Application Support/Clams/backend
  • Linux: $XDG_DATA_HOME/clams/backend or $HOME/.local/share/clams/backend
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:
  • prod (default): requires https and a non-loopback host
  • dev: allows http and loopback hosts
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 workspaces
  • invite-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:

  1. Instance admin runs setup and creates the first workspace
  2. Admin invites users via POST /v1/workspaces/{id}/invites
  3. Invited users accept the invite
  4. 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 running
  • GET /metrics (internal): Prometheus text format (only when CLAMS_SERVER_INTERNAL_BIND is set)

The API surface is served under /v1. See the full references:

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:

  1. Stop clams server
  2. Copy/snapshot the data root directory
  3. 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=dev if you want http://localhost:...
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=true for general client IP extraction
  • CLAMS_SERVER_RATE_LIMIT_TRUST_X_FORWARDED_FOR=true if 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.