Records API
Read records for a collection by slug, write them with the version's ids, and delete them by id.
Records are read by collection slug, not id - which makes the read path stable across environments where ids differ.
curl -G https://api.console.buildbase.app/api/collections/data/contact-requests \
-H "Authorization: $BUILDBASE_TOKEN" \
--data-urlencode 'latest=true'Returns every record for the resolved version, unpaginated.
Warning
version is required unless latest=true. Without either, the request
returns 400. No other query parameters are accepted, including the usual
$page and $limit.
Choosing a version
| Query | Reads |
|---|---|
latest=true | The live, published version, whatever its number |
version=3 | Version number 3 specifically, live or not |
latest=true is what you want in an application - it follows the published
version, so creating a draft version in the console does not change what your
app reads until you publish it. version is ignored when latest=true. Pin a
number when you are migrating and need the old shape.
Records are stored against the version they were written to. After you promote
a new version, latest=true returns only the records stored against it;
records written under an older version are still there, and you read them with
version=<n>.
Failures are 404:
| Condition | Response |
|---|---|
| No collection with that slug | {"error": true, "message": "not found collection:<slug>"} |
| No version with that number | {"error": true, "message": "not found version:<n>"} |
latest=true, nothing live | {"error": true, "message": "not found version:live"} |
The response
An array of record documents. Each carries the submitted values under data,
keyed by field slug, plus the version it was written against:
[
{
"_id": "665f…",
"collectionId": "664a…",
"versionId": "664b…",
"formId": "664c…",
"data": { "email": "[email protected]", "message": "Interested" },
"createdAt": "2026-08-15T09:12:44.108Z"
}
]formId is present only on records created through a
form submission.
Warning
This endpoint returns every record for the version with no pagination. On
a collection behind a busy public form that grows without bound. Read it from
a background job rather than a request handler, and to page, use the paginated
list, GET /api/collections/:collectionId/versions/:versionId/records.
Writing records
Records are written under their version, by id:
| Method | Path | Purpose |
|---|---|---|
GET | /api/collections/:collectionId/versions/:versionId/records | List, paginated, with filter |
GET | /api/collections/:collectionId/versions/:versionId/records/:recordId | One record |
POST | /api/collections/:collectionId/versions/:versionId/records | Create a record |
PATCH | /api/collections/:collectionId/versions/:versionId/records/:recordId | Update a record |
DELETE | /api/collections/:collectionId/versions/:versionId/records/:recordId | Delete a record |
The body is the record's data, keyed by field slug:
curl -X POST \
https://api.console.buildbase.app/api/collections/$COLLECTION_ID/versions/$VERSION_ID/records \
-H "Authorization: Bearer $BUILDBASE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "title": "Release 23", "version": "1.0.0", "published": true }'A key that is not one of the version's field slugs is dropped. A file field takes asset ids, as the overview describes.
How values are stored
Each value is checked against its field's type and stored by that type, not by how it looks. The rules are the ones a form submission follows:
| Field type | Takes | Stored |
|---|---|---|
text, rich-text, color | a string | the same string: "5550100" stays text |
email, link | a string in that format | the same string |
number | a number, or a string that is one ("42") | a number |
bool | true, false, "true" or "false" | true or false |
date | a string or a number that reads as a date | as sent |
file | an asset id, { "assetId": "..." }, or a list of them | the asset |
POST checks every field, and a required field needs a value: a string of
spaces does not count. PATCH checks only the fields it sends and keeps the
others as stored, so a required field is refused only when it is sent empty.
A null value counts as not sent, and an empty string clears an optional
field.
A write with any problem is refused with 400 and nothing is stored.
errors lists every problem at once, and message holds them all:
{
"error": true,
"message": "Name must be text. Age must be a number",
"errors": ["Name must be text", "Age must be a number"]
}A file larger than its field's maxMb is one of those problems, and its
sentence states the limit and the file's size in bytes. The answer then also
carries maxBytes, the limit of the first such file, and tooLarge, one
entry per file over its field's limit with field, maxBytes and size:
{
"error": true,
"message": "Photo: Files can be up to 5 MB (5,242,880 bytes) here. This one is 5,242,881 bytes",
"errors": [
"Photo: Files can be up to 5 MB (5,242,880 bytes) here. This one is 5,242,881 bytes"
],
"maxBytes": 5242880,
"tooLarge": [{ "field": "photo", "maxBytes": 5242880, "size": 5242881 }]
}It stays a 400, not the 413 an upload answers: the file is already
stored, and the fix is a smaller file or a larger maxMb on the field.
Records written before these checks may hold a number in a text field, such
as 5550100 for a phone number. If you have older records, read text fields
with String(value).
Deleting a record
curl -X DELETE \
https://api.console.buildbase.app/api/collections/$COLLECTION_ID/versions/$VERSION_ID/records/$RECORD_ID \
-H "Authorization: $BUILDBASE_TOKEN"Records are nested under their version, so deletion needs all three ids. There is no bulk delete.
Deletion is decided by role, not by who created the record. Any role with delete permission on collection records can delete any record in the org, including records created by a public form submission, which have no creator.
Value types
Whichever way a record arrives - the console, the API, or a public form - its
values are stored by their fields' types (how values are stored).
A form submitted with FormData sends "12" to a number field and "true"
to a bool field; they are stored as 12 and true. See
custom forms.
From the server SDK
Read a collection's records from your own server, for example to render a
page. createServerClient from @buildbase/sdk/server reads the same records,
with caching built in, on Node 18+, Bun, Deno, Cloudflare Workers and edge
runtimes. It refuses to run in a browser, so the token stays on your server.
import { createServerClient } from '@buildbase/sdk/server';
const content = createServerClient({
serverUrl: 'https://api.console.buildbase.app',
apiToken: process.env.BUILDBASE_API_TOKEN!, // <orgId>:<secret>
});
// The live version; pass { version: 3 } to pin one
export const releases = await content.collections.data<{
title: string;
version: string;
}>('release-notes');Reads are cached. A fresh entry (60 seconds by default) costs no request.
After that, a read asks the server whether anything changed (with
If-None-Match), so an unchanged collection costs one short 304 Not Modified
answer. Identical reads made at the same moment share one request.
content.collections.list() lists the collections themselves.
When records change, a content webhook clears the cached read:
collection.record_created, collection.record_updated,
collection.record_deleted or collection.version_published. And
npx buildbase content pull writes every live collection to a JSON file at
build time. See Delivering content for the cache
stores, the webhook handler and the build-time pull. All of it is in the
published @buildbase/sdk, 1.3.0.
Next Steps
- Collections overview - schemas and versions.
- Forms - the public write path into a collection.
- Delivering content - caching, webhooks and the build-time pull.