Open Spanner
Open Spanner is an open source metering service for tracking product usage:
- Who used something
- What meter was used
- When it happened
- How much was used
- Where / in what context through typed metadata
It is API-first and intentionally small. Sign in to the dashboard, create API keys for server-side clients, define meters, ingest usage events, query usage buckets, export data, and inspect activity from the embedded dashboard.
Features
- Meter definitions with units, aggregation mode, retention policy, and metadata schema
- Single and bulk usage ingestion with idempotency
- Bucketed usage queries with filtering, grouping, direct CSV export, and queued export jobs
- Dashboard registration, cookie sessions, and API key management
- Raw usage event search, pagination, CSV export, and retention pruning in the service API
- SQLite and Postgres storage
- Embedded React dashboard
- Swagger/OpenAPI docs
- Generated SDKs for Go, Python, TypeScript, and C#
Use Cases
Open Spanner is built for products that need a trusted usage record for billing, limits, reporting, and audits. Common patterns include:
| Use case |
What you can meter |
| API request metering |
Request volume by endpoint, method, status, region, and service tier |
| AI token usage |
Tokens by model, provider, operation, and cache path |
| Storage usage |
Capacity by tier, region, and resource type |
| Active users |
Seats, workspaces, roles, plans, and active accounts |
| Background jobs |
Queue throughput, job outcomes, and worker regions |
| Feature usage |
Product adoption, entitlement usage, and plan-level behavior |
| Historical backfill |
Older usage imported with stable idempotency keys |
Each advanced use case has runnable Go, TypeScript, Python, and C# examples in examples/advance.
Quick Start
Install Task:
winget install Task.Task
# or
brew install go-task/tap/go-task
# or
npm install -g @go-task/cli
Run the API with SQLite storage:
task run:sqlite
Queued export jobs are processed by the export worker. Start it in a second terminal when you want dashboard export jobs to produce downloadable files:
task run:export-worker
Open:
Dashboard: http://localhost:18081/login
API docs: http://localhost:18081/docs
Health: http://localhost:18081/health
Ready: http://localhost:18081/ready
Basic Flow
Register or log in through the dashboard, then create an API key from the API Keys page. Copy the key when it is created; only its prefix is shown after that.
Use the API key from SDKs or direct API calls:
API_KEY="osp_..."
Create a meter:
curl -X POST http://localhost:18081/v1/meters \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "api_requests",
"description": "API requests accepted by the service",
"unit": "request",
"aggregation": "sum",
"event_retention_days": 90,
"dimensions": [
{ "name": "region-name", "type": "string" },
{ "name": "service.tier", "type": "string" },
{ "name": "status_code", "type": "number" }
]
}'
Record usage:
curl -X POST http://localhost:18081/v1/usages \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "usage_001",
"subject": "org_123",
"meter": "api_requests",
"quantity": 1,
"timestamp": "2026-06-09T12:00:00Z",
"metadata": {
"region-name": "us-east-1",
"service": {
"tier": "gold"
},
"status_code": 200
}
}'
Query usage:
curl "http://localhost:18081/v1/usages?subject=org_123&meter=api_requests&from=2026-06-01T00:00:00Z&to=2026-07-01T00:00:00Z&bucket_size=day&metadata.service.tier=gold&limit=100" \
-H "Authorization: Bearer $API_KEY"
Concepts
A meter defines what can be measured, such as api_requests, storage_bytes, seats, or tokens.
A usage event records one measurement for one subject at one time. It includes a subject, meter, quantity, timestamp, optional metadata, and an idempotency key.
Meters support these aggregation modes:
sum, count, avg, min, max, first, last, rate
Metadata schemas support:
string, number, boolean
API
The full API reference is available in Swagger UI when the server is running:
http://localhost:18081/docs
Dashboard access uses HttpOnly cookies. SDKs and service-to-service clients use API keys in the Authorization: Bearer <key> header. API keys are created and deleted from the dashboard.
SDKs
Regenerate SDKs:
task sdk:go
task sdk:python
task sdk:typescript
task sdk:csharp
Configuration
| Variable |
Default |
Description |
OPEN_SPANNER_HTTP_ADDR |
:18081 |
API listen address |
OPEN_SPANNER_DB_DRIVER |
sqlite |
Storage driver: sqlite or postgres |
OPEN_SPANNER_SQLITE_PATH |
open-spanner.db |
SQLite database path |
OPEN_SPANNER_POSTGRES_DSN |
|
Postgres connection string when OPEN_SPANNER_DB_DRIVER=postgres |
OPEN_SPANNER_DB_MAX_OPEN_CONNS |
0 |
Maximum open SQL connections; 0 keeps Go's default |
OPEN_SPANNER_DB_MAX_IDLE_CONNS |
0 |
Maximum idle SQL connections; 0 keeps Go's default |
OPEN_SPANNER_DB_CONN_MAX_LIFETIME |
0 |
Maximum SQL connection lifetime; 0 disables recycling |
OPEN_SPANNER_DB_CONN_MAX_IDLE_TIME |
0 |
Maximum SQL connection idle time; 0 disables idle-time recycling |
OPEN_SPANNER_EXPORT_STORAGE_PATH |
open-spanner-exports |
Directory used by the API and export worker for generated export files |
OPEN_SPANNER_EXPORT_WORKER_INTERVAL |
5s |
How often the export worker checks for queued jobs |
OPEN_SPANNER_EXPORT_WORKER_LOCK_TTL |
5m |
Lease duration for a claimed export job |
OPEN_SPANNER_EXPORT_WORKER_TIMEOUT |
10m |
Maximum processing time for one export job |
OPEN_SPANNER_EXPORT_WORKER_MAX_ATTEMPTS |
3 |
Maximum claim attempts before expired running jobs stop being retried |
OPEN_SPANNER_RETENTION_PRUNE_ENABLED |
false |
Enable automatic retention pruning |
OPEN_SPANNER_RETENTION_PRUNE_INTERVAL |
1h |
Background prune interval |
OPEN_SPANNER_RETENTION_PRUNE_TIMEOUT |
30m |
Maximum duration for one background prune run |
Run with Postgres storage:
task postgres:up
task run:postgres
task run:export-worker:postgres
Run the API and export worker in separate terminals so both processes stay active.
For a containerized Postgres deployment, docker-compose.app.yml starts the API, export worker, Postgres, and a shared export volume together.
Run Postgres integration tests:
task test:postgres
Development
task test
task vet
task openapi
task openapi:convert
task openapi:sdk
task docs:build
task admin:build
Run the React dashboard dev server:
task admin:dev
Project Structure
cmd/api API entrypoint
cmd/export-worker Queued export worker entrypoint
internal/config Environment configuration
internal/server/http HTTP server wiring
internal/ui Embedded dashboard routes/assets
internal/metering Domain, app services, adapters, workers
web React dashboard source
docs Fumadocs documentation site
openapi Generated Swagger/OpenAPI artifacts
sdk Generated SDKs
examples SDK examples
License
Open Spanner is licensed under the MIT License. See LICENSE.