Create a sub-task under an existing task
Adds a task under an existing task of the same project and returns it.
Creates a task one level below ParentTaskID, at the parent's next sub-WBS number, on the contract you name (default: the parent's contract). Everything else — body rules, TDR payment handling, project re-save, events, response — is as on POST /v1/task. The parent must be a task of ProjectID: it is matched by WBS number inside ProjectID, so a parent from another project silently resolves to a different task. Turning a task into a parent DELETES the parent's own instalments (non-contract-sub-group tasks).
Use it to:
- Build a multi-level task structure (phase → task) from an external project plan
Not for:
- A top-level task →
POST /v1/task - A dry run of a sub-task —
POST /v1/task/validate-new-taskvalidates a ROOT task only
Rules across fields:
- R1: Time & material contract: BudgetHours, BudgetAmount and HourlyRateID may not ALL be non-zero (400).
- R2: TDR contract: BudgetHours, BudgetAmount and TaskHourlyRate may not ALL be non-zero (400).
- R3: TDR contract: TaskHourlyRate must be 0 when HourlyRateID is not 0 (400).
- R4: TDR contract: PaymentRecognitionModel must be 1 or 2; with 2, PaymentProductNo, PaymentName and PaymentInvoiceDate are required (400).
- R5: EndDate must be on or after StartDate (400).
- R6: ParentTaskID must be a task of ProjectID. Not checked as such: the parent is matched by WBS number inside ProjectID.
Behaviour:
- Not idempotent.
- Not safe to retry blindly. Every call inserts a new sub-task. After a timeout or a 500, list the project's tasks before retrying.
- Side effect: The parent becomes a parent task: its parent properties are recalculated and every instalment linked to the parent is DELETED from the billing plan (unless the parent is a contract sub-group task).
- Side effect: Billing plan notification, TDR / TMB instalment update and project re-save as on
POST /v1/task. - On failure: As on create: the task, the parent's instalment deletion and the project re-save are committed before MapTDRTaskWithPayment.
- Changes are visible to the next read immediately.
Call first: GET /v1/task, POST /v1/task
Next: GET /v1/task/{taskID}
See also: POST /v1/task
curl -X POST "https://app3.timelog.com/your-account/api/v1/task/create-sub-task" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"ProjectID": 123,
"ParentTaskID": "null",
"TaskName": "John Doe",
"TaskNo": "null",
"Description": "null",
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"AdditionalTextIsRequired": true,
"IsBillable": true,
"IsReadyForInvoicing": true,
"ProjectSubContractID": "null",
"HourlyRateID": 123,
"BudgetHours": 3.14,
"BudgetAmount": 3.14,
"TaskTypeID": "null",
"PaymentRecognitionModel": 42,
"PaymentAmount": 3.14,
"TaskHourlyRate": 3.14,
"PaymentProductNo": "null",
"PaymentName": "null",
"PaymentInvoiceDate": "null"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/task/create-sub-task"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"ProjectID": 123,
"ParentTaskID": "null",
"TaskName": "John Doe",
"TaskNo": "null",
"Description": "null",
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"AdditionalTextIsRequired": true,
"IsBillable": true,
"IsReadyForInvoicing": true,
"ProjectSubContractID": "null",
"HourlyRateID": 123,
"BudgetHours": 3.14,
"BudgetAmount": 3.14,
"TaskTypeID": "null",
"PaymentRecognitionModel": 42,
"PaymentAmount": 3.14,
"TaskHourlyRate": 3.14,
"PaymentProductNo": "null",
"PaymentName": "null",
"PaymentInvoiceDate": "null"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/task/create-sub-task", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"ProjectID": 123,
"ParentTaskID": "null",
"TaskName": "John Doe",
"TaskNo": "null",
"Description": "null",
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"AdditionalTextIsRequired": true,
"IsBillable": true,
"IsReadyForInvoicing": true,
"ProjectSubContractID": "null",
"HourlyRateID": 123,
"BudgetHours": 3.14,
"BudgetAmount": 3.14,
"TaskTypeID": "null",
"PaymentRecognitionModel": 42,
"PaymentAmount": 3.14,
"TaskHourlyRate": 3.14,
"PaymentProductNo": "null",
"PaymentName": "null",
"PaymentInvoiceDate": "null"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"ProjectID": 123,
"ParentTaskID": "null",
"TaskName": "John Doe",
"TaskNo": "null",
"Description": "null",
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"AdditionalTextIsRequired": true,
"IsBillable": true,
"IsReadyForInvoicing": true,
"ProjectSubContractID": "null",
"HourlyRateID": 123,
"BudgetHours": 3.14,
"BudgetAmount": 3.14,
"TaskTypeID": "null",
"PaymentRecognitionModel": 42,
"PaymentAmount": 3.14,
"TaskHourlyRate": 3.14,
"PaymentProductNo": "null",
"PaymentName": "null",
"PaymentInvoiceDate": "null"
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/task/create-sub-task", 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/create-sub-task')
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 = '{
"ProjectID": 123,
"ParentTaskID": "null",
"TaskName": "John Doe",
"TaskNo": "null",
"Description": "null",
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"AdditionalTextIsRequired": true,
"IsBillable": true,
"IsReadyForInvoicing": true,
"ProjectSubContractID": "null",
"HourlyRateID": 123,
"BudgetHours": 3.14,
"BudgetAmount": 3.14,
"TaskTypeID": "null",
"PaymentRecognitionModel": 42,
"PaymentAmount": 3.14,
"TaskHourlyRate": 3.14,
"PaymentProductNo": "null",
"PaymentName": "null",
"PaymentInvoiceDate": "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": [
{}
]
}
{
"Code": 200,
"Message": "Invalid model state error.",
"Details": [
"…"
],
"Parameters": "…",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/task/create-sub-task"
}
{
"Message": "Authorization has been denied for this request."
}
{
"Code": 100,
"Details": [],
"Message": "Unknown error occur in system, please contact system administrator.",
"Parameters": "taskModel: {...,"ProjectID":0,"ParentTaskID":null,...}",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/task/create-sub-task"
}
/v1/task/create-sub-task
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.
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.The media type of the request body
The project the task is created on.
Can be set on create only.
Refers to ProjectRead.ProjectID. Look up valid values with GET /v1/project/get-all.
- 0 or omitted — Should be 400 'Project ID must be greater than 0', but the validator's contract rules crash first: 500 Code 100.
- Unknown ID — 500 Code 102 / 37014 (rest.projectmanagement.project-not-found) — or 500 Code 100 when the validator's contract lookup runs first.
Valid values come from GET /v1/project/get-all.
The existing task the new task is placed under (create-sub-task only).
Can be set on create only.
Refers to TaskRead.TaskID. Look up valid values with GET /v1/task.
- Sent as 0 or negative — 400 (must be > 0 when present).
- create-sub-task: the task is on ANOTHER project — The parent is re-resolved by its WBS number inside ProjectID: the sub-task lands under the task of ProjectID that has the same WBS number, or the call fails with 500.
- create-sub-task: null — Treated as task 0: 500 rest.projectmanagement.task-not-found (37001).
Valid values come from GET /v1/task.
Display name of the task.
The task number shown next to the name (searchable by the time-tracking searches). Copied as sent when the account's task number series is off. When the task number series is on, the number you send is overwritten with the next number from the series (except on template projects).
Free-text description of the task.
Planned start of the task. - Omitted (0001-01-01) — 400 'StartDate is not a valid date.'
Planned end of the task. - Omitted (0001-01-01) — 400 with the message 'StartDate is not a valid date.'. - Before StartDate — 400 'EndDate must be equal to or greater than StartDate.' - Time-and-material basic contract — Becomes the invoice date of the task's instalment.
When true, time registrations on this task must carry an additional comment.
Default billable flag of time registered on the task. Omitted = false (non-billable task). Read back as TaskRead.IsDefaultBillable.
Marks the task (and, for TDR fixed-price-payment / time-and-material-basic tasks, its instalment) ready for invoicing.
The contract of the project the task is invoiced on.
Refers to ContractRead.ContractID. Look up valid values with GET /v1/contract.
- Omitted / null on POST /v1/task and validate-new-task — The project's main contract.
- Omitted / null on create-sub-task — The parent task's contract.
- 0 or negative — 400 (must be > 0 when present).
- Not a contract of ProjectID — 500 Code 102 / 37042 (rest.projectmanagement.task-contract-not-on-project).
Valid values come from GET /v1/contract.
The contract hourly rate used for the task; 0 = the employee's default hourly rate.
Refers to ContractHourlyRateID.
- Null / omitted — Required (NotNull) — but an empty body fails earlier with 500 Code 100.
- > 0 and not a rate of the contract — 500 Code 102 / 37944 or 37043 (rest.projectmanagement.task-hourly-rate-not-on-contract).
- > 0, fixed-price contract on a project with hourly-rate model Calculated — Set to 0; the task's hourly rate is derived instead (TDR: TaskHourlyRate; other fixed price: the contract's target hourly rate).
- TDR contract and HourlyRateID != 0 — TaskHourlyRate must then be 0 (400 'Hourly rate must be 0 when HourlyRateID is selected.').
Budgeted hours of the task. Unit: hours - Null / omitted — Required (NotNull, >= 0) — an empty body fails earlier with 500 Code 100. - Time & material contract, BudgetAmount and HourlyRateID also non-zero — 400: only two of BudgetHours, BudgetAmount, HourlyRateID may be non-zero. - HourlyRateID > 0 and BudgetHours = 0 — Derived: BudgetAmount / the rate's amount, rounded to InvoiceSetting_NumberOfDecimals. - TDR on a Calculated-rate project and BudgetHours = 0 — Derived: BudgetAmount / TaskHourlyRate (1 when the rate is 0).
Budgeted revenue of the task. Unit: project currency - Null / omitted — Required (NotNull, >= 0) — an empty body fails earlier with 500 Code 100. - HourlyRateID > 0 and BudgetAmount = 0 — Derived: BudgetHours x the rate's amount. - TDR contract — Stored as the budget; the task's allocated revenue is PaymentAmount instead. - Otherwise — Also becomes the task's allocated revenue amount.
The task type (classification) of the task.
Refers to TaskTypeRead.TaskTypeID.
- 0 or negative — 400 (must be > 0 when present).
- Unknown ID (create only) — 500 Code 102 / 37010 (rest.projectmanagement.task-type-not-found). validate-new-task does not check it.
- Null — Stored as 0 (no task type). The account's 'task type required' setting is deliberately not enforced by the save rules.
How a task-driven-revenue (TDR) task recognises revenue.
Conditional. Required (1 or 2) when the task's contract is TDR; ignored and reset to 0 otherwise.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
| 0 | Undefined | Not a TDR task. Rejected (400) on a TDR contract. |
| 1 | OverallPaymentPlan | Revenue follows the contract's overall payment plan; no instalment of its own. |
| 2 | FixedPricePayment | The task has its own fixed-price instalment: PaymentAmount, PaymentProductNo, PaymentName and PaymentInvoiceDate become required and are written to it. |
TDR only — the task's allocated revenue, and with FixedPricePayment the amount of its instalment. Conditional. TDR contract: >= 0 (0 when omitted). Ignored otherwise. Can be set on create only. Unit: project currency - TDR contract — Becomes the task's allocated revenue amount (replaces BudgetAmount for that). - TDR + FixedPricePayment and the task has exactly one instalment after the save — Written as that instalment's Rate and Amount.
TDR only — the task's own hourly rate on a project whose hourly-rate model is Calculated. Conditional. TDR + FixedPricePayment: >= 0. Must be 0 when HourlyRateID != 0, and not all three of BudgetHours, BudgetAmount, TaskHourlyRate may be non-zero. Ignored on other contracts. Can be set on create only. Unit: project currency per hour - TDR, Calculated hourly-rate model, TaskHourlyRate = 0 — Derived: BudgetAmount / BudgetHours (1 when hours are 0). - TDR, Calculated hourly-rate model, TaskHourlyRate > 0 — Stored as the task's hourly rate; used to derive BudgetHours or BudgetAmount when one of them is 0.
TDR fixed-price-payment only — product number of the task's instalment.
Conditional. Required (non-empty) when the contract is TDR and PaymentRecognitionModel = 2; must exist in the project's legal entity whenever sent on a TDR contract.
Can be set on create only.
Refers to ProductNumber.
- TDR contract, unknown number — 500 Code 102 / 37939 (rest.projectmanagement.task-payment-product-number-not-found).
TDR fixed-price-payment only — name (invoice text) of the task's instalment. Conditional. Required (non-empty) when the contract is TDR and PaymentRecognitionModel = 2. Ignored otherwise. Can be set on create only.
TDR fixed-price-payment only — invoice date of the task's instalment. Conditional. Required when the contract is TDR and PaymentRecognitionModel = 2. Ignored otherwise. Can be set on create only. - Feature LockPaymentDateToFPTTaskDate on — On later task saves the instalment's invoice date is re-synced to the task's EndDate.
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.
Body
The project the task is created on.
Can be set on create only.
Refers to ProjectRead.ProjectID. Look up valid values with GET /v1/project/get-all.
-
0 or omitted — Should be 400 'Project ID must be greater than 0', but the validator's contract rules crash first: 500 Code 100.
-
Unknown ID — 500 Code 102 / 37014 (rest.projectmanagement.project-not-found) — or 500 Code 100 when the validator's contract lookup runs first.
Valid values come from GET /v1/project/get-all.
The existing task the new task is placed under (create-sub-task only).
Can be set on create only.
Refers to TaskRead.TaskID. Look up valid values with GET /v1/task.
-
Sent as 0 or negative — 400 (must be > 0 when present).
-
create-sub-task: the task is on ANOTHER project — The parent is re-resolved by its WBS number inside ProjectID: the sub-task lands under the task of ProjectID that has the same WBS number, or the call fails with 500.
-
create-sub-task: null — Treated as task 0: 500 rest.projectmanagement.task-not-found (37001).
Valid values come from GET /v1/task.
Display name of the task.
The task number shown next to the name (searchable by the time-tracking searches).
Copied as sent when the account's task number series is off. When the task number series is on, the number you send is overwritten with the next number from the series (except on template projects).
Free-text description of the task.
Planned start of the task.
- Omitted (0001-01-01) — 400 'StartDate is not a valid date.'
Planned end of the task.
-
Omitted (0001-01-01) — 400 with the message 'StartDate is not a valid date.'.
-
Before StartDate — 400 'EndDate must be equal to or greater than StartDate.'
-
Time-and-material basic contract — Becomes the invoice date of the task's instalment.
When true, time registrations on this task must carry an additional comment.
Default billable flag of time registered on the task.
Omitted = false (non-billable task). Read back as TaskRead.IsDefaultBillable.
Marks the task (and, for TDR fixed-price-payment / time-and-material-basic tasks, its instalment) ready for invoicing.
The contract of the project the task is invoiced on.
Refers to ContractRead.ContractID. Look up valid values with GET /v1/contract.
-
Omitted / null on POST /v1/task and validate-new-task — The project's main contract.
-
Omitted / null on create-sub-task — The parent task's contract.
-
0 or negative — 400 (must be > 0 when present).
-
Not a contract of ProjectID — 500 Code 102 / 37042 (rest.projectmanagement.task-contract-not-on-project).
Valid values come from GET /v1/contract.
The contract hourly rate used for the task; 0 = the employee's default hourly rate.
Refers to ContractHourlyRateID.
-
Null / omitted — Required (NotNull) — but an empty body fails earlier with 500 Code 100.
-
0 and not a rate of the contract — 500 Code 102 / 37944 or 37043 (rest.projectmanagement.task-hourly-rate-not-on-contract).
-
0, fixed-price contract on a project with hourly-rate model Calculated — Set to 0; the task's hourly rate is derived instead (TDR: TaskHourlyRate; other fixed price: the contract's target hourly rate).
-
TDR contract and HourlyRateID != 0 — TaskHourlyRate must then be 0 (400 'Hourly rate must be 0 when HourlyRateID is selected.').
Budgeted hours of the task.
Unit: hours
-
Null / omitted — Required (NotNull, >= 0) — an empty body fails earlier with 500 Code 100.
-
Time & material contract, BudgetAmount and HourlyRateID also non-zero — 400: only two of BudgetHours, BudgetAmount, HourlyRateID may be non-zero.
-
HourlyRateID > 0 and BudgetHours = 0 — Derived: BudgetAmount / the rate's amount, rounded to InvoiceSetting_NumberOfDecimals.
-
TDR on a Calculated-rate project and BudgetHours = 0 — Derived: BudgetAmount / TaskHourlyRate (1 when the rate is 0).
Budgeted revenue of the task.
Unit: project currency
-
Null / omitted — Required (NotNull, >= 0) — an empty body fails earlier with 500 Code 100.
-
HourlyRateID > 0 and BudgetAmount = 0 — Derived: BudgetHours x the rate's amount.
-
TDR contract — Stored as the budget; the task's allocated revenue is PaymentAmount instead.
-
Otherwise — Also becomes the task's allocated revenue amount.
The task type (classification) of the task.
Refers to TaskTypeRead.TaskTypeID.
-
0 or negative — 400 (must be > 0 when present).
-
Unknown ID (create only) — 500 Code 102 / 37010 (rest.projectmanagement.task-type-not-found). validate-new-task does not check it.
-
Null — Stored as 0 (no task type). The account's 'task type required' setting is deliberately not enforced by the save rules.
How a task-driven-revenue (TDR) task recognises revenue.
Conditional. Required (1 or 2) when the task's contract is TDR; ignored and reset to 0 otherwise.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
0 | Undefined | Not a TDR task. Rejected (400) on a TDR contract. |
1 | OverallPaymentPlan | Revenue follows the contract's overall payment plan; no instalment of its own. |
2 | FixedPricePayment | The task has its own fixed-price instalment: PaymentAmount, PaymentProductNo, PaymentName and PaymentInvoiceDate become required and are written to it. |
012TDR only — the task's allocated revenue, and with FixedPricePayment the amount of its instalment.
Conditional. TDR contract: >= 0 (0 when omitted). Ignored otherwise.
Can be set on create only.
Unit: project currency
-
TDR contract — Becomes the task's allocated revenue amount (replaces BudgetAmount for that).
-
TDR + FixedPricePayment and the task has exactly one instalment after the save — Written as that instalment's Rate and Amount.
TDR only — the task's own hourly rate on a project whose hourly-rate model is Calculated.
Conditional. TDR + FixedPricePayment: >= 0. Must be 0 when HourlyRateID != 0, and not all three of BudgetHours, BudgetAmount, TaskHourlyRate may be non-zero. Ignored on other contracts.
Can be set on create only.
Unit: project currency per hour
-
TDR, Calculated hourly-rate model, TaskHourlyRate = 0 — Derived: BudgetAmount / BudgetHours (1 when hours are 0).
-
TDR, Calculated hourly-rate model, TaskHourlyRate > 0 — Stored as the task's hourly rate; used to derive BudgetHours or BudgetAmount when one of them is 0.
TDR fixed-price-payment only — product number of the task's instalment.
Conditional. Required (non-empty) when the contract is TDR and PaymentRecognitionModel = 2; must exist in the project's legal entity whenever sent on a TDR contract.
Can be set on create only.
Refers to ProductNumber.
- TDR contract, unknown number — 500 Code 102 / 37939 (rest.projectmanagement.task-payment-product-number-not-found).
TDR fixed-price-payment only — name (invoice text) of the task's instalment.
Conditional. Required (non-empty) when the contract is TDR and PaymentRecognitionModel = 2. Ignored otherwise.
Can be set on create only.
TDR fixed-price-payment only — invoice date of the task's instalment.
Conditional. Required when the contract is TDR and PaymentRecognitionModel = 2. Ignored otherwise.
Can be set on create only.
- Feature LockPaymentDateToFPTTaskDate on — On later task saves the instalment's invoice date is re-synced to the task's EndDate.
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.