Update a project
Updates a project's header fields (name, customer, people, type, dates, budgets).
MIXED replace / patch semantics: Name, CustomerID, ContactID, Description and ProjectManagerID are always written; the nullable fields are written only when sent. Answers 200 with the body null; read the project back with GET. Status is changed with PUT /v1/project/{projectID}/status.
Use it to:
- Sync name, project manager or budget changes from a CRM / PSA
Not for:
- Status changes →
PUT /v1/project/{projectID}/status
Rules across fields:
- R1: Effective end date (sent, else stored) must be on or after the effective start date (sent, else stored); checked only when at least one date is sent.
Behaviour:
- Idempotent.
- Safe to retry only under a condition. Repeating the same body gives the same state, but omitted Description / ContactID are cleared each time.
- Side effect: Writes the project row (last-modified fields set), saves invoice settings, adds the project manager to the resource group, and notifies contract customers, task search and sales-share read models.
- Side effect: When project dates are locked to the task plan, Save recalculates the project dates from the task plan.
- Changes are visible to the next read immediately.
Call first: GET /v1/project/{projectID}
Next: GET /v1/project/{projectID}
See also: PUT /v1/project/{projectID}/status
curl -X PUT "https://app3.timelog.com/your-account/api/v1/project/123" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"ContactID": 123,
"Description": "null",
"DepartmentID": "null",
"ProjectManagerID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ProjectTypeID": "null",
"ProjectCategoryID": "null",
"ProjectStartDate": "null",
"ProjectEndDate": "null",
"BudgetWorkHours": "null",
"BudgetWorkAmount": "null",
"LanguageID": "null"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/project/123"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"ContactID": 123,
"Description": "null",
"DepartmentID": "null",
"ProjectManagerID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ProjectTypeID": "null",
"ProjectCategoryID": "null",
"ProjectStartDate": "null",
"ProjectEndDate": "null",
"BudgetWorkHours": "null",
"BudgetWorkAmount": "null",
"LanguageID": "null"
}
response = requests.put(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/project/123", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"ContactID": 123,
"Description": "null",
"DepartmentID": "null",
"ProjectManagerID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ProjectTypeID": "null",
"ProjectCategoryID": "null",
"ProjectStartDate": "null",
"ProjectEndDate": "null",
"BudgetWorkHours": "null",
"BudgetWorkAmount": "null",
"LanguageID": "null"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"ContactID": 123,
"Description": "null",
"DepartmentID": "null",
"ProjectManagerID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ProjectTypeID": "null",
"ProjectCategoryID": "null",
"ProjectStartDate": "null",
"ProjectEndDate": "null",
"BudgetWorkHours": "null",
"BudgetWorkAmount": "null",
"LanguageID": "null"
}`)
req, err := http.NewRequest("PUT", "https://app3.timelog.com/your-account/api/v1/project/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/project/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 = '{
"Name": "John Doe",
"ProjectNo": "example_string",
"CustomerID": 123,
"ContactID": 123,
"Description": "null",
"DepartmentID": "null",
"ProjectManagerID": 123,
"AccountManagerID": "null",
"PartnerID": "null",
"ProjectTypeID": "null",
"ProjectCategoryID": "null",
"ProjectStartDate": "null",
"ProjectEndDate": "null",
"BudgetWorkHours": "null",
"BudgetWorkAmount": "null",
"LanguageID": "null"
}'
response = http.request(request)
puts response.body
"null"
{
"Code": 200,
"Message": "Invalid model state error.",
"Details": [
"…"
],
"Parameters": "…",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/project/{projectID}"
}
{
"Message": "Authorization has been denied for this request."
}
{
"Message": "Page Not Found",
"Timestamp": "…",
"RawUrl": "https://app{server-number}.timelog.com/{account}/api/v1/project/{projectID}"
}
{
"Code": 100,
"Message": "Unknown error occur in system, please contact system administrator.",
"Details": [],
"Parameters": "…",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/project/{projectID}"
}
/v1/project/{projectID}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 project to update.
The media type of the request body
Project name. Always written.
Project number. Written only when non-empty AND the project-number auto-number series is off; otherwise ignored. - Empty / omitted — Number unchanged. - Project-number auto-number is active — Number unchanged, no error.
The project's customer. Always written; changing it runs ProjectCanChangeCustomer.
Refers to CustomerRead.CustomerID. Look up valid values with GET /v1/customer.
Valid values come from GET /v1/customer.
Customer contact. Always written: omitted = 0 = the contact is removed.
Refers to ContactRead.ContactID.
Default: 0 (clears the contact)
- Omitted — 0 is written — the stored contact is cleared.
Project description. Always written: omitted = null = the description is cleared. - Omitted or null — null is written — the stored description is cleared.
Handling department. Written only when sent and the Departments feature is on.
Refers to DepartmentRead.DepartmentID. Look up valid values with GET /v1/department.
- Feature Departments is off — ignored
Valid values come from GET /v1/department.
Project manager, as a USER ID (existing, active). Always written.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
Valid values come from GET /v1/user.
Account manager, as a USER ID. Written only when sent and the AccountManager feature is on. Cannot be cleared (null = keep).
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Feature AccountManager is off — ignored
Valid values come from GET /v1/user.
Partner, as a USER ID. Written only when sent and the Partner feature is on.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Feature Partner is off — ignored
Valid values come from GET /v1/user.
Project type. Written only when sent and the ProjectType feature is on.
Refers to ProjectTypeRead.ProjectTypeID.
- Feature ProjectType is off — ignored
Project category. Written only when sent and the ProjectCategory feature is on.
Refers to ProjectCategoryRead.ProjectCategoryID.
- Feature ProjectCategory is off — ignored
New start date (date part compared). Rejected (400) unless the ProjectSchedule feature is on and LockProjectDateWithTaskPlan is off. - ProjectSchedule feature off — 400 "ProjectStartDate cannot be updated because the Project schedule feature is not active on this site." - LockProjectDateWithTaskPlan feature on — 400 "…project dates are locked to the task plan on this site."
New end date (stored as ContractEndDate). Same feature gates as ProjectStartDate. Must be on or after the effective start date. - End before start (both sent) — 400 validation "ProjectEndDate must be on or after ProjectStartDate." - Only one date sent and it crosses the stored other date — 400 Code 400, same message.
Budgeted work hours; null = keep. Unit: hours
Budgeted work amount; null = keep. Unit: project currency
Invoice language; written only when sent and InvoiceMultilingual is on.
- Any other value — Validation (400).
- Feature InvoiceMultilingual is off — ignored
Allowed values:
| Value | Name | Meaning |
|---|---|---|
| 1 | Danish | Invoices in Danish. |
| 2 | English | Invoices in English. |
| 4 | Swedish | Invoices in Swedish. |
| 15 | Norwegian | Invoices in Norwegian. |
| 16 | German | Invoices in German. |
| 17 | Dutch | Invoices in Dutch. |
| 18 | French | Invoices in French. |
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.
Path Parameters
The project to update.
Body
Project name. Always written.
Project number. Written only when non-empty AND the project-number auto-number series is off; otherwise ignored.
-
Empty / omitted — Number unchanged.
-
Project-number auto-number is active — Number unchanged, no error.
The project's customer. Always written; changing it runs ProjectCanChangeCustomer.
Refers to CustomerRead.CustomerID. Look up valid values with GET /v1/customer.
Valid values come from GET /v1/customer.
Customer contact. Always written: omitted = 0 = the contact is removed.
Refers to ContactRead.ContactID.
Default: 0 (clears the contact)
- Omitted — 0 is written — the stored contact is cleared.
Project description. Always written: omitted = null = the description is cleared.
- Omitted or null — null is written — the stored description is cleared.
Handling department. Written only when sent and the Departments feature is on.
Refers to DepartmentRead.DepartmentID. Look up valid values with GET /v1/department.
- Feature Departments is off — ignored
Valid values come from GET /v1/department.
Project manager, as a USER ID (existing, active). Always written.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
Valid values come from GET /v1/user.
Account manager, as a USER ID. Written only when sent and the AccountManager feature is on. Cannot be cleared (null = keep).
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Feature AccountManager is off — ignored
Valid values come from GET /v1/user.
Partner, as a USER ID. Written only when sent and the Partner feature is on.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Feature Partner is off — ignored
Valid values come from GET /v1/user.
Project type. Written only when sent and the ProjectType feature is on.
Refers to ProjectTypeRead.ProjectTypeID.
- Feature ProjectType is off — ignored
Project category. Written only when sent and the ProjectCategory feature is on.
Refers to ProjectCategoryRead.ProjectCategoryID.
- Feature ProjectCategory is off — ignored
New start date (date part compared). Rejected (400) unless the ProjectSchedule feature is on and LockProjectDateWithTaskPlan is off.
-
ProjectSchedule feature off — 400 "ProjectStartDate cannot be updated because the Project schedule feature is not active on this site."
-
LockProjectDateWithTaskPlan feature on — 400 "…project dates are locked to the task plan on this site."
New end date (stored as ContractEndDate). Same feature gates as ProjectStartDate. Must be on or after the effective start date.
-
End before start (both sent) — 400 validation "ProjectEndDate must be on or after ProjectStartDate."
-
Only one date sent and it crosses the stored other date — 400 Code 400, same message.
Budgeted work hours; null = keep.
Unit: hours
Budgeted work amount; null = keep.
Unit: project currency
Invoice language; written only when sent and InvoiceMultilingual is on.
-
Any other value — Validation (400).
-
Feature InvoiceMultilingual is off — ignored
Allowed values:
| Value | Name | Meaning |
|---|---|---|
1 | Danish | Invoices in Danish. |
2 | English | Invoices in English. |
4 | Swedish | Invoices in Swedish. |
15 | Norwegian | Invoices in Norwegian. |
16 | German | Invoices in German. |
17 | Dutch | Invoices in Dutch. |
18 | French | Invoices in French. |
12415161718Responses
Success without content: HTTP 200 with the literal JSON body null. Literal JSON null.