Developers

REST API

Automate TimeWave over a simple HTTP API: projects, tasks, time entries and the timer. The same engine the MCP server runs on.

Base URL

https://timewave.cloud/api

Authentication

Send an API key in the Authorization: Bearer tw_… header. Create and revoke keys in Account & API keys. A key acts as you and can never do more than you can.

curl https://timewave.cloud/api/v1/tasks \
  -H "Authorization: Bearer tw_your_key_here"

Each key carries scopes. A request missing the required scope returns 403.

readRead projects, tasks, time & timer statustasksManage tasks (create, edit, move, comment)timerRun the timer & log time

Endpoints

Everything below is generated from the openapi.json the API itself serves, so these docs cannot drift from the code.

Auth

POST/v1/auth/loginExchange email and password for an API keyno key

Request bodyapplication/json

FieldTypeDescription
email*string
password*string
totpstringSix-digit code, or a backup code, when the account has 2FA
deviceNamestringShown in the key list. Defaults to "Mobile app".
orgIdstringRequired only when the account belongs to more than one organisation
POST/v1/auth/logoutRevoke the API key used for this callno key

Account

GET/v1/meWho this API key belongs toread
GET/v1/me/summaryYour own hours and earnings for a windowread

Query parameters

FieldTypeDescription
fromstringDefaults to the first of the current month
tostringInclusive. Defaults to the end of the current month
POST/v1/me/avatarUpload your profile pictureread
DELETE/v1/me/avatarRemove your profile pictureread
GET/v1/avatars/{userId}Somebody's profile pictureread
GET/v1/notificationsWhat happened while you were not lookingread

Query parameters

FieldTypeDescription
unreadbooleanOnly the ones not read yet
limitinteger(default 50)
POST/v1/notifications/{id}/readMark one as readread
POST/v1/notifications/read-allClear the badgeread
GET/v1/devicesEverything holding a key for youread
POST/v1/devices/pushRegister this device for pushread

Request bodyapplication/json

FieldTypeDescription
token*stringThe FCM registration token for this install
DELETE/v1/devices/pushStop pushing to this deviceread
DELETE/v1/devices/{id}Revoke a keyread
GET/v1/membersList members — the source of assigneeIdread

Projects

GET/v1/projectsList projects with their kanban columnsread

Tasks

GET/v1/tasksList tasksread

Query parameters

FieldTypeDescription
projectIdstring
status"open" | "done" | "all"(default all)
assigneeIdstringA user id, "me" for the caller, or "none" for unassigned tasks.
POST/v1/tasksCreate a tasktasks

Request bodyapplication/json

FieldTypeDescription
projectId*string
title*string
descriptionstring | null
priority"LOW" | "NORMAL" | "HIGH" | "URGENT"(default NORMAL)
assigneeIdstring | nullA userId from /v1/members
estimateMinutesinteger | null
dueDatestring | null
columnstringColumn name or id. Defaults to the first column.
GET/v1/tasks/{id}One task, with checklist and activityread
PATCH/v1/tasks/{id}Update a tasktasks

Request bodyapplication/json

Every field is optional. Send null to clear a nullable one; omit it to leave it alone.

FieldTypeDescription
titlestring
descriptionstring | null
priority"LOW" | "NORMAL" | "HIGH" | "URGENT"
assigneeIdstring | null
estimateMinutesinteger | null
dueDatestring | null
DELETE/v1/tasks/{id}Delete a tasktasks
POST/v1/tasks/{id}/moveMove a task to a kanban columntasks

Request bodyapplication/json

FieldTypeDescription
column*stringColumn name or id, within the task's own project
POST/v1/tasks/{id}/completeMark a task done, or reopen ittasks

Request bodyapplication/json

FieldTypeDescription
donebooleanfalse reopens(default true)
POST/v1/tasks/{id}/commentsComment on a tasktasks

Request bodyapplication/json

FieldTypeDescription
body*stringComment text
POST/v1/tasks/{id}/checklistAdd a checklist itemtasks

Request bodyapplication/json

FieldTypeDescription
text*string
PATCH/v1/tasks/{id}/checklist/{itemId}Tick or untick a checklist itemtasks

Request bodyapplication/json

FieldTypeDescription
doneboolean(default true)
DELETE/v1/tasks/{id}/checklist/{itemId}Remove a checklist itemtasks

Time

GET/v1/time-entriesList time entriesread

Query parameters

FieldTypeDescription
projectIdstring
taskIdstring
fromstringInclusive, YYYY-MM-DD
tostringExclusive, YYYY-MM-DD
limitinteger(default 50)
POST/v1/time-entriesLog time that has already happenedtimer

Request bodyapplication/json

Give the length as either durationSeconds or durationMinutes, and the day as date or startedAt.

FieldTypeDescription
durationSecondsinteger
durationMinutesnumber
datestringYYYY-MM-DD; recorded at midday that day
startedAtstring
projectIdstring | null
taskIdstring | nullAttaching the task is what makes the time show up against it
descriptionstring | null
billableboolean(default true)
trackedbooleanThe time was measured by a clock rather than typed in — for a client that ran its own timer while it could not reach the server. Recorded as tracked rather than manual, so the entry is not marked as handwritten on a timesheet.(default false)
GET/v1/time-entries/{id}One time entryread
PATCH/v1/time-entries/{id}Edit a time entrytimer

Request bodyapplication/json

Every field is optional. Edited entries are flagged as such in the UI.

FieldTypeDescription
durationSecondsinteger
durationMinutesnumber
datestringYYYY-MM-DD
startedAtstring
descriptionstring | null
projectIdstring | null
taskIdstring | null
billableboolean
DELETE/v1/time-entries/{id}Delete a time entrytimer

Timer

GET/v1/timerWhether a timer is running, and for how longread
POST/v1/timer/startStart the trackertimer

Request bodyapplication/json

All optional. A running timer is stopped first — only one can run at a time.

FieldTypeDescription
taskIdstring | nullStarting on a task fills in its project and description
projectIdstring | null
descriptionstring | null
startedAtstringWhen the timer was really started. For a client that was offline at the time; omit it and the timer starts now. Clamped to the past, and refused if that week's timesheet is locked.
POST/v1/timer/stopStop the running timertimer

Request bodyapplication/json

Optional. Without a body the timer stops now, which is what an online client means.

FieldTypeDescription
endedAtstringWhen the timer was really stopped, for a client that could not say so at the time. Clamped into [startedAt, now]. Not treated as an edit — the entry keeps its provenance.

Knowledge

GET/v1/docsDocuments you can see, newest firstdocs

Query parameters

FieldTypeDescription
qstringMatch on the title. Bodies are not searched — see the note in doc-service.
projectIdstring
parentIdstringChildren of one document, or `root` for the top of the tree
limitinteger(default 50)
POST/v1/docsCreate a documentdocs

Request bodyapplication/json

FieldTypeDescription
title*string
bodystringMarkdown. Defaults to a heading made from the title.
kind"REFERENCE" | "WIKI" | "TASK"
visibility"PRIVATE" | "RESTRICTED" | "ORG"Defaults to PRIVATE. A document created by an integration is no more visible than one created by hand.
projectIdstring | null
parentIdstring | null
GET/v1/docs/{id}One document, with its Markdowndocs
PATCH/v1/docs/{id}Change the title, the body, or who sees itdocs

Request bodyapplication/json

Only the fields sent are touched. The previous body is kept as a version.

FieldTypeDescription
titlestring
bodystringMarkdown, in full
visibility"PRIVATE" | "RESTRICTED" | "ORG"Author only
DELETE/v1/docs/{id}Delete a document. Author only.docs
GET/v1/docs/{id}/attachmentsFiles on a documentdocs
POST/v1/docs/{id}/attachmentsAttach a filedocs
DELETE/v1/docs/{id}/attachments/{attachmentId}Remove a file from a documentdocs
GET/v1/docs/{id}/versionsWhat this document said beforedocs

Query parameters

FieldTypeDescription
limitinteger(default 50)
GET/v1/docs/{id}/versions/{versionId}One old version, in fulldocs

Timesheets

GET/v1/timesheetsYour recent weeks and what happened to themread

Query parameters

FieldTypeDescription
limitintegerHow many weeks back, counting the current one(default 8)
GET/v1/timesheets/{week}One week, day by day and project by projectread
POST/v1/timesheets/{week}/submitHand the week intimer
POST/v1/timesheets/{week}/withdrawTake it back before anyone reviews ittimer

Secrets

GET/v1/secretsThere is no reading of the vaultsecrets
POST/v1/secretsPut a secret in, or add a version to one that existssecrets

Request bodyapplication/json

FieldTypeDescription
name*stringMatched without regard to case: writing `session_secret` over `SESSION_SECRET` adds a version to the one that is there rather than making a twin.
valuestringThe secret. Up to 8 KB — this is not a file store.
referencestringInstead of `value`: where the secret is actually kept, e.g. "1Password, vault Ops, item «prod db»". Nothing sensitive is stored.
notestringWhat this is and where it is used. Appears in lists — not secret.
warningstringShown next to the value whenever a person reveals it. For what breaks if this is rotated.
POST/v1/secrets/generateGenerate a secret, store it, and return it this oncesecrets

Request bodyapplication/json

FieldTypeDescription
name*string
lengthinteger(default 32)
alphabet"alnum" | "symbols" | "hex"`alnum` leaves out characters that get misread off a screen (0/O, 1/l/I).
notestring
warningstring

Prefer an AI assistant? The same actions are available over MCP.