Skip to main content
Version: 0.16

API Endpoints

The Stalwart API is deliberately small. The bulk of server configuration and mailbox data is accessed through JMAP (under /jmap), which is a separate API with its own documentation; the endpoints below are helpers for authentication, account introspection, configuration schema retrieval and live (Server-Sent Events) telemetry.

All paths are relative to the server's base URL, e.g. https://mail.example.com.

Authentication

Requests to the Management API are authenticated with one of:

SchemeHow to sendWhen to use
BearerAuthorization: Bearer <access_token>Normal programmatic access with an OAuth2 access token.
BasicAuthorization: Basic <base64(user:secret)>Simple scripts or initial bootstrap.
Live token?token=<short_lived_token> in the URL queryServer-Sent Events streams from browsers where an Authorization header cannot be set on EventSource. The token is obtained from /api/token/{kind} and expires after 60 seconds.

Anonymous endpoints (/api/auth, /api/discover/{email}) are rate-limited.

Failed authentication returns 401 Unauthorized with a WWW-Authenticate: Bearer realm="Stalwart Server" header and an application/problem+json body (RFC 7807).


POST /api/auth

Authenticate a user and obtain an OAuth authorization code. Used by the web UI and by device-flow clients to complete login. Anonymous, rate-limited.

Request body — tagged union discriminated by type:

authCode — authorization-code flow (optionally with PKCE)

{
"type": "authCode",
"accountName": "[email protected]",
"accountSecret": "s3cret",
"mfaToken": null,
"clientId": "webadmin",
"redirectUri": "https://mail.example.com/login",
"nonce": null,
"scope": null,
"codeChallenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"codeChallengeMethod": "S256",
"state": null
}
FieldTypeRequiredNotes
accountNamestringyesAccount name or email.
accountSecretstringyesAccount password.
mfaTokenstringnoProvided on a retry after a previous mfaRequired response.
clientIdstringyesOAuth client identifier.
redirectUristring (URI)noMust use https:// unless the server is in recovery or dev mode.
noncestringnoOpenID Connect nonce.
scopestringnoRequested OAuth scope.
codeChallengestringnoPKCE code challenge (RFC 7636).
codeChallengeMethodstringnoplain or S256. Defaults to plain when a challenge is present.
statestringnoOpaque value echoed back to the client.

authDevice — device-flow completion

{
"type": "authDevice",
"accountName": "[email protected]",
"accountSecret": "s3cret",
"code": "BDWP-HQPK"
}
FieldTypeRequiredNotes
accountNamestringyes
accountSecretstringyes
mfaTokenstringno
codestringyesUser-facing device code issued by POST /auth/device.

Response body — tagged union discriminated by type:

typeExtra fieldsMeaning
authenticatedclientCode (string)Credentials accepted. Exchange clientCode at POST /auth/token.
verifiedDevice-flow verification succeeded.
mfaRequiredMulti-factor is required; retry with a valid mfaToken.
failureCredentials rejected.

Example success:

{ "type": "authenticated", "clientCode": "3F7A9C1E4B2D8E6F" }

GET /api/discover/{email}

Return the OpenID Connect discovery document for the directory that owns the domain of email. If the domain is not bound to an external OIDC directory, the server's own discovery document (equivalent to /.well-known/openid-configuration) is returned.

Anonymous, rate-limited.

Path parameters

NameInTypeDescription
emailpathstringEmail address or account name.

Response200 OK, application/json. Standard OIDC discovery metadata (issuer, authorization_endpoint, token_endpoint, etc. per RFC 8414).


GET /api/account

Return the authenticated account's effective permissions, server edition and preferred locale. Used by the web UI to tailor the interface.

Response200 OK, application/json:

{
"permissions": [
"authenticate",
"jmap-email-get",
"sys-account-settings-get"
],
"edition": "oss",
"locale": "en-US"
}
FieldTypeNotes
permissionsstring[]Effective permissions, filtered to exclude internal/system-only entries. In bootstrap mode, limited to sys-bootstrap-get/-update.
editionstringOne of oss, community, enterprise.
localestringBCP 47–style language tag.

GET /api/schema

Redirect (302) to /api/schema/{hash}, where {hash} is the SHA-256 hex digest of the current configuration schema. Callers that do not already know the current hash should follow this redirect; callers that have cached a schema at a specific hash can fetch it directly — that URL is immutable.


GET /api/schema/{hash}

Return the JSON Schema describing the full Stalwart configuration tree. The response is always Content-Encoding: gzip and is served with an immutable cache policy: the schema at a given hash never changes. If the supplied hash does not match the server's current schema, the server redirects to the correct URL.

Path parameters

NameInTypeDescription
hashpathstringSHA-256 hex digest of the schema document.

Response200 OK, application/json (gzip-encoded) containing a JSON Schema document.


GET /api/token/{kind}

Issue a short-lived (60 second) bearer token for a specific Server-Sent Events stream. The returned token is used as the ?token= query parameter on the matching /api/live/... endpoint, which lets browsers authenticate an EventSource connection without being able to set request headers.

Path parameters

kindPermission requiredEnterprise only
deliveryLiveDeliveryTestno
tracingLiveTracingyes
metricsLiveMetricsyes

Response200 OK, text/plain — the opaque token string, valid for 60 seconds and bound to the matching /api/live/{kind} path.

Requesting tracing or metrics on a non-Enterprise build returns 404 Not Found.


GET /api/live/delivery/{target}

Stream outbound-delivery diagnostics as Server-Sent Events. Opens a long-running text/event-stream that reports each stage of an outbound delivery attempt to target (MX lookup, MTA-STS fetch, DANE/TLSA validation, SMTP conversation, etc.) and ends with a completed event.

Requires the LiveDeliveryTest permission. May be authenticated via a normal Authorization header or via ?token= using a token from /api/token/delivery.

Path parameters

NameInTypeDescription
targetpathstringDomain or email address to diagnose.

Query parameters

NameTypeDefaultDescription
timeoutinteger30Maximum stream lifetime in seconds (≥ 1).
tokenstringShort-lived token from /api/token/delivery.

Response200 OK, Content-Type: text/event-stream. Each frame has the form:

event: event
data: [<DeliveryStage JSON>]

…where <DeliveryStage JSON> is an object tagged by type — for example {"type":"mxLookupStart","domain":"example.com"}, {"type":"mxLookupSuccess","mxs":[...],"elapsed":42}, and so on. The final frame is always {"type":"completed"}.


GET /api/live/tracing (Enterprise)

Stream live server trace events as Server-Sent Events. Requires the LiveTracing permission and may be authenticated via ?token= using a token from /api/token/tracing. Returns 404 Not Found on non-Enterprise builds.

Response200 OK, Content-Type: text/event-stream.


GET /api/live/metrics (Enterprise)

Stream live server metrics as Server-Sent Events. Requires the LiveMetrics permission and may be authenticated via ?token= using a token from /api/token/metrics. Returns 404 Not Found on non-Enterprise builds.

Response200 OK, Content-Type: text/event-stream.


Error responses

All error responses use RFC 7807 application/problem+json:

{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"detail": "Invalid or missing credentials."
}
StatusMeaning
400Malformed request body or invalid parameters.
401Missing or invalid credentials. Response includes WWW-Authenticate.
403Authenticated, but lacking the required permission.
404Resource not found, or Enterprise-only endpoint on an OSS build.
429Anonymous-request rate limit exceeded.