# Business Made · Timesheets

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /business-made/timesheets

**List timesheets**

`operationId: TimesheetController_getTimesheets`

Timesheets across the org.

#### Signature

```http
GET /business-made/timesheets () -> Timesheets
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /business-made/timesheets/status/pending`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Timesheets |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets

**Create a timesheet**

`operationId: TimesheetController_createTimesheet`

Opens a timesheet for an employee and pay period. **One per employee per period** — a second is refused with a `409` that names the existing one.

#### Signature

```http
POST /business-made/timesheets (body) -> The created timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | TIMESHEET_EXISTS | Timesheet already exists for this pay period | The employee already has a timesheet for that period. | The body identifies the existing timesheet — add entries to it instead. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/entries`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The timesheet to create.

```json
{
  "employeeId": "EMP-4821",
  "payPeriodStart": "2026-09-01",
  "payPeriodEnd": "2026-09-15"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created timesheet |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `409` | Timesheet already exists for this pay period — The employee already has a timesheet for that period. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/{id}

**Get a timesheet**

`operationId: TimesheetController_getTimesheet`

Fetches one timesheet with its entries and totals.

#### Signature

```http
GET /business-made/timesheets/{id} (id: string) -> The timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/submit`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The timesheet |
| `400` | invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /business-made/timesheets/{id}

**Delete a timesheet**

`operationId: TimesheetController_deleteTimesheet`

Deletes a draft or rejected timesheet. A submitted or approved one is refused — it is the record payroll is calculated from; reopen a rejected one and correct it instead.

#### Signature

```http
DELETE /business-made/timesheets/{id} (id: string) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | CANNOT_DELETE | Only draft or rejected timesheets can be deleted | The timesheet is submitted or approved; the body carries its `status`. | Leave it, or have it rejected first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/reopen`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `400` | Only draft or rejected timesheets can be deleted — The timesheet is submitted or approved; the body carries its `status`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/update

**Update a timesheet**

`operationId: TimesheetController_updateTimesheet`

Updates a timesheet's own fields. Entries are managed through the entry endpoints. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.

#### Signature

```http
POST /business-made/timesheets/update (body) -> The updated timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/entries`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The timesheet to update.

```json
{
  "sk": "TS-4821",
  "data": {
    "note": "Includes on-call hours"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated timesheet |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/employee/{employeeId}

**Get an employee's timesheets**

`operationId: TimesheetController_getTimesheetsByEmployee`

One employee's timesheet history.

#### Signature

```http
GET /business-made/timesheets/employee/{employeeId} (employeeId: string) -> Timesheets
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /business-made/timesheets/reports/summary/{employeeId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Timesheets |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/status/pending

**List pending timesheets**

`operationId: TimesheetController_getPendingTimesheets`

Timesheets submitted and awaiting approval — the approval queue before a payroll run.

#### Signature

```http
GET /business-made/timesheets/status/pending () -> Pending timesheets
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/approve`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Pending timesheets |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/status/{status}

**List timesheets by status**

`operationId: TimesheetController_getTimesheetsByStatus`

Timesheets in any given status — the general form of the pending list.

#### Signature

```http
GET /business-made/timesheets/status/{status} (status: string) -> Timesheets
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /business-made/timesheets/status/pending`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `status` | path | string | yes | Timesheet status. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Timesheets |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/{id}/submit

**Submit a timesheet**

`operationId: TimesheetController_submitTimesheet`

Sends a timesheet for approval. **An empty timesheet is refused** — submitting nothing would otherwise pass silently into payroll as zero hours.

#### Signature

```http
POST /business-made/timesheets/{id}/submit (id: string) -> The submitted timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |
| `400` | INVALID_STATUS | Only draft timesheets can be submitted | The timesheet is not a draft. | Check the timesheet status first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/approve`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The submitted timesheet |
| `400` | Only draft timesheets can be submitted — The timesheet is not a draft. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/{id}/approve

**Approve a timesheet**

`operationId: TimesheetController_approveTimesheet`

Approves a submitted timesheet, making its hours available to payroll. Correcting one after approval requires reopening it.

#### Signature

```http
POST /business-made/timesheets/{id}/approve (id: string, body) -> The approved timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |
| `400` | INVALID_STATUS | Only submitted timesheets can be approved | The timesheet is not submitted. | Check the timesheet status first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/reopen`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

Optional approval note.

```json
{
  "note": "Overtime verified against the schedule"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The approved timesheet |
| `400` | Only submitted timesheets can be approved — The timesheet is not submitted. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/{id}/reject

**Reject a timesheet**

`operationId: TimesheetController_rejectTimesheet`

Sends a timesheet back with a reason, so the employee can correct and resubmit it.

#### Signature

```http
POST /business-made/timesheets/{id}/reject (id: string, body) -> The rejected timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |
| `400` | INVALID_STATUS | Only submitted timesheets can be rejected | The timesheet is not submitted. | Check the timesheet status first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/submit`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

Why it was rejected.

```json
{
  "reason": "Thursday hours do not match the rota"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The rejected timesheet |
| `400` | Only submitted timesheets can be rejected — The timesheet is not submitted. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/{id}/reopen

**Reopen a timesheet**

`operationId: TimesheetController_reopenTimesheet`

Puts a **rejected** timesheet back to draft so it can be corrected and submitted again. An approved timesheet cannot be reopened here.

#### Signature

```http
POST /business-made/timesheets/{id}/reopen (id: string) -> The reopened timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |
| `400` | INVALID_STATUS | Only rejected timesheets can be reopened | The timesheet is not rejected. | Check the timesheet status first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/approve`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The reopened timesheet |
| `400` | Only rejected timesheets can be reopened — The timesheet is not rejected. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/{id}/entries

**Add a time entry**

`operationId: TimesheetController_addTimeEntry`

Adds an entry to a timesheet — hours worked on a day, optionally against a project or cost code.

#### Signature

```http
POST /business-made/timesheets/{id}/entries (id: string, body) -> The updated timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |
| `400` | TIMESHEET_NOT_EDITABLE | Can only add entries to draft timesheets | The timesheet is not a draft (the message names the operation). | Reopen a rejected timesheet first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/entries/{entryId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

The entry to add.

```json
{
  "date": "2026-09-03",
  "hours": 7.5,
  "project": "PLATFORM",
  "note": "Migration work"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated timesheet |
| `400` | Can only add entries to draft timesheets — The timesheet is not a draft (the message names the operation). |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/{id}/entries/{entryId}

**Update a time entry**

`operationId: TimesheetController_updateTimeEntry`

Updates one entry on a timesheet.

#### Signature

```http
POST /business-made/timesheets/{id}/entries/{entryId} (id: string, entryId: string, body) -> The updated timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |
| `400` | TIMESHEET_NOT_EDITABLE | Can only update entries in draft timesheets | The timesheet is not a draft (the message names the operation). | Reopen a rejected timesheet first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `DELETE /business-made/timesheets/{id}/entries/{entryId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |
| `entryId` | path | string | yes | Entry id. |

### Request body

Fields to change.

```json
{
  "hours": 8
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated timesheet |
| `400` | Can only update entries in draft timesheets — The timesheet is not a draft (the message names the operation). |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /business-made/timesheets/{id}/entries/{entryId}

**Delete a time entry**

`operationId: TimesheetController_removeTimeEntry`

Removes an entry from a timesheet and recalculates its totals.

#### Signature

```http
DELETE /business-made/timesheets/{id}/entries/{entryId} (id: string, entryId: string) -> The updated timesheet
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |
| `400` | TIMESHEET_NOT_EDITABLE | Can only remove entries from draft timesheets | The timesheet is not a draft (the message names the operation). | Reopen a rejected timesheet first. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/{id}/entries`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |
| `entryId` | path | string | yes | Entry id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated timesheet |
| `400` | Can only remove entries from draft timesheets — The timesheet is not a draft (the message names the operation). |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/clock-in/{employeeId}

**Clock in**

`operationId: TimesheetController_clockIn`

Starts a work session for an employee on their current timesheet. `capture` is what the device could see (location, photo) — where the org requires a geofence or a photo and it does not match, the clock-in still goes through and is flagged for review (GET /business-made/timesheets/clock-events/needs-review). A readiness block (missing required training or documents) can be overridden by a manager with `override`.

#### Signature

```http
POST /business-made/timesheets/clock-in/{employeeId} (employeeId: string, body) -> The clock-in record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- ClockIn gate, judged against the station/location of the shift the person is scheduled on now (unscheduled = no station). Block: 423 `{ message, reason: "readiness_block", blocking[], trainingAtClockIn[], shift, canOverride, override }`; a manager signed in here resends with `override`. Success adds `readiness: { effect, warnings[], reasons[], overridden }` (null when nothing applies) and `trainingAtClockIn[]`.
- Workforce Readiness: with no requirement rules this endpoint behaves exactly as before. `warn` never stops anything — it comes back as `readiness.warnings` in the response.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | ALREADY_CLOCKED_IN | Already clocked in | The employee has an open clock-in; the body carries its `entryId` and `startTime`. | Clock out first. |
| `423` | READINESS_BLOCK | <name> can't clock in: <requirement titles>. | A requirement rule whose `enforcement.clockIn` effect is block (or override-with-reason) is unmet for this person, and no override is active. | Show `message` and `reasons`. A location manager or HR can resend the same request with `override: { reasonCode, reason, expiresAt }` — it is written to the readiness ledger with the approver and expiry. The person’s own override is refused. |
| `403` | OVERRIDE_FORBIDDEN | Sign in as a location manager or HR to override. | The caller may not approve an override (no caller, the person themselves, or only their own supervisor). | — |

Plus the standard platform errors: `401`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/clock-out/{employeeId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee id. |

### Request body

Optional clock-in details.

```json
{
  "notes": "Opening shift",
  "capture": {
    "lat": 51.5072,
    "lng": -0.1276
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The clock-in record |
| `400` | Already clocked in — The employee has an open clock-in; the body carries its `entryId` and `startTime`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Sign in as a location manager or HR to override. — The caller may not approve an override (no caller, the person themselves, or only their own supervisor). |
| `423` | <name> can't clock in: <requirement titles>. — A requirement rule whose `enforcement.clockIn` effect is block (or override-with-reason) is unmet for this person, and no override is active. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/clock-out/{employeeId}

**Clock out**

`operationId: TimesheetController_clockOut`

Ends the current work session and writes the hours to the timesheet. Refused when the employee is not clocked in, so a stray clock-out cannot create a phantom entry.

#### Signature

```http
POST /business-made/timesheets/clock-out/{employeeId} (employeeId: string, body) -> The completed session
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Someone who forgets to clock out leaves an open session — the on-the-clock report is where those surface.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NOT_CLOCKED_IN | Not clocked in | The employee has no active clock-in. | Check with `GET /business-made/timesheets/clock-in/{employeeId}/active`. |
| `404` | TIMESHEET_NOT_FOUND | Timesheet not found | No timesheet has that id. | The body carries `code` and the `timesheetId`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /business-made/timesheets/reports/on-the-clock`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee id. |

### Request body

Optional clock-out details.

```json
{
  "note": "Left early — appointment"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The completed session |
| `400` | Not clocked in — The employee has no active clock-in. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Timesheet not found — No timesheet has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/clock-events/needs-review

**Clock-ins waiting for review**

`operationId: TimesheetController_clockEventsNeedingReview`

Clock events (bm_clock_event) that did not match the rules — outside the allowed area, no location, no photo. Nobody is turned away at the door, so they land here instead. Newest first; reviewing one takes it off the list.

#### Signature

```http
GET /business-made/timesheets/clock-events/needs-review (employeeId?: string, from?: string, to?: string, page?: integer, pageSize?: integer) -> `{ data: bm_clock_event[], total, … }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/clock-events/{clockEventId}/review`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | query | string | — |  |
| `from` | query | string | — | Earliest event time (ISO). |
| `to` | query | string | — | Latest event time (ISO). |
| `page` | query | integer | — |  |
| `pageSize` | query | integer | — | Default 200. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ data: bm_clock_event[], total, … }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/clock-events/timesheet/{timesheetId}

**Clock events behind a timesheet**

`operationId: TimesheetController_clockEventsForTimesheet`

Every clock in and out recorded against one timesheet, oldest first (up to 500) — where its paid hours came from.

#### Signature

```http
GET /business-made/timesheets/clock-events/timesheet/{timesheetId} (timesheetId: string) -> `{ data: bm_clock_event[], total }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `timesheetId` | path | string | yes | bm_timesheet sk. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ data: bm_clock_event[], total }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/clock-events/{clockEventId}/review

**Accept a flagged clock-in**

`operationId: TimesheetController_reviewClockEvent`

Marks a clock event reviewed: records who looked (`reviewedBy`, default the caller) and why it was accepted, and clears `needsReview`.

#### Signature

```http
POST /business-made/timesheets/clock-events/{clockEventId}/review (clockEventId: string, body) -> The updated clock event
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `clockEventId` | path | string | yes | bm_clock_event sk. |

### Request body

```json
{
  "note": "Parked across the street — confirmed with the shift lead"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated clock event |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/timesheets/clock-ins/close-stale

**Close stale clock-ins**

`operationId: TimesheetController_closeStaleClockIns`

Closes clock-ins left open longer than `olderThanHours` (default 24), and ones whose employee no longer exists, **at 0 hours** — nobody is paid for a forgotten clock-out. `employeeIds` limits it to those people. `dryRun: true` returns the same list without writing.

#### Signature

```http
POST /business-made/timesheets/clock-ins/close-stale (body) -> `{ closed, skipped, dryRun, olderThanHours, oldestClockIn, entries:[{timesheetId, entryId, employeeId, employeeName, reason, clockInTime, elapsedHours}] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "olderThanHours": 18,
  "dryRun": true
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | `{ closed, skipped, dryRun, olderThanHours, oldestClockIn, entries:[{timesheetId, entryId, employeeId, employeeName, reason, clockInTime, elapsedHours}] }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/clock-in/{employeeId}/active

**Get an active clock-in**

`operationId: TimesheetController_getActiveClockIn`

Whether an employee is currently clocked in, and since when. What a clock-in button reads to decide which action to offer.

#### Signature

```http
GET /business-made/timesheets/clock-in/{employeeId}/active (employeeId: string) -> The active session, or none
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /business-made/timesheets/clock-out/{employeeId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The active session, or none |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/reports/summary/{employeeId}

**Get an employee time summary**

`operationId: TimesheetController_getTimesheetSummary`

Hours worked by one employee over a period, broken down by project or cost code.

#### Signature

```http
GET /business-made/timesheets/reports/summary/{employeeId} (employeeId: string) -> The time summary
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /business-made/timesheets/reports/team-status`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee id. |
| `startDate` | query | string | yes |  |
| `endDate` | query | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The time summary |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/reports/on-the-clock

**List who is on the clock**

`operationId: TimesheetController_getOnTheClock`

Everyone currently clocked in. Also where forgotten clock-outs show up — a session running far longer than a shift usually means someone left without clocking out.

#### Signature

```http
GET /business-made/timesheets/reports/on-the-clock () -> Employees currently clocked in
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /business-made/timesheets/reports/team-status`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Employees currently clocked in |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/reports/team-status

**Get team time status**

`operationId: TimesheetController_getTeamTimesheetStatus`

Who is working, on leave or absent right now — the shift-supervisor view.

#### Signature

```http
GET /business-made/timesheets/reports/team-status () -> Team status
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /business-made/timesheets/reports/on-the-clock`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `periodStart` | query | string | yes |  |
| `periodEnd` | query | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Team status |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/timesheets/reports/missing

**List missing timesheets**

`operationId: TimesheetController_getMissingTimesheets`

Employees who have not submitted a timesheet for a period — the chase list before a payroll run, and the one to clear first.

#### Signature

```http
GET /business-made/timesheets/reports/missing () -> Missing timesheets
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /business-made/timesheets/status/pending`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `start` | query | string | yes |  |
| `end` | query | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Missing timesheets |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

