Skip to content

Authentication

All Mantis API endpoints require authentication. This guide covers the supported authentication methods and flows.

Mantis supports two authentication methods:

  1. JWT Bearer Tokens (recommended for user sessions)
  2. API Keys (recommended for automation and CI/CD)

Exchange your email and password for an access token:

Terminal window
POST /api/v1/auth/login
Content-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"
}
}

Include the token in the Authorization header with the Bearer prefix:

Terminal window
curl https://your-mantis-server/api/v1/deployments \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1cnJlbnQifQ..."

Before your access token expires, refresh it to get a new one:

Terminal window
POST /api/v1/auth/refresh
Cookie: 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).

Revoke all tokens for the current user:

Terminal window
POST /api/v1/auth/logout
Authorization: Bearer <token>

Response:

{
"message": "Successfully logged out"
}

If 2FA is enabled for your account, the login flow has an additional step.

Terminal window
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "your-password"
}

Response (2FA Required):

{
"requires_2fa": true,
"pending_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1cnJlbnQifQ..."
}
Terminal window
POST /api/v1/auth/2fa/verify
Content-Type: application/json
{
"code": "123456",
"pending_token": "<pending_token>"
}

Response (Success):

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1cnJlbnQifQ...",
"token_type": "Bearer",
"expires_in": 3600,
"user": { ... }
}

API keys provide long-lived authentication for automation and CI/CD pipelines.

Terminal window
POST /api/v1/auth/api-keys
Authorization: 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."
}

Use the API key as a Bearer token (same as JWT):

Terminal window
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_at and last_used_ip
Terminal window
GET /api/v1/auth/api-keys
Authorization: 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"
}
]
}
Terminal window
DELETE /api/v1/auth/api-keys/{id}
Authorization: Bearer <jwt-token>

Response:

{
"message": "API key revoked successfully"
}

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.

Exchange a valid JWT (or API key) for an SSE token:

Terminal window
POST /api/v1/auth/sse-token
Authorization: Bearer <jwt-token>
Content-Type: application/json
{}

Response:

{
"sse_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCI...",
"expires_in": 60,
"session_id": "b3c1...",
"refresh_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}`
);

To renew an SSE session without a full re-login, use the session_id and refresh_token from the original response:

Terminal window
POST /api/v1/auth/sse-token/refresh
Content-Type: application/json
{
"session_id": "b3c1...",
"refresh_token": "..."
}

Get information about the currently authenticated user:

Terminal window
GET /api/v1/auth/me
Authorization: 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"
}

Web Applications:

  • Store access tokens in memory (JavaScript variable)
  • Never store tokens in localStorage or sessionStorage
  • 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)

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.

Error:

{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
}

Causes:

  • Missing Authorization header
  • Token not prefixed with Bearer
  • Malformed token

Error:

{
"error": {
"code": "TOKEN_EXPIRED",
"message": "Token expired"
}
}

Solution: Call /auth/refresh to get a new access token.

Error:

{
"error": {
"code": "INVALID_CREDENTIALS",
"message": "Invalid credentials"
}
}

Causes:

  • Wrong email or password
  • Account is suspended or inactive
  • API key is expired or revoked

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.

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();
}
}
// Usage
const client = new MantisClient('https://mantis.example.com');
await client.login('user@example.com', 'password');
const deployments = await client.listDeployments();
import requests
from 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()
# Usage
client = MantisClient("https://mantis.example.com")
client.login("user@example.com", "password")
deployments = client.list_deployments()
#!/bin/bash
set -e
MANTIS_URL="https://mantis.example.com"
API_KEY="mnt_your_api_key_here"
# Create deployment
curl -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"
}'