REST API

Every table you create gets a full REST API automatically. No code generation, no config — just create a table and start querying.

Endpoints are auto-generated for all tables except users and sessions, which are managed by the auth system.

Row-level scoping

If a table has a user_id (or userId) column, Based automatically scopes all CRUD operations to the authenticated user. No policy language, no configuration — add the column, get isolation.

OperationBehavior with user_id column
GET list (auth)Only returns the caller's own rows
GET list (anon)Rejected (403)
GET :id404 if the row belongs to another user
POSTuser_id is forced to the caller, ignoring any client value
PUT :idScoped to caller; user_id is immutable
DELETE :id404 for rows belonging to another user

For shared/public tables, simply omit the user_id column — all authenticated users see everything, and anon reads work.

GET /api/:table

List records with optional pagination and filtering.

request
# Paginated list
curl "https://my-app.based.yourdomain.com/api/posts?limit=10&offset=0" \
  -H "apikey: <anonKey>"

# With filtering
curl "https://my-app.based.yourdomain.com/api/posts?status=draft&limit=5" \
  -H "apikey: <anonKey>"
response — 200
{
  "data": [
    { "id": 1, "title": "Hello", "status": "draft", ... },
    { "id": 2, "title": "World", "status": "draft", ... }
  ],
  "total": 2
}

Use limit and offset for pagination. Any other query parameter is treated as a filter on the corresponding column.

GET /api/:table/:id

Fetch a single record by ID.

request
curl "https://my-app.based.yourdomain.com/api/posts/1" \
  -H "apikey: <anonKey>"
response — 200
{
  "data": { "id": 1, "title": "Hello", "status": "draft", ... }
}

POST /api/:table

Create a new record. Requires authentication — anon keys cannot create.

request
curl -X POST "https://my-app.based.yourdomain.com/api/posts" \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{ "title": "New Post", "status": "draft" }'
response — 201
{
  "data": { "id": 3, "title": "New Post", "status": "draft", ... }
}

PUT /api/:table/:id

Upsert: creates the record if it doesn't exist, updates it if it does. Requires authentication. Returns 201 on create, 200 on update.

request
# Update existing
curl -X PUT "https://my-app.based.yourdomain.com/api/posts/3" \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{ "status": "published" }'

# Upsert with custom id (creates if missing)
curl -X PUT "https://my-app.based.yourdomain.com/api/settings/user-preferences" \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{ "theme": "dark" }'
response — 200 (update) or 201 (create)
{
  "data": { "id": 3, "title": "New Post", "status": "published", ... }
}

DELETE /api/:table/:id

Delete a record. Requires authentication.

request
curl -X DELETE "https://my-app.based.yourdomain.com/api/posts/3" \
  -H "Authorization: Bearer <accessToken>"

Authentication

MethodHeaderAccess
Bearer tokenAuthorization: Bearer <accessToken>Full read/write
Anon keyapikey: <anonKey>Read-only

Error responses

Errors follow a consistent format across all endpoints.

error response
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Record not found"
  }
}