Records API
How to query, filter, sort, and paginate any of the 1,151 FlowOS tables through the unified REST Records API.
Base Endpoint
Every table is accessible through a single endpoint pattern:
GET /api/v1/tables/:slug/records
POST /api/v1/tables/:slug/records
GET /api/v1/tables/:slug/records/:id
PATCH /api/v1/tables/:slug/records/:id
DELETE /api/v1/tables/:slug/records/:idWhere :slug is the table slug (e.g. incidents, itsm_incident_tasks, releases). All requests require Authorization: Bearer {token} and X-Workspace: {slug} headers.
Filtering
Use filter[field]=value query parameters to filter records. Multiple filters are ANDed together.
# Exact match
GET /api/v1/tables/incidents/records?filter[status]=open
# Multiple filters (AND)
GET /api/v1/tables/incidents/records?filter[status]=open&filter[priority]=critical
# Nested reference field (dot notation)
GET /api/v1/tables/incidents/records?filter[assignedTo.email]=alice@acme.comFilter Operators
Append an operator in brackets after the field name:
| Operator | Meaning | Example |
|---|---|---|
| filter[field]=… | Exact match | filter[status]=open |
| filter[field][ne]=… | Not equal | filter[status][ne]=closed |
| filter[field][gt]=… | Greater than | filter[priority][gt]=medium |
| filter[field][gte]=… | Greater or equal | filter[createdAt][gte]=2026-01-01 |
| filter[field][lt]=… | Less than | filter[durationMinutes][lt]=60 |
| filter[field][lte]=… | Less or equal | filter[score][lte]=100 |
| filter[field][in]=… | In list | filter[status][in]=open,investigating |
| filter[field][nin]=… | Not in list | filter[priority][nin]=low,planning |
| filter[field][like]=… | LIKE match | filter[title][like]=database% |
| filter[field][ilike]=… | Case-insensitive LIKE | filter[title][ilike]=%outage% |
| filter[field][null]=… | Is null | filter[resolvedAt][null]=true |
| filter[field][null]=… | Is not null | filter[assignedTo][null]=false |
| filter[field][exists]=… | Field exists | filter[metadata.runId][exists]=true |
Sorting
# Ascending (default)
GET /api/v1/tables/incidents/records?sort=createdAt
# Descending
GET /api/v1/tables/incidents/records?sort=-createdAt
# Multiple sort fields
GET /api/v1/tables/incidents/records?sort=-priority,createdAtPagination
All list responses use cursor-based pagination. The response includes a meta.nextCursor token — pass it as cursor to fetch the next page.
# First page (default limit: 50, max: 500)
GET /api/v1/tables/incidents/records?limit=100
# Next page
GET /api/v1/tables/incidents/records?limit=100&cursor=eyJpZCI6Ij...{
"data": [ { "id": "inc_01...", ... }, ... ],
"meta": {
"total": 4821,
"count": 100,
"nextCursor": "eyJpZCI6Ij...",
"hasMore": true
}
}Field Selection
Use fields to return only specific fields. Reduces payload size significantly on wide tables.
GET /api/v1/tables/incidents/records?fields=id,number,status,assignedTo,createdAtExpanding References
Use expand to inline reference fields instead of returning just the ID.
# Expand single reference
GET /api/v1/tables/incidents/records?expand=assignedTo
# Expand multiple
GET /api/v1/tables/incidents/records?expand=assignedTo,groupId,ciId
# Expand nested (2 levels)
GET /api/v1/tables/incidents/records?expand=assignedTo.department{
"id": "inc_01...",
"assignedTo": {
"id": "usr_01...",
"name": "Alice Chen",
"email": "alice@acme.com"
}
}Creating Records
POST /api/v1/tables/incidents/records
Content-Type: application/json
{
"title": "Database connection pool exhausted",
"status": "open",
"priority": "critical",
"category": "database",
"assignedTo": "usr_01..."
}The response returns the full created record including auto-set fields (id, number, createdAt, tenantId, workspaceId).
Updating Records
Use PATCH for partial updates. Only include the fields you want to change.
PATCH /api/v1/tables/incidents/records/inc_01...
Content-Type: application/json
{
"status": "resolved",
"resolvedAt": "2026-06-02T14:30:00Z",
"resolution": "Increased connection pool size from 10 to 50."
}Deleting Records
DELETE /api/v1/tables/incidents/records/inc_01...deletedAt timestamp and are excluded from queries. Pass ?hard=true for a permanent delete (requires admin role).Bulk Operations
# Bulk create (up to 500 records)
POST /api/v1/tables/incidents/records/bulk
{ "records": [ {...}, {...} ] }
# Bulk update
PATCH /api/v1/tables/incidents/records/bulk
{ "ids": ["inc_01...", "inc_02..."], "data": { "status": "closed" } }
# Bulk delete
DELETE /api/v1/tables/incidents/records/bulk
{ "ids": ["inc_01...", "inc_02..."] }SQL Endpoint
For complex cross-table queries, use the SQL endpoint. Read-only. Returns up to 10,000 rows.
POST /api/v1/sql
Content-Type: application/json
{
"query": "SELECT i.number, i.title, u.name as assignee FROM incidents i LEFT JOIN users u ON i.assigned_to = u.id WHERE i.status = 'open' ORDER BY i.created_at DESC LIMIT 50"
}incidents), not the MongoDB collection name. The query engine handles tenant/workspace scoping automatically based on your API token.Rate Limits
| Tier | Reads/min | Writes/min | Bulk/min |
|---|---|---|---|
| Standard | 600 | 300 | 30 |
| Pro | 3000 | 1500 | 150 |
| Enterprise | Unlimited | Unlimited | Unlimited |
Rate limit headers are returned on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
Ticket Number Generation
Every ITSM record gets a human-readable auto-number (e.g. INC0000042) on create. Numbers are generated atomically by the generateNumber utility which increments a per-workspace counter in sys_counters. The full signature used internally is:
generateNumber(tenantId: string, workspaceId: string, prefix: string): Promise<string>Numbers are 7-digit zero-padded with no separator between prefix and digits. All prefixes used across the platform:
| Prefix | Example | Entity |
|---|---|---|
| INC | INC0000001 | Incidents |
| PRB | PRB0000001 | Problems |
| CHG | CHG0000001 | Changes |
| REQ | REQ0000001 | Service requests |
| RITM | RITM0000001 | Request items |
| CTASK | CTASK0000001 | Change tasks |
| KE | KE0000001 | Known errors |
| UC | UC0000001 | Underpinning contracts |
| REL | REL0000001 | Releases |
| SVC | SVC0000001 | Service portfolio entries |
| CI | CI0000001 | CMDB configuration items |
| EVT | EVT0000001 | Platform events |
| CS | CS0000001 | Customer support / CSM cases |
| ORD | ORD0000001 | Commerce orders |
| EMP | EMP0000001 | HR employee IDs |
| HRC | HRC0000001 | HR cases |
| LEV | LEV0000001 | HR leave requests |
INC0000001 in ws_prod and INC0000001 inws_staging are independent sequences. The counter state lives inroot_<tenantId>_<workspaceId>_sys_counters and is atomically incremented via MongoDB$inc with upsert: true, making it safe under concurrent creates. Counter values are never reset or reused — even if the entity is deleted.