API docs

zpe-stats

mount /analytics · base https://zpe.vc/analytics · v1.1.5 · 4 operations · 1 write · 0 need no account · stable

the registry's copy of this app's contract. No address: the app answers no document route, so apis/zpe-stats.json in the registry is the only published form of these bytes · sha256 fe317fef53dd

GET /api/v1/data/runs/latest The newest census run, and the stores it read getLatestCensusRun

The run with the greatest `ran_at`, its totals, and one row per store. `limit` and `cursor` page the STORES inside that run, not the runs, because a census of the estate is larger than one answer: the first answer carries the run and the first `limit` stores, and `next` is the cursor for the rest of them.

Parameters

nameinrequiredtypenotes
cursorquerynostringThe `next` value from a previous answer for the same run. It is a position in that run's stores and means nothing in another run.
limitquerynointegerHow many stores to return. Absent, the app answers its own default.

Request body

None: this operation takes its arguments in the path and the query.

Responses

statusmeaningbody
200The newest run, with its totals and the stores this page carries. `next` is the cursor for the following page of stores, and is `null` on the last one.object
400`limit` is not a number in range, or `cursor` is not a cursor this app wrote: nothing was read.object
401No credential, or one the app could not verify: the census is not public, so nothing was read.object
403A real credential that may not read this surface, or the door named no account for it.object
404No run is recorded yet, so there is no newest one: the census has not been posted to this deployment.object
500This app failed while answering, so nothing here describes the estate. The problem's `detail` says what it was doing.object
503`zpe-stats-db` could not be read, so the newest run could not be named. The problem's `detail` names the store that refused it.object

Example

The newest census run, and the stores it read, with a token from the door.

GET /api/v1/data/runs/latest
curl -X GET 'https://zpe.vc/analytics/api/v1/data/runs/latest?cursor=string&limit=50' \
  -H 'Authorization: Bearer $ZPE_TOKEN'
200 application/json
{
  "run": {
    "run_id": "string",
    "ran_at": "2026-10-31T09:00:00Z",
    "instrument_version": "string",
    "instrument_commit": "string",
    "totals": {
      "stores": 0,
      "disagreements": 0,
      "unclassified": 0
    }
  },
  "stores": [
    {
      "name": "string",
      "state": "read",
      "error": "string",
      "binders": [
        {
          "repo": "string",
          "config": "string",
          "binding": "string"
        }
      ],
      "declared": {
        "statements": 0,
        "source": "schema.sql",
        "absent": [
          "string"
        ]
      },
      "database_uuid": null,
      "account_id": null,
      "uuid_source": null,
      "live": null,
      "live_tables": null,
      "tables": [
        {
          "name": "string",
          "classification": "person",
          "keys": [
            "string"
          ],
          "reason": "string",
          "agreement": "agree"
        }
      ]
    }
  ],
  "findings": null,
  "next": null
}

The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.

GET /api/v1/data/runs/{run_id} One census run by its id, and the stores it read getCensusRun

The run with that `run_id`, its totals, and one row per store — the SAME answer shape `/runs/latest` gives while that run is the newest. `/latest` moves as soon as a newer run is posted, so this is the only address a run keeps once it stops being the newest: a reader comparing tonight's declarations with the previous run's reads the previous run here instead of keeping a copy of it. `limit` and `cursor` page the STORES inside this run, exactly as they do on `/latest`, and a cursor carries the run it belongs to: a cursor from another run is a `400`, because it would otherwise be a position in a list this answer does not contain. An id this deployment does not hold is a `404` problem document naming the id it asked for.

Parameters

nameinrequiredtypenotes
run_idpathyesstringThe run's own id: the hash the instrument computed over what it measured, which is also the id `/runs` lists and the one the POST answered. It is the primary key, so the id a caller holds is the id this route answers.
cursorquerynostringThe `next` value from a previous answer for the SAME run. It is a position in that run's stores and means nothing in another run.
limitquerynointegerHow many stores to return. Absent, the app answers its own default.

Request body

None: this operation takes its arguments in the path and the query.

Responses

statusmeaningbody
200The run, with its totals and the stores this page carries. The shape is `/runs/latest`'s, for the run that was asked for rather than the newest one. `next` is the cursor for the following page of stores, and is `null` on the last one.object
400`limit` is not a number in range, `cursor` is not a cursor this app wrote, or the cursor belongs to a different run: nothing was read.object
401No credential, or one the app could not verify: the census is not public, so nothing was read.object
403A real credential that may not read this surface, or the door named no account for it.object
404No run with that `run_id` is recorded in `zpe-stats-db`: the id is not one this deployment holds, or the run has been pruned by the store's retention. The problem's `detail` names the id it asked for.object
500This app failed while answering, so nothing here describes the estate. The problem's `detail` says what it was doing.object
503`zpe-stats-db` could not be read, so the run's stores could not be named. The problem's `detail` names the store that refused it.object

Example

One census run by its id, and the stores it read, with a token from the door.

GET /api/v1/data/runs/{run_id}
curl -X GET 'https://zpe.vc/analytics/api/v1/data/runs/string?cursor=string&limit=50' \
  -H 'Authorization: Bearer $ZPE_TOKEN'
200 application/json
{
  "run": {
    "run_id": "string",
    "ran_at": "2026-10-31T09:00:00Z",
    "instrument_version": "string",
    "instrument_commit": "string",
    "totals": {
      "stores": 0,
      "disagreements": 0,
      "unclassified": 0
    }
  },
  "stores": [
    {
      "name": "string",
      "state": "read",
      "error": "string",
      "binders": [
        {
          "repo": "string",
          "config": "string",
          "binding": "string"
        }
      ],
      "declared": {
        "statements": 0,
        "source": "schema.sql",
        "absent": [
          "string"
        ]
      },
      "database_uuid": null,
      "account_id": null,
      "uuid_source": null,
      "live": null,
      "live_tables": null,
      "tables": [
        {
          "name": "string",
          "classification": "person",
          "keys": [
            "string"
          ],
          "reason": "string",
          "agreement": "agree"
        }
      ]
    }
  ],
  "findings": null,
  "next": null
}

The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.

GET /api/v1/data/runs The census history, newest run first listCensusRuns

One row per run, newest first, with each run's totals and no stores: the history is what answers whether the census is current, and `/runs/latest` is what answers what it found. `limit` and `cursor` page the runs, and `next` is the cursor for the following page.

Parameters

nameinrequiredtypenotes
cursorquerynostringThe `next` value from a previous answer: a position in the run history, newest first.
limitquerynointegerHow many runs to return. Absent, the app answers its own default.

Request body

None: this operation takes its arguments in the path and the query.

Responses

statusmeaningbody
200The runs this page carries, newest first, and the cursor for the next page. `next` is `null` on the last page, and the app answers an empty `runs` list when no run has been recorded.object
400`limit` is not a number in range, or `cursor` is not a cursor this app wrote: nothing was read.object
401No credential, or one the app could not verify: the history is not public, so nothing was read.object
403A real credential that may not read this surface, or the door named no account for it.object
500This app failed while answering, so the history is not an answer about the estate. The problem's `detail` says what it was doing.object
503`zpe-stats-db` could not be read, so the history could not be listed. The problem's `detail` names the store that refused it.object

Example

The census history, newest run first, with a token from the door.

GET /api/v1/data/runs
curl -X GET 'https://zpe.vc/analytics/api/v1/data/runs?cursor=string&limit=20' \
  -H 'Authorization: Bearer $ZPE_TOKEN'
200 application/json
{
  "runs": [
    {
      "run_id": "string",
      "ran_at": "2026-10-31T09:00:00Z",
      "instrument_version": "string",
      "instrument_commit": "string",
      "totals": {
        "stores": 0,
        "disagreements": 0,
        "unclassified": 0
      }
    }
  ],
  "next": null
}

The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.

POST /api/v1/data/runs Record a census run the instrument measured postCensusRun

The instrument posts one run per measurement: the run's own `run_id`, when it ran, which instrument version and commit read the estate, and one row per store. A run is KEYED by `run_id`, which is the hash of what was measured, so the same run posted twice answers `200` with the row the first call wrote and makes no second row. A store the instrument could not read is posted as a row whose `state` is `error`, with the reason in `error`; that is a fact about the store, not a refusal of this call.

Parameters

No parameters.

Request body application/json · required

fieldtyperequirednotes
run_idstringyesThe run's identity: the sha256 of what the instrument measured. Two runs that measured the same estate have the same `run_id`, which is why a retry is not a second row.
ran_atstring (date-time)yesWhen the instrument finished the walk, as UTC.
instrument_versionstringyesThe instrument's own version, so a run can be read against the rules it measured with.
instrument_commitstringyesThe commit the instrument ran from.
totalsobjectnoWhat the walk counted, as the instrument posts it: all eight counts. `configs` is the wrangler configs it opened, `stores` the databases they name, and `scratch_only` the stores only a scratch or development config binds; `tables` is how many tables the run saw, `live_tables` how many of them were in a database rather than a declaration, `findings` how many stores the walk could not settle, and `disagreements` and `unclassified` are the two numbers a reader acts on. This object is the POST body's. A recorded run answers `CensusRunTotals`, the three counts the service stores.
storesarray of objectyesOne row per database the walk found.
disagreementsarray of objectnoEvery table whose declaration and live shape disagree, named rather than counted: the instrument's own summary, and the reason a person reads this body. Accepted, and used for `totals.disagreements` only when `totals` is missing. The names are not stored, so the run this service answers does not echo them; each store row already carries them.
findingsarray of objectnoEvery store the walk could not settle, named rather than counted. This is where a store the account has no database for appears: `kind` says which failure and `detail` carries the sentence the account answered. The service KEEPS these (1.1.3), so `/runs/latest` answers them whole; omitting the key is not the same as posting `[]`, because an instrument that did not look must not read as one that looked and found nothing.

Responses

statusmeaningbody
200The run was already recorded: `run_id` matched a row, so this answers that row and writes no second one. The body is the run the first call stored, not the one in this request.object
201The run was new and is now recorded, with one row per store it carried.object
400The body is not a census run: `run_id`, `ran_at`, `instrument_version`, `instrument_commit`, or a store row is missing or malformed. The problem's `detail` names the store whose row it was, so the instrument that posted it knows which read to look at.object
401No service token, or one that is not live: nothing was read and nothing was written.object
403A real credential that may not write the census: a person's session and an account key may read it, and only the service token records a run.object
500This app failed while answering, and whether the run was recorded is NOT answered by this response. The problem's `detail` says what it was doing, so a caller can post the same `run_id` again and be told.object
503`zpe-stats-db` could not be reached, so nothing was written and this run is not recorded. The problem's `detail` names the store that refused the write.object

Example

Record a census run the instrument measured, with a token from the door.

POST /api/v1/data/runs
curl -X POST 'https://zpe.vc/analytics/api/v1/data/runs' \
  -H 'Authorization: Bearer $ZPE_TOKEN' \
  -H 'content-type: application/json' \
  -d '{
  "run_id": "string",
  "ran_at": "2026-10-31T09:00:00Z",
  "instrument_version": "string",
  "instrument_commit": "string",
  "totals": {
    "configs": 0,
    "stores": 0,
    "scratch_only": 0,
    "tables": 0,
    "live_tables": 0,
    "findings": 0,
    "disagreements": 0,
    "unclassified": 0
  },
  "stores": [
    {
      "name": "string",
      "state": "read",
      "error": "string",
      "binders": [
        {
          "repo": "string",
          "config": "string",
          "binding": "string"
        }
      ],
      "declared": {
        "statements": 0,
        "source": "schema.sql",
        "absent": [
          "string"
        ]
      },
      "database_uuid": null,
      "account_id": null,
      "uuid_source": null,
      "live": null,
      "live_tables": null,
      "tables": [
        {
          "name": "string",
          "classification": "person",
          "keys": [
            "string"
          ],
          "reason": "string",
          "agreement": "agree"
        }
      ]
    }
  ],
  "disagreements": [
    {
      "store": "string",
      "table": "string",
      "kind": "declared-absent",
      "detail": "string"
    }
  ],
  "findings": [
    {
      "store": "string",
      "kind": "string",
      "detail": "string"
    }
  ]
}'
200 application/json
{
  "run_id": "string",
  "ran_at": "2026-10-31T09:00:00Z",
  "instrument_version": "string",
  "instrument_commit": "string",
  "totals": {
    "stores": 0,
    "disagreements": 0,
    "unclassified": 0
  }
}

The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.