Approve a timesheet
Crew module
POST/timesheets/{id}/approvetimesheets.approve
Records an approval decision on the timesheet. The submitter cannot approve it, and an approval is voided when its hours change. Spend authority and separation of duties are enforced in the approval RPC; a refusal returns 422 and names the rule. Requires `Idempotency-Key`; a replay returns the original response. A rule that refuses the request returns 422 with `refusal` and `reason`.
- Requires the capability crew.timesheet.approve.
- OAuth scopes: crew.
- Send an Idempotency-Key header. A replay with the same key returns the original response.
- Error statuses: 400, 401, 404, 409, 422, 429. Errors are RFC 9457 problem details.
Path and query parameters
| Field | Type | Required | Description |
|---|---|---|---|
| id | string (uuid) | Yes | Identifier of the timesheet. |
Response 200: Timesheet
| Field | Type | Required | Description |
|---|---|---|---|
| id | string (uuid) | Yes | Identifier of the timesheet. |
| org_id | string (uuid) | Yes | Identifier of the owning organization, derived from the parent row and never accepted from the caller. |
| created_at | string (date-time) | Yes | When the row was created. |
| created_by | string (uuid) | null | Yes | Identifier of the person who created the row; null when a seed, platform or system process created it. |
| updated_at | string (date-time) | Yes | When the row was last updated. |
| updated_by | string (uuid) | null | Yes | Identifier of the person who last updated the row; null when a seed, platform or system process updated it. |
| timesheet_state | "Proposed" | "Submitted" | "Approved" | "Rejected" | Yes | Timesheet state. |
| project_id | string (uuid) | Yes | Identifier of the project worked. |
| membership_id | string (uuid) | null | Yes | Identifier of the membership of the person who worked; null for external crew, who carry an engagement. |
| engagement_id | string (uuid) | null | Yes | Identifier of the engagement of the person who worked when external crew; null for members. |
| period_start | string (date) | Yes | First day of the period. |
| period_end | string (date) | Yes | Last day of the period. |
| submitted_by | string (uuid) | null | Yes | Identifier of the person who submitted the timesheet. |
{
"id": "0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f",
"org_id": "0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f",
"created_at": "2026-10-08T14:30:00Z",
"created_by": "0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f",
"updated_at": "2026-10-08T14:30:00Z",
"updated_by": "0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f",
"timesheet_state": "Proposed",
"project_id": "0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f",
"membership_id": "0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f",
"engagement_id": "0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f",
"period_start": "2026-10-08",
"period_end": "2026-10-08",
"submitted_by": "0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f"
}Code samples
curl -X POST "$XOS_BASE_URL/timesheets/0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f/approve" \
-H "Authorization: Bearer $XOS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"const { data } = await xos.timesheets.approve("0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f");response = xos.timesheets.approve("0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f")xos timesheets approve 0192f1e2-7c3a-7b4d-9e8f-1a2b3c4d5e6f