Reports
Report tiles ("reports") are per-table widgets that summarise a table's records as markdown, a single value, a progress bar, an aggregate table, or a chart (bar / line / area / pie). Over the Public API you can list, read, create, update, delete, reorder, preview, and fetch the computed values of a table's report tiles.
Access
- Account API keys (
il_live_) with access to the workspace get full report access. Reads (list,get,execute) work for any table member; mutations (create,update,delete,reorder,preview) require the key to act as a workspace admin — report authoring is an admin-level surface. - Personal API keys (
il_user_) are read-only for reports:reports/list,report/<id>/get, andreport/<id>/execute. Authoring endpoints are not available to personal keys. - Read-only keys can read/execute but cannot
create,update,delete, orreorder.
A report lives on a display table (table_id) but aggregates a source
table (source_table_id). The source defaults to the display table itself,
but may be a table exactly one lookup-hop away in the same workspace (a parent it
looks up to, or a child / sub-table that looks up to it). Field, group, and
filter ids are validated against the source table.
The report object
{
"id": 12,
"account_id": 1,
"table_id": 101,
"source_table_id": 101,
"title": "Open deals by stage",
"type": "vbar",
"config": { "action": "count", "group": "fld_stage", "sort": "value_desc", "limit": 200, "filters": [] },
"position": 0,
"created_by": 5,
"created_at": "2026-07-01 09:00:00",
"updated_at": "2026-07-01 09:00:00"
}
Types and their config shape
The type selects the tile, and config is validated/normalized per type. Field
ids (field, group, filter column) must exist on the source table.
| Type | Purpose | config keys |
|---|---|---|
markdown |
Static markdown text | text |
value |
Single aggregate number | action, field?, filters?, prefix?, suffix?, numlabel?, decimals? |
progress |
Progress bar vs a target | value keys plus max, threshold1?, threshold2?, direction ("higher"|"lower") |
table |
Grouped aggregate table | action, field?, group, filters?, limit?, sort? |
vbar / hbar / line / area / pie |
Charts | same as table |
actionis one ofcount,sum,avg,min,max. Every action exceptcountrequiresfield(the numeric field to aggregate).group(table/chart types) is required and must be a groupable field.filtersis an array of{column, compare, value, connector?}:compare∈=,!=,>,<,>=,<=,contains,not_contains,starts_with,ends_with,is_empty,is_not_empty.valueis ignored foris_empty/is_not_empty.connector(AND|OR, defaultAND) joins the row with the previous one.
sort∈value_desc,value_asc,label_asc,label_desc(defaults:label_ascfor line/area,value_descotherwise).limiton grouped types is capped at 200 rows.
Unknown / malformed config is normalized away, and the config is compiled against the source table on save — an invalid field, group, or filter is rejected with a validation error rather than persisted.
List reports for a table
GET /api/table/<table_id>/reports/list
Returns the report objects for the table, ordered by position. Readable by any
table member.
Get a single report
GET /api/table/<table_id>/report/<report_id>/get
Returns the full report object (including config). The read-before-edit
endpoint.
Fetch computed values (execute)
GET /api/table/<table_id>/report/<report_id>/execute
Runs the report against the source table and returns the renderer payload. Cached
for ~30s and auto-invalidated when the source table's data changes. Shape depends
on type:
// markdown
{ "type": "markdown", "text": "…" }
// value / progress
{ "type": "value", "scalar": 42 }
// table
{ "type": "table", "columns": ["Stage", "Count"], "rows": [{ "label": "Won", "value": 12 }] }
// chart (vbar/hbar/line/area/pie)
{ "type": "vbar", "group": [{ "label": "Won", "value": 12 }, { "label": "Lost", "value": 3 }] }
// chart grouped by a lookup field: gkey is the referenced record's id
{ "type": "vbar", "group": [{ "label": "Acme Ltd", "gkey": "41", "value": 12 }] }
Grouped rows carry an extra gkey only when the group field is a lookup. The
label there is the referenced record's title, which is not what a record filter on
that column compares against, so gkey gives you the record id to filter by (see
Records). Every other groupable field type labels each group
with the stored value itself, so no gkey is sent and the label is the value to
filter on. gkey is null for the empty group.
If the report can't be computed (e.g. the source table was removed), the payload
carries an error string instead of the data keys:
{ "type": "value", "error": "Source table not found" }.
Create a report
POST /api/table/<table_id>/reports/create
{
"title": "Open deals by stage",
"type": "vbar",
"source_table_id": 101,
"config": { "action": "count", "group": "fld_stage", "sort": "value_desc" }
}
title and a valid type are required. source_table_id is optional (defaults
to the display table; must be self or a one-hop related table in the same
workspace). Returns the created report. Admin-only.
Update a report
POST /api/table/<table_id>/report/<report_id>/update
Body may contain any of title, type, source_table_id, config. Omitted
keys keep their current value. The resulting config is re-validated against the
source table. Returns the updated report. Admin-only.
Delete a report
POST /api/table/<table_id>/report/<report_id>/delete
Admin-only. Returns no body.
Reorder reports
POST /api/table/<table_id>/reports/reorder
Body {"ids": [12, 9, 15]} — the desired left-to-right / top-to-bottom order.
Ids not on the table are ignored. Returns the reordered list. Admin-only.
Preview (dry run)
POST /api/table/<table_id>/reports/preview
Body {"type": "...", "config": {...}, "source_table_id": <id?>}. Runs the
aggregation without saving and returns the same payload shape as execute.
Use it to build a tile interactively before create. Admin-only.
Recommended workflow
- Fetch the source table schema (
GET /api/table/<id>/get) for field ids. reports/previewyourtype+configuntil the payload looks right.reports/createthe tile.- Poll
report/<id>/executefor live values.
Errors
| Status | Message | When |
|---|---|---|
| 403 | Endpoint not available to personal API keys |
A personal (il_user_) key called a mutating report endpoint. |
| 403 | API key is read-only |
Read-only key called create / update / delete / reorder. |
| 500 | API key not authorized for this workspace |
Key's space whitelist excludes the table's workspace. |
| 500 | Not Authorized |
Key's user is not a workspace admin (mutations). |
| 500 | Invalid Table |
table_id doesn't exist or isn't accessible. |
| 500 | Report not found |
report_id doesn't belong to the table. |
| 500 | Invalid report type / validation message |
type or config failed validation on create/update. |