Update a task
Changes the fields, status or plan position of an existing task and returns it.
A partial update (TaskUpdate): only the fields you send change. It can rename the task, change its dates, budget, hourly rate, task type and additional-text flag, change its status (Completed / Cancelled close it for registration), and move it under another task of the same project (ParentTaskID, 0 = root). The task's contract cannot be changed. All field, move and up-front status rules are checked on a detached copy before anything is saved, but the call is not atomic: a move is saved on its own first, and a status change does its own writes before the final project save. Answers 200 with the task read back as TaskRead — which has no dates, budget or status, so the response cannot confirm what changed.
Use it to:
- Keep task names, dates and budgets in sync from a PSA / ERP project plan
- Close a task for registration when the deliverable is done (Status 3) or cancelled (Status 4)
- Restructure a plan: move a task under another task, or back to the root
Not for:
- Changing the task's contract — not supported; a move into a contract sub-group is rejected
- A dry run →
POST /v1/task/{taskID}/validate-update-task
Rules across fields:
- R1: When both dates are sent, EndDate must be on or after StartDate (400 Code 200).
- R2: BudgetHours and BudgetAmount may not both be sent when an hourly rate drives the task's budget (400).
Behaviour:
- Not idempotent.
- Re-sending the same field values is a no-op for those fields and the same ParentTaskID is skipped; a status already reached is skipped. The project is still re-saved.
- Safe to retry only under a condition. Safe after a 4xx from the dry-run stage (nothing written). After a 500 or a timeout, GET the task (and list the project's tasks) before retrying: the move and status writes may have happened.
- Side effect: ParentTaskID: the task (with its sub-tree) is moved and the project saved immediately; WBS numbers are renumbered; the old parent's payment is rebuilt when it lost its last child. The task keeps its contract.
- Side effect: Status: closing / reopening runs TimeLog's task status processing — allocations set completed / not completed, the contract reopened when a closed task is reopened, vouchers reopened when leaving Completed, completion processing ('causes invoicing') when completing, the status pushed to sub-tasks of a parent; recurring tasks notify hard bookings.
- On failure: A rejected status change (400) or a later 500 leaves an earlier move saved; status-change writes done before a failure stay.
- Changes are visible to the next read immediately.
Call first: GET /v1/task, GET /v1/task/{taskID}
Next: GET /v1/task/{taskID}
See also: POST /v1/task/{taskID}/validate-update-task, POST /v1/task, DELETE /v1/task/{taskID}
Not yet documented: behaviour → events emitted; behaviour → side effects; examples.
curl -X PUT "https://app3.timelog.com/your-account/api/v1/task/123" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"TaskName": "null",
"TaskNo": "null",
"Description": "null",
"StartDate": "null",
"EndDate": "null",
"AdditionalTextIsRequired": "null",
"BudgetHours": "null",
"BudgetAmount": "null",
"HourlyRateID": "null",
"TaskTypeID": "null",
"Status": "null",
"ParentTaskID": "null"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/task/123"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"TaskName": "null",
"TaskNo": "null",
"Description": "null",
"StartDate": "null",
"EndDate": "null",
"AdditionalTextIsRequired": "null",
"BudgetHours": "null",
"BudgetAmount": "null",
"HourlyRateID": "null",
"TaskTypeID": "null",
"Status": "null",
"ParentTaskID": "null"
}
response = requests.put(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/task/123", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"TaskName": "null",
"TaskNo": "null",
"Description": "null",
"StartDate": "null",
"EndDate": "null",
"AdditionalTextIsRequired": "null",
"BudgetHours": "null",
"BudgetAmount": "null",
"HourlyRateID": "null",
"TaskTypeID": "null",
"Status": "null",
"ParentTaskID": "null"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"TaskName": "null",
"TaskNo": "null",
"Description": "null",
"StartDate": "null",
"EndDate": "null",
"AdditionalTextIsRequired": "null",
"BudgetHours": "null",
"BudgetAmount": "null",
"HourlyRateID": "null",
"TaskTypeID": "null",
"Status": "null",
"ParentTaskID": "null"
}`)
req, err := http.NewRequest("PUT", "https://app3.timelog.com/your-account/api/v1/task/123", 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/task/123')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Put.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request.body = '{
"TaskName": "null",
"TaskNo": "null",
"Description": "null",
"StartDate": "null",
"EndDate": "null",
"AdditionalTextIsRequired": "null",
"BudgetHours": "null",
"BudgetAmount": "null",
"HourlyRateID": "null",
"TaskTypeID": "null",
"Status": "null",
"ParentTaskID": "null"
}'
response = http.request(request)
puts response.body
{
"Properties": {
"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"
},
"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": "Forbidden",
"message": "You don't have permission to access this resource",
"code": 403
}
{
"error": "Internal Server Error",
"message": "An unexpected error occurred on the server",
"code": 500,
"requestId": "req_1234567890"
}
/v1/task/{taskID}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 task to update. Route constraint :int.
The media type of the request body
New display name of the task. Null: null / omitted = unchanged; there is no way to clear the name. - Sent as empty string — 400 'TaskName cannot be empty when supplied.' > Not yet documented: constraints → max length.
New task number. Null: null / omitted = unchanged. - The account's task-number setting auto-generates or hides task numbers — Passed to Task.ChangeNo with the ShowTaskNo setting; per the model's comment the value is then ignored. > Not yet documented: behaviour.
New free-text description. Null: null / omitted = unchanged. An empty string clears it.
New planned start of the task. Null: null / omitted = unchanged. Time zone: not yet documented. - Not a valid date (e.g. 0001-01-01) — 400 'StartDate is not a valid date.' - After the stored EndDate (EndDate not sent) — Not checked by the body validator; left to the task rules (task.Validate / save rules) — 400 from the controller. > Not yet documented: constraints → timezone. > Not yet documented: behaviour.
New planned end of the task. Null: null / omitted = unchanged. Time zone: not yet documented. - Not a valid date — 400 'EndDate is not a valid date.'. - Both dates sent and EndDate < StartDate — 400 'EndDate must be equal to or greater than StartDate.' > Not yet documented: constraints → timezone.
When true, time registrations on this task must carry an additional comment. Null: null / omitted = unchanged.
New budgeted hours of the task. Null: null / omitted = unchanged. Unit: hours - Negative — 400 (>= 0). - BudgetAmount also sent and an hourly rate drives the budget (time & material or Allocated rate model with HourlyRateID > 0; fixed price Calculated with a task hourly rate > 0) — 400 'BudgetHours and BudgetAmount cannot both be supplied when an hourly rate applies to the task…'. > Not yet documented: behaviour.
New budgeted revenue of the task. Null: null / omitted = unchanged. Unit: project currency - Negative — 400 (>= 0). - BudgetHours also sent and an hourly rate drives the budget — 400 (see BudgetHours). > Not yet documented: behaviour.
New contract hourly rate of the task; 0 = the default hourly rate.
Refers to ContractHourlyRateID.
Null: null / omitted = unchanged.
- Negative — 400 (>= 0).
- Changed, fixed-price contract with the Calculated rate model — ChangeHourlyRateId does nothing for this case, yet the call answers 200 (an existence check still runs for > 0).
- Applied BEFORE the budget fields — The adapter applies HourlyRateID first, so BudgetHours / BudgetAmount in the same request are judged against the NEW rate.
> Not yet documented: values → lookup op.
New task type (classification).
Refers to TaskTypeRead.TaskTypeID.
Null: null / omitted = unchanged. 0 is rejected, so a task type cannot be removed through this body.
- 0 or negative — 400 (> 0).
> Not yet documented: values → lookup op.
> Not yet documented: behaviour.
New status of the task. Completed and Cancelled close it for time registration.
Null: null / omitted = unchanged.
- Not a member — 400.
- Planning task and a status other than NotStarted / Completed — Violation 'A planning task can only be "not started" or "completed"'.
- Reopening (from Completed or Cancelled) — Reopens the task's contract when it is closed, sets allocations back to not completed; from Completed also re-opens the task's vouchers.
- Parent task (not a contract-sub-group primary) — The status is pushed down to all sub-tasks.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
| 0 | NotStarted | Not started. Rejected when the task already has time registrations (Task_ChangeState violation). |
| 1 | InProgress | Open and in progress. |
| 2 | OnHold | Open, paused. |
| 3 | Completed | Closed. |
> Not yet documented: values → members → meaning.
> Not yet documented: values → members → meaning.
Moves the task in the project plan — under this task, or to the root with 0.
Refers to TaskRead.TaskID. Look up valid values with GET /v1/task.
Null: null / omitted = placement unchanged. 0 = move to the end of the plan's root level.
- Negative — 400 (>= 0).
- Same as the current parent — No move.
- A task of another project — MovedTaskIsUnderSameProject violation (400).
- The task itself or one of its descendants — TaskCannotMoveToOwnSubTask violation (400).
- A contract sub-group task — 400 'ParentTaskID points at a contract sub group task…' — that move would change the task's contract and payments.
- Recurring / ordinary mix, a receiver that cannot become a parent, WBS depth exceeded, contract-sub-group WBS rule, project-level rules — 400 with the violation messages.
- Accepted — The move is SAVED on its own (MoveTaskUnderParentTask saves the project) before the other fields; the task keeps its contract (changeToParentContract false) and the old parent's payment is rebuilt.
Valid values come from GET /v1/task.
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.
Path Parameters
The task to update. Route constraint :int.
Body
New display name of the task.
Null: null / omitted = unchanged; there is no way to clear the name.
- Sent as empty string — 400 'TaskName cannot be empty when supplied.'
Not yet documented: constraints → max length.
New task number.
Null: null / omitted = unchanged.
- The account's task-number setting auto-generates or hides task numbers — Passed to Task.ChangeNo with the ShowTaskNo setting; per the model's comment the value is then ignored.
Not yet documented: behaviour.
New free-text description.
Null: null / omitted = unchanged. An empty string clears it.
New planned start of the task.
Null: null / omitted = unchanged.
Time zone: not yet documented.
-
Not a valid date (e.g. 0001-01-01) — 400 'StartDate is not a valid date.'
-
After the stored EndDate (EndDate not sent) — Not checked by the body validator; left to the task rules (task.Validate / save rules) — 400 from the controller.
Not yet documented: constraints → timezone.
Not yet documented: behaviour.
New planned end of the task.
Null: null / omitted = unchanged.
Time zone: not yet documented.
-
Not a valid date — 400 'EndDate is not a valid date.'.
-
Both dates sent and EndDate < StartDate — 400 'EndDate must be equal to or greater than StartDate.'
Not yet documented: constraints → timezone.
When true, time registrations on this task must carry an additional comment.
Null: null / omitted = unchanged.
New budgeted hours of the task.
Null: null / omitted = unchanged.
Unit: hours
-
Negative — 400 (>= 0).
-
BudgetAmount also sent and an hourly rate drives the budget (time & material or Allocated rate model with HourlyRateID > 0; fixed price Calculated with a task hourly rate > 0) — 400 'BudgetHours and BudgetAmount cannot both be supplied when an hourly rate applies to the task…'.
Not yet documented: behaviour.
New budgeted revenue of the task.
Null: null / omitted = unchanged.
Unit: project currency
-
Negative — 400 (>= 0).
-
BudgetHours also sent and an hourly rate drives the budget — 400 (see BudgetHours).
Not yet documented: behaviour.
New contract hourly rate of the task; 0 = the default hourly rate.
Refers to ContractHourlyRateID.
Null: null / omitted = unchanged.
-
Negative — 400 (>= 0).
-
Changed, fixed-price contract with the Calculated rate model — ChangeHourlyRateId does nothing for this case, yet the call answers 200 (an existence check still runs for > 0).
-
Applied BEFORE the budget fields — The adapter applies HourlyRateID first, so BudgetHours / BudgetAmount in the same request are judged against the NEW rate.
Not yet documented: values → lookup op.
New task type (classification).
Refers to TaskTypeRead.TaskTypeID.
Null: null / omitted = unchanged. 0 is rejected, so a task type cannot be removed through this body.
- 0 or negative — 400 (> 0).
Not yet documented: values → lookup op.
Not yet documented: behaviour.
New status of the task. Completed and Cancelled close it for time registration.
Null: null / omitted = unchanged.
-
Not a member — 400.
-
Planning task and a status other than NotStarted / Completed — Violation 'A planning task can only be "not started" or "completed"'.
-
Reopening (from Completed or Cancelled) — Reopens the task's contract when it is closed, sets allocations back to not completed; from Completed also re-opens the task's vouchers.
-
Parent task (not a contract-sub-group primary) — The status is pushed down to all sub-tasks.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
0 | NotStarted | Not started. Rejected when the task already has time registrations (Task_ChangeState violation). |
1 | InProgress | Open and in progress. |
2 | OnHold | Open, paused. |
3 | Completed | Closed. |
Not yet documented: values → members → meaning.
Not yet documented: values → members → meaning.
01234Moves the task in the project plan — under this task, or to the root with 0.
Refers to TaskRead.TaskID. Look up valid values with GET /v1/task.
Null: null / omitted = placement unchanged. 0 = move to the end of the plan's root level.
-
Negative — 400 (>= 0).
-
Same as the current parent — No move.
-
A task of another project — MovedTaskIsUnderSameProject violation (400).
-
The task itself or one of its descendants — TaskCannotMoveToOwnSubTask violation (400).
-
A contract sub-group task — 400 'ParentTaskID points at a contract sub group task…' — that move would change the task's contract and payments.
-
Recurring / ordinary mix, a receiver that cannot become a parent, WBS depth exceeded, contract-sub-group WBS rule, project-level rules — 400 with the violation messages.
-
Accepted — The move is SAVED on its own (MoveTaskUnderParentTask saves the project) before the other fields; the task keeps its contract (changeToParentContract false) and the old parent's payment is rebuilt.
Valid values come from GET /v1/task.
Responses
A short task representation for time-tracking pickers: the task's IDs, name and number, its parent, its project, and three flags that change how a registration on it behaves (fixed price, default billable, additional text required). It is not the full task (no dates, budget, status or contract). The TAF self link points to GET /v1/task/{TaskID}; entities also carry TAF actions (add-registration, start-time-tracker and, depending on the operation, create-task / create-sub-task / validate-new-task) describing other POST operations.