> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trellis.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Authenticate

> Exchange an API key for a JWT token

<ParamField header="X-API-Key" type="string" required>
  Your organization's API key
</ParamField>

<ParamField body="email" type="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()`).
</ParamField>

<ParamField body="user_key" type="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](/authentication) for the derivation algorithm and reference implementations.
</ParamField>

## Response

<ResponseField name="token" type="string" required>
  JWT token to use for subsequent API requests. Valid for 6 hours.
</ResponseField>

<ResponseField name="member_id" type="string" required>
  Unique identifier of the authenticated user.
</ResponseField>

<ResponseField name="organization_id" type="string" required>
  Unique identifier of the organization the user belongs to.
</ResponseField>

<ResponseField name="expires_at" type="string" required>
  ISO 8601 timestamp when the token expires.
</ResponseField>

<RequestExample>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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"}'
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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"]
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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();
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJvcmciOiJvcmdfYWJjMTIzIiwiaWF0IjoxNzA0OTY3MjAwLCJleHAiOjE3MDQ5ODg4MDB9.signature",
    "member_id": "550e8400-e29b-41d4-a716-446655440000",
    "organization_id": "org_abc123",
    "expires_at": "2025-01-11T18:00:00+00:00"
  }
  ```

  ```json 400 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "detail": "Invalid email format"
  }
  ```

  ```json 401 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "detail": "Authentication failed"
  }
  ```

  ```json 422 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "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}$"}
      }
    ]
  }
  ```

  ```json 429 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "code": "RATE_LIMIT",
    "retry_after_seconds": 13
  }
  ```
</ResponseExample>
