Move time registrations to another task
Moves one or more project time registrations to another task (of any project), keeping each registration's ID, hours and date.
For each ID in timeRegistrationIds, in order, the registration is re-pointed to TargetTaskId: the employee is allocated to the target task when not yet allocated, the registration's allocation (and so its rate) changes, billable duration is re-evaluated (billable-hours / rounding features), and the registration is saved in place — same TimeRegistrationId, GUID, hours, date, comment, factor and approval status (a rejected one becomes "rejected and resolved"). Attached mileage keeps its project and only its registration reference is updated; a running time tracker on the registration is notified as deleted. Refused per registration: absence registrations, target project Cancelled / Archived, target task closed or a parent task, invoiced, on a draft invoice, dated in a closed accounting period, outside the employee's employment, missing mandatory comment / additional text, exceeding allocated hours (feature). A closed (approved) timesheet does NOT block a move. Answers 204 when every registration moved, otherwise 422 with a bare array of MoveHoursResultRead (one entry per ID — moved ones included, and they STAY moved). An unknown or non-integer ID aborts the loop with 500 after earlier IDs were moved.
Use it to:
- Correct registrations booked on the wrong task or project before invoicing
Not for:
- Changing hours, date or comment →
PUT /v1/time-registration
Behaviour:
- Idempotent.
- Moving a registration to the task it is already on re-saves it unchanged in substance.
- Safe to retry only under a condition. Yes after a 422 or 500 — re-read the registrations first (moved ones are already on the target) and resend the rest.
- Duplicates: A duplicated ID is moved twice (second time a no-op move).
- Side effect: Per registration: allocation on the target task created when missing; registration re-pointed to it (rate of the target allocation applies); billable duration re-evaluated.
- Side effect: A running time tracker on the registration is notified as deleted.
- On failure: Registrations before the failing ID stay moved (422 array, or 500 with no per-item result).
- On failure: An allocation created on the target task can remain even when the move is then refused by a later rule.
- Changes are visible to the next read immediately.
- Batch: Sequential; each registration in its own transaction; refusals are collected (422), any other error aborts the remainder (500).
Call first: GET /v2/adjustments/move-hours, GET /v2/adjustments/move-hours/projects/{projectId}/tasks
Next: GET /v2/adjustments/move-hours
See also: PUT /v1/time-registration
Not yet documented: behaviour → side effects on failure; examples.
curl -X PUT "https://app3.timelog.com/your-account/api/v2/adjustments/move-hours?timeRegistrationIds=example_string" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"TargetTaskId": 123
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v2/adjustments/move-hours?timeRegistrationIds=example_string"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"TargetTaskId": 123
}
response = requests.put(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v2/adjustments/move-hours?timeRegistrationIds=example_string", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"TargetTaskId": 123
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"TargetTaskId": 123
}`)
req, err := http.NewRequest("PUT", "https://app3.timelog.com/your-account/api/v2/adjustments/move-hours?timeRegistrationIds=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/adjustments/move-hours?timeRegistrationIds=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 = '{
"TargetTaskId": 123
}'
response = http.request(request)
puts response.body
{}
{
"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": "Not Found",
"message": "The requested resource was not found",
"code": 404
}
{
"error": "Unprocessable Entity",
"message": "The request was well-formed but contains semantic errors",
"code": 422,
"details": [
{
"field": "password",
"message": "Password must be at least 8 characters long"
}
]
}
{
"error": "Internal Server Error",
"message": "An unexpected error occurred on the server",
"code": 500,
"requestId": "req_1234567890"
}
/v2/adjustments/move-hours
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 integer TimeRegistrationIds, processed in order. Omitted → 404 action not matched (captured). Unknown or non-integer entry → 500 after earlier entries were moved.
Several values: one string, separated by ,.
The media type of the request body
The task to move the registrations to — any task of any project; the registration's employee is allocated to it automatically when not yet allocated. The task must be open, not a parent task, and on a project that is neither Cancelled nor Archived (checked per registration; a violation is reported per ID in the 422 array).
Refers to TaskRead.TaskID. Look up valid values with GET /v2/adjustments/move-hours/projects/{projectId}/tasks.
- Omitted or 0 / negative — Validator GreaterThan(0) — 400 Invalid model state.
Valid values come from GET /v2/adjustments/move-hours/projects/{projectId}/tasks.
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 integer TimeRegistrationIds, processed in order. Omitted → 404 action not matched (captured). Unknown or non-integer entry → 500 after earlier entries were moved.
Several values: one string, separated by ,.
Body
The task to move the registrations to — any task of any project; the registration's employee is allocated to it automatically when not yet allocated. The task must be open, not a parent task, and on a project that is neither Cancelled nor Archived (checked per registration; a violation is reported per ID in the 422 array).
Refers to TaskRead.TaskID. Look up valid values with GET /v2/adjustments/move-hours/projects/{projectId}/tasks.
- Omitted or 0 / negative — Validator GreaterThan(0) — 400 Invalid model state.
Valid values come from GET /v2/adjustments/move-hours/projects/{projectId}/tasks.