Timesheet Approvals API
These endpoints run the same approval workflow as Time → Approval in Keito, including its preflight review, notifications, and audit log. They require the Timesheet approvals feature (Pro or Business) to be switched on for the workspace. Detect support through time-approvals-v1 in X-Keito-Features.
Each time entry’s current state is in its approval_status field. See Approval Status.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v2/approvals/submit |
Submit your own time for approval |
POST |
/api/v2/approvals/approve |
Approve a person’s submitted time and expenses |
POST |
/api/v2/approvals/reject |
Reject a person’s submitted time and expenses |
Dates are YYYY-MM-DD and inclusive, and a period can cover at most 366 days. Bodies are strict JSON: unknown fields return 400. Every endpoint accepts Idempotency-Key; a 409 response releases the key so you can retry with corrected input.
Permissions
| Identity | Submit | Approve and reject |
|---|---|---|
| Owner or Administrator | Own time | Anyone in the workspace |
| Manager | Own time | Teammates assigned to them, and time on projects they manage only with the approve time on projects they manage permission |
| Member or contractor | Own time | No |
| Personal read-only sync key | No | No |
A person outside your approval reach returns 403. A person or project from another workspace returns 404. If approvals are not available on the plan or are switched off, the endpoints return 403.
Submit Time
POST /api/v2/approvals/submit
Submits the authenticated person’s completed, unsubmitted, and unbilled time in the period, like Submit for approval on the Day and Week views, and notifies their approvers. There is no user_id: you cannot submit on behalf of a teammate.
| Field | Type | Required | Description |
|---|---|---|---|
from |
string | Yes | Start date |
to |
string | Yes | End date |
fingerprint |
string | No | Fingerprint from an earlier submit response. If the period has changed since, the request fails with submission_stale. |
curl -X POST https://app.keito.ai/api/v2/approvals/submit \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: submit-2026-09-21" \
--data '{"from":"2026-09-21","to":"2026-09-27"}'
A successful response returns submitted_count, submitted_seconds, and fingerprint. A running timer in the period returns 409 with error: "running_timer".
Approve Time
POST /api/v2/approvals/approve
Approves every submitted time entry and expense for one person in the period, optionally for one project. Approved time is locked, and its week refuses new or changed time (approved_timesheet_week_locked) until an approver withdraws the approval in Keito.
| Field | Type | Required | Description |
|---|---|---|---|
user_id |
string | Yes | Person whose time to approve |
from |
string | Yes | Start date |
to |
string | Yes | End date |
project_id |
string | No | Limit approval to one project |
preflight_fingerprint |
string | No | Fingerprint from the preflight you reviewed |
acknowledged_warning_codes |
string[] | No | Every warning code from that preflight |
curl -X POST https://app.keito.ai/api/v2/approvals/approve \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: approve-2026-09-21-user" \
--data '{"user_id":"user_id","from":"2026-09-21","to":"2026-09-27"}'
A successful response returns time_entry_count, expense_count, preflight_fingerprint, and acknowledged_warning_codes. If nothing was submitted in the period, the counts are 0.
Preflight Review
Approval runs the same preflight review as the app, and the API never skips it:
409 error |
Meaning | What to do |
|---|---|---|
approval_blocked |
Something prevents approval, such as a running timer or unsubmitted time in the period | Resolve the blocker. Blockers cannot be acknowledged. |
approval_warnings_unacknowledged |
The review found warnings, such as hours below expected, time outside working capacity, or overlapping entries | Check the returned preflight, then retry with its fingerprint as preflight_fingerprint and every warning code in acknowledged_warning_codes |
approval_preflight_stale |
The time changed after you reviewed it | Review the fresh preflight in the response and retry |
approval_conflict |
Time changed while the approval was being saved | Retry the request |
Each preflight error includes the current preflight with its fingerprint, blockers, and warnings.
Reject Time
POST /api/v2/approvals/reject
Rejects a person’s submitted time and expenses in the period and returns them to the person to correct and resubmit. The reason is included in their notification and the audit log.
| Field | Type | Required | Description |
|---|---|---|---|
user_id |
string | Yes | Person whose time to reject |
from |
string | Yes | Start date |
to |
string | Yes | End date |
project_id |
string | No | Limit to one project |
reason |
string | No | Explanation for the person, up to 2000 characters |
curl -X POST https://app.keito.ai/api/v2/approvals/reject \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID" \
-H "Content-Type: application/json" \
--data '{"user_id":"user_id","from":"2026-09-21","to":"2026-09-27","reason":"Please split the Tuesday entry by task."}'
A successful response returns time_entry_count and expense_count.
Withdrawing an approval is available in Keito but not yet through the API.