Skip to content

REST API

Rebase automatically generates a complete API from your collection definitions:

  • REST API — CRUD endpoints for every collection at /api/data/:slug
  • OpenAPI spec — Machine-readable spec at /api/docs
  • Swagger UI — Interactive API explorer at /api/swagger (dev mode only)

No code is required — define your collections and the API appears automatically.

For each collection, the following endpoints are generated:

Method Path Description
GET /api/data/:slug List entities
GET /api/data/:slug/count Count entities
GET /api/data/:slug/:id Get a single entity
POST /api/data/:slug Create a entity
PUT /api/data/:slug/:id Update a entity
DELETE /api/data/:slug/:id Delete a entity

Nested relations are accessible via URL paths:

GET /api/data/authors/42/posts → list author's posts
GET /api/data/authors/42/posts/7 → get a specific post by author
POST /api/data/authors/42/posts → create a post for author
PUT /api/data/authors/42/posts/7 → update the post
DELETE /api/data/authors/42/posts/7 → delete the post

To handle arbitrary nested subcollection depths, Rebase routes incoming requests using Hono’s :rest{.+} parameter regex. The internal segment parsing engine analyzes paths by counting slash-separated segments:

  • Odd segment count (e.g., authors/42/posts -> 3 segments) represents a collection list request.
  • Even segment count (e.g., authors/42/posts/7 -> 4 segments) represents an operation on a specific entity ID. The last segment is popped as the target entityId.

The engine filters out reserved system namespaces (e.g., history) from the path segment analysis to prevent collisions with built-in endpoints.

All data endpoints require authentication by default. Include a Bearer token in the Authorization header:

curl -H "Authorization: Bearer <access-token>" \
https://api.example.com/api/data/products

For server-to-server calls, use the service key:

curl -H "Authorization: Bearer <service-key>" \
https://api.example.com/api/data/products

Use PostgREST-style query parameters to filter results. The format is ?field=operator.value:

# Exact match
GET /api/data/products?active=eq.true
# Comparison operators
GET /api/data/products?price=gt.100
GET /api/data/products?price=lte.50
# Multiple filters (AND)
GET /api/data/products?active=eq.true&price=gt.10
# IN operator — match any value in a set
GET /api/data/products?status=in.(draft,published)
# NOT IN
GET /api/data/products?status=nin.(archived,deleted)
# Array contains
GET /api/data/products?tags=cs.electronics
# Array contains any
GET /api/data/products?tags=csa.(electronics,books)
Operator Meaning Example
eq Equals (==) ?active=eq.true
neq Not equals (!=) ?status=neq.draft
gt Greater than (>) ?price=gt.100
gte Greater or equal (>=) ?price=gte.100
lt Less than (<) ?price=lt.50
lte Less or equal (<=) ?price=lte.50
in In array ?status=in.(a,b,c)
nin Not in array ?status=nin.(a,b)
cs Array contains ?tags=cs.value
csa Array contains any ?tags=csa.(a,b)

Use or and and for complex conditions:

# OR: match products that are either cheap or on sale
GET /api/data/products?or=(price.lt.10,on_sale.eq.true)
# AND: explicit conjunction
GET /api/data/products?and=(active.eq.true,price.gt.0)

Use orderBy with the format field:direction:

# Sort by price descending
GET /api/data/products?orderBy=price:desc
# Sort by name ascending (default)
GET /api/data/products?orderBy=name:asc

Use limit and offset, or page:

# Limit and offset
GET /api/data/products?limit=20&offset=40
# Page-based (uses default limit of 20)
GET /api/data/products?page=3

The default limit is 20, the maximum is 100.

List responses include pagination metadata:

{
"data": [
{ "id": 1, "name": "Widget", "price": 29.99 },
{ "id": 2, "name": "Gadget", "price": 49.99 }
],
"meta": {
"total": 150,
"limit": 20,
"offset": 0,
"hasMore": true
}
}

Single entity responses return a flat object:

{
"id": 1,
"name": "Widget",
"price": 29.99,
"created_at": "2026-01-15T10:30:00Z"
}

Use searchString for full-text search across string fields:

GET /api/data/products?searchString=wireless%20keyboard

If a collection defines a property with a type of vector, you can perform high-speed similarity searches using pgvector distance operations compiled directly in the database query.

GET /api/data/products?vector_search=embedding&vector=[0.15,0.22,-0.05]&vector_distance=cosine&vector_threshold=0.8
Parameter Type Description
vector_search string The name of the vector property to query against.
vector string A JSON-serialized array of floats representing the query vector.
vector_distance string The distance metric to evaluate. Supported values: cosine (default, <=>), l2 (<->), inner_product (<#>).
vector_threshold number Maximum distance threshold. Only records with distance less than this threshold are returned.

Use the include parameter to embed related entities:

# Include specific relations
GET /api/data/articles?include=author,categories
# Include all relations
GET /api/data/articles?include=*

Included relations are embedded directly in the response:

{
"id": 1,
"title": "Getting Started",
"author_id": 42,
"author": {
"id": 42,
"name": "Jane Doe",
"email": "jane@example.com"
}
}

Use fields to select specific columns:

GET /api/data/products?fields=id,name,price

Every REST mutation operation (POST, PUT, DELETE) runs through a strict, sequential hook execution pipeline:

Request ──► beforeSave/beforeDelete (blocking) ──► DB Operation ──► afterSave/afterDelete (deferred) ──► Response
  1. Blocking Hooks (beforeSave, beforeDelete) These hooks are executed synchronously in the main request cycle before committing the database transaction. They can modify incoming payloads, run custom validations, or abort the request entirely by throwing an error.

  2. Deferred Hooks (afterSave, afterDelete) These hooks execute asynchronously after the database transaction has successfully committed. They use deferred promises (fire-and-forget), meaning they run in the background and do not block the client’s HTTP response. Ideal for sending webhooks, triggering push notifications, or queuing external tasks.

  • OpenAPI spec: GET /api/docs — Returns the full OpenAPI 3.0 JSON specification
  • Swagger UI: GET /api/swagger — Interactive API explorer (dev mode only)

The OpenAPI spec is auto-generated from your collection definitions and includes all endpoints, query parameters, and response schemas.

API keys provide machine-to-machine authentication for agents, MCP servers, CI pipelines, and external integrations. They support per-collection permission scoping and optional full admin access.

# Via CLI
rebase api-keys create --name "My Integration" \
--permissions '[{"collection":"orders","operations":["read","write"]}]'
# Via REST (requires admin auth)
curl -X POST http://localhost:3000/api/admin/api-keys \
-H "Authorization: Bearer <service-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "My Integration",
"permissions": [{ "collection": "orders", "operations": ["read", "write"] }]
}'

The response includes the full plaintext key (rk_live_...) exactly once — store it immediately.

curl http://localhost:3000/api/data/orders \
-H "Authorization: Bearer rk_live_abc123..."

Permissions and RLS: two independent gates

Section titled “Permissions and RLS: two independent gates”

An API key’s request passes through two authorization checks, and both must allow it:

  1. The key’s permission list — collection × operation, checked at the route layer.
  2. Row-Level Security — API keys do not bypass RLS. A key runs as uid: "api-key:<id>" with the service role (plus admin when admin: true). Admin keys pass via the built-in admin policies; a non-admin key only sees rows that a security rule explicitly grants to the service role or to the public. Owner-style rules (owner_id = auth.uid()) never match an API key.

So a non-admin key with "*" permissions can still get empty results — that’s RLS working, not a bug. Either grant the service role in the relevant collections’ security rules, or use an admin key.

Function invocations are scoped like collections, under the functions namespace: {"collection": "functions", "operations": ["write"]} grants every function, "functions/<name>" grants one, and the global "*" wildcard grants all. A key without such an entry cannot invoke functions at all.

Storage works the same way, under the storage namespace: {"collection": "storage", "operations": ["read", "write"]} lets the key download/list (read), upload and create folders (write), and delete files (delete). The global "*" wildcard also grants storage. A key without such an entry cannot touch storage. TUS resumable-upload routes count as write for every step (including the offset check and cancel), so a write-scoped key can complete an upload on its own.

By default, API keys get the service role (data access only). Add "admin": true to grant the key full admin access — including /api/admin/* routes for schema management, user management, and more, plus cron, backups, and logs. Use this for agents, MCP servers, and CI:

# CLI
rebase api-keys create --name "My Agent" --admin --full-access
# REST
curl -X POST http://localhost:3000/api/admin/api-keys \
-H "Authorization: Bearer <service-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "My Agent",
"admin": true,
"permissions": [{ "collection": "*", "operations": ["read", "write", "delete"] }]
}'
Field Type Description
name string Human-readable label
permissions ApiKeyPermission[] Per-collection access ("*" = everything; "functions/<name>" = one function; "storage" = file storage)
admin boolean Grant admin role — admin routes + RLS admin policies
rate_limit number | null Requests per 15-min window (null = the server default, 1000)
expires_at string | null ISO-8601 expiration timestamp

The CLI requires an explicit scope: pass --permissions '<json>' or opt into --full-access — there is no silent full-access default.

Keys can be listed, updated, and revoked via /api/admin/api-keys or the rebase api-keys CLI commands.

Get a list of all available collections and their structure:

GET /api/collections