Skip to content

The metric layer, as an API.

Send contracts and financials. Get back ARR waterfalls, cohort retention, burn multiple — 706 metric definitions, computed by the same engine that runs the Plantactic app. Not a second implementation that drifts.

A test key costs nothing and cannot reach live data — call the API on rows you invented first.

POST /v1/datasets/{id}/metrics
curl -X POST \
  https://ptx-api.plantactic.com/v1/datasets/9f2c4b1e-7a83-4d02-9c15-6b0e2a1d4f77/metrics \
  -H "Authorization: Bearer ptx_test_4e91c8..." \
  -H "Content-Type: application/json" \
  -d '{ "horizon": "2028-09" }'
200 OK
{
  "horizon":   "2028-09",
  "axisStart": "2028-04",
  "axisEnd":   "2029-04",
  "months":    13,
  "periods":   ["2028-04", "2028-05", "2028-06", ...],
  "sections": [
    {
      "sectionId": "arr_waterfall",
      "title": "Live ARR",
      "rows": [
        {
          "rowId":       "arr_waterfall.starting_live_arr",
          "metric":      "STARTING_LIVE_ARR",
          "displayName": "Starting ARR",
          "formatType":  "ACCURATE_CURRENCY_VALUE",
          "values":      [4421586.75, 4269352.75, 4175862.01, ...]
        },
        {
          "rowId":       "arr_waterfall.churned_live_arr",
          "metric":      "CHURNED_LIVE_ARR",
          "displayName": "Churned Live ARR",
          "formatType":  "ACCURATE_CURRENCY_VALUE",
          "preset":      "DANGER",
          "values":      [199000.0, 200253.4, 227456.0, ...]
        }
      ]
    }
  ]
}

Raw numbers on the wire. Your formatting on screen.

Every row is a dense array aligned to one period axis, carrying a formatType and a stable rowId. No pre-formatted strings, no locale baked in, and null means not-computable within a row the engine could source at all — a trailing-window metric with no history behind it is null, not 0. Rows it cannot source, like P&L on a contracts-only dataset, come back as zeros; the catalog is what tells you which.

Live ARR waterfall for the Lexop test company, six months
Live ARR Apr 28 May 28 Jun 28 Jul 28 Aug 28 Sep 28
Starting ARR $4.42M $4.27M $4.18M $4.12M $4.00M $3.76M
New Live ARR $38k $85k $152k $0 $0 $0
Expansion Live ARR $9k $22k $21k $17k $5k $11k
Downsell Live ARR $0 $0 $0 $1 $0 $0
Churned Live ARR $199k $200k $227k $139k $245k $284k
Net New Live ARR −$152k −$93k −$54k −$122k −$239k −$274k
Closing ARR $4.27M $4.18M $4.12M $4.00M $3.76M $3.49M

Live ARR waterfall · 380 contracts · 4,234 customer-month facts · company: Lexop (non-active test company, generated data)

Three calls to a computed metric.

Everything after step three is the same endpoint with a different question.

  1. 01

    Create a dataset

    A dataset is your customer, addressed by your own identifier. You never learn ours.

    POST /v1/datasets
  2. 02

    Upload a version

    Contracts as JSON. Validation returns every bad row at once — a 422 with the complete error array, nothing written. Small uploads activate inline; large ones come back 202 and you poll the version until it is active.

    POST /v1/datasets/{id}/versions
  3. 03

    Ask for metrics

    Send a horizon — the month the engine treats as now. You get the whole grid, monthly, across every section your data supports. Computed on demand; no precompute step to wait for.

    POST /v1/datasets/{id}/metrics

Succeed before you trust us with data.

A test key gives you an account that is never billed and cannot see live data. Upload a handful of rows you made up, then call every endpoint — ingest, query, export — and see exactly what your client will receive before sending anything real.

The two prefixes are never interchangeable. They are greppable in your logs, revocable independently, and every dataset and version response carries livemode, so nothing ships against the sandbox by accident.

test
ptx_test_4e91c8…

Issued on request. Sandbox data. Never bills.

live
ptx_live_………

Issued on request. Your data, metered.

oauth
ptx_oauth_………

Connecting to Claude provisions this on approval — the one route that needs nobody's help. Always live.

The catalog tells you what your data can answer.

What is computable depends on what you send, and it is not the same question as what is cohortable. A transactional dataset has no contracts, so bookings and CMRR do not exist for it. A booking posts entirely in its signed month, so it can be computed but never cohorted.

Rather than let you discover that in production, GET /v1/datasets/{id}/metrics/catalog returns both axes for the data you actually uploaded.

Input model Atomic input Not available
contracts one row per contract nothing — the full 706
revenue_events transactions or usage bookings, contract stats, CMRR, contracted users — and no ingest path yet: pick contracts

Paste a curl. See your first metric.

Connect it to Claude and approval provisions you on the spot. For a key of your own to call with, ask us — both prefixes are issued against your account.