All reads below use Authorization: Bearer YOUR_API_KEY and return only that tenant's data. Use logical table names, not physical database table names. Public clients do not need an internal secret.
List records
GET /api/tables/payments?limit=25&sort_by=payment_date&sort_order=desc
Authorization: Bearer YOUR_API_KEY{
"data": [{"payment_id": "PAY-2024-001", "disbursement_id": "DSB-2024-001", "payment_total": 250, "currency": "USD"}],
"pagination": {"limit": 25, "has_more": false}
}| Parameter | Contract |
|---|---|
limit | Integer 1-1000, default 50; invalid values return 400, not silent clamping |
cursor | Opaque token from pagination.next_cursor; URL-encode it |
sort_by | Defaults to created_at; asset_status and disbursement_status use on_date |
sort_order | asc or desc, default desc |
include_count | true adds total and total_pages; counts may be cached |
date_field | Required if either date bound is supplied |
start_date, end_date | Inclusive bounds on the selected date field |
search | Partial match across searchable ID/status columns for the table |
Sorting uses primary-key tiebreakers for deterministic pagination. Rows with a null selected sort value are excluded. Keep the same table, filters, and sort on every page. Discard legacy raw-ID/timestamp cursors and start again from the first page.
List business IDs
GET /api/tables/clients/ids?limit=50
Authorization: Bearer YOUR_API_KEY{
"data": ["client-001", "client-002"],
"pagination": {
"limit": 50,
"has_more": true,
"next_cursor": "eyJ2IjoxLCJzIjoiY2xpZW50LTAwMiIsImsiOltdfQ"
}
}The limit is 1-1000, default 50, and IDs sort ascending. Pass the returned token as cursor, not client-002 itself. The token is opaque even if you recognize how this illustrative token is encoded.
Example pagination loop (supply your environment's base URL and API key securely):
async function listClientIds(baseUrl, apiKey) {
const ids = new Set();
const url = new URL('/api/tables/clients/ids', baseUrl);
url.searchParams.set('limit', '1000');
while (true) {
const response = await fetch(url, {headers: {Authorization: `Bearer ${apiKey}`}});
if (!response.ok) throw new Error(`Read failed: ${response.status}`);
const page = await response.json();
page.data.forEach((id) => ids.add(id));
if (!page.pagination.has_more) return ids;
if (!page.pagination.next_cursor) throw new Error('Missing continuation token');
url.searchParams.set('cursor', page.pagination.next_cursor);
}
}Choose create versus update
Compare source IDs with stored IDs: absent IDs use create; existing IDs use update. Do not probe with a create and fall back after 409: terminal failures can reserve the key and add error noise. For a single interactive record, GET /api/tables/{table}/{id} gives 200 or 404.
Use sync-state when you also need version-2 hashes to skip redundant updates. Missing source IDs should be reviewed, not automatically deleted.
Get one record
GET /api/tables/disbursements/DSB-2024-001
Authorization: Bearer YOUR_API_KEY{"data": {"disbursement_id": "DSB-2024-001", "client_id": "BR-2024-001", "disbursement_amount": 10000, "currency": "USD"}}For the four main tables, the path ID is the business ID (client_id, disbursement_id, payment_id, or operation_id). Other allowed tables use their table primary key. Returned database rows are not valid ingest bodies: select allowed fields and add the correct envelope/step instead of copying audit fields or content_hash back to ingestion.
Allowed tables
clients, disbursements, payments, operations, purchase_clients, asset_status, disbursement_status, write_offs, rebates, extensions, restructures.
Internal event tables, deleted mirrors, and silver/gold tables are not exposed through these routes.
Errors
400: invalid table, cursor, limit, sort/date-field name, or date filters withoutdate_field.401: missing or invalid API key.403: an unnecessary internal header failed verification; normal public requests use bearer authentication only.404: requested record does not exist.500: unexpected read failure; retainX-Correlation-IDfor support.
