Mutual TLS
On the mutual TLS (mTLS) hostname, https://api-mtls.brellium.com, a request needs two things: a valid access token, and a client certificate registered to your organization, presented when the connection opens. The paths, tokens and responses are the same as on api.brellium.com.
The certificate comes from one of two places. Brellium can sign a certificate for a key pair you generate, or you can register your own certificate authority (CA) and use the certificates it issues. Once a certificate works, you can require mTLS for live keys, sandbox keys or both. Until you do, api.brellium.com keeps working with a token alone.
Who can set it up
Anyone who can manage your organization's API keys on the dashboard can request certificates, register a CA and turn enforcement on. Open Settings and find the Mutual TLS section. To get that access, see Get access to the dashboard.
Get a certificate from Brellium
Generate the private key and a certificate signing request (CSR) on the machine that will use them. The private key never leaves that machine, and Brellium never sees it.
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
-pkeyopt ec_param_enc:named_curve -nodes \
-keyout client.key -out client.csr -subj "/CN=billing-sync"
openssl req -new -newkey rsa:2048 -nodes \
-keyout client.key -out client.csr -subj "/CN=billing-sync"
The key can be RSA of 2048 to 8192 bits or EC on P-256, P-384 or P-521, and the CSR must be signed with SHA-256 or stronger. The subject you put in the CSR does not matter: Brellium replaces it, and the certificate's common name is your organization id.
Then, in Settings > Mutual TLS on the dashboard:
- Click Request certificate and paste the contents of
client.csr, including theBEGINandENDlines. - Choose a Validity: 90, 180, 365 (the default) or 730 days.
- Under Key (optional), leave Any key to let every key of your organization use the certificate, or pick one key to bind it to that key only.
- Click Issue certificate. The certificate is shown once: click Copy PEM or Download certificate and save it as
client.crtnext toclient.key. Download CA chain saves the Brellium CA certificates that signed it.
If you close the dialog before you save the certificate, request a new one.
Bring your own CA
Register the CA that signs your client certificates. If an intermediate CA signs them, register the intermediate. A certificate issued under a registered CA works for every key of your organization.
- In Settings > Mutual TLS, click Register CA.
- Give it a Label and paste exactly one CA certificate in PEM format, not a full chain.
- Click Register certificate authority.
The CA certificate must be a current CA certificate with a Subject Key Identifier, and its key follows the same rules as a client key. A CA whose public key another organization already uses is refused, and so is the Brellium CA.
Until Brellium trusts a new CA, its row shows Propagating with the most minutes that can take. Certificates it issues fail the connection until that state clears. If the update fails, the row shows a Retry button.
Call the API with the certificate
Mint a token as usual (Authenticate with OAuth2 client credentials), then send each request to https://api-mtls.brellium.com with the certificate and its private key. POST /v2/oauth/token works on every hostname and returns the same token; on the mTLS hostname the token request needs the certificate too, because every connection there does.
curl "https://api-mtls.brellium.com/v2/audits?limit=1" \
--cert client.crt \
--key client.key \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--include
import { readFileSync } from "node:fs";
const res = await fetch("https://api-mtls.brellium.com/v2/audits?limit=1", {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
tls: {
cert: readFileSync("client.crt", "utf8"),
key: readFileSync("client.key", "utf8"),
},
});
console.log(res.status, res.headers.get("X-Brellium-Mtls"));
import { readFileSync } from "node:fs";
import { Agent, fetch } from "undici";
const dispatcher = new Agent({
connect: {
cert: readFileSync("client.crt", "utf8"),
key: readFileSync("client.key", "utf8"),
},
});
const res = await fetch("https://api-mtls.brellium.com/v2/audits?limit=1", {
headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
dispatcher,
});
console.log(res.status, res.headers.get("X-Brellium-Mtls"));
import os
import requests
res = requests.get(
"https://api-mtls.brellium.com/v2/audits",
params={"limit": 1},
headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
cert=("client.crt", "client.key"),
timeout=30,
)
print(res.status_code, res.headers.get("X-Brellium-Mtls"))
The Node example needs the undici package. Any HTTP client that can send a client certificate works.
Check that the certificate is used
Every successful /v2 response carries the X-Brellium-Mtls header: verified on the mTLS hostname, none on api.brellium.com. In the dashboard, the certificate's or the CA's Last used column shows when a request last presented it.
The certificate table also shows the kind, the bound key, the fingerprint, the expiry, last use and who created each certificate. The expiry turns amber 30 days before expiry and red 7 days before expiry.
Require mTLS
The section has one switch per environment: Require mTLS for live keys and Require mTLS for sandbox keys. You can turn a switch on only after a certificate or CA has been used over api-mtls.brellium.com in the last 24 hours and does not expire within the next day. A certificate bound to a key counts only for that key's environment; an unbound certificate or a CA counts for both.
With the switch on, a /v2 request for that environment on api.brellium.com returns 403 with the code MTLS_REQUIRED, and the message names the mTLS hostname. POST /v2/oauth/token is outside this check and keeps working on every hostname. A change to a switch reaches every request within about a minute.
Turning mTLS off again is always allowed. If you revoke, remove or block the last usable credential for an environment while its switch is on, the dashboard asks you to confirm and then turns the switch off, so your keys keep working. Expiry does not turn the switch off. When your last certificate or CA for an environment expires with its switch on, requests for that environment fail until you turn the switch off or add a credential: api.brellium.com returns 403 with the code MTLS_REQUIRED, and api-mtls.brellium.com refuses the connection during the TLS handshake. Renew before the expiry turns red.
Rotate a certificate
Rotate with an overlap so no request fails:
- Request a second certificate (or issue one under your CA) and deploy it beside the first.
- Check that requests return
X-Brellium-Mtls: verifiedand that the new certificate's Last used updates. - Revoke the first certificate.
To replace your own CA, register the new one, wait until it stops showing Propagating, move your clients to certificates it issued, then remove the old CA.
Revoke and block
Revoke in a Brellium certificate's row stops it from working within about a minute: requests with it get 403 MTLS_CERTIFICATE_MISMATCH, and once the revocation list is published the connection fails at the handshake. Remove in a CA's row stops every certificate it issued within about a minute the same way: 403 MTLS_CERTIFICATE_MISMATCH until Brellium stops trusting the CA, then the handshake fails. Revoking an API key also revokes the certificates bound to it.
To stop one certificate issued by your own CA while the CA stays registered, click Block certificate and paste that certificate in PEM format. It must be a client certificate issued under one of your registered CAs. A blocked certificate gets 403 MTLS_CERTIFICATE_MISMATCH; the handshake still succeeds. A block by serial number alone is not offered.
What is not covered
The upload URLs that POST /v2/documents returns point to storage outside the API and use ordinary TLS, so a PUT to them needs no client certificate. The official TypeScript and Python SDKs cannot send a client certificate yet; call the mTLS hostname with your own HTTP client.
Troubleshooting
- The connection closes during the TLS handshake, before any HTTP response: the client sent no certificate, an expired one, one not issued by a CA Brellium trusts, or a revoked Brellium certificate after its revocation list is published. A CA that still shows Propagating gives the same result.
403 MTLS_CERTIFICATE_MISMATCH: the certificate passed the handshake but is not registered to the token's organization (for example, it was issued under another organization's CA), or it is bound to a different key. Check the certificate's bound key in the dashboard. A blocked certificate, a revoked one before its revocation list is published, and one from a removed CA until Brellium stops trusting it also get this code.403 MTLS_REQUIRED: the request went toapi.brellium.comwhile your organization requires mTLS for that environment. Send it tohttps://api-mtls.brellium.comwith the certificate.403or503 MTLS_POLICY_UNAVAILABLE: the certificate check is briefly unavailable. Retry shortly with backoff.
For the error envelope and request ids, see Rate limits and errors.
Next
Authenticate with OAuth2 client credentials
Create a key, mint a token with it, cache the token, and handle 401, 429 and 502.
Rate limits and errors
The rate limits and their headers, how to retry a 429, and every error code.