Skip to main content
X-API-Key
string
required
Your organization’s API key
email
string
required
Email address of the user to authenticate. The user must already be provisioned in Trellis with an active status — auto-creation on first login is no longer supported. Send in normalized form (email.strip().lower()).
user_key
string
required
HMAC-SHA256 of the normalized email under the shared secret issued to your backend, base64url-encoded without padding. Exactly 43 ASCII characters from [A-Za-z0-9_-]. The HMAC binds this specific email to the request — Trellis recomputes HMAC(secret, email) server-side using the email from the request and rejects any pair where the two don’t match. A user_key is a signature over one specific email, not a standalone identifier; mixing one user’s email with another user’s user_key always returns 401. See Authentication for the derivation algorithm and reference implementations.

Response

token
string
required
JWT token to use for subsequent API requests. Valid for 6 hours.
member_id
string
required
Unique identifier of the authenticated user.
organization_id
string
required
Unique identifier of the organization the user belongs to.
expires_at
string
required
ISO 8601 timestamp when the token expires.
curl -X POST https://api.trellis.sh/v1/authenticate \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "user_key": "your_computed_user_key"}'
import requests

response = requests.post(
    "https://api.trellis.sh/v1/authenticate",
    headers={"X-API-Key": "your_api_key_here"},
    json={
        "email": "user@example.com",
        "user_key": compute_user_key("user@example.com", shared_secret),
    },
)
token = response.json()["token"]
const response = await fetch("https://api.trellis.sh/v1/authenticate", {
  method: "POST",
  headers: {
    "X-API-Key": "your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    email: "user@example.com",
    user_key: computeUserKey("user@example.com", sharedSecret),
  }),
});
const { token } = await response.json();
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJvcmciOiJvcmdfYWJjMTIzIiwiaWF0IjoxNzA0OTY3MjAwLCJleHAiOjE3MDQ5ODg4MDB9.signature",
  "member_id": "550e8400-e29b-41d4-a716-446655440000",
  "organization_id": "org_abc123",
  "expires_at": "2025-01-11T18:00:00+00:00"
}
{
  "detail": "Invalid email format"
}
{
  "detail": "Authentication failed"
}
{
  "detail": [
    {
      "type": "string_pattern_mismatch",
      "loc": ["body", "user_key"],
      "msg": "String should match pattern '^[A-Za-z0-9_-]{43}$'",
      "ctx": {"pattern": "^[A-Za-z0-9_-]{43}$"}
    }
  ]
}
{
  "code": "RATE_LIMIT",
  "retry_after_seconds": 13
}