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"| Scope | Header value | RLS |
|---|---|---|
anon | the anon key | enforced (public rows only) |
authenticated | anon key + user JWT | enforced (the user’s rows) |
service_role | the service_role key | bypassed (admin) |
See API Keys & Scopes.
The five operations
| Verb | Path | Action |
|---|---|---|
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:
- parses the PostgREST query string,
- builds parameterized SQL with validated, quoted identifiers (injection-safe),
- opens a transaction and injects RLS context with
SET LOCAL, - 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 theContent-Rangeheader). - Every response carries rate-limit headers (
X-RateLimit-Limit,X-RateLimit-Remaining, andRetry-Afteron429).
Rate limits
Per project, per scope:
| Scope | Limit | Window |
|---|---|---|
anon | 100 requests | 60s |
authenticated | 1,000 requests | 60s |
service_role | 5,000 requests | 60s |
Errors
Errors return a JSON envelope with a hint and details where available:
{ "error": "invalid query", "statusCode": 400, "hint": "...", "details": "..." }Quick taste
# Read published posts, newest first, 10 at a timecurl "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:
- Reading Data — select, filters, ordering, pagination, embeds
- Modifying Data — insert, update, upsert, delete
- RPC — call functions
- GraphQL API — query the same tables over GraphQL (pg_graphql), same RLS
- Reference → REST Query Syntax — the full operator table