Documentation
PulseBoard is a single F# binary that ingests metrics, logs, and traces; serves a live dashboard; and surfaces cost + AI insights over your own data. Everything below speaks plain HTTP.
Architecture in one minute
PulseBoard is one binary that plays two distinct roles. Pick the role at startup with command-line flags — you never need two different builds.
| Surface | What it is | Audience |
|---|---|---|
/, /docs, /pricing, /signup, /signin |
The public marketing site — static pages, unauthenticated. Same content for every visitor. | Prospects, search engines. |
/app |
The dashboard SPA — charts, queries, alerts. Authenticated with an API key (Bearer). | A signed-in member of one specific tenant. |
/admin |
The workspace admin — keys, members, plan,
billing, audit. Same Bearer auth, requires the admin scope. |
A workspace owner / operator. |
/ingest/*, /v1/*, /loki/*, /api/v1/write |
The data plane. Bearer-authenticated, RBAC-scoped. | Your agents, SDKs, scrapers. |
The dashboard and admin are per-workspace surfaces. In a real
hosted deployment they live behind a tenant-specific URL (for example
https://acme.pulseboard.cloud/app), and the public website at
pulseboard.cloud never links to them directly — you reach your
workspace by following the URL you got at sign-up, or by signing in with
your API key.
Deployment modes
The same binary runs in two configurations:
Single-tenant (default)
dotnet run -- --port=8080
One workspace per process. No /api/signup — the operator
provisions keys directly. Best for self-hosting one team or running
locally for development. This is what you get when you clone the repo
and run dotnet run with no flags.
Multi-tenant (the “edge”)
dotnet run -- --multi-tenant --port=8080 \
--postgres="Host=db;Database=pulse;Username=pulse"
Many workspaces share one process, isolated by tenant ID. The public
onboarding endpoint /api/signup is enabled and creates a new
tenant + API key on demand. Recommended for a hosted product. The
public marketing pages are served alongside, so a small deployment can
keep “website” and “edge” in one process.
pulseboard.cloud host that only serves
/, /docs, /pricing, /signup, and proxies the actual
POST /api/signup to a provisioning service that spins up a
per-tenant workspace on a subdomain like https://acme.pulseboard.cloud.
See self-hosting for the current recipe and the
project PLAN.md for the hosted/provisioner roadmap.
Quickstart
Sign up gets you an API key. Use it as a Bearer on every data-plane call.
# 1. Create a tenant + key
curl -sS -X POST http://127.0.0.1:8080/api/signup \
-H 'content-type: application/json' \
-d '{"slug":"acme","email":"you@acme.com"}'
# → {"tenantId":"...","apiKey":"pk_xxx.yyy",...}
export PB_KEY="pk_xxx.yyy"
# 2. Push a metric
curl -sS -X POST http://127.0.0.1:8080/ingest/metrics \
-H "Authorization: Bearer $PB_KEY" \
-H 'content-type: application/json' \
-d '[{"name":"payments.latency.p99","value":42.5}]'
# → {"accepted":1}
Authentication
Two paths:
- API key (
pk_id.secret) — for ingest, query, admin. Pass asAuthorization: Bearer pk_.... - OIDC — optional SSO for human dashboard / admin users. Configure via the OIDC env vars in self-hosting.
RBAC scopes: ingest, query, admin.
A key carries one or more scopes; admin endpoints require admin.
Ingest — metrics & logs
Metrics (native JSON)
POST /ingest/metrics
[
{"name":"payments.latency.p99", "value":42.5, "ts": 1717000000},
{"name":"payments.qps", "value":1240}
]
Returns {"accepted":N} on success. The series name is the
attribution key for cost transparency: by default
PulseBoard groups by the first dot-segment, so payments.* rolls
up under the payments team.
Logs (native JSON)
POST /ingest/logs
[
{"ts": 1717000000, "level":"warn", "msg":"slow query", "service":"api"}
]
OTLP / Prometheus / Loki
The edge also accepts:
| Endpoint | Format |
|---|---|
/v1/metrics | OTLP HTTP/JSON |
/v1/logs | OTLP HTTP/JSON |
/api/v1/write | Prometheus remote_write (snappy/protobuf) |
/loki/api/v1/push | Loki push API |
All four honor the same Bearer auth and the same per-tenant quotas.
Query
GET /api/series?name=&from=&to=
Returns time-bucketed samples for the given series in the bearer's tenant. For full PromQL-style expressions see the in-app query console under /app.
Alerts & live dashboards
Alerts evaluate on a tick and fire via the configured Notify channels
(SMTP, webhook, …). Live dashboards stream over WebSocket at /ws;
the bundled /live page is a minimal viewer.
AI assist
POST /api/ai/explain
{
"seriesName": "payments.latency.p99",
"samples": [{"ts":1,"value":1.0},{"ts":2,"value":1.1},{"ts":3,"value":9.0}],
"question": "why the spike?"
}
Returns:
{
"provider": "echo",
"summary": "...mean=3.70, stddev=3.78, max=9.00 @ 3.
Largest single-step jump was 7.90 at 3 (> 2× stddev)...",
"annotations": { "mean":"3.70", "stdDev":"3.78", "spikeTs":"3" }
}
The provider is pluggable (IAiProvider). OSS ships
EchoAiProvider: deterministic mean/stddev/jump analyzer that
runs in-process — no network calls, no leaked data. The hosted edition can
plug an OpenAI/Anthropic/local-vLLM adapter behind the same interface,
gated by a per-tenant ai.enabled flag.
Cost transparency
Every accepted ingest call is attributed to its series. Two admin reads:
| Endpoint | Returns |
|---|---|
GET
/api/admin/tenants/<id>/cost/series?top=N |
Top-N series by sample count, with estimated bytes and projected monthly USD at the Pro IngestBytes rate. |
GET
/api/admin/tenants/<id>/cost/teams |
Aggregated by team policy (default = first dot-segment of the series
name; payments.* → payments). |
The numbers are projected at the public Pro rate of $0.50 / GiB ingest, so the "what would this cost me?" answer always matches the pricing calculator.
Pricing & billing
Two public, unauthenticated endpoints back the calculator UI:
- GET
/api/pricing— the full rate card (plans, included caps, per-unit overage rates). - POST
/api/pricing/estimate— pass a usage map, get a line-itemed estimate for every plan.
Per-tenant usage and billing snapshots are admin-scoped:
GET /api/admin/tenants/<id>/billing/current
GET /api/admin/tenants/<id>/billing/rollup?from=&to=
Admin & RBAC
The /admin UI is API-key gated. It surfaces:
- Tenants, members, scopes, key rotation
- Plan upgrades + soft caps
- Billing rollups + invoice CSVs
- Cost attribution (series + teams)
- Audit log
Self-hosting
git clone https://github.com/.../pulseboard
cd pulseboard/src/edge
dotnet run -- --multi-tenant --port=8080
# With Postgres (recommended for prod):
dotnet run -- --multi-tenant \
--postgres="Host=db;Port=5432;Database=pulse;Username=pulse" \
--port=8080
Useful env vars:
| Var | Purpose |
|---|---|
PULSE_DATA_DIR | Local data dir (default: ./data) |
PULSE_OIDC_AUTHORITY | Enable OIDC SSO for humans |
PULSE_OIDC_CLIENT_ID | OIDC client |
PULSE_NOTIFY_SMTP_* | SMTP notify channel |
License
PulseBoard core is released under MIT. See
LICENSE in the source tree.