Authentication
Every Data API request carries a credential:
Authorization: Bearer <credential>
There is no anonymous access. A request without a valid credential is 401 AUTHENTICATION_REQUIRED. The only routes open without one are the public authentication routes, public workflow webhooks, and files marked PUBLIC.
There are two kinds of credential. Both identify a user, and that user's roles are read again on every request.
| Credential | Obtained by | Good for |
|---|---|---|
| Session | signing in with email and password, an invitation, or an auth provider | an application acting for a person who signed in |
Personal access token (tb_…) | created by its owner or an administrator | a program, device or agent: API tokens |
Signing in
curl -s -X POST https://ops.acme-field.example/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email": "[email protected]", "password": "…"}'
{ "data": { "token": "Yk2…", "user": { "id": "…", "email": "[email protected]", "name": "Lucía Paz" } } }
The user needs a role with API access. The authentication routes take their fields directly, not inside data.
A failed sign-in always answers 401 with "Invalid email or password", whatever the reason: unknown email, wrong password, locked or inactive account. The one exception is an unverified email (403 EMAIL_NOT_VERIFIED), which is only revealed after the right password.
A session lasts auth.session_ttl_hours (30 days by default) from when it was created. It isn't extended by use. End it with:
POST /api/auth/logout
Authorization: Bearer <session>
GET /api/auth/me returns the signed-in user.
The authentication routes
| Route | Does |
|---|---|
POST /api/auth/login | sign in with email and password |
POST /api/auth/logout | end the session |
GET /api/auth/me | the current user |
POST /api/auth/register | create an account, if auth.registration_enabled |
POST /api/auth/password/forgot | email a reset link (always answers success) |
POST /api/auth/password/reset | set a new password with a reset link's token |
POST /api/auth/password/change | change your password (session only) |
POST /api/auth/email/verify | verify an email with a link's token |
POST /api/auth/email/resend | send a new verification link |
POST /api/auth/invitations/accept | accept an invitation and set a password |
GET /api/auth/oauth/{provider} | sign in with Google, GitHub or OIDC: Auth providers |
Links in emails are built from the instance's public URL and point at the admin's own pages (/admin/auth/...), which call these routes. An application with its own pages can use the routes directly.
Sessions versus tokens
| Session | Personal access token | |
|---|---|---|
| Lifetime | auth.session_ttl_hours | 1-3650 days, chosen when created |
| Account routes (password, email, sessions) | yes | no |
| Recorded in the audit log as | the user | the token |
$USER.* in row filters | the user's row | the owner's row |
| Needs | API access | API access (plane=API) or Administrators (plane=ADMIN) |
Either way, nothing about permissions is stored in the credential. A role removed from the user, a deactivated account, or a revoked token takes effect on the next request.
Time zone
Dates are calculated in the caller's time zone: what "today" means for $PERIOD.today, and where date_trunc cuts days. The zone is the user's own preference, else the instance's (system.timezone), else the one the client sends:
X-TotalBase-Timezone: America/Argentina/Salta
CORS
A browser application on another origin can call the API once its origin is allowed in Settings › Network (security.cors_origins). CORS is off by default and never allows * by default.