> ## 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.

# Employee PIN Verify

> POST /api/v1/auth/employee/verify — authenticate an employee with phone number and PIN and receive a JWT.

Authenticate an **employee** with their phone number and PIN. On success returns
the employee record, current clock status, recent time entries, and a JWT, and
sets an `auth-token` cookie.

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

**Authentication:** none (this is how an employee obtains a token).

## Request body

<ParamField body="phone" type="string" required>
  The employee's phone number — at least 10 characters. The server normalizes
  the value (strips spaces, dashes, parentheses, dots) and also matches on the
  **last 4 digits**, so common formatting variations are tolerated.
</ParamField>

<ParamField body="pin" type="string" required>
  The employee's PIN — **4 to 6 digits** (regex `^\d{4,6}$`). Verified against
  the stored salted PIN hash.
</ParamField>

## Response

On success returns `200` with the employee (PIN and salt stripped), their clock
status, up to 5 recent time entries, and a JWT.

<ResponseField name="data.employee" type="object">
  The employee record with its `company` (id, name, payPeriodType, active,
  bookkeeperOrgId, and the org's overtime settings + timezone). The `pin` and
  `pinSalt` fields are removed.
</ResponseField>

<ResponseField name="data.clockStatus" type="object">
  <Expandable title="clockStatus">
    <ResponseField name="isClockedIn" type="boolean">
      `true` if the employee currently has an open time entry (no `clockOut`).
    </ResponseField>

    <ResponseField name="openTimeEntry" type="object | null">
      `{ id, clockIn }` of the open entry, or `null` if not clocked in.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="data.recentTimeEntries" type="array">
  The 5 most recent time entries (newest first), each with `id`, `clockIn`,
  `clockOut`, `totalHours`, `regularHours`, `overtimeHours`, `status`.
</ResponseField>

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

## Errors

| Status | `error`                                            | Cause                                                 |
| ------ | -------------------------------------------------- | ----------------------------------------------------- |
| `400`  | `Invalid PIN format` / `Invalid verification data` | PIN is not 4–6 digits, or the body failed validation. |
| `401`  | `Authentication failed`                            | No matching active employee, or PIN incorrect.        |
| `403`  | `Company inactive`                                 | The employee's company is deactivated.                |
| `500`  | `Verification failed`                              | Unexpected server error.                              |

<Note>
  Phone matching is lenient (exact match **or** last-4-digit suffix match) and
  only considers `active` employees. If two active employees share the same
  last 4 digits, the first match wins — pass a fully-formatted phone number to
  disambiguate.
</Note>

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.paypunch.io/api/v1/auth/employee/verify \
    -H "Content-Type: application/json" \
    -d '{
      "phone": "512-555-0142",
      "pin": "1234"
    }'
  ```

  ```json Request theme={null}
  {
    "phone": "512-555-0142",
    "pin": "1234"
  }
  ```

  ```json Response (200) theme={null}
  {
    "success": true,
    "data": {
      "employee": {
        "id": "e1f2...",
        "firstName": "John",
        "lastName": "Smith",
        "phone": "+15125550142",
        "employeeNumber": "EMP-001",
        "active": true,
        "companyId": "c9d8...",
        "company": {
          "id": "c9d8...",
          "name": "Builders R Us",
          "payPeriodType": "BI_WEEKLY",
          "active": true,
          "bookkeeperOrgId": "a1c2...",
          "bookkeeperOrg": {
            "overtimeThreshold": 40,
            "overtimeMultiplier": 1.5,
            "timezone": "America/New_York"
          }
        }
      },
      "clockStatus": {
        "isClockedIn": false,
        "openTimeEntry": null
      },
      "recentTimeEntries": [
        {
          "id": "t1...",
          "clockIn": "2026-06-16T13:00:00.000Z",
          "clockOut": "2026-06-16T21:30:00.000Z",
          "totalHours": 8,
          "regularHours": 8,
          "overtimeHours": 0,
          "status": "APPROVED"
        }
      ],
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  }
  ```
</CodeGroup>
