Hyperstruck
API

Reporting API

Query the daily facts behind your usage, and save the views your team looks at, with the cost assumptions stated rather than hidden.

In one line

One query endpoint over daily reporting facts, a field catalog telling you what you can ask for, and a saved-view layer of charts and dashboards on top.

The Usage API answers fixed questions: what did this agent do, what did it cost. The Reporting API is the open version of the same data. You choose the dimensions, the measures, the grain, and the window, and you get rows back.

Reading takes an API key; writing does not

Every read on this page requires reports:read, which you can grant to an API key. Every write requires reports:write, which you cannot: it is not a grantable key scope, and it is held only by the organization developer, admin, and owner roles through a portal session. So charts, dashboards, and space defaults are authored from the portal today, and an API key that tries answers 403. The write endpoints are documented here because they are the same surface, not because a key can reach them.

Ask what is available

GET /reporting/metric-catalog returns the fields you can bind, each with a field, a kind, a description, and whether it is derived. It also returns default_constants, which matter more than they look, and are explained below. Required scope: reports:read.

curl -H "Authorization: Bearer <API_KEY>" \
  "https://api.hyperstruck.com/reporting/metric-catalog"

kind has three values, and the middle one is easy to miss.

kindWhat it holds
dimensionWhat you group by, such as day or host.
baseObserved counts: runs, retrievals, applications, misleads, spend.
measureValues computed on top of the base counts, such as time saved or assist coverage. Every one of these is derived.

A field picker that filters to dimensions and measures finds none of the counts, because the counts are all base. Bind them from base, as the example below does.

Read the catalog rather than hardcoding a field list. It is a curated starter set rather than a projection of the fact tables, so a new fact column appears here when someone adds it, not automatically; reading it still beats a list frozen into your client.

Run a query

POST /reporting/query aggregates the daily facts server-side for one query binding. Required scope: reports:read.

curl -X POST "https://api.hyperstruck.com/reporting/query" \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "space_id": "<SPACE_ID>",
    "window": "last_30_days",
    "binding": {
      "dimensions": ["day", "host"],
      "measures": ["learnings_applied", "learnings_retrieved"],
      "grain": "day",
      "limit": 500
    }
  }'

Prop

Type

The response carries rows, row_count, truncated, period_start and period_end_exclusive, the window and query_binding it actually ran, plus constants and is_illustrative.

Check truncated before you total anything

limit caps the rows returned. A truncated response is a partial answer, and summing it gives a number that looks precise and is wrong.

The dollar measures are derived, and you own the assumptions

Money is not measured, it is calculated. A dollar measure comes from a count of things that happened multiplied by assumptions about what each one is worth: minutes saved per retrieval, minutes of rework per mislead, minutes to relearn something forgotten, an hourly rate. Those live per space.

GET /reporting/defaults/{space_id} returns them, and PUT replaces them. Reading takes reports:read; writing takes reports:write, so it is a portal action rather than an API-key one.

curl -H "Authorization: Bearer <API_KEY>" \
  "https://api.hyperstruck.com/reporting/defaults/<SPACE_ID>"

On top of the scope, reading requires space viewer and writing requires space steward, which organization admins also qualify as. A space with no row of its own falls back to product defaults.

is_illustrative is a flag you set, not one we work out

Every query response carries it, alongside the exact constants used, and it means these figures are illustrative rather than measured. It is not computed from whether you customised anything: it is stored exactly as it was last written, and it defaults to true. So replacing the constants without also sending is_illustrative: false leaves every response still saying illustrative, and sending false while leaving the product defaults in place makes them read as measured when they are not. Set both together, honestly, before anyone puts a figure in a board pack.

The base counts are not affected by any of this. Runs, retrievals, applications, misleads, and spend are observed. Every derived measure is not, and that includes the ones denominated in minutes and rates rather than dollars, so read the derived flag rather than assuming money is the only calculated thing.

Save a view

Charts pin a query_binding and a viz_spec together so a question can be re-asked without rebuilding it.

EndpointWhat it does
GET /reporting/chartsLists charts you can see.
POST /reporting/chartsCreates one in a space. Needs space_id, title, viz_spec, query_binding.
GET, PATCH, DELETE /reporting/charts/{chart_id}Read, update, or remove one.
POST /reporting/charts/{chart_id}/dataRuns the chart's stored binding and returns rows.

Reading takes reports:read. Creating, updating, or deleting takes reports:write plus can_publish on the space, and reports:write is portal-session only.

A draft is private until it is linked

A chart made by a portal user is private to them until it is published, and linking it onto a dashboard is what publishes it to that space's viewers. The service also carries a tenant-shared draft state for a chart with no personal owner, which is what an API-key author would produce, but nothing can reach it while reports:write is not grantable on a key.

Assemble a dashboard

EndpointWhat it does
GET /reporting/dashboardsLists dashboards in spaces you can read. Filter with space_id.
POST /reporting/dashboardsCreates an empty dashboard in a space.
GET, PATCH, DELETE /reporting/dashboards/{dashboard_id}Read, update metadata, or delete. Deleting keeps the charts.
PUT /reporting/dashboards/{dashboard_id}/itemsReplaces every placement at once.

Everything in this table needs reports:write except the two reads, which need reports:read. A placement carries chart_id, row_index, col_index, and optionally col_span and sort_key. col_index + col_span must not exceed the dashboard's columns, and a request that breaks that is refused rather than silently clipped. Because PUT replaces the whole set, send the placements you want to keep along with the ones you are adding.