SDKs & integrations

REST API

Call OrbitNest over HTTPS with your project API keys. Base URL, headers, errors, and the app-facing database, storage, function, realtime and auth endpoints.

Everything the SDKs do is a plain HTTPS request with a JSON body. Use the REST API from languages without an SDK, from scripts, or when you need full control over headers.

Base URL

text
https://api.orbitnest.io/api

App-facing endpoints identify the project by its slug. Watch the path segment: some groups use project (singular) and others projects (plural).

AreaPath prefix
Database/api/project/:slug/database
Storage/api/project/:slug/storage
Public files/api/public/:slug/storage
Edge functions/api/projects/:slug/functions/v1
Realtime (REST)/api/projects/:slug/realtime
End-user auth/api/projects/:slug/auth
Realtime (WebSocket)wss://api.orbitnest.io/realtime/v1/ws?project=:slug (no /api)

Studio, the CLI and the MCP server use separate admin endpoints, such as /api/projects/:projectId/database/sql and /api/admin/projects/:projectId/.... Those take a project UUID and a Studio account session, not an API key, and aren't meant for apps.

Authentication

Each project has two API keys, shown on the project's API Keys page. Both are JWTs that carry the project slug and a role. See API keys & JWT.

KeyUse it inCan do
anonBrowsers and mobile appsReads, inserts and single-statement SELECT/INSERT SQL, subject to RLS. Updates and deletes need a user access token as well.
service_roleServers onlyEverything, including DDL, multi-statement SQL, bulk inserts and RLS management. Bypasses RLS on the SQL and query endpoints, table data reads, single-row writes and bulk inserts.

Keep the service-role key on the server

Never ship the service-role key in a browser or mobile app. Anyone who has it can read and change all of your data.

Send the key in one of these headers:

HeaderNotes
Authorization: Bearer <api key>Works on every app-facing endpoint.
apikey: <api key>Database and storage endpoints. On database endpoints it takes precedence over Authorization; on storage endpoints Authorization wins. Allowed by CORS.
x-api-key: <api key>Database, storage and function endpoints. Not allowed by CORS, so don't use it from browsers.
Terminal
curl "https://api.orbitnest.io/api/project/my-app/database/tables/list" \
  -H "Authorization: Bearer $ORBITNEST_ANON_KEY"

User requests

To act as a signed-in end user on the database endpoints, send the anon key in apikey and the user's access token in Authorization. The API verifies the token and runs table data reads, row inserts, updates and deletes, and bulk updates and deletes as the authenticated role, so RLS policies can use auth.uid(). The SQL and query endpoints ignore the user token.

Terminal
curl -X PUT "https://api.orbitnest.io/api/project/my-app/database/tables/posts/rows/$POST_ID" \
  -H "apikey: $ORBITNEST_ANON_KEY" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Edited title"}'
  • Only an access token counts. Refresh tokens, MFA challenge tokens, expired tokens and tokens from a signed-out session are not used. The request then continues with the anon key alone: reads run without a user, and updates and deletes return 401.
  • The user token is only applied together with the anon key. With the service-role key it is ignored.
  • A user access token on its own isn't an API key: database endpoints reject it. Storage endpoints accept a user access token directly in Authorization.

Get access tokens from the auth endpoints.

Errors

Every error response has the same shape:

JSON
{
  "statusCode": 400,
  "timestamp": "2026-10-10T12:00:00.000Z",
  "path": "/api/project/my-app/database/tables/posts/rows",
  "method": "POST",
  "error": "Bad Request",
  "message": "Constraint violation: null value in column \"title\" violates not-null constraint"
}

Some errors add a machine-readable code. message can be a string or, for request validation errors, an array of strings.

StatusTypical cause
400Invalid input, a SQL or constraint error (the PostgreSQL message is included), an invalid filter, or a row update or delete that matched no row or was denied by RLS
401Missing or invalid API key, a revoked key, a key for another project, or an operation that needs the service-role key or a user token
402A plan quota is used up. The body has code: "QUOTA_EXCEEDED", quota (database, storage, functions, invocations or projects), plan, limit and usage. See Database size quota.
403SQL rejected by the SQL checks, a protected auth_* table, a plan restriction (code: "PLAN_REQUIRED"), or permission denied by PostgreSQL
404Unknown project, table or function
429Rate limit exceeded on auth or analytics ingestion endpoints
5xxA server or infrastructure failure

Database

MethodPathKeyDescription
GET/api/project/:slug/database/tables/listAnyTable names, as an array of strings
GET/api/project/:slug/database/tables?table=:tableAnyColumn metadata and rls_enabled for each table. Omit table for all tables.
GET/api/project/:slug/database/tables/:table/dataAnyRows, with paging, sorting and filters
POST/api/project/:slug/database/tables/:table/rowsAnyInsert one row
PUT/api/project/:slug/database/tables/:table/rows/:idService role, or anon plus userUpdate the row whose id matches
DELETE/api/project/:slug/database/tables/:table/rows/:idService role, or anon plus userDelete the row whose id matches
POST/api/project/:slug/database/tables/:table/rows/bulkService roleInsert many rows
PUT/api/project/:slug/database/tables/:table/rows/bulkService role, or anon plus userUpdate many rows in one transaction
DELETE/api/project/:slug/database/tables/:table/rows/bulkService role, or anon plus userDelete many rows in one transaction
POST/api/project/:slug/database/sqlAny (anon: SELECT/INSERT only)Run SQL
POST/api/project/:slug/database/queryAnyStructured SELECT
POST/api/project/:slug/database/tables/:table/rls/enableService roleEnable RLS
POST/api/project/:slug/database/tables/:table/rls/disableService roleDisable RLS
GET/api/project/:slug/database/tables/:table/policiesService roleList policies
POST/api/project/:slug/database/tables/:table/policiesService roleCreate a policy. See Database → RLS.
DELETE/api/project/:slug/database/tables/:table/policies/:nameService roleDelete a policy

The table data endpoint and the row endpoints can't be used on the built-in auth_* tables. Use the auth endpoints instead.

Read rows

GET /api/project/:slug/database/tables/:table/data accepts these query parameters:

ParameterDefaultDescription
page11-based page number
limit50Rows per page, 1 to 100
from, to0-based, inclusive row range. When both are set they replace page and limit and return exactly rows from through to, up to 100 rows.
sortByColumn to sort by. Must exist on the table.
sortOrderASCASC or DESC, case-insensitive
filtercolumn:operator:value. Repeat the parameter to add filters. Filters are combined with AND.

Filter operators {#filters}

OperatorSQLExample
eq=filter=status:eq:active
neq (or ne)!=filter=status:neq:archived
gt>filter=views:gt:100
gte (or ge)>=filter=views:gte:100
lt<filter=price:lt:20
lte (or le)<=filter=price:lte:20
likeLIKEfilter=title:like:Intro%
ilikeILIKEfilter=title:ilike:%launch%
inIN (…), comma-separatedfilter=status:in:draft,review
isIS NULL, or = true / = falsefilter=deleted_at:is:null
  • The filter is split at its first two colons, so the value can contain :, as in filter=created_at:gte:2026-10-01T00:00:00Z.
  • These return 400 with a message instead of being ignored: a column that doesn't exist on the table, a filter without two colons, an unknown operator, and an is value other than null, true or false.
  • Values are sent as bound text parameters and cast by PostgreSQL. URL-encode them, for example % as %25.
  • This is not PostgREST syntax: ?status=eq.active and ?order= are ignored.
Terminal
curl -G "https://api.orbitnest.io/api/project/my-app/database/tables/posts/data" \
  -H "Authorization: Bearer $ORBITNEST_ANON_KEY" \
  --data-urlencode "filter=published:eq:true" \
  --data-urlencode "filter=title:ilike:%launch%" \
  --data-urlencode "sortBy=created_at" \
  --data-urlencode "sortOrder=DESC" \
  --data-urlencode "limit=20"
JSON
{
  "success": true,
  "table_name": "posts",
  "total_rows": 42,
  "returned_rows": 20,
  "page": 1,
  "limit": 20,
  "columns": [
    { "name": "id", "type": "uuid", "is_primary_key": true },
    { "name": "title", "type": "text", "is_primary_key": false }
  ],
  "data": [{ "id": "8f0c…", "title": "Launch week" }],
  "count": 42
}

Who the query runs as depends on the credentials:

  • Anon key alone: the project database role, with RLS and no user context.
  • Anon key plus a user access token: the authenticated role with the user's claims, so per-user SELECT policies apply. total_rows counts only the rows the user can see.
  • Service-role key: RLS is bypassed.

With the anon key, auth_* tables return 403.

Write rows

Insert a row by sending its columns as a JSON object. Objects and arrays are stored as JSON in json and jsonb columns.

Terminal
curl -X POST "https://api.orbitnest.io/api/project/my-app/database/tables/posts/rows" \
  -H "Authorization: Bearer $ORBITNEST_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Hello", "body": "World", "tags": ["intro"]}'
JSON
{
  "success": true,
  "rows_affected": 1,
  "message": "Row inserted successfully",
  "data": [{ "id": "8f0c…", "title": "Hello", "body": "World", "tags": ["intro"] }]
}
  • Insert returns 201. Update returns 200 with the same shape and the updated row in data.
  • Update and delete match on the table's id column. If no row matches, or RLS denies the change, the response is 400 with Row not found, or update denied by row-level security policy.
  • Delete returns { "success": true }.
  • With the anon key and no user token, inserts run without a user context.
  • With the anon key, with or without a user token, auth_* tables return 403.
  • When the workspace is over its database size quota, inserts and updates return 402. Deletes still work.

Bulk operations

All three bulk endpoints take a JSON array.

EndpointBodyResponse
POST …/rows/bulkRows to insert. Every object must have the same keys.{ success, rows_affected, message, data }
PUT …/rows/bulk[{ "where": { "id": "…" }, "data": { "status": "done" } }]. Each where matches columns by equality.{ success, rows_affected, message }
DELETE …/rows/bulk[{ "id": "…" }, { "status": "archived" }]. Each object is one equality condition.{ success, rows_affected, message }

Bulk updates and deletes run in one transaction: if one item fails, none are applied. Bulk insert with the service-role key bypasses RLS. Bulk updates and deletes run as the project database role, so RLS applies to them even with the service-role key; with the anon key plus a user token they run with the user's claims.

Run SQL

POST /api/project/:slug/database/sql takes sql and optional positional params for $1, $2 and so on.

Terminal
curl -X POST "https://api.orbitnest.io/api/project/my-app/database/sql" \
  -H "Authorization: Bearer $ORBITNEST_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT id, title FROM posts WHERE published = $1 LIMIT 10", "params": [true]}'
JSON
{
  "success": true,
  "execution_time_ms": 4,
  "rows_affected": 10,
  "columns": [{ "name": "id", "type": "uuid" }, { "name": "title", "type": "text" }],
  "data": [{ "id": "8f0c…", "title": "Launch week" }],
  "message": "Query executed successfully. 10 row(s) returned."
}
  • With the anon key, the SQL must be a single statement that starts with SELECT, SHOW, DESCRIBE or INSERT. Anything else, including a leading comment or a WITH clause, returns 400, and a second statement after ; returns 403. Anon SQL runs as the project database role, with RLS and no user context, even when a user token is sent.
  • With the service-role key, any SQL that passes the SQL checks runs, and RLS is bypassed. You can send several statements, including BEGIN; … COMMIT;, as one atomic script if you leave out params. A multi-statement script returns data: [], columns: [] and rows_affected: 0.
  • Statements that can grow the database return 402 while the workspace is over its database size quota.
  • The body can be up to 200 KB.

Structured query

POST /api/project/:slug/database/query builds a SELECT from JSON. where matches columns by equality and combines them with AND.

Terminal
curl -X POST "https://api.orbitnest.io/api/project/my-app/database/query" \
  -H "Authorization: Bearer $ORBITNEST_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "table": "posts",
    "select": ["id", "title"],
    "where": { "published": true },
    "orderBy": { "column": "created_at", "direction": "desc" },
    "limit": 10,
    "offset": 0
  }'

The response is { "rows": [...], "total": 10 }, where total is the number of rows returned. Table and column names must be plain identifiers, direction is asc or desc, and limit and offset are integers from 0 to 1,000,000.

Storage

Details, bucket settings and image transforms are on the Storage page.

MethodPathDescription
GET/api/project/:slug/storage/:bucket?prefix=&limit=List files (default limit 100)
POST/api/project/:slug/storage/:bucket/uploadUpload a file as multipart/form-data with fields file, path and optional upsert. Up to 50 MB.
GET/api/project/:slug/storage/:bucket/download/*pathDownload a file
GET/api/project/:slug/storage/buckets/:bucket/objects/*pathDownload a file (alias)
GET/api/project/:slug/storage/:bucket/public-url/*pathGet a file's public URL
DELETE/api/project/:slug/storage/:bucketDelete files. Body: { "paths": ["…"] }. Users can delete only their own files.
POST/api/project/:slug/storage/:bucket/createCreate a bucket (service role)
DELETE/api/project/:slug/storage/:bucket/deleteDelete a bucket (service role)
GET/api/public/:slug/storage/:bucket/*pathServe a file from a public bucket, no key needed. Accepts width, height, format, quality and fit for images.

Edge functions

Invoke a deployed function with POST, GET, PUT or DELETE. Send an API key as Authorization: Bearer or x-api-key. The function's response is returned as is. See Edge functions.

Terminal
curl -X POST "https://api.orbitnest.io/api/projects/my-app/functions/v1/hello" \
  -H "Authorization: Bearer $ORBITNEST_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Ada"}'

Realtime

The WebSocket protocol is described on the Realtime page. These REST endpoints take the key as Authorization: Bearer:

MethodPathKeyDescription
GET/api/projects/:slug/realtime/tablesAnyList realtime-enabled tables
POST/api/projects/:slug/realtime/tablesService roleEnable realtime. Body: { "table": "messages", "schema": "public" }
DELETE/api/projects/:slug/realtime/tables/:schema/:tableService roleDisable realtime
POST/api/projects/:slug/realtime/broadcastService roleBroadcast. Body: { "channel", "event", "payload" }

Auth

End-user sign-up and sign-in endpoints don't need an API key. Endpoints marked "User" need the user's access token as Authorization: Bearer; a refresh token or MFA challenge token is rejected there. Rate limits are per IP address: 120 requests per minute for refresh, verify, signout, signout-all, GET user and GET mfa/factors, and 10 per minute for the other endpoints. See Authentication for request bodies and flows.

MethodPathDescription
POST/api/projects/:slug/auth/signup-with-emailStart sign-up; emails a one-time code
POST/api/projects/:slug/auth/verify-signupFinish sign-up with the code
POST/api/projects/:slug/auth/signinSign in with { "email", "password" }
POST/api/projects/:slug/auth/signin-with-emailStart passwordless sign-in
POST/api/projects/:slug/auth/verify-signinFinish passwordless sign-in
POST/api/projects/:slug/auth/sms/send-otpSend an SMS code
POST/api/projects/:slug/auth/sms/verify-otpSign in with the SMS code
POST/api/projects/:slug/auth/verify-mfaFinish a sign-in that requires a TOTP code
POST/api/projects/:slug/auth/refreshExchange { "refresh_token" } for new tokens
POST/api/projects/:slug/auth/recoverEmail a password reset code
POST/api/projects/:slug/auth/reset-passwordSet a new password with the code
GET/api/projects/:slug/auth/userUser: get the profile
PUT/api/projects/:slug/auth/userUser: update the profile
DELETE/api/projects/:slug/auth/userUser: delete the account
POST/api/projects/:slug/auth/signoutUser: end the current session
POST/api/projects/:slug/auth/signout-allUser: end all sessions

A successful sign-in returns access_token, refresh_token, expires_in and user, or an MFA challenge when the user has TOTP enabled.

Limits and other behavior

  • Requests time out after 60 seconds. Edge function invocations get longer, based on the function's timeout.
  • SQL request bodies can be up to 200 KB, and file uploads up to 50 MB.
  • Table data pages hold at most 100 rows.
  • Responses carry Cache-Control: private, no-store unless an endpoint sets its own caching.
  • Browsers can call the API from the project's App URLs (Project Settings), *.orbitnest.io, localhost and 127.0.0.1. Add your web app's origin under App URLs.
  • GET /api/project/:slug/health reports the project's status without a key.
  • The hosted API doesn't serve Swagger UI or an OpenAPI document.