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

# Timesheet

> Unified view of all time-tracking records (read-only).

Unified view of all time-tracking records. Aggregates attendance check-ins and deliverable work sessions.

<Warning>
  **Read-only.** Only `GET /`, `GET /:id`, `GET /count`, `POST /count`, `POST /query`, and `POST /group-and-count` are available.
</Warning>

**Search columns:** `type`, `externalSync`, `externalSource`

## Fields

| Field            | Type           | Notes                                                                          |
| ---------------- | -------------- | ------------------------------------------------------------------------------ |
| `id`             | UUID           | Auto-generated                                                                 |
| `organizationId` | UUID           | From API key                                                                   |
| `startDate`      | date           | Session start (UTC)                                                            |
| `endDate`        | date \| null   | Session end; `null` = still open                                               |
| `workerId`       | UUID           | Worker who performed the work; resolve via `worker`                            |
| `type`           | string         | See types below                                                                |
| `shopId`         | UUID           | Shop the session belongs to                                                    |
| `isEdited`       | boolean        | Whether manually edited                                                        |
| `lastUpdatedBy`  | UUID \| null   | User who last modified                                                         |
| `buildOrderId`   | UUID \| null   | Always `null` — retained in the schema but no longer populated by any row type |
| `overheadId`     | UUID \| null   | Always `null` — retained in the schema but no longer populated by any row type |
| `stageId`        | UUID \| null   | Always `null` — retained in the schema but no longer populated by any row type |
| `deliverableId`  | UUID \| null   | Populated **only** for `deliverable_work_session` records                      |
| `shiftWorkHours` | object \| null | Shift schedule (see below)                                                     |
| `externalSource` | string \| null | Source system, e.g. `"quickbase"`                                              |
| `externalSync`   | string         | `"synced"` \| `"not synced"` \| `"none"`                                       |
| `metadata`       | object         | Flexible key-value store (e.g. `costCode`, `unionCode`)                        |
| `createdAt`      | date           | Auto-generated                                                                 |
| `updatedAt`      | date           | Auto-generated                                                                 |

## `type` values

Only two row types exist today:

| Value                      | Description                                     | Populated FK    |
| -------------------------- | ----------------------------------------------- | --------------- |
| `attendance`               | Attendance check-in/out record                  | *(none)*        |
| `deliverable_work_session` | Time logged against a specific deliverable node | `deliverableId` |

<Note>
  `build_order_work_session` and `overhead_work_session` row types (and the `buildOrderId` / `overheadId` / `stageId` foreign keys they used) have been removed server-side. `buildOrderId`, `overheadId`, and `stageId` remain in the response shape for backward compatibility but are always `null`. Order/stage/phase attribution for `deliverable_work_session` rows now goes exclusively through `deliverableId` — see the join example below.
</Note>

## `shiftWorkHours` shape

```json theme={null}
{
  "startTime": "08:00",
  "endTime": "17:00",
  "breaks": [{ "startTime": "12:00", "durationInSec": 1800 }]
}
```

Times are in `HH:mm` format. `breaks` is optional.

## Example queries

```bash theme={null}
# All open sessions (endDate is null)
GET /timesheet?endDate:isn=true

# Sessions for a specific worker in a date range
GET /timesheet?workerId=<uuid>&startDate:ge=2024-03-01&startDate:lt=2024-04-01

# Count by type
POST /timesheet/group-and-count
{ "groupByField": "type" }

# Unsynced deliverable sessions
GET /timesheet?type=deliverable_work_session&externalSync:ne=synced
```

## Loading hours with Order / Stage / Phase attribution

For `deliverable_work_session` rows, the time is logged against a stage/phase/component node, and the **only** link to the build tree is `deliverableId` (`buildOrderId` and `stageId` are always `null`). To attribute each row to its Order, Stage and Phase, join the deliverable node and then **chain** further joins off it:

* a join's condition may reference **another join's alias** (not just the base table), so you can hop `timesheet → deliverable → order` in a single request;
* declare the joins in dependency order — the join you reference (here `d`) must come **before** the joins that depend on it (`o`, `s`).

```bash theme={null}
curl "https://app.buildingswell.com/api/v2/timesheet?type=deliverable_work_session\
&startDate:ge=2024-03-01&startDate:lt=2024-04-01\
&join:deliverable%20as%20d:id,name,type,rootParentId,data,computed=deliverableId%20eq%20d.id\
&join:deliverable%20as%20o:id,name,identifier=o.id%20eq%20d.rootParentId\
&join:stage%20as%20s:id,name=s.id%20eq%20d.data.stageId" \
  -H "X-API-Key: <key>"
```

Each row comes back with:

* `o.name`, `o.identifier` → the **Order** (joined via the node's `rootParentId`)
* `s.name` → the **stage** (joined via the node's `data.stageId`)
* `d.computed.phase` → the **Phase name** (read straight off the joined node)

<Note>
  The `s` join matches `stage.id` against `data->>'stageId'`; the engine casts both sides to `uuid` automatically. Phase/component nodes won't have a linked stage, so `s` is `null` for those rows — read `computed.phase` for the phase regardless.
</Note>

<Tip>
  **Alternative (no chaining):** join only the deliverable node, read `d.computed.phase` directly, collect the distinct `d.rootParentId` / `d.data.stageId` values, and resolve their names with bulk `POST /deliverable/query` and `POST /stage/query` calls. Same result, more round-trips.
</Tip>

`attendance` rows have no build-tree attribution — `deliverableId`, `buildOrderId`, `overheadId`, and `stageId` are all `null` for them.
