Move several projects to a project stage
Sets the current project stage of every listed project, one project after another.
Project stages are a account-configured, ordered sequence (e.g. Initiation, Planning, Execution & Controlling, Closing). For each project: the chosen stage gets Date (or keeps its date when the project is already in that stage, else today), every LATER stage's date is cleared, the dates are normalised by the stage date rule, and the project's current stage is re-derived. Answers 200 with an empty TAF list whose Properties.UpdatedProjectsCount is the number of IDs processed. The first invalid project or stage aborts with 500 and earlier projects keep the new stage. Stage 0 / clearing is not possible.
Use it to:
- Move a batch of projects to the next lifecycle stage from a portfolio tool
Behaviour:
- Idempotent.
- The same stage again keeps its existing date when Date is omitted; with Date, the date is re-set and later stages cleared again.
- Safe to retry.
- Side effect: Per project: stage-unit dates saved (chosen stage set, later stages cleared, date rule applied); the project's current stage re-derived.
- Side effect: ProjectStagesChanged event (audit log) when the project's stage actually changes.
- On failure: Projects before the failing one keep the new stage.
- Changes are visible to the next read immediately.
- Batch: Sequential, no transaction, no per-item result.
Call first: GET /v2/projects/actions, GET /v2/projects
Not yet documented: behaviour → events emitted; examples.
curl -X PUT "https://app3.timelog.com/your-account/api/v2/projects/stage?projectsIds=example_string" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"Stage": 25,
"Date": "null"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v2/projects/stage?projectsIds=example_string"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"Stage": 25,
"Date": "null"
}
response = requests.put(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v2/projects/stage?projectsIds=example_string", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"Stage": 25,
"Date": "null"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"Stage": 25,
"Date": "null"
}`)
req, err := http.NewRequest("PUT", "https://app3.timelog.com/your-account/api/v2/projects/stage?projectsIds=example_string", 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/v2/projects/stage?projectsIds=example_string')
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 = '{
"Stage": 25,
"Date": "null"
}'
response = http.request(request)
puts response.body
{
"Properties": {
"UpdatedProjectsCount": "example_string"
},
"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
}
{
"code": "UnsupportedApiVersion",
"error": "No route providing a controller name with API version '2' was found to match HTTP method 'PUT' and request URI 'https://app{server-number}.timelog.com/{account}/api/v2/projects/stage'.",
"traceId": "…",
"type": "https://docs.api-versioning.org/problems#unsupported",
"title": "Unsupported API version",
"status": 405,
"detail": "The requested resource with API version '2' does not support HTTP method 'PUT'."
}
{
"error": "Internal Server Error",
"message": "An unexpected error occurred on the server",
"code": 500,
"requestId": "req_1234567890"
}
/v2/projects/stage
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.Comma-separated project IDs, processed in order. Omitted → 405 (captured). Unknown / <= 0 ID or non-integer entry → 500 after earlier entries were applied.
Several values: one string, separated by ,.
The media type of the request body
The project stage ID (one of the account's project stages; the stage items of GET /v2/projects/actions carry them). Non-nullable: an omitted Stage is 0 → 500.
Refers to ProjectStageID. Look up valid values with GET /v2/projects/actions.
- Negative — 400 (validator GreaterThanOrEqualTo(0)).
- A valid stage — The stage's date is set; every LATER stage's date is cleared; the dates are then normalised by the stage date rule.
Valid values come from GET /v2/projects/actions.
Date for the stage. When omitted: the stage's existing date if the project is already in that stage, otherwise today. Date only — no time component. Default: existing date when the stage is unchanged, else today > Not yet documented: constraints → timezone.
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.
Query Parameters
Comma-separated project IDs, processed in order. Omitted → 405 (captured). Unknown / <= 0 ID or non-integer entry → 500 after earlier entries were applied.
Several values: one string, separated by ,.
Body
The project stage ID (one of the account's project stages; the stage items of GET /v2/projects/actions carry them). Non-nullable: an omitted Stage is 0 → 500.
Refers to ProjectStageID. Look up valid values with GET /v2/projects/actions.
-
Negative — 400 (validator GreaterThanOrEqualTo(0)).
-
A valid stage — The stage's date is set; every LATER stage's date is cleared; the dates are then normalised by the stage date rule.
Valid values come from GET /v2/projects/actions.
Date for the stage. When omitted: the stage's existing date if the project is already in that stage, otherwise today.
Date only — no time component.
Default: existing date when the stage is unchanged, else today
Not yet documented: constraints → timezone.
Responses
How many projects a /v2/projects bulk write processed or changed.