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

# Pay Periods

> Admin API for listing, creating, reading, updating, and deleting pay periods, scoped to the admin's bookkeeper organization.

Manage pay periods for the client companies in your bookkeeper organization. All
endpoints require an **admin** JWT and enforce tenant isolation: you can only
touch pay periods whose company belongs to your `bookkeeperOrgId`.

| Method   | Path                             | Purpose                                              |
| -------- | -------------------------------- | ---------------------------------------------------- |
| `GET`    | `/api/v1/admin/pay-periods`      | List pay periods (paginated)                         |
| `POST`   | `/api/v1/admin/pay-periods`      | Create a pay period                                  |
| `GET`    | `/api/v1/admin/pay-periods/{id}` | Get one pay period (with entries + totals)           |
| `PUT`    | `/api/v1/admin/pay-periods/{id}` | Update status / lock / pay date / export metadata    |
| `DELETE` | `/api/v1/admin/pay-periods/{id}` | Delete a pay period (only if it has no time entries) |

***

## List pay periods

```http theme={null}
GET /api/v1/admin/pay-periods
```

### Query parameters

<ParamField query="companyId" type="string">
  Filter to one company. The company must belong to your organization, or the
  request returns `403`. If omitted, all pay periods within your organization
  are returned.
</ParamField>

<ParamField query="status" type="string">
  Filter by status: `OPEN`, `SUBMITTED`, `APPROVED`, `PAID`, or `CLOSED`.
</ParamField>

<ParamField query="page" type="integer" default="1" />

<ParamField query="pageSize" type="integer" default="20" />

### Response

Paginated envelope (`items`, `total`, `page`, `pageSize`, `totalPages`), ordered
by `startDate` descending. Each item includes its `company` (`id`, `name`) and a
`_count` of `timeEntries`.

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://app.paypunch.io/api/v1/admin/pay-periods?companyId=c9d8...&status=OPEN" \
    -H "Authorization: Bearer <admin-token>"
  ```

  ```json Response (200) theme={null}
  {
    "success": true,
    "data": {
      "items": [
        {
          "id": "pp_01...",
          "companyId": "c9d8...",
          "periodType": "BI_WEEKLY",
          "startDate": "2026-06-01T00:00:00.000Z",
          "endDate": "2026-06-14T00:00:00.000Z",
          "payDate": "2026-06-19T00:00:00.000Z",
          "status": "OPEN",
          "locked": false,
          "company": { "id": "c9d8...", "name": "Builders R Us" },
          "_count": { "timeEntries": 23 }
        }
      ],
      "total": 1,
      "page": 1,
      "pageSize": 20,
      "totalPages": 1
    }
  }
  ```
</CodeGroup>

***

## Create a pay period

```http theme={null}
POST /api/v1/admin/pay-periods
```

### Request body

<ParamField body="companyId" type="string" required>
  UUID of the client company. Must belong to your organization (`403` otherwise,
  `404` if the company does not exist).
</ParamField>

<ParamField body="periodType" type="string" required>
  One of `WEEKLY`, `BI_WEEKLY`, `SEMI_MONTHLY`, `MONTHLY`.
</ParamField>

<ParamField body="startDate" type="string" required>
  ISO-8601 datetime or a `YYYY-MM-DD` date.
</ParamField>

<ParamField body="endDate" type="string">
  ISO-8601 datetime or `YYYY-MM-DD`. If omitted, it is computed from
  `periodType` and `startDate`.
</ParamField>

<ParamField body="payDate" type="string">
  ISO-8601 datetime or `YYYY-MM-DD`. Optional.
</ParamField>

New pay periods are created with `status: "OPEN"` and `locked: false`. The
server rejects date ranges that overlap an existing pay period for the same
company with `400` `Overlapping pay period`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.paypunch.io/api/v1/admin/pay-periods \
    -H "Authorization: Bearer <admin-token>" \
    -H "Content-Type: application/json" \
    -d '{
      "companyId": "c9d8...",
      "periodType": "BI_WEEKLY",
      "startDate": "2026-06-15",
      "payDate": "2026-07-03"
    }'
  ```

  ```json Response (201) theme={null}
  {
    "success": true,
    "data": {
      "id": "pp_02...",
      "companyId": "c9d8...",
      "periodType": "BI_WEEKLY",
      "startDate": "2026-06-15T00:00:00.000Z",
      "endDate": "2026-06-28T00:00:00.000Z",
      "payDate": "2026-07-03T00:00:00.000Z",
      "status": "OPEN",
      "locked": false,
      "company": { "id": "c9d8...", "name": "Builders R Us" }
    },
    "message": "Pay period created successfully"
  }
  ```
</CodeGroup>

***

## Get a pay period

```http theme={null}
GET /api/v1/admin/pay-periods/{id}
```

<ParamField path="id" type="string" required>
  The pay period's UUID.
</ParamField>

Returns the pay period with its `company`, all `timeEntries` (each with a
trimmed `employee`), and a computed `totals` object summing `totalHours`,
`regularHours`, and `overtimeHours`. Returns `404` if not found, `403` if the
period belongs to another organization.

```json Response (200) theme={null}
{
  "success": true,
  "data": {
    "id": "pp_01...",
    "status": "OPEN",
    "company": { "id": "c9d8...", "name": "Builders R Us", "bookkeeperOrgId": "a1c2..." },
    "timeEntries": [
      {
        "id": "t1...",
        "totalHours": 8,
        "regularHours": 8,
        "overtimeHours": 0,
        "employee": { "id": "e1f2...", "firstName": "John", "lastName": "Smith", "employeeNumber": "EMP-001" }
      }
    ],
    "totals": { "totalHours": 8, "regularHours": 8, "overtimeHours": 0 }
  }
}
```

***

## Update a pay period

```http theme={null}
PUT /api/v1/admin/pay-periods/{id}
```

<ParamField path="id" type="string" required>
  The pay period's UUID.
</ParamField>

All body fields are optional; only provided fields are applied.

<ParamField body="status" type="string">
  One of `OPEN`, `SUBMITTED`, `APPROVED`, `PAID`, `CLOSED`.
</ParamField>

<ParamField body="locked" type="boolean">
  Lock or unlock the pay period.
</ParamField>

<ParamField body="payDate" type="string">
  ISO-8601 datetime or `YYYY-MM-DD`.
</ParamField>

<ParamField body="exportedBy" type="string">
  Marks the period as exported; the server also stamps `exportedAt` with the
  current time.
</ParamField>

<ParamField body="iifFilename" type="string">
  Filename of the generated QuickBooks IIF export.
</ParamField>

Returns `200` with the updated pay period. Returns `404` if not found, `403` for
cross-organization access.

***

## Delete a pay period

```http theme={null}
DELETE /api/v1/admin/pay-periods/{id}
```

<ParamField path="id" type="string" required>
  The pay period's UUID.
</ParamField>

Permanently deletes the pay period. Returns `400` `Cannot delete pay period` if
it has any associated time entries. Returns `404` if not found, `403` for
cross-organization access.

```json Response (200) theme={null}
{
  "success": true,
  "message": "Pay period deleted successfully"
}
```
