Clams REST API Guide | For Developers & Coding Agents
Developer and coding agent guide to the Clams Server REST API. Bitcoin accounting with auth, wallet connections, syncing, and reports.
The Clams Server exposes a REST API that lets you build Bitcoin accounting into your own tools. Automate tax reporting, build portfolio dashboards, or let a coding agent manage your books. This post walks through the full workflow, from authenticating to pulling reports. The full API reference is available at clams.tech/docs/api.
Contents:
Authentication
Creating a Workspace
Setting Up a Profile
Connecting Wallets
Syncing
Real-Time Updates
Processing Journals
Metadata and Annotations
Reports
Tooling Tips
Before anything else, download and run Clams Server. Two prerequisites for everything below: your server is set up (clams server setup), and you sign in as a user with an active license. Paid workflows (connecting wallets, syncing, importing, processing journals, generating reports) require one, and creating profiles draws on your instance's profile capacity.
If you're wiring up Clams from Claude Code, Codex, Open Code, or any other agent framework, everything here applies. The curl commands are copy-pasteable and both the Auth and Server API docs are designed to be machine-readable.
Setting up your agent? Point it at these resources in order:
- Auth quickstart: plain markdown that explains how the auth layer works. It's the fastest way for an LLM to understand token flows, refresh semantics, and client credentials.
- Auth API reference: full Swagger/OpenAPI spec for the auth server.
- REST API reference: full Swagger/OpenAPI spec for the Clams Server API.
Authentication
The API uses Bearer tokens (EdDSA-signed JWTs) issued by the Clams auth server at auth.clams.tech. You don't need to run the auth server yourself. We host it.
The basic flow: log in via a browser, exchange the resulting token for an audience-scoped access token and refresh token, then refresh as needed. The access token goes in the Authorization header on every API call. The auth quickstart walks through each step with curl commands.
Key things to know:
- Access tokens expire in 5 minutes. If you're calling the API directly, refresh is your responsibility.
- Refresh tokens are opaque, 90-day TTL, and single-use: every refresh returns a new one, and presenting an old one revokes the entire session. Always persist the newest token. When the 90-day TTL expires, you'll need to re-authenticate via the browser login flow.
- Audiences: when requesting a token, you specify an audience. Use svc for the Clams Server API, rates for exchange rates, or feedback for the feedback endpoint. Most of what this guide covers uses svc.
- Client credentials (provisioned via POST /v1/oauth-clients) mint tokens for the rates and feedback audiences only. There is no machine-to-machine path to svc: everything that touches your books runs as a signed-in, licensed user.
Give your automation its own session. Don't read or rewrite the CLI's stored credentials.json: refresh tokens are single-use, so a script and the CLI refreshing from the same session will invalidate each other and can revoke the session outright. Instead, log in once for the automation context and keep its tokens separate:
- Start a login session:
POST https://auth.clams.tech/v1/web/login/session/start. The response includes abrowser_url, alogin_session_id, and apoll_secret. - Open
browser_urland sign in with Google, Apple, or an email magic link. - Poll
GET /v1/web/login/session/{login_session_id}with thex-clams-login-session-poll-secretheader until it returns"status": "completed"and a short-livedaccess_token. - Exchange it for svc tokens:
POST /v1/token/exchangewith the access token assubject_tokenand"audience": "svc". - Store the returned refresh token in your own secrets store. Refresh with
POST /v1/token("grant_type": "refresh_token","audience": "svc"), saving the rotated token every time.
The auth quickstart has copy-pasteable curl commands for each step, and the Auth API reference documents the full request fields, including the billing fields the exchange endpoint expects.
Postman users: You can automate token refresh using a pre-request script on your collection. The script calls the refresh endpoint before each request and stores the new token in an environment variable. No more manual 401 cycling.
Every curl command below assumes $TOKEN and $CLAMS_BASE_URL are set.
Set your base URL before making any API calls. If you've deployed Clams Server to a remote host, replace this with your server's URL (e.g. https://api.your-domain.com).
export CLAMS_BASE_URL="http://127.0.0.1:8080"Creating a Workspace
A workspace is your top-level organizational unit. Profiles live inside workspaces and each profile isolates its own user data. Create a workspace first:
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
$CLAMS_BASE_URL/v1/workspaces \
-d '{"label": "my-workspace"}'
Grab the id from the response. You'll need it for everything that follows.
Setting Up a Profile
A profile holds your connections, settings, and data. The setup endpoint combines four separate calls (create profile, create onchain config, select it, set profile settings) into one:
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/setup \
-d '{
"profile_label": "main",
"fiat_currency": "USD",
"gains_algorithm": "FIFO",
"tor_proxy": null,
"onchain_label": "my-electrum",
"onchain_kind": "Electrum",
"onchain_url": "tcp://your-electrum-server:50001",
"rpc_cookie": null,
"rpc_user": null,
"rpc_password": null,
"select_as_default": true
}'
onchain_kind types:
- Electrum
- Esplora
- BitcoinRpc (requires rpc_user/rpc_password or rpc_cookie)
Tor users: If your node is on Tor (Start9, Umbrel, etc.), you'll need to set the tor_proxy field. The port depends on how you run Tor: standalone Tor daemon defaults to 127.0.0.1:9050, and Tor Browser defaults to 127.0.0.1:9150.
If you need granular control, you can make the four calls individually: create profile, create onchain config, select it, set profile settings. See the API reference for each endpoint.
Connecting Wallets
With a profile ready, add a connection. Most kinds (Descriptor, XPub, Address, etc.) require configuration at creation time. For a descriptor wallet:
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/connections \
-d '{"label": "my-wallet", "kind": "Descriptor", "configuration": {"descriptor": "wpkh([fingerprint/84h/0h/0h]xpub.../0/*)"}}'
You can update a connection's configuration later with PATCH:
curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/connections/{connection_id} \
-d '{"configuration": {"descriptor": "wpkh([fingerprint/84h/0h/0h]xpub.../0/*)"}}'
List your connections to confirm:
curl -s -H "Authorization: Bearer $TOKEN" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/connections?limit=50"
Connection kinds available at time of writing:
- CoreLn — Core Lightning via Commando
- Lnd — LND via LNC pairing phrase or gRPC
- Nwc — Nostr Wallet Connect
- Phoenixd — phoenixd's HTTP API
- XPub, Descriptor, Address — watch-only Bitcoin chain scanning
- LiquidDescriptor — Liquid CT descriptors (requires a Liquid chain source)
- Phoenix, River — built-in CSV imports
- Custom — CSV imports with your own mapping
Each kind has its own configuration fields. See the API reference for details on each. You can also list available kinds from the API directly:
curl -s -H "Authorization: Bearer $TOKEN" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/connections/kinds"Syncing
Sync all connections for a profile:
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/connections/sync
This returns an operation_id. Check its status:
curl -s -H "Authorization: Bearer $TOKEN" \
$CLAMS_BASE_URL/v1/operations/{operation_id}
The status will move through Queued → Running → Succeeded, SucceededWithFailures, or Failed. SucceededWithFailures means some connections synced while others hit errors — check the failures array in the result for details.
Real-Time Updates
Instead of polling, you can connect to the SSE notifications stream:
curl -H "Authorization: Bearer $TOKEN" \
"$CLAMS_BASE_URL/v1/notifications/stream?operation_id={operation_id}"
Events arrive as JSON:
{
"operation_id": "...",
"phase": "Progress",
"level": "Info",
"message": "Fetching transactions",
"progress": { "current": 12, "total": 50 }
}
Useful if you're building a UI or want to trigger the next step automatically.
Processing Journals (Required Before Reports)
Syncing only imports raw records. To turn them into journal entries (which power all the reports), you need to process journals separately. If you skip this step, reports will return empty or incomplete data.
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/journals/process
Unlike syncing, this call is synchronous — it returns a JournalRunOutcome directly with the number of quarantined events (if any). Run this after every sync before pulling reports.
Metadata and Annotations
Once journals are processed, you can add notes, tags, and exclusions to journal events before pulling reports. First, list your journal events to grab an event ID:
curl -s -H "Authorization: Bearer $TOKEN" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/journals/events?limit=10"
Add a note:
curl -s -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/metadata/records/{event_id}/note" \
-d '{"note": "Office hardware purchase"}'
Create a tag:
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/metadata/tags" \
-d '{"code": "business-expense", "label": "Business Expense"}'
The code is a canonical identifier (automatically lowercased). The label is the display name shown in UIs.
Exclude an event from reports:
curl -s -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/metadata/records/{event_id}/excluded" \
-d '{"excluded": true}'Reports
With your data synced, journals processed, and any annotations in place, you can pull reports. The balance sheet and portfolio summary always return current state. Capital gains supports date range filtering.
Balance sheet:
curl -s -H "Authorization: Bearer $TOKEN" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/reports/balance-sheet?limit=50"
Portfolio summary:
curl -s -H "Authorization: Bearer $TOKEN" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/reports/portfolio-summary"
Capital gains CSV (with optional date range for tax reporting):
curl -s -H "Authorization: Bearer $TOKEN" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/reports/capital-gains?start=2025-01-01T00:00:00Z&end=2025-12-31T23:59:59Z" \
-o capital-gains-2025.csv
Journal entries CSV:
curl -s -H "Authorization: Bearer $TOKEN" \
"$CLAMS_BASE_URL/v1/workspaces/{workspace_id}/profiles/{profile_id}/reports/journal-entries" \
-o journal-entries.csvTooling Tips
Readable output: Pipe everything through jq . for formatted JSON.
API docs: Browse the full API reference at clams.tech/docs/api and the auth API at clams.tech/docs/auth.
Postman: Import both OpenAPI specs directly into Postman to get the full collection for each API. Just paste these URLs into Postman's import dialog:
- https://clams.tech/docs/auth/openapi.json
- https://clams.tech/docs/api/openapi.json
Error format: Errors return JSON with a code, message, and optionally details and request_id.
Rate limiting: The self-hosted server has concurrency limits (default 128 concurrent requests) and request timeouts (default 30s), both configurable via environment variables. CSV exports are limited to 2 concurrent downloads.
Go Build Something
Clams v1 is out. We're actively improving the API based on what people build with it. If you run into issues or want to share what you're working on, use the built-in feedback endpoint (POST /v1/feedback) or reach out at support@clams.tech.
Clams Team