Auth API¶
Base path: /api/v1/auth
Handles all user identity concerns: self-service registration, password and Google OAuth login, password reset, team invitations, short-lived access token refresh, logout with full token blocklisting, profile management, and organisation member administration.
See API Reference for auth, errors, and pagination.
POST /register¶
Register the first user for a brand-new organisation. Creates the organisation on the trial plan (14-day window) and the user as its owner with the admin role. Issues access + refresh tokens immediately — no email verification required.
Auth: None · Any plan · Rate limit: 10/min
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
email |
string | Yes | Valid email |
password |
string | Yes | 8–128 chars, at least one uppercase letter, at least one digit |
name |
string | Yes | 1–255 chars, non-blank |
org_name |
string | Yes | 2–255 chars, non-blank |
department |
string | No | Max 255 chars |
curl -s -c cookies.txt -X POST https://api.knora.io/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"password": "Secure123",
"name": "Alice Smith",
"org_name": "Acme Corp"
}'
201 Created
{
"access_token": "<jwt>",
"token_type": "bearer",
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "alice@example.com",
"name": "Alice Smith",
"role": "admin",
"org_id": "f0e1d2c3-b4a5-6789-abcd-ef0987654321",
"is_org_owner": true,
"department": "Engineering",
"is_active": true,
"created_at": "2026-05-31T10:00:00Z",
"updated_at": "2026-05-31T10:00:00Z",
"org": {
"id": "f0e1d2c3-b4a5-6789-abcd-ef0987654321",
"name": "Acme Corp",
"slug": "acme-corp",
"plan": "trial",
"trial_ends_at": "2026-06-14T10:00:00Z",
"is_active": true
}
},
"org": {
"id": "f0e1d2c3-b4a5-6789-abcd-ef0987654321",
"name": "Acme Corp",
"slug": "acme-corp",
"plan": "trial",
"trial_ends_at": "2026-06-14T10:00:00Z",
"is_active": true
}
}
Sets Set-Cookie: knora_refresh_token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/.
Errors
| Status | Code | Cause |
|---|---|---|
409 |
REGISTRATION_FAILED |
Email address is already registered |
422 |
VALIDATION_ERROR |
Missing or invalid fields |
429 |
RATE_LIMITED |
Too many requests from this IP |
POST /login¶
Authenticate with email and password credentials. Issues access + refresh tokens.
Auth: None · Any plan · Rate limit: 10/min
Body
| Field | Type | Required |
|---|---|---|
email |
string | Yes |
password |
string | Yes |
curl -s -c cookies.txt -X POST https://api.knora.io/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "alice@example.com", "password": "Secure123"}'
200 OK — same shape as POST /register.
Sets Set-Cookie: knora_refresh_token=....
Errors
| Status | Code | Cause |
|---|---|---|
401 |
INVALID_CREDENTIALS |
Wrong email/password, or account is inactive |
422 |
VALIDATION_ERROR |
Missing or invalid fields |
429 |
RATE_LIMITED |
Too many requests from this IP |
POST /google¶
Google OAuth login and signup. The frontend completes the Google Sign-In flow to obtain a Google ID token, then passes it here. The backend verifies it with Google's servers.
Three flows: (1) existing Google-linked account — log in; (2) email already registered but no Google link — link the Google identity and log in; (3) brand-new user — create a new organisation and account (org_name required).
Auth: None · Any plan · Rate limit: 10/min
Body
| Field | Type | Required | Description |
|---|---|---|---|
id_token |
string | Yes | Google ID token from the frontend OAuth flow |
org_name |
string | Conditional | Required only when creating a brand-new account. Max 255 chars. |
curl -s -c cookies.txt -X POST https://api.knora.io/api/v1/auth/google \
-H "Content-Type: application/json" \
-d '{"id_token": "<google-id-token>"}'
200 OK — same shape as POST /register.
Sets Set-Cookie: knora_refresh_token=....
Errors
| Status | Code | Cause |
|---|---|---|
400 |
GOOGLE_OAUTH_DISABLED |
GOOGLE_CLIENT_ID is not configured on the server |
400 |
ORG_NAME_REQUIRED |
New Google user but org_name was not provided |
401 |
INVALID_GOOGLE_TOKEN |
Token failed Google verification |
401 |
EMAIL_NOT_VERIFIED |
The Google account's email is not verified |
401 |
ACCOUNT_DEACTIVATED |
The matched Knora account has been deactivated |
401 |
GOOGLE_ACCOUNT_CONFLICT |
Email already linked to a different Google identity |
422 |
VALIDATION_ERROR |
Missing or invalid fields |
429 |
RATE_LIMITED |
Too many requests from this IP |
POST /forgot-password¶
Initiate a password reset for the given email address. Generates a signed reset token (1-hour TTL), stores it in Redis, and dispatches a reset email. Always returns 200 regardless of whether the account exists — prevents user enumeration.
Auth: None · Any plan · Rate limit: 5/min
Body
| Field | Type | Required |
|---|---|---|
email |
string | Yes |
curl -s -X POST https://api.knora.io/api/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email": "alice@example.com"}'
200 OK
Errors
| Status | Code | Cause |
|---|---|---|
422 |
VALIDATION_ERROR |
Invalid email format |
429 |
RATE_LIMITED |
Too many requests from this IP |
POST /reset-password¶
Complete a password reset using the token from the reset email.
Auth: None · Any plan · Rate limit: 5/min
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
token |
string | Yes | Signed reset token from the email link |
password |
string | Yes | 8–128 chars, at least one uppercase letter, at least one digit |
curl -s -X POST https://api.knora.io/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{"token": "<reset-token>", "password": "NewSecure456"}'
200 OK
Errors
| Status | Code | Cause |
|---|---|---|
400 |
INVALID_RESET_TOKEN |
Token is invalid, expired, or already used |
422 |
VALIDATION_ERROR |
Missing or invalid fields |
429 |
RATE_LIMITED |
Too many requests from this IP |
POST /accept-invite¶
Accept a team invitation by setting a password and activating the account. The invite token arrives via the email link sent by POST /invite and expires after 48 hours.
Auth: None · Any plan · Rate limit: 10/min
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
token |
string | Yes | Signed invite token from the email link |
password |
string | Yes | 8–128 chars, at least one uppercase letter, at least one digit |
curl -s -c cookies.txt -X POST https://api.knora.io/api/v1/auth/accept-invite \
-H "Content-Type: application/json" \
-d '{"token": "<invite-token>", "password": "NewSecure456"}'
200 OK — same shape as POST /register. Issues access + refresh tokens immediately.
Sets Set-Cookie: knora_refresh_token=....
Errors
| Status | Code | Cause |
|---|---|---|
400 |
INVALID_INVITE_TOKEN |
Token is invalid, expired, or already used |
400 |
INVITE_ALREADY_ACCEPTED |
The invitation was previously accepted |
422 |
VALIDATION_ERROR |
Missing or invalid fields |
429 |
RATE_LIMITED |
Too many requests from this IP |
POST /refresh¶
Issue a fresh access token using the knora_refresh_token httpOnly cookie. The browser sends the cookie automatically when credentials: 'include' is set. No body required.
Auth: Valid knora_refresh_token cookie (refresh JWT) · Rate limit: 20/min
200 OK
Errors
| Status | Code | Cause |
|---|---|---|
401 |
AUTHENTICATION_FAILED |
Cookie absent, expired, or token blocklisted |
401 |
ACCOUNT_DEACTIVATED |
User account was deactivated since token was issued |
401 |
ORG_DEACTIVATED |
Organisation was deactivated since token was issued |
401 |
ORG_NOT_FOUND |
Organisation was deleted since token was issued |
429 |
RATE_LIMITED |
Too many requests from this IP |
POST /logout¶
Logout the current user. Blocklists both the access token JTI and the refresh token JTI in Redis so neither can be reused before natural expiry. Clears the refresh cookie.
Auth: JWT · Any plan
curl -s -b cookies.txt -X POST https://api.knora.io/api/v1/auth/logout \
-H "Authorization: Bearer <access_token>"
200 OK
Clears Set-Cookie: knora_refresh_token=; Max-Age=0; ....
Errors
| Status | Code | Cause |
|---|---|---|
401 |
AUTHENTICATION_FAILED |
Access token missing, expired, or already blocklisted |
GET /me¶
Return the authenticated user's profile.
Auth: JWT · Any plan
200 OK
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "alice@example.com",
"name": "Alice Smith",
"role": "admin",
"org_id": "f0e1d2c3-b4a5-6789-abcd-ef0987654321",
"is_org_owner": true,
"department": "Engineering",
"is_active": true,
"created_at": "2026-05-31T10:00:00Z",
"updated_at": "2026-05-31T10:00:00Z",
"org": {
"id": "f0e1d2c3-b4a5-6789-abcd-ef0987654321",
"name": "Acme Corp",
"slug": "acme-corp",
"plan": "trial",
"trial_ends_at": "2026-06-14T10:00:00Z",
"is_active": true
}
}
Errors
| Status | Code | Cause |
|---|---|---|
401 |
AUTHENTICATION_FAILED |
Token missing or invalid |
401 |
ACCOUNT_DEACTIVATED |
Account was deactivated after token was issued |
404 |
USER_NOT_FOUND |
User record deleted after token was issued |
PUT /me¶
Update the authenticated user's profile. Only fields included in the body are modified. Send "department": null to clear it; omit to leave it unchanged. name cannot be cleared.
Auth: JWT · Any plan
Body (all fields optional)
| Field | Type | Constraints |
|---|---|---|
name |
string | 1–255 chars, non-blank |
department |
string | null | Max 255 chars; null clears the field |
curl -s -X PUT https://api.knora.io/api/v1/auth/me \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"name": "Alice J. Smith"}'
200 OK — updated user object, same shape as GET /me.
Errors
| Status | Code | Cause |
|---|---|---|
401 |
AUTHENTICATION_FAILED |
Token missing or invalid |
401 |
ACCOUNT_DEACTIVATED |
Account was deactivated after token was issued |
422 |
VALIDATION_ERROR |
Field value violates constraints |
POST /invite¶
Invite a new user into the caller's organisation. Creates an inactive account, generates a signed invite token (48-hour TTL) stored in Redis, and dispatches an invite email. The invitee completes signup via POST /accept-invite.
Auth: JWT · admin or manager · Any plan · Rate limit: 20/min
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
email |
string | Yes | Valid email |
name |
string | Yes | 1–255 chars, non-blank |
role |
string | No | admin, manager, or member (default: member) |
department |
string | No | Max 255 chars |
curl -s -X POST https://api.knora.io/api/v1/auth/invite \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"email": "bob@example.com", "name": "Bob Jones", "role": "member"}'
201 Created — UserResponse for the newly created (inactive) user. No tokens.
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"email": "bob@example.com",
"name": "Bob Jones",
"role": "member",
"org_id": "f0e1d2c3-b4a5-6789-abcd-ef0987654321",
"is_org_owner": false,
"department": null,
"is_active": false,
"created_at": "2026-06-01T09:00:00Z",
"updated_at": "2026-06-01T09:00:00Z",
"org": null
}
Errors
| Status | Code | Cause |
|---|---|---|
400 |
USER_LIMIT_EXCEEDED |
Organisation has reached its plan's user seat limit |
401 |
AUTHENTICATION_FAILED |
Token missing or invalid |
403 |
INSUFFICIENT_ROLE |
Caller does not have admin or manager role |
409 |
EMAIL_ALREADY_EXISTS |
Email is already registered |
422 |
VALIDATION_ERROR |
Invalid field value (e.g. unknown role) |
429 |
RATE_LIMITED |
Too many requests |
GET /members¶
List all active members of the caller's organisation.
Auth: JWT · Any plan
200 OK
{
"members": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "alice@example.com",
"name": "Alice Smith",
"role": "admin",
"org_id": "f0e1d2c3-b4a5-6789-abcd-ef0987654321",
"is_org_owner": true,
"department": "Engineering",
"is_active": true,
"created_at": "2026-05-31T10:00:00Z",
"updated_at": "2026-05-31T10:00:00Z",
"org": null
}
],
"total": 1
}
Returns active members only. No pagination — all members returned in a single response.
Errors
| Status | Code | Cause |
|---|---|---|
401 |
AUTHENTICATION_FAILED |
Token missing or invalid |
401 |
ACCOUNT_DEACTIVATED |
Caller's account was deactivated |
PATCH /members/:user_id¶
Change the role of a member within the caller's organisation. The caller cannot change their own role. The org owner's role cannot be changed.
Auth: JWT · admin · Any plan
Path parameter: user_id — UUID of the target member.
Body
| Field | Type | Required | Constraints |
|---|---|---|---|
role |
string | Yes | admin, manager, or member |
curl -s -X PATCH https://api.knora.io/api/v1/auth/members/b2c3d4e5-f6a7-8901-bcde-f12345678901 \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"role": "manager"}'
200 OK — updated UserResponse for the target member.
Errors
| Status | Code | Cause |
|---|---|---|
400 |
CANNOT_CHANGE_OWN_ROLE |
Caller tried to change their own role |
400 |
CANNOT_CHANGE_OWNER_ROLE |
Target is the organisation owner |
401 |
AUTHENTICATION_FAILED |
Token missing or invalid |
403 |
INSUFFICIENT_ROLE |
Caller does not have admin role |
404 |
USER_NOT_FOUND |
Target user not found or belongs to a different org |
422 |
VALIDATION_ERROR |
role value is not one of the allowed values |
DELETE /members/:user_id¶
Deactivate (soft-delete) a member from the caller's organisation. The user record is retained but marked is_active = false, excluding them from all member listings and blocking authentication. The caller cannot remove themselves.
Auth: JWT · admin · Any plan
Path parameter: user_id — UUID of the member to remove.
curl -s -X DELETE https://api.knora.io/api/v1/auth/members/b2c3d4e5-f6a7-8901-bcde-f12345678901 \
-H "Authorization: Bearer <access_token>"
200 OK
Errors
| Status | Code | Cause |
|---|---|---|
400 |
CANNOT_REMOVE_SELF |
Caller attempted to remove themselves |
401 |
AUTHENTICATION_FAILED |
Token missing or invalid |
403 |
INSUFFICIENT_ROLE |
Caller does not have admin role |
404 |
USER_NOT_FOUND |
Target user not found or belongs to a different org |