Every request to the Gurb API is authenticated with a JWT bearer token obtained by logging in. There is currently no API-key or personal-access-token system — see Machine access below.
POST /api/auth/login{
"email": "admin@example.com",
"password": "your-password"
}The response contains a signed JWT. Send it on every subsequent request:
Authorization: Bearer <token>
The token payload carries userId, email, role, tenantId, and tenantSchema —
so the token itself identifies which tenant you are acting in.
Lifetime: 7 days by default (JWT_EXPIRES_IN).
Two endpoints exist:
| Endpoint | Auth required | Use |
|---|---|---|
POST /api/auth/refresh | Bearer token | Rotate while still valid |
POST /api/auth/refresh-token | none — send the refresh token in the body | Rotate after the access token expired |
Most endpoints are tenant-scoped. The API resolves which community you mean in this priority order:
- Custom domain — the request's hostname (highest priority)
communityIdin the URL — path, query, body, or theX-Community-IdheaderX-Tenant-Idheader- The JWT's tenant claim (lowest priority)
In practice, call tenant-scoped endpoints with the community in the path and send the tenant header as well:
Authorization: Bearer <token>
X-Tenant-Id: <tenantId>
Debugging tip. A
400 Tenant context requiredor401 No authentication token provideddoes not reliably mean your credentials are wrong. Six routers are mounted at the API root, so a URL that matches no route falls into their middleware before Express can conclude "no such route" — a typo'd path answers 400/401 instead of 404. Confirm the route exists before you debug your token.
| Status | Code | Meaning |
|---|---|---|
401 | UNAUTHORIZED | Missing, malformed, or expired token |
403 | FORBIDDEN | Authenticated, but lacking the required permission |
403 | FEATURE_NOT_AVAILABLE | The community's plan does not include this feature |
400 | TENANT_CONTEXT_REQUIRED | No community could be resolved from the request |
FEATURE_NOT_AVAILABLE includes a featureKey field naming the feature that is off.
A community admin cannot currently issue an API token. Verified against the codebase:
- there is no
ApiKey,ApiToken, orPersonalAccessTokenmodel in the Prisma schema - no API-key issuing or verification code exists anywhere in
src/ - every authenticated route resolves identity from a login-issued JWT
So server-to-server integrations today have only one option: log in as a real user account and refresh the JWT before it expires. That works, but it carries the usual costs of password-based machine access — the integration holds a human's credentials, the token inherits that human's full permissions, it cannot be scoped, and revoking it means changing the password or disabling the account.
If first-class machine access is needed, it requires building:
- a scoped, revocable credential (
ApiKey) owned by a community rather than a person - a hashed-at-rest secret shown exactly once at creation
- per-key permission scopes reusing the existing permission system
- an admin UI plus an audit trail of issue/revoke events
That is a design decision, not a documentation gap — this page records the current state accurately rather than describing something that does not exist.