Authentication
All Mantis API endpoints require authentication. This guide covers the supported authentication methods and flows.
Authentication Methods
Section titled “Authentication Methods”Mantis supports two authentication methods:
- JWT Bearer Tokens (recommended for user sessions)
- API Keys (recommended for automation and CI/CD)
JWT Bearer Token Authentication
Section titled “JWT Bearer Token Authentication”Login Flow
Section titled “Login Flow”Exchange your email and password for an access token:
POST /api/v1/auth/loginContent-Type: application/json
{ "email": "user@example.com", "password": "your-password"}Response:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1cnJlbnQifQ...", "token_type": "Bearer", "expires_in": 3600, "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "username": "johndoe", "display_name": "John Doe", "status": "active", "roles": ["operator", "user"], "permissions": ["deployments:create", "deployments:read"], "created_at": "2024-01-15T10:30:00Z" }}Using the Access Token
Section titled “Using the Access Token”Include the token in the Authorization header with the Bearer prefix:
curl https://your-mantis-server/api/v1/deployments \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1cnJlbnQifQ..."Token Refresh
Section titled “Token Refresh”Before your access token expires, refresh it to get a new one:
POST /api/v1/auth/refreshCookie: refresh_token=<automatic>Response:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1cnJlbnQifQ...", "token_type": "Bearer", "expires_in": 3600, "user": { ... }}A new refresh token is also set as a cookie (refresh token rotation).
Logout
Section titled “Logout”Sign out the current session/device: the presented access token is revoked and the
caller’s session row is deleted, which cascades to the tokens issued for that
session. Other devices keep working. To sign out every OTHER device, use
DELETE /api/v1/auth/sessions — it revokes the account’s other sessions and leaves
the caller’s own in place.
POST /api/v1/auth/logoutAuthorization: Bearer <token>Response:
{ "message": "Successfully logged out"}Two-Factor Authentication (2FA)
Section titled “Two-Factor Authentication (2FA)”If 2FA is enabled for your account, the login flow has an additional step.
Step 1: Initial Login
Section titled “Step 1: Initial Login”POST /api/v1/auth/loginContent-Type: application/json
{ "email": "user@example.com", "password": "your-password"}Response (2FA Required):
{ "requires_2fa": true, "pending_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1cnJlbnQifQ..."}Step 2: Verify TOTP Code
Section titled “Step 2: Verify TOTP Code”POST /api/v1/auth/2fa/verifyContent-Type: application/json
{ "code": "123456", "pending_token": "<pending_token>"}Response (Success):
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1cnJlbnQifQ...", "token_type": "Bearer", "expires_in": 3600, "user": { ... }}API Key Authentication
Section titled “API Key Authentication”API keys provide long-lived authentication for automation and CI/CD pipelines.
Creating an API Key
Section titled “Creating an API Key”POST /api/v1/auth/api-keysAuthorization: Bearer <jwt-token>Content-Type: application/json
{ "name": "CI/CD Pipeline", "description": "Automation token for GitHub Actions", "expires_in_days": 90, "scopes": ["deployments:create", "deployments:read"]}Response:
{ "api_key": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "CI/CD Pipeline", "description": "Automation token for GitHub Actions", "key_prefix": "mnt_a1b2c3d4...", "scopes": ["deployments:create", "deployments:read"], "last_used_at": null, "last_used_ip": null, "created_at": "2024-01-15T10:30:00Z", "expires_at": "2024-04-15T10:30:00Z" }, "full_key": "mnt_a1b2c3d4e5f67890abcdef1234567890abcdef1234567890", "warning": "Store this key securely. It will not be shown again."}Using an API Key
Section titled “Using an API Key”Use the API key as a Bearer token (same as JWT):
curl https://your-mantis-server/api/v1/deployments \ -H "Authorization: Bearer mnt_a1b2c3d4e5f67890abcdef1234567890abcdef1234567890"API keys:
- Start with the prefix
mnt_ - Don’t expire unless you set
expires_in_days - Are scoped to the tenant of the user who created them
- Automatically track
last_used_atandlast_used_ip
Listing Your API Keys
Section titled “Listing Your API Keys”GET /api/v1/auth/api-keysAuthorization: Bearer <jwt-token>Response:
{ "api_keys": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "CI/CD Pipeline", "description": "Automation token for GitHub Actions", "key_prefix": "mnt_a1b2c3d4...", "scopes": ["deployments:create", "deployments:read"], "last_used_at": "2024-01-20T15:45:00Z", "last_used_ip": "203.0.113.42", "created_at": "2024-01-15T10:30:00Z", "expires_at": "2024-04-15T10:30:00Z" } ]}Revoking an API Key
Section titled “Revoking an API Key”DELETE /api/v1/auth/api-keys/{id}Authorization: Bearer <jwt-token>Response:
{ "message": "API key revoked successfully"}SSE Tokens
Section titled “SSE Tokens”Server-Sent Events (SSE) endpoints — such as GET /api/v1/sse/{resource_type}
and GET /api/v1/deployments/{id}/logs — do not accept your regular JWT in
an Authorization header. Because EventSource cannot set request headers, SSE
connections authenticate with a short-lived, single-use token passed in the
?sse_token= query parameter.
Obtaining an SSE Token
Section titled “Obtaining an SSE Token”Exchange a valid JWT (or API key) for an SSE token:
POST /api/v1/auth/sse-tokenAuthorization: Bearer <jwt-token>Content-Type: application/json
{}Response:
{ "sse_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCI...", "expires_in": 60, "session_id": "b3c1...", "refresh_token": "..."}Using an SSE Token
Section titled “Using an SSE Token”Pass the token in the ?sse_token= query parameter when opening the connection:
const { sse_token } = await fetch('/api/v1/auth/sse-token', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: '{}',}).then((r) => r.json());
const eventSource = new EventSource( `/api/v1/sse/deployments?sse_token=${sse_token}`);Refreshing an SSE Session
Section titled “Refreshing an SSE Session”To renew an SSE session without a full re-login, use the session_id and
refresh_token from the original response:
POST /api/v1/auth/sse-token/refreshContent-Type: application/json
{ "session_id": "b3c1...", "refresh_token": "..."}Current User Information
Section titled “Current User Information”Get information about the currently authenticated user:
GET /api/v1/auth/meAuthorization: Bearer <token>Response:
{ "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "username": "johndoe", "display_name": "John Doe", "status": "active", "email_verified": true, "roles": ["operator", "user"], "permissions": ["deployments:create", "deployments:read", "targets:read"], "last_login_at": "2024-01-20T08:30:00Z", "created_at": "2023-06-01T12:00:00Z"}Security Best Practices
Section titled “Security Best Practices”Token Storage
Section titled “Token Storage”Web Applications:
- Store access tokens in memory (JavaScript variable)
- Never store tokens in
localStorageorsessionStorage - Refresh tokens are handled automatically via HTTP-only cookies
Mobile/Desktop Apps:
- Use secure platform-specific storage (Keychain on iOS, Keystore on Android)
- Never log token values
CI/CD Pipelines:
- Use API keys instead of JWT tokens
- Store API keys in secret management systems (GitHub Secrets, HashiCorp Vault, etc.)
- Rotate API keys regularly (set
expires_in_days)
Token Security
Section titled “Token Security”API Key Scopes
Section titled “API Key Scopes”When creating API keys, use the minimum required scopes:
{ "name": "Read-Only Monitoring", "scopes": ["deployments:read", "targets:read"]}Available scopes match the permission system. See the Permissions guide for the full list.
Troubleshooting
Section titled “Troubleshooting”401 Unauthorized
Section titled “401 Unauthorized”Error:
{ "error": { "code": "UNAUTHORIZED", "message": "Authentication required" }}Causes:
- Missing
Authorizationheader - Token not prefixed with
Bearer - Malformed token
TOKEN_EXPIRED
Section titled “TOKEN_EXPIRED”Error:
{ "error": { "code": "TOKEN_EXPIRED", "message": "Token expired" }}Solution: Call /auth/refresh to get a new access token.
INVALID_CREDENTIALS
Section titled “INVALID_CREDENTIALS”Error:
{ "error": { "code": "INVALID_CREDENTIALS", "message": "Invalid credentials" }}Causes:
- Wrong email or password
- Account is suspended or inactive
- API key is expired or revoked
TWO_FACTOR_REQUIRED
Section titled “TWO_FACTOR_REQUIRED”Error:
{ "error": { "code": "TWO_FACTOR_REQUIRED", "message": "Two-factor authentication required" }}Cause: You’re using a pending_token on a regular endpoint. Complete the 2FA flow first by calling /auth/2fa/verify.
Code Examples
Section titled “Code Examples”JavaScript/TypeScript
Section titled “JavaScript/TypeScript”class MantisClient { private accessToken: string | null = null; private baseUrl: string;
constructor(baseUrl: string) { this.baseUrl = baseUrl; }
async login(email: string, password: string): Promise<void> { const response = await fetch(`${this.baseUrl}/api/v1/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), credentials: 'include', // Include cookies for refresh token });
if (!response.ok) { throw new Error('Login failed'); }
const data = await response.json(); this.accessToken = data.access_token; }
async listDeployments(): Promise<any> { const response = await fetch(`${this.baseUrl}/api/v1/deployments`, { headers: { Authorization: `Bearer ${this.accessToken}`, }, });
return response.json(); }}
// Usageconst client = new MantisClient('https://mantis.example.com');await client.login('user@example.com', 'password');const deployments = await client.listDeployments();Python
Section titled “Python”import requestsfrom typing import Optional
class MantisClient: def __init__(self, base_url: str): self.base_url = base_url self.access_token: Optional[str] = None self.session = requests.Session()
def login(self, email: str, password: str) -> None: response = self.session.post( f"{self.base_url}/api/v1/auth/login", json={"email": email, "password": password} ) response.raise_for_status() data = response.json() self.access_token = data["access_token"]
def list_deployments(self) -> list: response = self.session.get( f"{self.base_url}/api/v1/deployments", headers={"Authorization": f"Bearer {self.access_token}"} ) response.raise_for_status() return response.json()
# Usageclient = MantisClient("https://mantis.example.com")client.login("user@example.com", "password")deployments = client.list_deployments()cURL (API Key)
Section titled “cURL (API Key)”#!/bin/bashset -e
MANTIS_URL="https://mantis.example.com"API_KEY="mnt_your_api_key_here"
# Create deploymentcurl -X POST "$MANTIS_URL/api/v1/deployments" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "solution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }'Next Steps
Section titled “Next Steps”- Error Handling - Understand API error responses
- Pagination - Work with paginated results
- Deployments API - Create and manage deployments
