Developers/API Reference/Authentication
Authentication
How to authenticate against the Supero API with a login-issued JWT or a long-lived API key, and how to manage token lifecycle.
Overview
Every Supero API request (other than login and a handful of public endpoints) must be authenticated. There are two supported methods, and you can mix them depending on the caller:
- •Bearer JWT — obtained from POST /api/v1/auth/login. Best for interactive users and short-lived sessions (browser apps, CLI tools, admin scripts).
- •API key (X-API-Key header) — a long-lived opaque secret starting with ak_, minted per domain. Best for servers, backend jobs, and service-to-service calls where there's no human logging in.
All requests go to the base URL https://api.supero.dev. Scope (which domain/project/tenant a request operates on) comes from the JWT claims or the API key itself, optionally narrowed with project and tenant query parameters — you never need to pass separate project or tenant ID headers.
| Method | Header | Typical caller |
|---|---|---|
| JWT (access token) | Authorization: Bearer <access_token> | Logged-in users, browser/CLI sessions |
| API key | X-API-Key: ak_... | Backend services, scripts, CI, server-to-server |
⚠️ Don't mix the two
An API key is an opaque secret — send it in the X-API-Key header, never as a Bearer token. A JWT access token goes in Authorization: Bearer, never in X-API-Key.
Logging in
POST /api/v1/auth/login exchanges an email and password for a session (an access token plus a refresh token). Domain is optional — if you omit it, Supero resolves it from the email's registered domain automatically. You can optionally narrow the login to a specific project and tenant.
bash
curl -X POST https://api.supero.dev/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "your-secure-password",
"domain": "acme-corp",
"project": "default-project",
"tenant": "northwind"
}'| Field | Required | Description |
|---|---|---|
| Yes | The account email (username is also accepted). | |
| password | Yes | The account password. |
| domain | No | Domain name to authenticate against. If omitted, Supero looks up the account's registered domain. |
| project | No | Project name to scope the session to. Defaults to the default project. |
| tenant | No | Tenant name to scope the session to. Defaults to the default tenant. |
ℹ️ domain_name is accepted as an alias here
Older examples and some client code send domain_name on login instead of domain. Both work on this endpoint and mean the same thing; domain takes precedence if you send both. domain is the spelling used throughout these docs. Note that this is specific to login — user registration takes domain_name, not domain.
Response
A successful login returns a top-level auth block, a user block, a context block, and a policy block. The access token lives at auth.access_token — not at the top level.
json
{
"auth": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 28800,
"session_id": "a1b2c3d4-..."
},
"user": {
"user_uuid": "5e8f...",
"email": "[email protected]",
"role": "developer",
"base_role": "developer",
"domain_name": "acme-corp",
"project": "default-project",
"tenant_name": "northwind",
"permissions": ["crud:create", "crud:read", "..."]
},
"context": {
"domain": "acme-corp",
"domain_uuid": "...",
"project": "default-project",
"project_uuid": "...",
"tenant": "northwind",
"tenant_uuid": "...",
"schema_namespace": "acme_corp",
"surface": "admin"
},
"policy": { "role": "developer", "entities": { "...": "..." } }
}ℹ️ API key for admin roles
For domain_admin, project_admin, platform_admin, and platform_user, the auth block also includes api_key — the tenant's API key — so admin-level sessions can bootstrap service credentials without a separate mint call.
Using the access token
Send the access token on every subsequent request as a standard Bearer token.
bash
curl https://api.supero.dev/api/v1/crud/acme-corp/project \
-H "Authorization: Bearer $ACCESS_TOKEN"The Python SDK handles this for you automatically once you're logged in.
python
from supero import login
org = login(
domain_name="acme-corp",
email="[email protected]",
password="your-secure-password",
)
# org.crud.* calls now carry the session's access token automatically
products = org.crud.list("product", limit=20)Token lifecycle: refresh and logout
| Token | Lifetime |
|---|---|
| access_token | 8 hours (expires_in: 28800 seconds) |
| refresh_token | 7 days |
When the access token nears expiry, exchange the refresh token for a new access token instead of logging in again. Send the refresh token as the Bearer credential on the refresh call.
bash
curl -X POST https://api.supero.dev/api/v1/auth/refresh \
-H "Authorization: Bearer $REFRESH_TOKEN"json
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 28800
}To end a session explicitly (invalidate the access token and its session), call logout with the current access token.
bash
curl -X POST https://api.supero.dev/api/v1/auth/logout \
-H "Authorization: Bearer $ACCESS_TOKEN"json
{
"message": "Logged out successfully",
"session_id": "a1b2c3d4-..."
}💡 Check who you are
GET /api/v1/auth/me returns the current user's profile for a given access token, and GET /api/v1/auth/session returns the current session's details — handy for verifying a token is still valid before a long-running job starts.
Access token claims
The access token is a standard JWT. You can decode the payload client-side to read context without an extra round trip, but always let the server validate the signature — never trust an unverified decode for authorization decisions.
| Claim | Description |
|---|---|
| user_uuid | Unique identifier of the authenticated user. |
| username | The user's username/email. |
| role | The user's role (see Roles below). |
| session_id | Identifier of the session this token belongs to; used for refresh and logout. |
| token_type | "access" or "refresh". |
| permissions | List of action-string capabilities derived from the role. |
| domain_uuid / domain_name | The domain this token is scoped to. |
| project_uuid / project_name | The project this token is scoped to, if any. |
| tenant_uuid / tenant_name | The tenant this token is scoped to, if any. |
| schema_namespace | The schema namespace resolved for this user's domain. |
| iat / nbf / exp | Standard issued-at, not-before, and expiration timestamps (Unix epoch seconds). |
API keys
API keys are for callers that aren't a logged-in human — backend services, scheduled jobs, CI pipelines. A key is an opaque secret string starting with ak_; treat it as a bearer credential and don't attempt to parse structure out of it.
Mint a key
🚨 Minting against default-tenant gives you a project-wide key
Read this before you copy the call below. A key's reach comes from the tenant it is issued against, not from a setting. A key minted against any project's default-tenant resolves to project_admin and can read every tenant in that project — which is what you want for a build or admin credential, and exactly what you do not want for anything your application runs with. For a credential confined to one customer's data, mint it against a named tenant you created. See Building Multi-Tenant Apps.
bash
curl -X POST https://api.supero.dev/api/v1/domains/acme-corp/api-key \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_name": "default-project",
"tenant_name": "default-tenant",
"environment": "prod",
"description": "billing worker"
}'json
{
"success": true,
"message": "API key generated",
"api_key": "ak_9f1c...",
"key_uuid": "d4e5f6...",
"project_name": "default-project",
"tenant_name": "default-tenant",
"environment": "prod",
"warning": "Save this API key securely - it will not be shown again"
}🚨 Shown once
The plaintext key is only ever returned in the mint (and rotate) response. Store it in a secrets manager immediately — Supero cannot show it to you again.
Use a key
bash
curl https://api.supero.dev/api/v1/crud/acme-corp/order \
-H "X-API-Key: ak_9f1c..."python
from supero import connect
org = connect(domain_name="acme-corp", api_key="ak_9f1c...")
orders = org.crud.list("order", limit=50)Rotate a key
Rotation issues a new secret for the same key_uuid — every reference to that key elsewhere (service configs, connectors) stays valid; you only need to swap the secret value.
bash
curl -X POST https://api.supero.dev/api/v1/auth/api-key/d4e5f6.../rotate \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rotated_by": "[email protected]"}'You can also list, fetch, and delete API keys through the standard CRUD surface: GET/DELETE /api/v1/crud/{domain}/api_key and /api/v1/crud/{domain}/api_key/{uuid}.
Roles
Every user and API key carries exactly one role, which determines both what they can do and the scope of data they can see. Role checks apply on top of — not instead of — the domain/project/tenant scope already carried by the token.
| Role | Scope | Typical use |
|---|---|---|
| domain_admin | Domain | Owns the domain; full access across all projects and tenants. |
| domain_user | Domain | Views all projects/tenants in the domain, full CRUD, no admin actions. |
| project_admin | Project | Manages all tenants within a project; defines schemas. |
| project_user | Project | Views all tenants in a project, full CRUD, no admin actions. |
| developer | Project | Technical role for schema, SDK and CRUD work. Scoped to its own tenant for record access — it is not cross-tenant. |
| tenant_admin | Tenant | Manages a specific tenant; uses schemas (doesn't define them). |
| tenant_user | Tenant | Regular tenant member with full CRUD, no admin actions. |
| viewer | Tenant | Read-only access within a tenant. |
platform_admin and platform_user are reserved for Supero-operated infrastructure and are not assignable to customer accounts.
Security best practices
- •Always call the API over HTTPS; never send an access token or API key over plaintext HTTP.
- •Store API keys and refresh tokens in a secrets manager or environment variables — never commit them to source control.
- •Prefer the shortest-lived credential that works: a per-request access token for interactive flows, a scoped API key for long-running services.
- •Assign the least-privileged role that lets the caller do its job — use viewer for read-only integrations rather than a broader role.
- •Rotate API keys periodically and immediately after any suspected exposure; rotation preserves the key_uuid so you only need to update the secret value.
- •Call logout to invalidate a session explicitly when you're done with it, rather than letting the access token merely expire.
Next steps
- •CRUD Endpoints — for the create/read/update/delete and query endpoints your tokens authorize.
- •Roles & Permissions Reference — for a full breakdown of what each role can do.
- •Python SDK — for installing supero and wiring login/connect into an application.
- •Access Control (RBAC) — for granting a caller only the permissions its role needs.
On this page