Start a time tracker on a task
Starts a stopwatch for the calling user on a task, optionally continuing an existing registration.
Creates a running time tracker for the authenticated user on TaskID, or on the task of TimeRegistrationID. If the caller already has a running tracker it is stopped first (at this tracker's start time) and converted into a time registration. If the source registration is dated the start day, the new tracker is linked to it and stopping adds the tracked time to that registration. Returns the new tracker.
Use it to:
- A timer button in an external client: start tracking on a task, stop later with the stop operation.
Rules across fields:
- R1: At least one of TaskID or TimeRegistrationID must be supplied. When both are sent, TaskID decides the task.
Behaviour:
- Not idempotent.
- Not safe to retry blindly. A retry starts a second tracker and stops (converts) the first one.
- Side effect: Stops the caller's running tracker (if any) at this tracker's start time and converts it into time registration(s) — full registration side effects.
- Side effect: Creates the tracker (TimeTrackerCreated event); notifies invoicing potential.
- Changes are visible to the next read immediately.
Call first: GET /v1/time-registration/{timeRegistrationID}
Next: POST /v1/time-registration/stop-time-tracker/{timeTrackerId}, GET /v1/time-tracker/active-time-tracker
Not yet documented: errors; examples (success).
curl -X POST "https://app3.timelog.com/your-account/api/v1/time-tracker/task/start-time-tracker" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"TaskID": "null",
"TimeRegistrationID": "null",
"IsBillable": "null",
"Minute": 42,
"BillableMinute": "null",
"Comment": "null",
"MonthlyPeriod": "null",
"AdditionalTextField": "null",
"Factor": "null",
"StartTime": "null"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/time-tracker/task/start-time-tracker"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"TaskID": "null",
"TimeRegistrationID": "null",
"IsBillable": "null",
"Minute": 42,
"BillableMinute": "null",
"Comment": "null",
"MonthlyPeriod": "null",
"AdditionalTextField": "null",
"Factor": "null",
"StartTime": "null"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/time-tracker/task/start-time-tracker", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"TaskID": "null",
"TimeRegistrationID": "null",
"IsBillable": "null",
"Minute": 42,
"BillableMinute": "null",
"Comment": "null",
"MonthlyPeriod": "null",
"AdditionalTextField": "null",
"Factor": "null",
"StartTime": "null"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"TaskID": "null",
"TimeRegistrationID": "null",
"IsBillable": "null",
"Minute": 42,
"BillableMinute": "null",
"Comment": "null",
"MonthlyPeriod": "null",
"AdditionalTextField": "null",
"Factor": "null",
"StartTime": "null"
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/time-tracker/task/start-time-tracker", bytes.NewBuffer(data))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
require 'net/http'
require 'json'
uri = URI('https://app3.timelog.com/your-account/api/v1/time-tracker/task/start-time-tracker')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request.body = '{
"TaskID": "null",
"TimeRegistrationID": "null",
"IsBillable": "null",
"Minute": 42,
"BillableMinute": "null",
"Comment": "null",
"MonthlyPeriod": "null",
"AdditionalTextField": "null",
"Factor": "null",
"StartTime": "null"
}'
response = http.request(request)
puts response.body
{
"Properties": {
"TimeTrackerID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"UserID": 123,
"Date": "2024-12-25T10:00:00Z",
"StartTime": "2024-12-25T10:00:00Z",
"EntityType": 42,
"EntityID": "null",
"Task": {
"TaskID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Name": "John Doe",
"No": "example_string",
"IsFixedPrice": true,
"IsDefaultBillable": true,
"AdditionalTextIsRequired": true,
"ParentFullName": "John Doe",
"ProjectID": 123,
"Project": {
"ProjectID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Name": "John Doe",
"No": "example_string",
"ExpenseIsBillable": "null",
"CustomerID": 123,
"Customer": {
"CustomerID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"Name": "John Doe",
"No": "example_string",
"DefaultMileageDistance": 25,
"CustomerStatusID": 123,
"VATNo": "example_string",
"OrganizationNo": "example_string"
},
"MainContractId": 123
},
"ParentTaskID": "null",
"IsTravelTimeTask": true,
"IsFavourite": "null"
},
"AbsenceCode": {
"AbsenceCodeID": 123,
"AbsenceCode": "example_string",
"Name": "John Doe",
"Description": "example_string",
"IsActive": true,
"FlexPositiveFactor": 3.14,
"SalaryAccountFactor": 3.14,
"SalaryAccountFactorEffect": 10,
"SalaryAccountID": 123,
"AbsenceCodeTypeID": 123,
"DeductCapacity": true,
"TimeTrackingFormat": 42,
"RequireApproval": true,
"IsPublicVisible": true,
"IsFavourite": "null",
"ID": "123e4567-e89b-12d3-a456-426614174000"
},
"TimeRegistrationID": "null",
"Comment": "example_string",
"Minute": 42,
"BillableMinute": 42,
"IsBillable": "null",
"TimeTrackerStatus": 42,
"MonthlyPeriod": "null",
"AdditionalTextField": "null",
"Factor": 3.14
},
"Links": [
{
"Href": "example_string",
"Rel": "example_string"
}
],
"Actions": [
{}
]
}
{
"error": "Bad Request",
"message": "The request contains invalid parameters or malformed data",
"code": 400,
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
{
"error": "Internal Server Error",
"message": "An unexpected error occurred on the server",
"code": 500,
"requestId": "req_1234567890"
}
/v1/time-tracker/task/start-time-tracker
Target server for requests. Edit to use your own host.
OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp).
The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user.
> Not yet documented: how to obtain a token.
> Not yet documented: token lifetime.
Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp).
The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user.
> Not yet documented: how to obtain a token.
> Not yet documented: token lifetime.The media type of the request body
Conditional. Required when TimeRegistrationID is not sent (rule R1). When both are sent, TaskID wins over the registration's task.
Refers to TaskRead.TaskID.
Conditional. Required when TaskID is not sent (rule R1).
Refers to TimeRegistrationRead.TimeRegistrationID. Look up valid values with GET /v1/time-registration/{timeRegistrationID}.
- The registration is dated the start day and the user does not use timestamps on project time — The tracker is LINKED to the registration: stopping adds the tracked minutes to it.
- Otherwise — Task, additional text, billable flag, factor and monthly period are copied; stopping creates a new registration.
Billable flag of the tracked time.
Minutes already worked before now. Without TimeRegistrationID it is the tracker's starting offset. With TimeRegistrationID and a value > 0 it is the EXPECTED total, and the offset becomes Minute − the registration's minutes. Unit: minutes
Unit: minutes - TimeRegistrationID is sent — Only used for a tracker started without a registration.
- Always — Overwrites the tracker comment, also when TimeRegistrationID is sent and Comment is omitted — the registration's comment is then dropped.
Monthly period label. When omitted with TimeRegistrationID, the registration's monthly period is used.
Additional text field value. When omitted with TimeRegistrationID, the registration's value is kept.
Unit: percent - Feature OvertimeFactor is ON and the value is not in setting RoundingFactorOptionOfTimeRegistrations — 400 'Invalid Factor value.' - Omitted — Keeps the registration's factor (with TimeRegistrationID), else the tracker default.
When tracking started. Converted to the employee's time zone (user profile) before use; a value without an offset is read as server-local time. Omitted → server clock now. The tracker Date is StartTime's date. Ignored (server now used) when TimeRegistrationID is sent, the user uses timestamps and the registration is project time. Time zone: employee_local
Request Preview
Response
Response will appear here after sending the request
Authentication
Bearer token. OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp).
The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user.
Not yet documented: how to obtain a token.
Not yet documented: token lifetime.
Body
Conditional. Required when TimeRegistrationID is not sent (rule R1). When both are sent, TaskID wins over the registration's task.
Refers to TaskRead.TaskID.
Conditional. Required when TaskID is not sent (rule R1).
Refers to TimeRegistrationRead.TimeRegistrationID. Look up valid values with GET /v1/time-registration/{timeRegistrationID}.
-
The registration is dated the start day and the user does not use timestamps on project time — The tracker is LINKED to the registration: stopping adds the tracked minutes to it.
-
Otherwise — Task, additional text, billable flag, factor and monthly period are copied; stopping creates a new registration.
Billable flag of the tracked time.
Minutes already worked before now. Without TimeRegistrationID it is the tracker's starting offset. With TimeRegistrationID and a value > 0 it is the EXPECTED total, and the offset becomes Minute − the registration's minutes.
Unit: minutes
Unit: minutes
- TimeRegistrationID is sent — Only used for a tracker started without a registration.
- Always — Overwrites the tracker comment, also when TimeRegistrationID is sent and Comment is omitted — the registration's comment is then dropped.
Monthly period label. When omitted with TimeRegistrationID, the registration's monthly period is used.
Additional text field value. When omitted with TimeRegistrationID, the registration's value is kept.
Unit: percent
-
Feature OvertimeFactor is ON and the value is not in setting RoundingFactorOptionOfTimeRegistrations — 400 'Invalid Factor value.'
-
Omitted — Keeps the registration's factor (with TimeRegistrationID), else the tracker default.
When tracking started. Converted to the employee's time zone (user profile) before use; a value without an offset is read as server-local time. Omitted → server clock now. The tracker Date is StartTime's date. Ignored (server now used) when TimeRegistrationID is sent, the user uses timestamps and the registration is project time.
Time zone: employee_local
Responses
A time tracker (stopwatch) of one employee. A tracker is either running (TimeTrackerStatus 1) or ended but not yet turned into a time registration (TimeTrackerStatus 2). When a tracker is stopped and its data forms a valid registration, the registration is written and the tracker row is deleted, so a tracker is short-lived.