Skip to content

REST API Overview

AnvilBase auto-generates a REST API over every table in your project, served by a built-in REST→SQL engine inside the control plane. It speaks PostgREST query syntax, so the Supabase clients and any PostgREST tooling work against it.

This is the data plane. Base path:

/v1/rest/<project_id>/<table>

With the SDK, the client base URL is http://<host>/v1/<project_id> and it appends the rest/v1/... path for you.

Authentication

Send your project key in the apikey header, and (for a signed-in user) the JWT in Authorization:

apikey: anvilbase_anon_<slug>_…
Authorization: Bearer <user-jwt> # optional; makes the request "authenticated"
ScopeHeader valueRLS
anonthe anon keyenforced (public rows only)
authenticatedanon key + user JWTenforced (the user’s rows)
service_rolethe service_role keybypassed (admin)

See API Keys & Scopes.

The five operations

VerbPathAction
GET/v1/rest/<id>/<table>read rows (Reading Data)
POST/v1/rest/<id>/<table>insert rows (Modifying Data)
PATCH/v1/rest/<id>/<table>?<filter>update matching rows
DELETE/v1/rest/<id>/<table>?<filter>delete matching rows
POST/v1/rest/<id>/rpc/<function>call a Postgres function (RPC)

How it works (and why it’s cheap)

Instead of one PostgREST process per database, the engine maintains lazy per-project connection pools (~2–5 MB each), created on first request and evicted after idle. Each request:

  1. parses the PostgREST query string,
  2. builds parameterized SQL with validated, quoted identifiers (injection-safe),
  3. opens a transaction and injects RLS context with SET LOCAL,
  4. executes and returns a PostgREST-compatible JSON response.

This is what lets one binary serve hundreds of projects’ REST APIs. See Architecture.

Response shape & headers

  • Reads return a JSON array of rows by default. Request a single object with the header Accept: application/vnd.pgrst.object+json.
  • Writes return no body unless you ask: Prefer: return=representation.
  • Row counts come via Prefer: count=exact|planned|estimated (returned in the Content-Range header).
  • Every response carries rate-limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After on 429).

Rate limits

Per project, per scope:

ScopeLimitWindow
anon100 requests60s
authenticated1,000 requests60s
service_role5,000 requests60s

Errors

Errors return a JSON envelope with a hint and details where available:

{ "error": "invalid query", "statusCode": 400, "hint": "...", "details": "..." }

See Reference → Error Codes.

Quick taste

Terminal window
# Read published posts, newest first, 10 at a time
curl "http://localhost:39001/v1/rest/<id>/posts?status=eq.published&order=created_at.desc&limit=10" \
-H "apikey: $ANON_KEY"
const { data } = await db.from('posts')
.select('*').eq('status', 'published')
.order('created_at', { ascending: false }).limit(10)

Read on: