---
title: Records
description: Create, read, update and delete JSON records over HTTP, with versioning, bulk writes, soft delete and history.
group: Working with data
order: 2
keywords: [crud api, json records, rest api, create read update delete, soft delete, versioning]
---

# Records

A record is one JSON document in a [collection](/docs/collections). Your own fields live
under `data`; the API adds the rest.

```json
{
  "id": "01a12621-a7f6-7241-a836-853cd6595895",
  "collectionId": "41a46615-602c-461d-8155-3760b9468a38",
  "data": { "title": "Ship the landing page", "done": false },
  "metadata": {},
  "version": 1,
  "schemaVersion": 1,
  "createdBy": { "type": "user", "id": "97bbba40-…" },
  "updatedBy": { "type": "user", "id": "97bbba40-…" },
  "createdAt": "2026-10-10T14:05:02.301Z",
  "updatedAt": "2026-10-10T14:05:02.301Z",
  "deletedAt": null
}
```

`data` can be any JSON — an object, but also an array, a string or a number.
`metadata` is a small object you own, for bookkeeping like `{"source":"importer"}`; it is
never checked against your schema.

In the examples below, `…/records` means
`$BAAS_URL/workspaces/$BAAS_WS/collections/todos/records`.

## Create

```bash
curl -s -X POST "…/records" \
  -H "Authorization: Bearer $BAAS_KEY" -H "Content-Type: application/json" \
  -d '{"data":{"title":"Buy milk","done":false}}'
```

Returns `201 Created` with the stored record. Needs `records:create`.

To avoid double-writes when a request is retried, send an `Idempotency-Key` header with a
value you make up (a UUID is ideal). The first response is remembered for 24 hours and
replayed if the same key arrives again:

```bash
curl -s -X POST "…/records" \
  -H "Authorization: Bearer $BAAS_KEY" \
  -H "Idempotency-Key: 8f2c41e0-7b14-4f0e-9a51-2c7b3d6e1a90" \
  -H "Content-Type: application/json" \
  -d '{"data":{"title":"Buy milk"}}'
```

## Read

```bash
curl -s "…/records"            -H "Authorization: Bearer $BAAS_KEY"   # a page of records
curl -s "…/records/RECORD_ID"  -H "Authorization: Bearer $BAAS_KEY"   # one record
```

Lists come back as `{ "data": [...], "pagination": { "nextCursor": …, "hasMore": … } }`.
See [Filtering and sorting](/docs/queries) to narrow them down.

## Update

**Replace** the whole document:

```bash
curl -s -X PUT "…/records/RECORD_ID" \
  -H "Authorization: Bearer $BAAS_KEY" -H "Content-Type: application/json" \
  -d '{"data":{"title":"Buy oat milk","done":true}}'
```

**Change a few fields** and leave the rest alone:

```bash
curl -s -X PATCH "…/records/RECORD_ID" \
  -H "Authorization: Bearer $BAAS_KEY" -H "Content-Type: application/json" \
  -d '{"data":{"done":true}}'
```

In a `PATCH`, `null` removes a field: `{"data":{"due":null}}` deletes `due`. Arrays are
replaced whole, not merged.

Both need `records:update`.

## Not overwriting someone else's change

Every write bumps `version`. Send the version you based your edit on and the API refuses
to clobber a newer one:

```bash
curl -s -X PATCH "…/records/RECORD_ID" \
  -H "Authorization: Bearer $BAAS_KEY" -H "Content-Type: application/json" \
  -d '{"data":{"done":true},"expectedVersion":3}'
```

If someone else got there first you get `409 VERSION_CONFLICT`, and `details.currentVersion`
tells you where things stand. Fetch the record again, re-apply your change, and retry.

This is optional. Without it, the last write wins — but writes are still serialised, so
two simultaneous updates can never corrupt a record.

## Delete and restore

```bash
curl -s -X DELETE "…/records/RECORD_ID" -H "Authorization: Bearer $BAAS_KEY"
```

This is a **soft delete**: the record moves to the trash and stops appearing in lists.
Bring it back with:

```bash
curl -s -X POST "…/records/RECORD_ID/restore" -H "Authorization: Bearer $BAAS_KEY"
```

To see the trash, add `?deleted=only` (or `?deleted=include`) to a list request.

Erasing a record for good is a separate action and is not available to API keys — it
needs a signed-in owner or admin.

## History

Every version is kept.

```bash
curl -s "…/records/RECORD_ID/history" -H "Authorization: Bearer $BAAS_KEY"
```

You can look at one revision, or roll back to it — which creates a _new_ version rather
than rewriting the past, so the history stays honest:

```bash
curl -s -X POST "…/records/RECORD_ID/restore/2" -H "Authorization: Bearer $BAAS_KEY"
```

## Many at once

Up to 500 records per call, all-or-nothing:

```bash
# Create
curl -s -X POST "…/records/bulk" \
  -H "Authorization: Bearer $BAAS_KEY" -H "Content-Type: application/json" \
  -d '{"records":[{"data":{"title":"One"}},{"data":{"title":"Two"}}]}'

# Delete
curl -s -X POST "…/records/bulk-delete" \
  -H "Authorization: Bearer $BAAS_KEY" -H "Content-Type: application/json" \
  -d '{"ids":["01a12621-…","01a12622-…"]}'
```

For more than 500, use [import](/docs/collections#import-and-export).

## Limits

|                       |           |
| --------------------- | --------- |
| One record's `data`   | 256 KB    |
| `metadata`            | 16 KB     |
| JSON nesting depth    | 32 levels |
| Records per bulk call | 500       |

A key named `__proto__` is rejected, which stops a known class of JavaScript attack.

## Next steps

- [Filtering and sorting](/docs/queries) — searching, paging and `select`.
- [Schemas](/docs/schemas) — rejecting records that are the wrong shape.
- [Webhooks](/docs/webhooks) — getting told when records change.
