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

FieldTypeRequiredDescription
project_idstringYesProject ID
task_idstringYesTask ID
spent_datestringYesDate of work (YYYY-MM-DD)
hoursnumberNoDuration in decimal hours for completed entries
is_runningbooleanNoCreate a running timer
started_timestringNoStart time (HH:mm) in the workspace timezone
ended_timestringNoEnd time (HH:mm) in the workspace timezone for completed entries
notesstringNoDescription of work
billablebooleanNoOverride billable status
sourcestringNoweb, cli, api, or agent
metadataobjectNoJSON 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

ParameterTypeDescription
pagenumberPage number
per_pagenumberResults per page
sourcestringFilter by source
project_idstringFilter by project
task_idstringFilter by task
user_idstringFilter by user
client_idstringFilter by client
fromstringStart date, inclusive
tostringEnd date, inclusive
is_billedbooleanFilter billed status
is_runningbooleanFilter running timers
updated_sincestringISO 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.