Send your first Records
One endpoint, ordinary JSON, and no Collection configuration.
Post a Record
The first Record in each collection creates the Collection and locks its schema. type is available as an ordinary field.
curl -X POST https://statboy.ai/v1/data \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{"collection":"API_LATENCY","milliseconds":128.4,"healthy":true}'Or send a mixed-Collection batch
Send up to 500 Records in one JSON array. Valid Records are accepted even when another row fails validation.
curl -X POST https://statboy.ai/v1/data \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '[
{"collection":"API_LATENCY","milliseconds":128.4,"healthy":true},
{"collection":"DEPLOYMENTS","type":"production","healthy":true}
]'Watch the dashboard appear
The overview listens every two seconds. Numeric fields become chart options; a Collection with only strings charts Record count.
Open your metricsPayload rules
collectionRequiredLetters, numbers, and underscores; up to 64 characters.
timestampOptionalISO-8601 with a timezone. Defaults to server receive time.
_ prefixReservedSystem fields use this namespace, so user fields may not.
nested JSONNot in v0Flatten objects and arrays before sending them.
strings1,024 charactersEach compact Record is capped at 32 KiB; NUL characters are rejected.
Schema behavior
Strict is the default: unknown fields and mismatched types return legible 400 errors. Switch a Collection to evolve to add unknown fields automatically as nullable columns. Integer values are accepted by double fields; there are no other implicit coercions.
curl -X PATCH https://statboy.ai/v1/collections/API_LATENCY \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{"mode":"evolve"}'Read your data back
Use the same API key to list Collections and inspect their logical fields. records_this_month counts accepted Records in the current UTC month, and last_record_at is the latest acceptance time rather than a Record’s own timestamp. Null-only pending fields are omitted until their type is known. Physical storage details and Report presentation stay private.
curl https://statboy.ai/v1/collections \ -H "Authorization: Bearer sb_sk_..."
Then run one of the same validated widget queries used by a dashboard. Kinds are number, chart, or table; ranges are 1H, 24H, 7D, 30D, or 90D.
Send a query for record selection and calculation, and a separate visualization for presentation. annotation supplies a human explanation, column_labels renames output headings, column_decimals fixes numeric columns at 0–6 decimal places, and column_text_modes chooses truncate, full, or wrap for text columns. Text columns default to full. Results declare meta.shape as scalar, timeseries, categorical, or records.
curl -X POST https://statboy.ai/v1/collections/API_LATENCY/query \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{
"range":"7D",
"kind":"chart",
"query":{
"measure":{"aggregation":"avg","field":"milliseconds"},
"bucket":"auto"
},
"visualization":{
"annotation":"Average API latency over the selected period",
"column_labels":{"value":"Latency (ms)"},
"column_decimals":{"value":1}
}
}'To return one ranked group, use a number query with group_by, limit: 1, and order: "asc" for the lowest aggregate or "desc" for the highest. The result includes both the winning dimension and its numeric value.
curl -X POST https://statboy.ai/v1/collections/SPORTSBOOK_SPREAD_ACCURACY/query \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{
"range":"90D",
"kind":"number",
"query":{
"filters":[
{"field":"quality_status","op":"eq","value":"included"},
{"field":"bookmaker_name","op":"neq","value":null}
],
"measure":{"aggregation":"avg","field":"spread_abs_error"},
"group_by":"bookmaker_name",
"order":"asc",
"limit":1
},
"visualization":{
"annotation":"Lowest average spread error among included games"
}
}'A widget can follow the request range or declare its own time_scope. Supported scopes are rolling windows, UTC calendar periods, fixed timezone-aware intervals, and all time. A scoped query takes precedence over range; the response’s meta.from and meta.to are the resolved window.
Scalar queries can add compare: {"basis":"previous_period"}. The result keeps one scalar row and adds the baseline value, absolute delta, and percentage delta in meta.compare. For a complete month-over-month comparison, select the previous complete calendar month as shown here.
curl -X POST https://statboy.ai/v1/collections/HIRING_POSTS/query \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{
"range":"24H",
"kind":"number",
"query":{
"measure":{"aggregation":"count","field":null},
"time_scope":{"type":"calendar","period":"month","offset":-1},
"compare":{"basis":"previous_period"}
},
"visualization":{
"annotation":"Complete hiring posts last month"
}
}'Charts can add one secondary categorical dimension with series_by. Statboy selects the top 1–10 series across the whole resolved window and returns long-format rows with timestamp or dimension, series, and value. Records with a missing series value are excluded. A bucketed chart may also use normalize: {"mode":"index"}; each series’s first non-zero numeric bucket becomes 100.
curl -X POST https://statboy.ai/v1/collections/HIRING_POSTS/query \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{
"range":"90D",
"kind":"chart",
"query":{
"measure":{"aggregation":"count","field":null},
"bucket":"month",
"series_by":"role_family",
"series_limit":5,
"normalize":{"mode":"index"}
},
"visualization":{
"annotation":"Hiring activity by role family, indexed to each series start"
}
}'A share measure divides Records matching one to three conjunctive where predicates by all Records remaining after the query’s ordinary filters. Stored values are fractions from 0 to 1; result columns declare unit: "percent" so dashboards display percentages. Empty buckets remain null rather than becoming zero.
curl -X POST https://statboy.ai/v1/collections/HIRING_POSTS/query \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{
"range":"90D",
"kind":"chart",
"query":{
"measure":{
"aggregation":"share",
"field":null,
"where":[{"field":"remote","op":"eq","value":true}]
},
"bucket":"month"
},
"visualization":{"annotation":"Share of hiring posts open to remote work"}
}'Rankings compare the matching rows directly. If groups have uneven coverage, filter the input data appropriately; minimum group-size conditions are not currently part of the query language.
Call these endpoints from your server: browser cross-origin requests are intentionally unsupported so API keys do not end up in client code. Query results can lag ingestion by the short outbox dispatch interval and up to 15 seconds of query caching. Collection identifiers are exact and case-sensitive.
Collections are created implicitly by POST /v1/data; there is no separate create endpoint. Collection deletion and Report block authoring are not available through these routes.
GET /v1/metrics, POST /v1/query, the metric SEO routes, and POST /v1/events remain supported legacy contracts. Compatible Collections can receive Records through either ingest endpoint, but a required user field named type cannot be supplied through /v1/events, and a required user field named collection cannot be supplied through /v1/data.
Inspect and describe Reports
List the current Reports as lightweight definitions, then inspect one by its opaque ID. Detail responses include sharing, SEO, blocks, and widget definitions; they do not execute queries or record a dashboard view.
curl https://statboy.ai/v1/reports \ -H "Authorization: Bearer sb_sk_..."
curl https://statboy.ai/v1/reports/6e209bab-b74d-4ead-a5f8-17e32809810f \ -H "Authorization: Bearer sb_sk_..."
A Report can span multiple Collections; each widget names one supplying Collection. Create a non-default Report with an ordered Collection list. Supplying an explicit slug makes retries deterministic: a duplicate create returns 409 and the Report can be recovered from the list endpoint.
curl -X POST https://statboy.ai/v1/reports \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{
"title":"HN Hiring Index",
"description":"A monthly view of Hacker News hiring activity.",
"emoji":"📈",
"slug":"hn-hiring-index",
"collections":["HN_HIRING","HN_COMPANIES"]
}'Compose the complete ordered body atomically from Text Blocks and widgets. Put CommonMark headings inside a Text Block; all heading levels render with the same bold, body-sized treatment. Array order is display order. Include an existing block id to update it, omit an existing ID to delete it, or omit id to create a new block. Any invalid entry rolls back the complete body. Widgets accept either the split query and visualization contract or a legacy flat plan.
curl -X PUT https://statboy.ai/v1/reports/6e209bab-b74d-4ead-a5f8-17e32809810f/blocks \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{
"blocks":[
{"kind":"text","markdown":"## Hiring volume"},
{"kind":"text","markdown":"Counts use one top-level hiring comment per company."},
{"kind":"widget","widget":{
"title":"Posts per month",
"kind":"chart",
"collection":"HN_HIRING",
"query":{
"version":1,
"mode":"aggregate",
"filters":[],
"measure":{"aggregation":"count","field":null},
"bucket":"month",
"fill":"zero",
"group_by":null,
"summarize":null,
"columns":[],
"limit":20
},
"visualization":{"version":1}
}}
]
}'Preview the live snapshot without changing sharing, then turn on the Public Link. is_public is the sole sharing state; PATCH it to false to make the Report private again.
curl https://statboy.ai/v1/reports/6e209bab-b74d-4ead-a5f8-17e32809810f/snapshot?range=24H \
-H "Authorization: Bearer sb_sk_..."
curl -X PATCH https://statboy.ai/v1/reports/6e209bab-b74d-4ead-a5f8-17e32809810f \
-H "Authorization: Bearer sb_sk_..." \
-H "Content-Type: application/json" \
-d '{"is_public":true}'Use PUT /v1/reports/{report_id}/collections to replace ordered Collection membership, PATCH /v1/reports/{report_id}/seo for discovery metadata, and DELETE /v1/reports/{report_id} for a private non-default Report.
Sign-in is required to view private Collections and dashboards. API keys remain dedicated machine credentials for ingest and query.
Workspace owners can create or rotate the API key and configure capability webhook URLs from Connect data. New secrets are shown once.