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

# Admin Login

> POST /api/v1/auth/login — authenticate a bookkeeper admin with email and password and receive a JWT.

Authenticate a bookkeeper **admin** user with email and password. On success
returns the admin record plus a JWT, and sets an `auth-token` cookie.

```http theme={null}
POST /api/v1/auth/login
```

**Authentication:** none (this is how you obtain a token).
**Rate limit:** 5 attempts per 15 minutes, per client IP (returns `429` when exceeded).

## Request body

<ParamField body="email" type="string" required>
  The admin's email address. Must be a valid email; matched case-insensitively.
</ParamField>

<ParamField body="password" type="string" required>
  The admin's password. Must be at least 1 character (verified against the
  stored bcrypt hash).
</ParamField>

## Response

On success returns `200` with the authenticated admin user (password hash
stripped) and a signed JWT.

<ResponseField name="success" type="boolean">
  `true` on success.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="user" type="object">
      The admin user record, including its `bookkeeperOrg` (id, name, slug,
      email, phone, timezone, dateFormat, overtimeThreshold, overtimeMultiplier,
      active). The `passwordHash` field is removed.
    </ResponseField>

    <ResponseField name="token" type="string">
      The JWT to send as `Authorization: Bearer <token>` on subsequent admin
      requests. Also set as the `auth-token` httpOnly cookie.
    </ResponseField>
  </Expandable>
</ResponseField>

## Errors

| Status | `error`                   | Cause                                            |
| ------ | ------------------------- | ------------------------------------------------ |
| `400`  | `Invalid login data`      | Body failed validation (e.g. malformed email).   |
| `401`  | `Authentication failed`   | Email not found or password incorrect.           |
| `403`  | `Account inactive`        | The admin user is deactivated.                   |
| `403`  | `Organization inactive`   | The admin's bookkeeper organization is inactive. |
| `429`  | `Too many login attempts` | Login rate limit exceeded.                       |
| `500`  | `Login failed`            | Unexpected server error.                         |

<Note>
  Failed and successful logins are both audit-logged with the client IP and
  user agent. `Account inactive` and `Organization inactive` are returned
  before the password is checked once the user is found.
</Note>

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.paypunch.io/api/v1/auth/login \
    -H "Content-Type: application/json" \
    -d '{
      "email": "sarah@acmebookkeeping.com",
      "password": "Admin123!"
    }'
  ```

  ```json Request theme={null}
  {
    "email": "sarah@acmebookkeeping.com",
    "password": "Admin123!"
  }
  ```

  ```json Response (200) theme={null}
  {
    "success": true,
    "data": {
      "user": {
        "id": "0b6f...",
        "email": "sarah@acmebookkeeping.com",
        "firstName": "Sarah",
        "lastName": "Lee",
        "role": "SUPER_ADMIN",
        "active": true,
        "bookkeeperOrgId": "a1c2...",
        "lastLoginAt": "2026-06-17T14:03:11.000Z",
        "bookkeeperOrg": {
          "id": "a1c2...",
          "name": "Acme Bookkeeping",
          "slug": "acme-bookkeeping",
          "email": "billing@acmebookkeeping.com",
          "phone": "+15125550101",
          "timezone": "America/New_York",
          "dateFormat": "MM/DD/YYYY",
          "overtimeThreshold": 40,
          "overtimeMultiplier": 1.5,
          "active": true
        }
      },
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  }
  ```
</CodeGroup>
