Coffer is a small Go service that stores information about paying patrons and ledger balances in a local SQLite database. The command line tool (coffer) communicates with the HTTP server (started with coffer serve) to post ledger transactions, manage API keys and allocation rules, and to fetch summary metrics. The server also processes Stripe webhooks to keep patron records and payments in sync. All operations that modify state require an API key.
- Environment driven configuration - Paths to the database file and listen port are taken from environment variables (
DB_FILE_PATHandPORT). Additional credentials such as the Stripe key, webhook secret and bootstrap API key are loaded from files under a directory specified byCREDENTIALS_DIRECTORY. This conforms with the waysystemdexposes credentials to services.systemdunit files are provided out of the box in theinitfolder. - Pluggable storage via interfaces - The
servicepackage exposes interfaces for the ledger, patrons, allocation rules, metrics and Stripe events. Actual persistence uses theinternal/databasepackage, but the design allows other storage layers. - SQLite schema initialization - On startup the server opens the database and creates tables for customers, subscriptions, payments, payouts, transactions, allocation rules and API keys if they do not already exist. Default allocation rules are inserted when none are present.
- API key management - API tokens are salted and hashed in the database. Bootstrap initialization is an explicit
coffer initstep, and new keys are created and revoked through the/admin/keysendpoints. - CORS whitelist managment - Cross-Origin Resource Sharing origins are stored in the database and managed via the
/admin/corsAPI. The initial allowed origins can be seeded duringcoffer init. - Stripe integration - Webhook payloads are validated using the Stripe signature secret. Events update the customer, subscription, payment and payout tables and post ledger entries for successful payments.
- Allocation based ledger posting - Each payment is split across one or more ledgers using configurable percentage rules. The rules must sum to 100 percent.
- Authentication middleware - Protected endpoints require the
Authorization: Bearerheader and can enforceread,write, oradminpermissions. Tokens are verified against the stored API keys before the request is forwarded.
This document describes the HTTP endpoints implemented under the /api/v1 prefix.
Every response follows the structure defined in internal/api/api.go:
{
"error": { "code": int, "message": string } | null,
"data": <payload or null>
}Status codes and payloads for each route are listed below.
Checks basic application status.
Response Codes
200 OK– service and database reachable503 Service Unavailable– database check failed
Response Body (HealthResponse)
{
"status": "ok",
"db": "ok" | "unreachable"
}Retrieve a ledger snapshot.
Path parameter ledger is the ledger name.
Query Parameters
since(YYYY-MM-DD, optional) – start date. Defaults to epoch start.until(YYYY-MM-DD, optional) – end date. Defaults to current time.
Invalid dates return 400 Bad Request.
Response Codes
200 OKwith snapshot500 Internal Server Erroron storage errors
Response Body (LedgerSnapshot)
{
"opening_balance": int,
"incoming_funds": int,
"outgoing_funds": int,
"closing_balance": int
}List transactions for the ledger.
Query Parameters
limit(integer, optional, default 100)offset(integer, optional, default 0)
Non‑integer values return 400 Bad Request.
Response Codes
200 OKwith list500 Internal Server Erroron storage errors
Response Body – array of Transaction
[
{
"id": string,
"ledger": string,
"amount": int,
"date": "RFC3339 timestamp",
"label": string
}
]Create a new transaction.
Request Body (CreateTransactionRequest)
{
"id": string?,
"date": "RFC3339",
"amount": int,
"label": string
}Response Codes
201 Createdon success400 Bad Requestfor malformed JSON or invalid date401 Unauthorizedfor missing/invalid token500 Internal Server Erroron storage errors
Returns summary subscription metrics.
Response Codes
200 OKwith metrics500 Internal Server Errorif metrics collection fails
Response Body (Metrics)
{
"patrons_active": int,
"mrr_cents": int,
"avg_pledge_cents": int,
"payment_success_rate_pct": number,
}List known patrons.
Query Parameters
limit(integer, optional, default 100)offset(integer, optional, default 0)
Invalid values return 400 Bad Request.
Response Codes
200 OKwith array500 Internal Server Erroron storage errors
Response Body – array of Patron
[
{
"id": string,
"name": string,
"created_at": "RFC3339 timestamp",
"updated_at": "RFC3339 timestamp"
}
]Retrieve ledger allocation rules.
Response Codes
200 OKwith rules401 Unauthorizedfor missing/invalid token500 Internal Server Erroron retrieval error
Response Body – array of AllocationRule
[
{
"id": string,
"ledger": string,
"percentage": int
}
]Replace all allocation rules.
Request Body – array of the same AllocationRule objects. Percentages must sum to 100.
[
{
"id": string,
"ledger": string,
"percentage": int
}
]Response Codes
204 No Contenton success400 Bad Requestfor malformed JSON or invalid percentages401 Unauthorizedfor missing/invalid token500 Internal Server Erroron storage error
Retrieve the list of allowed CORS origins.
Response Codes
200 OKwith origins401 Unauthorizedif token missing/invalid500 Internal Server Erroron retrieval error
Response Body – array of AllowedOrigin
[
{
"url": string
}
]Replace all allowed origins.
Request Body – array of AllowedOrigin objects. URLs must start with http:// or https://.
[
{
"url": string
}
]Response Codes
204 No Contenton success400 Bad Requestfor malformed JSON or invalid origins401 Unauthorizedfor missing/invalid token500 Internal Server Erroron storage error
Create a new API key.
Response Codes
201 Createdwith generated token401 Unauthorizedif token missing/invalid500 Internal Server Erroron failure
Response Body
{
"token": "issued-token",
"permissions": [
{
"key": "admin",
"display": "Admin",
"description": "Administrative access"
}
]
}Return API key metadata by id.
Response Codes
200 OKwith key metadata400 Bad Requestif id is empty401 Unauthorizedif token invalid404 Not Foundif the key does not exist500 Internal Server Erroron failure
Replace the granted permissions for an existing key.
Response Codes
204 No Contenton success400 Bad Requestfor malformed JSON or invalid permissions401 Unauthorizedif token invalid404 Not Foundif the key does not exist500 Internal Server Erroron failure
Delete an API key by id.
Response Codes
204 No Contenton success400 Bad Requestif id is empty401 Unauthorizedif token invalid500 Internal Server Erroron failure
Stripe webhook endpoint. Payload is validated using the Stripe-Signature header. Only intended to be called by Stripe's API.
Headers
Stripe-Signature: signature provided by Stripe
Response Codes
200 OKwhen event accepted400 Bad Requestif signature verification or body parsing fails
Body content is ignored; no data returned.
Protected endpoints require an API key. Provide it via the Authorization header. Either Bearer <token> or just the raw token are accepted by the middleware.
The coffer CLI separates remote API calls under the api command and local configuration under env. Administrative API operations are exposed under api admin. A fresh deployment should be initialized with coffer init before running coffer serve. Settings are stored in an environments.json file under the configuration directory (default ~/.config/coffer).
# initialize keys and CORS defaults before first run
coffer init
# start the coffer HTTP API server
coffer serve
# list configured environments ("*" marks the active one)
coffer env list
# create a new environment and bootstrap an API key
coffer env create staging --base-url http://staging.example.com --bootstrap
# switch the CLI to use that environment
coffer env activate staging
# delete an environment
coffer env delete staging
# post a transaction to a ledger
coffer api ledger tx create main --amount 1000 --date 2024-05-01T00:00:00Z --label example
# inspect admin allocations through the API
coffer api admin allocations get
# show current status
coffer statusRun all unit tests with:
go test ./...All current tests should pass.