Reading and writing rows
Create, read, update and delete rows over REST, with references by uid and the errors you will meet.
The examples on this page use the example app's invoices table, which references vendors
and purchase_orders. Every request needs your app's uid (on the app's settings page) and a
bearer token for one of your app's users or an agent API key.
export AUTHORIC_URL="https://api.authoric.ai" # your API's address
export APP_UID="<your app uid>"
export TOKEN="<an access token>"Create a row
Send the columns as JSON. A reference is sent by its _uid name, holding the referenced row's
uid: vendor_uid, not vendor_id.
curl -X POST "$AUTHORIC_URL/$APP_UID/autocrud/invoices" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f4e9c2a-1b3d-4a8e-9c7f-2d1e6b0a4c88" \
-d '{
"invoice_number": "MS-88461",
"vendor_uid": "0199b8e1-9c04-7a8b-b2d3-6e5f1c8a4d07",
"purchase_order_uid": "0199b8e2-4b7e-7f10-a6c9-2d8e3f5b1a64",
"amount": 8760.00,
"memo": "Sterile kits"
}'The response is the new row's uid followed by the fields you sent:
{
"uid": "0199b8e4-2a6f-7c31-8d5e-41f0a9c37b12",
"invoice_number": "MS-88461",
"vendor_uid": "0199b8e1-9c04-7a8b-b2d3-6e5f1c8a4d07",
"purchase_order_uid": "0199b8e2-4b7e-7f10-a6c9-2d8e3f5b1a64",
"amount": 8760,
"memo": "Sterile kits"
}Columns the database fills in (status defaulting to received, currency to USD, the
timestamps) come back when you read the row.
You never send org_id. The row belongs to the caller's organization, or to no organization in
a shared table.
Read one row
curl "$AUTHORIC_URL/$APP_UID/autocrud/invoices/0199b8e4-2a6f-7c31-8d5e-41f0a9c37b12" \
-H "Authorization: Bearer $TOKEN"{
"uid": "0199b8e4-2a6f-7c31-8d5e-41f0a9c37b12",
"invoice_number": "MS-88461",
"vendor_uid": "0199b8e1-9c04-7a8b-b2d3-6e5f1c8a4d07",
"purchase_order_uid": "0199b8e2-4b7e-7f10-a6c9-2d8e3f5b1a64",
"amount": "8760.00",
"currency": "USD",
"status": "received",
"received_at": null,
"due_at": null,
"memo": "Sterile kits",
"spend_category": null,
"created_at": "2026-10-23T14:02:11.000Z",
"updated_at": "2026-10-23T14:02:11.000Z",
"deleted_at": null
}Two things to notice. DECIMAL columns such as amount come back as strings, which keeps
their exact precision; parse them with a decimal type, not a float. And a column marked
sensitive comes back as "[redacted]", on this read and on every list and event.
A uid that names no row, a row in another organization, a soft-deleted row and a value that is
not a uid at all are the same 404. The API does not say which.
Update a row
PATCH takes only the fields you are changing. Every column is optional on an update.
curl -X PATCH "$AUTHORIC_URL/$APP_UID/autocrud/invoices/0199b8e4-2a6f-7c31-8d5e-41f0a9c37b12" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "status": "matched" }'The response is the row as it now stands, re-read through the same governed path as a GET,
so redacted columns stay redacted. To re-point a reference, send its _uid name:
{ "purchase_order_uid": "<another purchase order's uid>" }. To clear a nullable reference,
send null.
Delete a row
curl -X DELETE "$AUTHORIC_URL/$APP_UID/autocrud/invoices/0199b8e4-2a6f-7c31-8d5e-41f0a9c37b12" \
-H "Authorization: Bearer $TOKEN"On a soft-delete table (the example app's invoices is one) the row gets a deleted_at and
disappears from every read, update and subscription; it is not purged. On a hard-delete table it
is removed.
A row still referenced under a Restrict or No action foreign key cannot be deleted. Delete or re-point the referencing rows first; the error says where they are:
{
"error": {
"error": "Cannot delete row because related records exist",
"code": "DELETE_BLOCKED_BY_DEPENDENCIES",
"details": {
"tableName": "invoices",
"uid": "0199b8e4-2a6f-7c31-8d5e-41f0a9c37b12",
"blockers": [
{ "childTable": "payments", "deleteRule": "RESTRICT", "count": 2 }
]
},
"requestId": "3f9a1c7e-2b54-4d8a-9f10-6c2e7b5a4d31"
},
"requestId": "3f9a1c7e-2b54-4d8a-9f10-6c2e7b5a4d31"
}Retry-safe writes
Networks fail after the server has done the work. Send an Idempotency-Key header with a fresh
UUID per logical operation on any POST, PATCH or DELETE, and reuse it on the retry:
| On retry | Result |
|---|---|
| Same key, same body | The stored response, for 24 hours. No second row. |
| Same key, different body | 409 IDEMPOTENCY_KEY_REUSED: the key was used for a different request. |
| Same key, first request still running | 409 IDEMPOTENCY_IN_PROGRESS: wait, then retry with the same key. |
| New key, or no key | A normal write. |
A key belongs to whoever sent it: the same UUID from another user, another org or another API key is a different key, and never returns your response. It is also scoped to its method and route, so the same UUID on two tables never collides. Policy and validation run before the key is recorded, so a refused request never uses up a key.
Errors
Every failure has the same envelope. Branch on error.code, never on the message:
{
"error": {
"error": "'vendor_id' holds an internal id. Use 'vendor_uid' (the uid of a 'vendors' row) instead.",
"code": "ROW_FIELD_NOT_ACCEPTED",
"details": { "field": "vendor_id", "use": "vendor_uid" },
"requestId": "3f9a1c7e-2b54-4d8a-9f10-6c2e7b5a4d31"
},
"requestId": "3f9a1c7e-2b54-4d8a-9f10-6c2e7b5a4d31"
}| Status | error.code | When |
|---|---|---|
400 | ROW_FIELD_NOT_ACCEPTED | The body named id, uid, org_id, an actor column or a raw foreign key. details.use is the field to send instead, when there is one. |
400 | ROW_REFERENCE_NOT_FOUND | A _uid reference names no row in the table it points at. details.field and details.references say which. |
400 | CREATE_REQUIRED_FIELD_MISSING | A column the table requires was absent. |
400 | ROW_VALUE_INVALID | A value does not fit its column: too long, out of range, the wrong type, or not one of an enum's options (listed). details.column names it. |
400 | VALIDATION_ERROR | The body failed the table's schema: an unknown column, a wrong type, a missing required column. details.field names the field, and the message says why. |
403 | none | Policy does not grant this operation on this table. |
404 | none | No row with that uid that you can see. |
409 | CREATE_DUPLICATE | A unique column already holds that value. |
409 | DELETE_BLOCKED_BY_DEPENDENCIES | Other rows still reference this one. |
409 | ROW_EDGE_COLLISION | The table cannot be served until a column is renamed (see Database). |
409 | none | An Idempotency-Key conflict. |
423 | TABLE_LOCKED | The operation is switched off for this table. |
A body that fails the table's schema is refused. Validate your payload against the table's schema in the API reference before you send it.
The full list of codes is in the error-code reference.