HTTP API
Call Sovaria from any language: base URL, authentication, the routes an app uses, response shapes, errors and rate limits.
The SDKs are thin wrappers over this HTTP API, so anything they do you can do with curl, fetch or any HTTP client. All bodies are JSON unless a route says otherwise.
Base URL and project
Send requests to your project URL, https://<project-ref>.sovaria.in (shown in the console under Connect). The project URL already identifies the project, so you never send project ids.
Tools that manage several projects can call https://api.sovaria.in instead, adding X-Sovaria-Org-ID and X-Sovaria-Project-ID headers. A key only works for the project it belongs to, whichever host you use.
Authentication
| Caller | Send | Acts as |
|---|---|---|
| Anonymous visitor | apikey: <anon key> (or Authorization: Bearer <anon key>) | anon — only what your policies allow visitors. |
| Signed-in app user | Authorization: Bearer <access token> | authenticated, with auth.uid() set to the user. |
| Your server or CI | apikey: <service role or custom key> | The key's role. The service role key bypasses policies and row-level security. |
Access tokens last 15 minutes. Exchange the refresh token for a new pair before they expire (POST /api/v1/auth/refresh); the SDKs do this automatically.
Sign up, sign in and sessions
| Route | Body | Returns |
|---|---|---|
POST /api/v1/auth/register | {email, password, name?} | The new user. 403 SIGNUP_DISABLED when sign-up is off; 422 for a weak password. |
POST /api/v1/auth/project/login | {email, password} | {access_token, refresh_token, user_id, …} |
POST /api/v1/auth/refresh | {refresh_token} | A new token pair. The old refresh token stops working (rotation). |
POST /api/v1/auth/logout | {refresh_token} | Ends the session. |
GET /api/v1/auth/me | — | The signed-in user. |
GET /api/v1/auth/public-config | — | The project's public auth settings (enabled providers, sign-up on/off) and project{id, ref}. |
GET /auth/v1/.well-known/jwks.json | — | Public keys for verifying access tokens yourself (ES256). |
URL=https://<project-ref>.sovaria.in
ANON=<anon-key>
curl -s -X POST $URL/api/v1/auth/register -H "apikey: $ANON" \
-H 'Content-Type: application/json' \
-d '{"email":"ada@example.com","password":"a-long-passphrase","name":"Ada"}'
TOKEN=$(curl -s -X POST $URL/api/v1/auth/project/login -H "apikey: $ANON" \
-H 'Content-Type: application/json' \
-d '{"email":"ada@example.com","password":"a-long-passphrase"}' | jq -r .access_token)Magic links, email codes, password reset and social sign-in are covered in Authentication overview.
Database (SQL)
POST /api/v1/sql/query runs one SELECT, INSERT, UPDATE, DELETE or EXPLAIN. Pass values as params for $1, $2, …; they are bound by the database and never become part of the SQL. Schema changes (CREATE, ALTER, DROP) belong in the SQL editor or Migrations, not in app code.
curl -s -X POST $URL/api/v1/sql/query \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"sql":"select id, body from notes where id = $1","params":[42]}'Rows are arrays in the order of columns. For UPDATE and DELETE, row_count is the number of rows changed — 0 when row-level security hid every row from the caller.
Documents
| Route | Body | Notes |
|---|---|---|
POST /api/v1/data/doc/{collection}/{id} | {data: {...}} | Create or replace. Nested collections: orders/123/notes/{id}. |
GET /api/v1/data/doc/{collection}/{id} | — | Returns {key, data, found}; 404 when it doesn't exist or the caller may not see it. |
DELETE /api/v1/data/doc/{collection}/{id} | — | |
POST /api/v1/data/query/scan | {prefix, start_key?, end_key?, limit?} | Returns {documents, count, truncated}. |
Storage
| Route | Body | Notes |
|---|---|---|
POST /api/v1/store/objects/{bucket}/{path} | multipart, field file | Upload. Bucket rules decide where a user may write. |
GET /api/v1/store/objects/{bucket}/{path} | — | Download. |
DELETE /api/v1/store/objects/{bucket}/{path} | — | |
GET /api/v1/store/objects/{bucket}?prefix= | — | List objects. |
POST /api/v1/store/objects/{bucket}/{path}/sign-download | {expires_in} (seconds) | Returns {url, expires_at, method}; the URL works without auth until it expires. |
POST /api/v1/store/objects/{bucket}/{path}/sign-upload | {expires_in} | A URL a browser can PUT the file to directly. |
GET /api/v1/store/public/{project-id}/{bucket}/{path} | — | Public buckets only. |
GET/POST /api/v1/store/buckets, DELETE /api/v1/store/buckets/{name} | {name, public, file_size_limit?, allowed_mime_types?} | Manage buckets (console members or the service role key). |
curl -s -X POST $URL/api/v1/store/objects/avatars/$USER_ID/me.png \
-H "Authorization: Bearer $TOKEN" -F file=@me.png
curl -s -X POST $URL/api/v1/store/objects/avatars/$USER_ID/me.png/sign-download \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"expires_in":600}'Realtime
| Route | Body | Notes |
|---|---|---|
POST /api/v1/auth/ws-ticket | {channel} | A one-time ticket for the WebSocket. Checked against your channel rules. |
wss://<project-ref>.sovaria.in/api/v1/rt/ws/{channel}?ticket=… | — | The connection. Tickets work once and expire within seconds. |
POST /api/v1/rt/stream/publish/{channel} | {payload: {...}} | Send to everyone on the channel. |
GET /api/v1/rt/stream/presence/{channel} | — | Who is connected. |
GET /api/v1/rt/stream/replay/{channel}?after=&limit= | — | Messages a client missed. |
Functions
POST /functions/v1/{name} calls a deployed function. The JSON body becomes event; the caller (user, anon or key) becomes context.caller. The response is whatever the function returns.
curl -s -X POST $URL/functions/v1/order-total \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"items":[{"sku":"BK-1","qty":2}]}'Errors
Errors use standard status codes. The body always has detail: a sentence, or an object with a code for errors you may want to handle in code. Some gateway errors also carry code and trace_id at the top level — include the trace_id (or the X-Request-ID response header) when you contact support.
| Status | Meaning | Typical cause |
|---|---|---|
400 | Bad request | Malformed JSON, a statement type that isn't allowed (SQL_ROUTE_VIOLATION), a database error such as a constraint violation. |
401 | Not authenticated | Missing, expired or revoked token or key. |
403 | Not allowed | A policy or row-level security refused it, a key used against another project, or a plan limit (see below). |
404 | Not found | Unknown bucket, object, document or function. |
409 | Conflict | The resource already exists or is not ready (for example a function that was saved but not deployed). |
413 | Too large | The body or file is over the bucket or plan limit. |
422 | Invalid input | A field failed validation (for example a weak password). |
429 | Rate limited | Too many requests — wait for Retry-After seconds. |
code | Status | What to do |
|---|---|---|
PLAN_LIMIT_REACHED, PLAN_QUOTA_EXHAUSTED, PLAN_FEATURE_REQUIRED | 403 | A plan limit — see Plans, usage and limits for the body. |
SIGNUP_DISABLED | 403 | Turn on Allow new users to sign up under Authentication → Settings, or create the user yourself. |
SQL_ROUTE_VIOLATION | 400 | Use only SELECT/INSERT/UPDATE/DELETE/EXPLAIN from app code. |
RATE_LIMIT_EXCEEDED, PROJECT_RATE_LIMIT_EXCEEDED | 429 | Back off and retry after Retry-After seconds. |
Rate limits
Requests are limited per project and per caller; the limits grow with your plan. Rate-limited responses are 429 with Retry-After, X-RateLimit-Limit and X-RateLimit-Reset headers (and X-RateLimit-Remaining where it applies). Retry with exponential backoff rather than in a tight loop.
Everything else
Organizations, members, policies, billing, function management and the rest are listed with their routes in the JavaScript SDK reference and Python SDK reference (each method shows the HTTP route it calls).