API and webhooks
Pipemetry exposes a read-only JSON API for your own workspace’s numbers, plus outbound webhook endpoints you can register and verify.
API keys
An org admin mints keys from workspace settings. Three things to know:
- The full key is returned exactly once, at creation. Pipemetry stores only its SHA-256 hash plus a short display prefix, so it can never show you the key again — copy it then.
- Listing keys returns metadata only: prefix, name, timestamps, revoked state.
- Revoking is immediate and idempotent. A revoked key stops resolving on its very next request.
Keys look like pm_live_…. A token that does not carry that prefix is rejected at the edge without
a database lookup.
The read API
Authenticate with the key in a Bearer header:
curl -H "Authorization: Bearer pm_live_…" \ "https://<your-pipemetry-api-host>/v1/forecast?as_of_day=2026-08-07"There is no development bypass on /v1 — a valid key is required in every environment.
| Endpoint | Returns |
|---|---|
GET /v1/forecast | The forecast-category roll-up and totals |
GET /v1/opportunities | Reconstructed opportunities, plus a full-cohort win-rate block |
GET /v1/accuracy | Submitted-vs-actual scorecards (MAPE, bias, compliance) |
Money values are decimal strings on the wire, not floats — so nothing is lost to binary floating point before it reaches your spreadsheet or warehouse.
GET /v1/forecast
| Parameter | Notes |
|---|---|
as_of_day | Required. The calendar day to roll up as of |
group_by | owner (default), segment, or stage |
model_id | Optional. Omitted means your workspace’s active model. Pro |
forecast_type | all (default), new_business, renewal, expansion. Pro when not all |
measure | bookings (default) or arr. Pro when arr |
The base roll-up — owner/segment/stage, your active model, whole-cohort bookings — is available on
Starter. The three parameters marked Pro are each checked only when you actually use them, so a
Starter key gets a working forecast and a 403 upgrade_required only if it asks for a Pro
capability. An unrecognised model_id is a 422.
GET /v1/opportunities
as_of_day is required. Narrow with stage, owner, segment, forecast_category, deal_type
(new_business / renewal / expansion) or q (free text over name and account). Page with
limit (1–200, default 50) and offset, and sort with sort + dir. An unsupported sort field
is a 422 — the sortable set is curated server-side.
The response also carries win_rate, settled_won and settled_lost, computed over every
settled deal the key can see — not just the page you requested.
GET /v1/accuracy
as_of_day is required. periods selects how many closed periods to score (default 4, max 8), and
group_by is owner or manager. model_id is accepted here too, and scores the AI line under
that model; an unrecognised value is a 422. See
Accuracy and backtesting for what the fields mean.
Two capabilities on this endpoint are sold on Pro — group_by=manager (manager roll-up) and
choosing a model — but neither is enforced on this path today, unlike on /v1/forecast. Build
against your plan rather than against what the endpoint currently allows.
Rate limits
Reads are limited to 120 requests per minute by default. Two things to know before you size an integration against that number:
- The limit is not per API key. It is keyed on the connecting host, which behind Pipemetry’s edge proxy is the proxy itself — so in practice it behaves as a shared ceiling rather than a per-caller allowance. A separate per-IP limit applies at the edge underneath it.
/v1shares the bucket with the app’s own reads. The same read limiter serves the browser-facing endpoints, so heavy API traffic and heavy app usage draw on the same budget.
Every response — allowed or rejected — carries X-RateLimit-Limit and X-RateLimit-Remaining, and
a 429 also carries Retry-After: 60. Cache and back off rather than polling hard.
Webhooks
An org admin registers an HTTPS endpoint per workspace.
- Register returns the signing secret (
whsec_…) once. Store it on your receiver; it is never shown again. - The URL is validated before it is accepted: HTTPS only, and no loopback or private-network hosts.
A rejected URL is a
422 invalid_webhook_url. This is what stops a registered webhook being used to reach internal addresses. - Listing returns metadata only — URL, description, active flag, last delivery state — never the secret.
- Send test dispatches a signed
test.pingenvelope to every active endpoint and reports each one’s outcome, so you can prove your receiver works end to end.
The envelope
{ "id": "…", "type": "test.ping", "workspace_id": "…", "created_at": "…", "data": {}}Verifying a delivery
Each POST carries a Pipemetry-Signature header in Stripe’s format:
Pipemetry-Signature: t=<unix-timestamp>,v1=<hex-hmac-sha256>To verify:
- Read
tandv1from the header. - Compute
HMAC-SHA256(secret, "<t>.<raw request body>")and hex-encode it. - Compare it to
v1using a constant-time comparison. - Reject the delivery if
tis more than 300 seconds away from your clock — that is the replay window Pipemetry’s own verifier uses.
Use the raw body bytes as received. Pipemetry serialises the envelope canonically — JSON with sorted keys and no whitespace — so re-serialising a parsed object will reproduce the same bytes, but reading the raw body is the safer habit.
Error responses
| Status | Meaning |
|---|---|
401 | Missing Bearer token, or a key that is malformed, unknown or revoked |
402 payment_required | Billing is suspended for the workspace |
403 upgrade_required | The plan does not grant the capability you asked for |
422 | Invalid input — an unknown model_id, an unsupported sort, a rejected webhook URL |
429 | Rate limited; retry after the Retry-After interval |
503 | The service is not able to serve that read right now |