Platform
API keys & JWT
The anon and service_role project keys, the project JWT secret, token lifetimes, key rotation, and how to keep credentials safe.
Every project has two API keys and a JWT secret. The anon key is public and goes in your app. The service_role key is a server-only master key. The JWT secret signs both keys and every token issued to your app's users.
The two keys
anon | service_role | |
|---|---|---|
| Where to use it | Browsers, mobile apps, any client | Your servers, scripts, CI, edge functions you trust |
| Row Level Security | Applies | Bypassed (full database access) |
| Safe to publish | Yes, if your tables have RLS policies | Never |
Both keys are JWTs. The SDKs read the project slug from the key's project_slug claim, so a key is all you need to create a client:
import { createClient } from '@orbitneststudio/js';
const orbit = createClient({ apiKey: import.meta.env.VITE_ORBITNEST_ANON_KEY });import { createClient } from '@orbitneststudio/node';
// Server only: this key bypasses RLS
const admin = createClient({ apiKey: process.env.ORBITNEST_SERVICE_KEY! });What each key can do
On the client database endpoints (/api/project/<slug>/database/...):
| Operation | anon key | anon key + user access token | service_role key |
|---|---|---|---|
| Read rows (table data endpoint) | Yes | Yes, with the user's claims | Yes, bypassing RLS |
| Insert a row | Yes | Yes, with the user's claims | Yes |
Run SQL (POST .../sql) | One SELECT or INSERT statement, as the anon role | Same, as authenticated with the user's claims | Any statements that pass the SQL checks, including multi-statement scripts |
| Update or delete rows (single or bulk) | No (401) | Yes, with the user's claims | Yes |
| Bulk insert | No | No | Yes |
| Enable or disable RLS, create policies | No | No | Yes |
Read or write auth_* tables | No (403) | No (403) | Through SQL and the single-row endpoints, but SQL can't drop, truncate, alter or DELETE FROM them |
See Database for how RLS policies are evaluated on each path. Other services, such as storage, functions and the email API, have their own rules on their pages. Sending email through the Email API requires the service_role key.
Find your keys
Open your project in Studio and go to API Keys:
- API Keys shows the
anonandservice_rolekeys (reveal and copy) and the Project URL,https://api.orbitnest.io/api/project/<slug>. - JWT Settings shows the JWT secret, the signing algorithm (HS256), and the token lifetimes for your app's users.
Every workspace member, admin or developer, can view and rotate keys. See Team & roles. The MCP server can also read them with orbitnest_get_api_keys.
Send a key with a request
Send the key as a bearer token:
curl "https://api.orbitnest.io/api/project/my-app/database/tables/posts/data?limit=10" \
-H "Authorization: Bearer $ORBITNEST_ANON_KEY"To act as a signed-in user, put the anon key in the apikey header (x-api-key also works) and the user's access token in Authorization. The database endpoints then apply the user's claims, so RLS policies can use auth.uid(). Only access tokens count: refresh tokens and MFA challenge tokens are never accepted as access tokens. See Authentication.
curl -X POST https://api.orbitnest.io/api/project/my-app/database/tables/notes/rows \
-H "apikey: $ORBITNEST_ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"My note"}'Key claims
A decoded key looks like this:
{
"role": "anon",
"project_slug": "my-app",
"permissions": { "read": true, "insert": true, "update": false, "delete": false },
"iat": 1790000000
}roleisanonorservice_role. This, together with your RLS policies, decides what a request may do.project_slugmust match the project in the URL, or the request is rejected.permissionsis descriptive only. No endpoint enforces it.- Keys created at project setup and keys re-signed by a JWT secret rotation never expire. Keys made with Regenerate Keys carry an
expone year out.
JWT settings
JWT secret
The project's JWT secret (HS256) signs the anon and service_role keys and your users' access and refresh tokens. The secret shown under JWT Settings is the exact signing key, so it verifies those tokens. MFA challenge tokens are signed with a separate key derived from the secret and only work at verify-mfa. Anyone who has the secret can mint a service_role key, so treat it like one.
Access tokens carry type: "access" and refresh tokens type: "refresh". Every OrbitNest service that accepts a user token (database, storage, realtime, edge functions and analytics ingestion) refuses refresh tokens and MFA challenge tokens.
Token lifetimes
| Setting | Default | Applies to |
|---|---|---|
| Access token expiry | 900 seconds (15 minutes) | access_token issued to your app's users |
| Refresh token expiry | 604,800 seconds (7 days) | refresh_token issued to your app's users |
Change them under API Keys → JWT Settings → Token Expiry. Studio requires at least 60 seconds for each. New values apply to tokens issued after you save. Existing tokens keep their original expiry.
Verify user tokens on your server
Verify an access token locally with the JWT secret, checking the audience (the project slug) and issuer. Accept only access tokens: reject anything whose type is set to a value other than access.
import jwt from 'jsonwebtoken';
export function verifyUser(token: string) {
const claims = jwt.verify(token, process.env.ORBITNEST_JWT_SECRET!, {
algorithms: ['HS256'],
audience: 'my-app',
issuer: 'orbitnest:project:my-app',
}) as jwt.JwtPayload;
if (claims.type !== undefined && claims.type !== 'access') {
throw new Error('Not an access token');
}
return claims; // sub, email, role, projectId, type, jti, iat, exp
}Local verification checks only the signature and expiry. It doesn't know whether the user has since signed out; GET /api/projects/<slug>/auth/verify does.
If you don't want to hold the secret, call GET /api/projects/<slug>/auth/verify with the token instead. The full claim list is in Authentication.
Rotate credentials
Regenerate the keys
API Keys → Regenerate Keys asks for a name and replaces both keys with a new pair that expires in one year. Studio shows the new keys right away.
The previous anon and service_role keys stop working immediately on every endpoint that accepts project keys (database, storage, the Email API, realtime over WebSocket and REST, edge function invocation and analytics ingestion), which accept only the project's current keys. The ORBITNEST_ANON_KEY environment variable of your edge functions is updated to the new anon key automatically. Your users' sessions are not affected.
Rotate the JWT secret
API Keys → JWT Settings → Regenerate Secret generates a new random secret and re-signs both keys (without an expiry). The change takes effect immediately:
- Every previously issued
anonandservice_rolekey stops working. - Every access and refresh token issued to your users stops working, so all users are signed out.
- Anything that verifies tokens with the old secret must be updated.
After rotating or regenerating, copy the new keys into every app, server and CI secret that uses them. Edge functions receive the current anon key as ORBITNEST_ANON_KEY, which OrbitNest updates for you. If you added ORBITNEST_SERVICE_ROLE_KEY as a function environment variable, update it yourself.
Keep keys safe
- Never ship the
service_rolekey or the JWT secret in a browser bundle, mobile app or public repository. Anyone who has either one has full access to the project's data. - Treat the
anonkey as public. Protect data with RLS policies, not by hiding the key. See Database. - Keep server keys in environment variables or your platform's secret store, never in source code.
- Give functions the least privilege. Edge functions get
ORBITNEST_ANON_KEYautomatically. The service key is only available to a function if you addORBITNEST_SERVICE_ROLE_KEYas an environment variable yourself. See Edge functions. - If a key leaks, regenerate the keys. The old keys stop working everywhere immediately and your users stay signed in. Rotate the JWT secret instead only if the secret itself leaked; that signs out all of your users.
Other credentials
| Credential | Used for | Where to manage it |
|---|---|---|
| Analytics ingest token | Sending analytics events (x-analytics-token header) | Analytics → Tokens. Shown once, can be deactivated or revoked. See Analytics. |
| Webhook signing secret | Verifying webhook deliveries | Shown once when you create a webhook. See Webhooks. |
| Studio sign-in (CLI, MCP) | Managing projects from a terminal or AI agent | orbitnest login or the MCP sign-in tools. They use your Studio account, not project keys, and store credentials in ~/.orbitnest/credentials.json. See CLI and MCP server. |