Reference
The endpoint, every parameter, the response shape and each error it can return.
Endpoint
GET /api/v1/resources/{resource_id}/data| Path parameter | Type | Description |
|---|---|---|
resource_id | integer | The resource to read. A non-numeric value is 422. |
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key. |
workspace_id | Yes | Numeric id of the workspace that owns the resource. |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | One-based page number. |
page_size | integer | 1000 | Rows per page. Minimum 1, maximum 10000 — higher values are capped silently. |
fields | string | (all columns) | Comma-separated column names to return. |
ordering | string | (unordered) | One column name. Prefix with - for descending. |
| anything else | string | — | A filter. See below. |
Filters
Any parameter that isn't one of the four reserved names is read as a filter on the column of that name.
| Form | Operator | Example |
|---|---|---|
column=value | Equal to | status=RECONCILED |
column__exact=value | Equal to | status__exact=RECONCILED |
column__gt=value | Greater than | A.amount__gt=1000 |
column__gte=value | Greater than or equal | A.date__gte=2026-09-01 |
column__lt=value | Less than | A.amount__lt=5000 |
column__lte=value | Less than or equal | A.date__lte=2026-09-30 |
column__in=a,b,c | Any of | A.country__in=Colombia,Mexico |
Multiple filters are combined with AND. Column names are the labels shown in Simetrik and are case-sensitive. Any other operator suffix is 400.
Paging
page is one-based, and paging is offset-based: page=3 skips the first 2 × page_size rows. Read pages until returned_count is lower than meta.page_size — that's the end of the result set, and it's a more robust test than comparing against total_pages.
Always send ordering when you read more than one page. Without an explicit sort, the order rows come back in isn't guaranteed to be the same between requests, so a row can be skipped or returned twice as you walk the pages. Sort by a column with unique values — an id is ideal. If rows are being written while you read, also filter to a closed window (a date range that's already over) so new rows stay out of the result set. That doesn't freeze it — a row that arrives late with a date inside the window can still shift the pages — so when completeness matters, de-duplicate on the unique column you sort by and treat total_count as a sanity check, not proof: a repeated row and a missed one can cancel out. If the counts disagree, re-read the window.
Caching
Identical requests are served from a cache for five minutes. Identical means the same workspace, resource, page, page size, fields, ordering and filters — change any one of them and it's a different entry.
So data can be up to five minutes stale, and polling faster than that returns the same response, including the same total_count. Cache entries are scoped per workspace and ownership is checked before the cache is read, so a cached response never crosses workspaces.
Response
200 OK:
{
"data": [
{"A.id": "TX-0001", "A.amount": 1500, "status": "RECONCILED"}
],
"meta": {
"page": 1,
"page_size": 1000,
"returned_count": 1,
"total_count": 1,
"total_pages": 1
}
}| Field | Type | Description |
|---|---|---|
data | array | The rows. Each object is keyed by column name. Empty when nothing matched. |
meta.page | integer | Page returned. |
meta.page_size | integer | Page size actually applied, after capping. |
meta.returned_count | integer | Rows in this response. |
meta.total_count | integer | Rows matching the filters across the resource. |
meta.total_pages | integer | total_count ÷ page_size, rounded up. |
Responses larger than about 1 KB are gzip-compressed.
Errors
| Status | detail | Cause |
|---|---|---|
400 | workspace_id must be a valid integer | The header isn't numeric. |
400 | Filter field '<name>' is not a valid column for this resource. | Unknown column in a filter — often a misspelled reserved parameter. |
400 | Field '<name>' is not a valid column for this resource. | Unknown column in fields. |
400 | Ordering field '<name>' is not a valid column for this resource. | Unknown column in ordering. |
400 | Operator '<op>' is not supported. | Unknown __suffix. |
400 | The requested filters or ordering could not be applied. | The query was rejected downstream — usually a value whose type doesn't match the column. |
401 | Could not validate credentials | Missing or invalid X-API-Key. |
403 | Resource does not belong to the specified workspace | Ownership mismatch. |
404 | Workspace not found | No workspace with that id. |
404 | Resource not found | No resource with that id. |
422 | Validation error object | Missing workspace_id header, page_size below 1, or a non-numeric resource_id. Headers and declared parameters are validated first, so a workspace_id sent in the query string fails here as a missing header rather than being read as a filter. |
503 | Data platform is temporarily unavailable. Please try again later. | Transient. Retry with backoff, and with a smaller page_size if it repeats. |
Errors carry a detail field. 422 follows the standard validation-error shape, with detail as an array identifying the offending parameter.
Health
GET /healthReturns {"status": "ok"}. No authentication, and no check of the data platform behind it.
OpenAPI
The service publishes its own OpenAPI document at /api/v1/openapi.json, which is handy for generating a client.