- Exchange your API key for a JWT token.
- Use the JWT token to authenticate every subsequent request.
Obtaining an API key
API keys are provisioned through the Trellis admin dashboard. Contact your organization administrator or reach out to Trellis support to obtain one.Computing the user_key
Every call to/v1/authenticate must include a user_key alongside the email. The user_key proves the request was authorized by your backend rather than forged client-side — any party with your API key can claim an email, so we require an HMAC-derived value that only your backend can produce.
[A-Za-z0-9_-]. Base64url-encoded without padding (strip trailing =). HMAC is over the raw bytes of the normalized email (lowercased, whitespace-trimmed), UTF-8 encoded.
The
user_key is a signature, not an identifier. Trellis recomputes HMAC(secret, email) server-side using the email from the request and compares it to the submitted user_key. A user_key is bound to one specific email — submitting one user’s email with another user’s user_key always returns 401 Authentication failed, regardless of whether both users are provisioned. The only way to authenticate as a given user is to present their specific (email, user_key) pair.user_key, so we coordinate timing with you.
Use the same normalization on both sides. The HMAC is computed over
email.strip().lower(). If your normalization differs from Trellis’s, the HMAC won’t verify and the request will return 401. Send the email in the JSON body in its normalized form too — Trellis re-normalizes on its side, but matching ahead of time avoids edge cases with mixed-case domains.Authentication flow
Step 1: Exchange API key for JWT
To start a session, send your API key, the user’s email address, and the computeduser_key to the authenticate endpoint.
- cURL
- Python
- JavaScript
Step 2: Use the JWT token
Include the JWT token in theAuthorization header for all API requests:
Token expiration
JWT tokens expire 6 hours after they are issued. Theexpires_at field in the authentication response tells you exactly when.
When a token expires, you’ll receive a 401 Unauthorized response. Simply re-authenticate to get a new token.
Token identity (jti) and rate-limit buckets
Every JWT issued by /v1/authenticate carries a unique jti (JSON Web Token ID) claim — a v4 UUID generated at sign time. Trellis uses jti as the bucket key for rate limits and the WebSocket concurrent-connection cap.
Practical implications:
- Re-authenticating returns a fresh
jti, so a new token resets every per-token throttle and concurrency slot. - Two services sharing one JWT also share its rate-limit and connection budgets. Issue separate tokens to separate them.
- The
jtiis opaque — clients don’t need to inspect or echo it; just keep using thetokenstring.
Error responses
All authentication failures return a single generic401 Authentication failed so the specific failure mode isn’t disclosed to attackers. Treat them all as “credentials rejected — prompt the user to retry login.”
| Status | Body | When |
|---|---|---|
| 400 | {"detail": "Invalid email format"} | The email address is structurally invalid (missing @, over 320 chars). |
| 401 | {"detail": "Authentication failed"} | Missing or invalid API key, unknown email, disabled user, wrong user_key, or cross-tenant misuse. |
| 422 | Pydantic validation error with field detail | user_key is missing or doesn’t match ^[A-Za-z0-9_-]{43}$. |
| 429 | {"code": "RATE_LIMIT", "retry_after_seconds": N} plus a Retry-After: N header | Per-API-key rate limit on /v1/authenticate exceeded (60 requests per minute). Respect Retry-After and back off. |
| 500 | {"detail": "JWT signing not configured"} or {"detail": "Server misconfiguration"} | Trellis-side configuration error. Contact support. |