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.
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" }' {
"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 | 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.
- 01
Create a dataset
A dataset is your customer, addressed by your own identifier. You never learn ours.
POST /v1/datasets - 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 - 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.
ptx_test_4e91c8… Issued on request. Sandbox data. Never bills.
ptx_live_……… Issued on request. Your data, metered.
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.