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

# REST API

> HTTP endpoints for Archie Auth — signup, login, password reset, refresh, JWKS, and the error code reference.

Archie Auth exposes its full surface as REST endpoints on the platform gateway. Use them when you'd rather call HTTP directly than go through GraphQL — anywhere a `curl` or a thin HTTP client is the right tool.

For the equivalent GraphQL operations, see the [GraphQL API reference](/features/backend/app-services/authentication-providers/archie-auth/graphql-api).

## Required headers

| Header          | Required                 | Default  | Description              |
| --------------- | ------------------------ | -------- | ------------------------ |
| `X-Project-Id`  | Yes                      | —        | Your project identifier. |
| `environment`   | No                       | `master` | Target environment name. |
| `Content-Type`  | Yes                      | —        | `application/json`.      |
| `Authorization` | Protected endpoints only | —        | `Bearer <accessToken>`.  |

**Base URL:** `https://your-gateway.example.com`

## Public endpoints

These don't require authentication — they're the way unauthenticated visitors enter the system.

### POST `/auth/signup`

Register a new user account.

**Request:**

```json theme={null}
{
  "email": "user@example.com",
  "password": "SecureP@ss1",
  "firstName": "John",
  "lastName": "Doe",
  "roleId": "optional-role-id"
}
```

**Responses:**

| Status | Body                                                         | When                                             |
| ------ | ------------------------------------------------------------ | ------------------------------------------------ |
| 201    | `{ "userId": "uuid", "message": "Verification email sent" }` | Created.                                         |
| 400    | `{ "error": "VALIDATION_ERROR", "violations": [...] }`       | Invalid input or weak password.                  |
| 409    | `{ "error": "AUTH_EMAIL_EXISTS" }`                           | Email already registered.                        |
| 404    | `{ "error": "AUTH_NOT_CONFIGURED" }`                         | Auth not enabled for this project + environment. |
| 429    | `{ "error": "RATE_LIMIT_EXCEEDED" }`                         | Too many signups (10/min per IP).                |

If email verification is disabled, the user is auto-verified and can log in immediately.

### POST `/auth/login`

Authenticate and receive an access/refresh token pair.

**Request:**

```json theme={null}
{
  "email": "user@example.com",
  "password": "SecureP@ss1"
}
```

**Successful response (200):**

```json theme={null}
{
  "accessToken": "eyJhbGciOiJSUzI1NiIs...",
  "refreshToken": "base64-encoded-token...",
  "user": {
    "id": "a1b2c3d4-e5f6-...",
    "email": "user@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "roles": ["Member"]
  }
}
```

**Error responses:**

| Status | Body                                                       | When                             |
| ------ | ---------------------------------------------------------- | -------------------------------- |
| 401    | `{ "error": "AUTH_INVALID_CREDENTIALS" }`                  | Wrong email or password.         |
| 403    | `{ "error": "AUTH_EMAIL_NOT_VERIFIED" }`                   | Email not yet confirmed.         |
| 423    | `{ "error": "AUTH_ACCOUNT_LOCKED", "lockedUntil": "..." }` | Locked from failed attempts.     |
| 429    | `{ "error": "RATE_LIMIT_EXCEEDED" }`                       | Too many logins (20/min per IP). |

When JWE encryption is enabled, the `accessToken` is a 5-part JWE string instead of a 3-part JWS. Both formats are accepted on inbound requests.

### POST `/auth/confirm-signup`

Confirm an email with the 6-digit code.

**Request:**

```json theme={null}
{
  "email": "user@example.com",
  "code": "123456"
}
```

**Responses:**

| Status | Body                                                             | When                                       |
| ------ | ---------------------------------------------------------------- | ------------------------------------------ |
| 200    | `{ "accessToken": "...", "refreshToken": "...", "user": {...} }` | Confirmed and logged in.                   |
| 400    | `{ "error": "AUTH_INVALID_CODE" }`                               | Code wrong, expired, or attempt limit hit. |

Codes expire after **1 hour**. Up to **5 attempts** are allowed before the code is invalidated.

### POST `/auth/recover-password`

Request a password recovery email. Always returns 200, regardless of whether the email exists, to prevent enumeration.

**Request:**

```json theme={null}
{
  "email": "user@example.com"
}
```

**Response (always 200):**

```json theme={null}
{
  "message": "If account exists, recovery email sent"
}
```

Rate-limited to **5 / minute per email**.

### POST `/auth/reset-password`

Reset a password using the recovery code.

**Request:**

```json theme={null}
{
  "email": "user@example.com",
  "newPassword": "NewSecureP@ss2",
  "code": "654321"
}
```

**Responses:**

| Status | Body                                           | When                   |
| ------ | ---------------------------------------------- | ---------------------- |
| 200    | `{ "message": "Password reset successfully" }` | Updated.               |
| 400    | `{ "error": "AUTH_INVALID_CODE" }`             | Code wrong or expired. |

A successful reset clears any active account lockout.

### POST `/auth/refresh-token`

Exchange a refresh token for a new access/refresh pair.

**Request:**

```json theme={null}
{
  "refreshToken": "base64-encoded-token..."
}
```

**Responses:**

| Status | Body                                              | When                                   |
| ------ | ------------------------------------------------- | -------------------------------------- |
| 200    | `{ "accessToken": "...", "refreshToken": "..." }` | New token pair.                        |
| 401    | `{ "error": "AUTH_TOKEN_INVALID" }`               | Invalid or already-used refresh token. |

<Warning>
  Refresh tokens rotate on every use. The previous refresh token is invalidated; store the new one from the response. Reusing an old refresh token returns `AUTH_TOKEN_INVALID` and triggers a session-wide invalidation as a token-theft signal.
</Warning>

### GET `/auth/.well-known/jwks.json`

Public JWKS endpoint for external services validating Archie-issued tokens. No authentication required.

**Response (200):**

```json theme={null}
{
  "keys": [
    {
      "kty": "RSA",
      "kid": "abc12345",
      "use": "sig",
      "alg": "RS256",
      "n": "...",
      "e": "AQAB"
    }
  ]
}
```

When JWE encryption is enabled, two keys are returned — one for signing and one for encryption:

```json theme={null}
{
  "keys": [
    {
      "kty": "RSA", "kid": "abc12345", "use": "sig", "alg": "RS256", "n": "...", "e": "AQAB"
    },
    {
      "kty": "RSA", "kid": "enc-def67890", "use": "enc", "alg": "RSA-OAEP-256", "n": "...", "e": "AQAB"
    }
  ]
}
```

The response is cached in-memory for 5 minutes. External services should refresh on `kid` mismatch.

## Protected endpoints

Require `Authorization: Bearer <accessToken>`.

### POST `/auth/logout`

Revoke the current access token and invalidate the associated refresh token.

```bash theme={null}
curl -X POST https://your-gateway.example.com/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "X-Project-Id: your-project-id" \
  -H "environment: master"
```

**Responses:**

| Status | Body                                       | When                      |
| ------ | ------------------------------------------ | ------------------------- |
| 200    | `{ "message": "Logged out successfully" }` | Token revoked.            |
| 401    | `{ "error": "AUTH_TOKEN_INVALID" }`        | Missing or invalid token. |

Revoked tokens are tracked in a distributed blacklist until their natural expiry, so re-presenting the access token after logout fails with `401`.

## Error codes reference

| Code                       | HTTP | Meaning                                                           |
| -------------------------- | ---- | ----------------------------------------------------------------- |
| `AUTH_EMAIL_EXISTS`        | 409  | Email already registered.                                         |
| `AUTH_INVALID_CREDENTIALS` | 401  | Wrong email or password.                                          |
| `AUTH_EMAIL_NOT_VERIFIED`  | 403  | Email not yet confirmed.                                          |
| `AUTH_ACCOUNT_LOCKED`      | 423  | Too many failed attempts; account temporarily locked.             |
| `AUTH_INVALID_CODE`        | 400  | Verification or recovery code wrong, expired, or out of attempts. |
| `AUTH_TOKEN_EXPIRED`       | 401  | Access token has expired.                                         |
| `AUTH_TOKEN_INVALID`       | 401  | Token malformed, revoked, or tampered.                            |
| `AUTH_NOT_CONFIGURED`      | 404  | Auth not enabled for this project + environment.                  |
| `RATE_LIMIT_EXCEEDED`      | 429  | Too many requests; honor `Retry-After`.                           |
| `VALIDATION_ERROR`         | 400  | Request body validation failed (see `violations`).                |

## Rate limits

| Endpoint                      | Limit       | Scope     |
| ----------------------------- | ----------- | --------- |
| `POST /auth/signup`           | 10 / minute | Per IP    |
| `POST /auth/login`            | 20 / minute | Per IP    |
| `POST /auth/recover-password` | 5 / minute  | Per email |

`429` responses include a `Retry-After` header indicating seconds to wait.

## FAQ

<AccordionGroup>
  <Accordion title="Why does `/auth/recover-password` always return 200?">
    To prevent email enumeration. If the response distinguished "exists" from "doesn't exist", an attacker could probe a list of emails and learn which are registered. Returning 200 either way removes that signal.
  </Accordion>

  <Accordion title="What's the safe way to validate tokens at the edge?">
    Fetch the JWKS from `/auth/.well-known/jwks.json` and validate signatures locally. Cache the JWKS on your edge service; refresh on `kid` mismatch (which signals key rotation). Don't call Archie on every request to validate.
  </Accordion>

  <Accordion title="My token validates locally but Archie rejects it on the API — why?">
    Most likely a revocation. Archie checks a distributed token blacklist on every request, so logged-out and force-logged-out tokens fail even if the signature is valid. The local-only validation can't see the blacklist.
  </Accordion>

  <Accordion title="How do I handle 423 Account Locked?">
    Surface a clear "account temporarily locked" message and either show the countdown from `lockedUntil` or offer a password reset link — a successful reset clears the lockout.
  </Accordion>

  <Accordion title="Can I rate-limit more aggressively for my own use case?">
    Put a [Custom API](/features/backend/app-services/custom-apis) in front of `/auth/login` with a tighter rate-limit policy. The auth endpoint defaults are platform-wide; per-route Custom API config lets you go stricter.
  </Accordion>
</AccordionGroup>
