Skip to content

API Overview & Authentication

The Vectis Mail API is a RESTful JSON API for managing domains, mailboxes, aliases, sending email, and administering your mail server. All endpoints are versioned under a single base path.

https://mail.example.com/api/v1

Replace mail.example.com with your Vectis hostname. All requests and responses use application/json.

Every endpoint requires authentication except GET /health, GET /version, and POST /auth/login. Vectis supports two authentication methods.

The admin dashboard authenticates via POST /auth/login, which sets a signed HttpOnly session cookie named vectis_session. The cookie is Secure, SameSite=Strict, and expires after the configured session TTL (default 24 hours).

Terminal window
curl -X POST https://mail.example.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "your-password"}' \
-c cookies.txt

If the admin has TOTP enabled, the first call returns a temporary totp_session token. Complete login by sending the TOTP code:

Terminal window
curl -X POST https://mail.example.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"totp_session": "abc123...", "totp_code": "482910"}'

API keys are prefixed with vectis_ and authenticate via the Authorization header. Keys can be scoped to specific domains and have configurable rate limits.

Terminal window
curl https://mail.example.com/api/v1/domains \
-H "Authorization: Bearer vectis_sk_abc123..."

API keys inherit the role of the admin who created them. They can be further restricted to specific domains via scoped_domain_ids at creation time. See the Admin & RBAC API for key management endpoints.

Every response uses a consistent JSON envelope:

{
"data": { ... },
"error": null,
"meta": {
"request_id": "0192abc0-def1-7000-8000-000000000001",
"timestamp": "2026-04-04T12:00:00Z"
}
}
Field Type Description
data object/array The response payload. Null on error.
error object/null Error details. Null on success.
error.code string Machine-readable error code (e.g. DOMAIN_EXISTS).
error.message string Human-readable error message.
error.details object/null Additional context for the error, when available.
meta.request_id string UUIDv7 identifying this request. Include in support tickets.
meta.timestamp string ISO 8601 UTC timestamp of the response.
meta.pagination object/null Present on paginated endpoints. See Pagination.

Errors return the appropriate HTTP status code along with a structured error object.

HTTP Status Code Description
400 INVALID_JSON Malformed JSON request body.
400 MISSING_FIELDS Required fields are absent.
400 INVALID_PERIOD Invalid period parameter for analytics.
400 INVALID_PAGINATION Malformed cursor or limit value.
401 AUTH_REQUIRED No session cookie or Bearer token provided.
401 SESSION_INVALID Session expired or token invalid.
401 API_KEY_INVALID API key not found or expired.
401 INVALID_CREDENTIALS Wrong email or password.
401 TOTP_INVALID Invalid TOTP code.
403 FORBIDDEN Insufficient role or domain access.
403 API_KEY_DOMAIN_SCOPE API key does not have access to the requested domain.
403 DOMAIN_INACTIVE The domain exists but is deactivated.
404 NOT_FOUND Resource does not exist.
409 DOMAIN_EXISTS Domain already registered.
409 CONFLICT Resource has dependents (e.g. domain has mailboxes).
409 ORCHESTRATOR_BUSY Orchestrator is currently running an operation.
429 RATE_LIMIT_EXCEEDED Too many requests or sending rate exceeded.
500 INTERNAL_ERROR Unexpected server error.

Example error response:

{
"data": null,
"error": {
"code": "DOMAIN_EXISTS",
"message": "Domain example.com already exists"
},
"meta": {
"request_id": "0192abc0-def1-7000-8000-000000000001",
"timestamp": "2026-04-04T12:00:00Z"
}
}

List endpoints use cursor-based pagination. Pass cursor and limit as query parameters.

Parameter Type Default Max Description
cursor string (none) Opaque cursor from a previous response.
limit integer 50 200 Number of items to return.

Paginated responses include a pagination object in the meta field:

{
"data": [ ... ],
"meta": {
"request_id": "...",
"timestamp": "...",
"pagination": {
"next_cursor": "MjAyNi0wNC0wNFQxMjowMDowMFo=",
"has_more": true
}
}
}

To fetch the next page, pass next_cursor as the cursor parameter:

Terminal window
curl "https://mail.example.com/api/v1/domains?cursor=MjAyNi0wNC0wNFQxMjowMDowMFo=&limit=50" \
-H "Authorization: Bearer vectis_sk_abc123..."

When has_more is false, you have reached the last page.

Most list endpoints support sorting and filtering via query parameters:

Parameter Example Description
sort created_at Field to sort by.
order desc Sort direction: asc or desc.
active true Filter by active status (domains, mailboxes).
domain_id uuid Filter by domain (mailboxes, aliases, messages).

Rate limiting is enforced at the Traefik reverse proxy layer and is configurable per endpoint group.

Tier Applies to Default limit
Authentication POST /auth/login 5 requests/second (throttled).
API key All authenticated endpoints 60 requests/minute per key (configurable at key creation).
Global All endpoints Configurable in config.yaml.

When rate-limited, the API returns HTTP 429 with the RATE_LIMIT_EXCEEDED error code.

All requests must send Content-Type: application/json. Request bodies are limited to 1 MB. The API returns application/json for all responses.

CORS is disabled by default. If you need to access the API from a custom frontend on a different origin, configure the CORS settings in config.yaml.

The API is versioned via the URL prefix (/api/v1). Breaking changes will be introduced under a new version prefix (e.g. /api/v2). Non-breaking additions (new fields, new endpoints) are made to the current version.

Every mutating API call (POST, PATCH, DELETE) is recorded in the audit log with the admin ID, action, resource, IP address, and a details object capturing what changed. See the Admin & RBAC API for querying the audit log.