Authentication
Admin authentication endpoints for accessing the management API.
Admin Login
Log in as an admin user to get an access token.
Endpoint
POST /api/auth/admin/loginRequest Body
{
username?: string // Optional: Username (e.g. "ziri" or another admin user)
email?: string // Optional: Email (e.g. "ziri@ziri.local")
password: string // Required: Password (root key for ziri, or dashboard user password)
deviceId?: string // Optional: Device identifier
}You can use either username or email. Both work.
Example Request
# Login as built-in root admin using the root key
curl -X POST http://localhost:3100/api/auth/admin/login \
-H "Content-Type: application/json" \
-d '{
"username": "ziri",
"password": "your-root-key-here"
}'Success Response
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600,
"tokenType": "Bearer",
"user": {
"userId": "ziri",
"email": "ziri@ziri.local",
"role": "admin",
"name": "Administrator"
}
}Using the Token
Include the token in the Authorization header:
curl -H "Authorization: Bearer your-access-token-here" \
http://localhost:3100/api/usersError Responses
Missing Credentials
{
"error": "username/email and password are required",
"code": "MISSING_CREDENTIALS"
}Status: 400
Invalid Credentials
{
"error": "Invalid username/email or password",
"code": "INVALID_CREDENTIALS"
}Status: 401
Account Disabled
{
"error": "Account is disabled",
"code": "ACCOUNT_DISABLED"
}Status: 403
User Login
Log in as a regular (non-admin) user to get a Bearer token for the /api/me endpoints.
Endpoint
POST /api/auth/loginRequest Body
{
userId: string // Required: User ID
password: string // Required: User password
deviceId?: string // Optional: Device identifier
}Example Request
curl -X POST http://localhost:3100/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"userId": "user-123",
"password": "user-password-here"
}'Success Response
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600,
"tokenType": "Bearer",
"user": {
"userId": "user-123",
"email": "alice@example.com",
"role": "user",
"name": "Alice"
}
}Error Responses
Same error codes as Admin Login: MISSING_CREDENTIALS (400), INVALID_CREDENTIALS (401), ACCOUNT_DISABLED (403).
Token Refresh
Refresh an expired access token using a refresh token.
Endpoint
POST /api/auth/refreshRequest Body
{
refreshToken: string; // Required: Refresh token from login
}Success Response
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 3600,
"tokenType": "Bearer"
}Error Responses
Missing Refresh Token
{
"error": "refreshToken is required",
"code": "MISSING_REFRESH_TOKEN"
}Status: 400
Invalid Refresh Token
{
"error": "Invalid or expired refresh token",
"code": "INVALID_REFRESH_TOKEN"
}Status: 401
Logout
Invalidate refresh tokens and end the session.
Endpoint
POST /api/auth/logoutRequest Body
{
refreshToken?: string // Optional: Specific refresh token to revoke
}If no refreshToken is provided, the endpoint still returns success.
Success Response
{
"success": true
}Token Expiration
- Access tokens - Expire after 1 hour (3600 seconds)
- Refresh tokens - Expire after 7 days, absolute expiration after 30 days
Use refresh tokens to get new access tokens without logging in again.
Root Key Login
You can log in using the root key that ZIRI generates on first start.
- The root key is stored as
.ziri-root-keyin the config directory (CONFIG_DIR). - In Docker, with
CONFIG_DIR=/data, the file is/data/.ziri-root-keyinside the container. - You can also set it explicitly via the
ZIRI_ROOT_KEYenvironment variable.
curl -X POST http://localhost:3100/api/auth/admin/login \
-H "Content-Type: application/json" \
-d '{
"username": "ziri",
"password": "your-root-key"
}'If ZIRI_ROOT_KEY is not set, ZIRI reads the existing .ziri-root-key from disk and keeps it stable across restarts. If no key file exists, ZIRI generates one on startup. If you set ZIRI_ROOT_KEY, that value is used and persisted to .ziri-root-key on first run when the file does not exist.