Time Entries API
All examples use:
https://app.keito.ai/api/v2
Add Authorization: Bearer <kto_...> and Keito-Account-Id: <company_id> to every API key request.
Create a Time Entry
POST /api/v2/time_entries
Creates a time entry. To start a running timer, set is_running to true.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
project_id | string | Yes | Project ID |
task_id | string | Yes | Task ID |
spent_date | string | Yes | Date of work (YYYY-MM-DD) |
hours | number | No | Duration in decimal hours for completed entries |
is_running | boolean | No | Create a running timer |
started_time | string | No | Start time (HH:mm) in the workspace timezone |
ended_time | string | No | End time (HH:mm) in the workspace timezone for completed entries |
notes | string | No | Description of work |
billable | boolean | No | Override billable status |
source | string | No | web, cli, api, or agent |
metadata | object | No | JSON object, max 4KB |
When is_running is true, omitting started_time starts the timer at the current time. To create a running timer that started in the past, send the past spent_date and optional started_time; the workspace must have Past timer starts enabled in Company Settings. Future start times are rejected.
Example Request
curl -X POST https://app.keito.ai/api/v2/time_entries \
-H "Authorization: Bearer kto_xxxxx" \
-H "Keito-Account-Id: your_company_id" \
-H "Content-Type: application/json" \
-d '{
"project_id": "project_id_here",
"task_id": "task_id_here",
"spent_date": "2026-05-05",
"hours": 1.5,
"notes": "Refactored authentication module",
"source": "api",
"metadata": {
"session_id": "550e8400-e29b-41d4-a716-446655440000"
}
}'
List Time Entries
GET /api/v2/time_entries
Returns a paginated list of time entries.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
page | number | Page number |
per_page | number | Results per page |
source | string | Filter by source |
project_id | string | Filter by project |
task_id | string | Filter by task |
user_id | string | Filter by user |
client_id | string | Filter by client |
from | string | Start date, inclusive |
to | string | End date, inclusive |
is_billed | boolean | Filter billed status |
is_running | boolean | Filter running timers |
updated_since | string | ISO timestamp lower bound |
Example Request
curl "https://app.keito.ai/api/v2/time_entries?source=cli&from=2026-05-01&to=2026-05-31" \
-H "Authorization: Bearer kto_xxxxx" \
-H "Keito-Account-Id: your_company_id"
Example Response Shape
{
"time_entries": [],
"per_page": 100,
"total_pages": 0,
"total_entries": 0,
"page": 1,
"links": {
"first": "/api/v2/time_entries?page=1&per_page=100",
"next": null,
"previous": null,
"last": "/api/v2/time_entries?page=1&per_page=100"
}
}
Update a Time Entry
PATCH /api/v2/time_entries/:id
Updates editable fields on a time entry. source is immutable after creation.
curl -X PATCH https://app.keito.ai/api/v2/time_entries/time_entry_id \
-H "Authorization: Bearer kto_xxxxx" \
-H "Keito-Account-Id: your_company_id" \
-H "Content-Type: application/json" \
-d '{
"hours": 1.5,
"notes": "Completed OAuth implementation"
}'
Stop a Running Timer
PATCH /api/v2/time_entries/:id/stop
Stops a running time entry and calculates elapsed duration server-side.
curl -X PATCH https://app.keito.ai/api/v2/time_entries/time_entry_id/stop \
-H "Authorization: Bearer kto_xxxxx" \
-H "Keito-Account-Id: your_company_id" \
-H "Content-Type: application/json" \
-d '{"notes": "Finished implementation"}'
Delete a Time Entry
DELETE /api/v2/time_entries/:id
Deletes a time entry. Approved or locked entries cannot be deleted.
curl -X DELETE https://app.keito.ai/api/v2/time_entries/time_entry_id \
-H "Authorization: Bearer kto_xxxxx" \
-H "Keito-Account-Id: your_company_id"
Returns HTTP 204 No Content on success.