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

# Bookkeeper Organizations

> Admin API for listing, creating, reading, updating, and deactivating bookkeeper organizations.

Manage bookkeeper organizations (the top-level tenant in PayPunch). All
endpoints require an **admin** JWT (`Authorization: Bearer <token>`).

| Method   | Path                                 | Purpose                        |
| -------- | ------------------------------------ | ------------------------------ |
| `GET`    | `/api/v1/admin/bookkeeper-orgs`      | List organizations (paginated) |
| `POST`   | `/api/v1/admin/bookkeeper-orgs`      | Create an organization         |
| `GET`    | `/api/v1/admin/bookkeeper-orgs/{id}` | Get one organization           |
| `PUT`    | `/api/v1/admin/bookkeeper-orgs/{id}` | Update an organization         |
| `DELETE` | `/api/v1/admin/bookkeeper-orgs/{id}` | Deactivate (soft delete)       |

***

## List organizations

```http theme={null}
GET /api/v1/admin/bookkeeper-orgs
```

### Query parameters

<ParamField query="page" type="integer" default="1">
  Page number (1-based).
</ParamField>

<ParamField query="pageSize" type="integer" default="20">
  Number of results per page.
</ParamField>

<ParamField query="search" type="string">
  Case-insensitive match against `name`, `slug`, and `email`.
</ParamField>

<ParamField query="active" type="string">
  Pass `true` to return only active organizations. Any other value returns all.
</ParamField>

### Response

<ResponseField name="data" type="object">
  Paginated envelope: `items`, `total`, `page`, `pageSize`, `totalPages`. Each
  item includes a `_count` of related `companies` and `adminUsers`.
</ResponseField>

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://app.paypunch.io/api/v1/admin/bookkeeper-orgs?page=1&pageSize=20&active=true" \
    -H "Authorization: Bearer <admin-token>"
  ```

  ```json Response (200) theme={null}
  {
    "success": true,
    "data": {
      "items": [
        {
          "id": "a1c2...",
          "name": "Acme Bookkeeping",
          "slug": "acme-bookkeeping",
          "email": "billing@acmebookkeeping.com",
          "timezone": "America/New_York",
          "overtimeThreshold": 40,
          "overtimeMultiplier": 1.5,
          "active": true,
          "_count": { "companies": 4, "adminUsers": 2 }
        }
      ],
      "total": 1,
      "page": 1,
      "pageSize": 20,
      "totalPages": 1
    }
  }
  ```
</CodeGroup>

***

## Create an organization

```http theme={null}
POST /api/v1/admin/bookkeeper-orgs
```

### Request body

<ParamField body="name" type="string" required>
  Organization name.
</ParamField>

<ParamField body="slug" type="string">
  URL slug. If omitted, generated from `name`. Must be unique.
</ParamField>

<ParamField body="email" type="string">
  Contact email (validated as an email when present).
</ParamField>

<ParamField body="phone" type="string" />

<ParamField body="address" type="string" />

<ParamField body="city" type="string" />

<ParamField body="state" type="string" />

<ParamField body="zipCode" type="string" />

<ParamField body="country" type="string" default="USA" />

<ParamField body="timezone" type="string" default="America/New_York" />

<ParamField body="dateFormat" type="string" default="MM/DD/YYYY" />

<ParamField body="overtimeThreshold" type="number" default="40">
  Weekly hours before overtime applies. Minimum `0`.
</ParamField>

<ParamField body="overtimeMultiplier" type="number" default="1.5">
  Overtime pay multiplier. Minimum `1`.
</ParamField>

Returns `201` with the created organization (and `_count`). Returns `400`
`Slug already exists` if the slug collides.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.paypunch.io/api/v1/admin/bookkeeper-orgs \
    -H "Authorization: Bearer <admin-token>" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Acme Bookkeeping",
      "email": "billing@acmebookkeeping.com",
      "timezone": "America/Chicago",
      "overtimeThreshold": 40,
      "overtimeMultiplier": 1.5
    }'
  ```

  ```json Response (201) theme={null}
  {
    "success": true,
    "data": {
      "id": "a1c2...",
      "name": "Acme Bookkeeping",
      "slug": "acme-bookkeeping",
      "active": true,
      "_count": { "companies": 0, "adminUsers": 0 }
    },
    "message": "Bookkeeper organization created successfully"
  }
  ```
</CodeGroup>

***

## Get an organization

```http theme={null}
GET /api/v1/admin/bookkeeper-orgs/{id}
```

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

Returns the organization including its `companies` (with employee counts) and
`adminUsers`. Returns `404` if not found.

```json Response (200) theme={null}
{
  "success": true,
  "data": {
    "id": "a1c2...",
    "name": "Acme Bookkeeping",
    "companies": [
      { "id": "c9d8...", "name": "Builders R Us", "active": true, "_count": { "employees": 12 } }
    ],
    "adminUsers": [
      { "id": "0b6f...", "firstName": "Sarah", "lastName": "Lee", "email": "sarah@acmebookkeeping.com", "role": "SUPER_ADMIN", "active": true }
    ]
  }
}
```

***

## Update an organization

```http theme={null}
PUT /api/v1/admin/bookkeeper-orgs/{id}
```

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

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

<ParamField body="name" type="string" />

<ParamField body="email" type="string" />

<ParamField body="phone" type="string" />

<ParamField body="address" type="string" />

<ParamField body="city" type="string" />

<ParamField body="state" type="string" />

<ParamField body="zipCode" type="string" />

<ParamField body="country" type="string" />

<ParamField body="timezone" type="string" />

<ParamField body="dateFormat" type="string" />

<ParamField body="overtimeThreshold" type="number">Minimum `0`.</ParamField>
<ParamField body="overtimeMultiplier" type="number">Minimum `1`.</ParamField>

<ParamField body="active" type="boolean">
  Set `false` to deactivate or `true` to reactivate.
</ParamField>

Returns `200` with the updated organization, or `404` if not found.

***

## Deactivate an organization

```http theme={null}
DELETE /api/v1/admin/bookkeeper-orgs/{id}
```

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

This is a **soft delete** — it sets `active = false` and returns the updated
record. Returns `404` if not found.

```json Response (200) theme={null}
{
  "success": true,
  "data": { "id": "a1c2...", "active": false },
  "message": "Bookkeeper organization deactivated successfully"
}
```

<Note>
  These handlers call `requireAuth(request, 'admin')`, so a valid admin token is
  required. The source comments mark create as "super admin only" but the
  handlers do not enforce a specific admin role beyond the `admin` token type.
</Note>
