Skip to content

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:

Terminal window
curl -H "Authorization: Bearer pm_live_…" \
"https://app.pipemetry.com/v1/forecast?as_of_day=2026-08-07"

There is no development bypass on /v1 — a valid key is required in every environment.

Machine-readable spec

GET https://app.pipemetry.com/v1/openapi.json returns an OpenAPI 3 document describing these endpoints. It needs no key, so you can generate a client or import it into Postman before you mint one. It is generated from the live routes, so it cannot drift from what the API actually serves.

Scopes

A key can be narrowed to the endpoints an integration actually needs, so a script that only pulls the accuracy scorecard never holds a credential that can also read every open deal.

ScopeGrants
forecast:readGET /v1/forecast
opportunities:readGET /v1/opportunities
accuracy:readGET /v1/accuracy

Pick scopes when you create the key. Tick none and the key gets all three — which is also what every key created before scopes existed carries, so nothing you already built has narrowed. Calling outside a key’s scopes returns 403 with insufficient_scope and the scope you needed. That is deliberately not upgrade_required: a bigger plan will not help, a key with the scope will.

EndpointReturns
GET /v1/forecastThe forecast-category roll-up and totals
GET /v1/opportunitiesReconstructed opportunities, plus a full-cohort win-rate block
GET /v1/accuracySubmitted-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

ParameterNotes
as_of_dayRequired. The calendar day to roll up as of
group_byowner (default), segment, or stage
model_idOptional. Omitted means your workspace’s active model. Pro
forecast_typeall (default), new_business, renewal, expansion. Pro when not all
measurebookings (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. settled_unknown counts deals whose closed status could not be determined; when it is above zero, win_rate is null rather than a rate computed over a partial set.

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 Progroup_by=manager (manager roll-up) and choosing a model — and both are now enforced here, exactly as on /v1/forecast: a key whose workspace is not on a plan that grants them gets a 403 with an upgrade_required body naming the missing flag. The default scorecard (group_by=owner, no model_id) is unaffected on every plan.

Rate limits

120 requests per minute per API key by default. Both caveats this section used to carry are gone: the budget is now per key rather than a shared ceiling, and /v1 no longer draws on the same bucket as the app’s own browser reads — one integration can neither throttle another nor slow the app down.

Every response — allowed or rejected — carries X-RateLimit-Limit and X-RateLimit-Remaining, and a 429 also carries Retry-After: 60. Pace yourself from the remaining budget rather than polling hard and backing off after the fact.

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.ping envelope 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:

  1. Read t and v1 from the header.
  2. Compute HMAC-SHA256(secret, "<t>.<raw request body>") and hex-encode it.
  3. Compare it to v1 using a constant-time comparison.
  4. Reject the delivery if t is 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

StatusMeaning
401Missing Bearer token, or a key that is malformed, unknown or revoked
402 payment_requiredBilling is suspended for the workspace
403 upgrade_requiredThe plan does not grant the capability you asked for
422Invalid input — an unknown model_id, an unsupported sort, a rejected webhook URL
429Rate limited; retry after the Retry-After interval
503The service is not able to serve that read right now