Validate a time registration import
Checks whether an import body would be accepted, without saving the registration.
Runs the import's existence checks (project, contract-on-project, task-on-contract, active user) and the time-registration business rules, then returns an empty success. It is stricter than import itself, which skips the existence checks. It is NOT side-effect free: the employee may be allocated to the task.
Use it to:
- Pre-flight a migration batch before calling import
Not for:
- Checking rates — HourlyRate is not evaluated (no rate lookup or swap is simulated, so 38037 cannot be pre-flighted)
Rules across fields:
- R1: Same as import R1–R3.
- R2: GroupType ≠ Absence → ProjectID must exist, ContractID must belong to ProjectID, TaskID must be on ContractID.
Behaviour:
- Idempotent.
- Safe to retry.
- Side effect: If the employee is not allocated to TaskID: adds the employee to the project resource group and contract team and creates a 0-hour allocation. This persists.
- Read-after-write consistency: not yet documented.
Next: POST /v1/time-registration/import
Not yet documented: behaviour → consistency; examples (success).
curl -X POST "https://app3.timelog.com/your-account/api/v1/time-registration/validate-import" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"TaskID": 123,
"ProjectID": 123,
"ContractID": 123,
"UserID": 123,
"Date": "2024-12-25T10:00:00Z",
"Hours": 3.14,
"GroupType": 42,
"AbsenceCodeID": 123,
"Billable": true,
"Comment": "example_string",
"AdditionalComment": "example_string",
"BillableHours": 3.14,
"MonthlyPeriod": "example_string",
"HourlyRate": 3.14,
"HourlyRateName": "John Doe",
"CostPrice": 29.99,
"CostPriceName": "John Doe"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/time-registration/validate-import"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"TaskID": 123,
"ProjectID": 123,
"ContractID": 123,
"UserID": 123,
"Date": "2024-12-25T10:00:00Z",
"Hours": 3.14,
"GroupType": 42,
"AbsenceCodeID": 123,
"Billable": true,
"Comment": "example_string",
"AdditionalComment": "example_string",
"BillableHours": 3.14,
"MonthlyPeriod": "example_string",
"HourlyRate": 3.14,
"HourlyRateName": "John Doe",
"CostPrice": 29.99,
"CostPriceName": "John Doe"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/time-registration/validate-import", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"TaskID": 123,
"ProjectID": 123,
"ContractID": 123,
"UserID": 123,
"Date": "2024-12-25T10:00:00Z",
"Hours": 3.14,
"GroupType": 42,
"AbsenceCodeID": 123,
"Billable": true,
"Comment": "example_string",
"AdditionalComment": "example_string",
"BillableHours": 3.14,
"MonthlyPeriod": "example_string",
"HourlyRate": 3.14,
"HourlyRateName": "John Doe",
"CostPrice": 29.99,
"CostPriceName": "John Doe"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"TaskID": 123,
"ProjectID": 123,
"ContractID": 123,
"UserID": 123,
"Date": "2024-12-25T10:00:00Z",
"Hours": 3.14,
"GroupType": 42,
"AbsenceCodeID": 123,
"Billable": true,
"Comment": "example_string",
"AdditionalComment": "example_string",
"BillableHours": 3.14,
"MonthlyPeriod": "example_string",
"HourlyRate": 3.14,
"HourlyRateName": "John Doe",
"CostPrice": 29.99,
"CostPriceName": "John Doe"
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/time-registration/validate-import", 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/time-registration/validate-import')
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 = '{
"TaskID": 123,
"ProjectID": 123,
"ContractID": 123,
"UserID": 123,
"Date": "2024-12-25T10:00:00Z",
"Hours": 3.14,
"GroupType": 42,
"AbsenceCodeID": 123,
"Billable": true,
"Comment": "example_string",
"AdditionalComment": "example_string",
"BillableHours": 3.14,
"MonthlyPeriod": "example_string",
"HourlyRate": 3.14,
"HourlyRateName": "John Doe",
"CostPrice": 29.99,
"CostPriceName": "John Doe"
}'
response = http.request(request)
puts response.body
"null"
{
"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": "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"
}
/v1/time-registration/validate-import
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 media type of the request body
The task the time is registered on. Determines the allocation (and so the contract actually used).
Conditional. Required (non-zero) unless GroupType = Absence (3).
Refers to TaskRead.TaskID.
- GroupType = Project or TravelTime — If the employee is not allocated to the task, the allocation is CREATED (staffing side effect, see operation).
Import does NOT check that TaskID belongs to ContractID/ProjectID (only validate-import does).
> Not yet documented: values → lookup op.
The project. On import it is only used to pick the currency of a newly created hourly rate.
Conditional. Required (non-zero) unless GroupType = Absence (3).
Refers to ProjectRead.ProjectID.
> Not yet documented: values → lookup op.
The project contract (sub-contract) on which the import's project-specific hourly rate is found or created.
Conditional. Required (non-zero) unless GroupType = Absence (3).
Refers to ContractRead.ContractID.
> Not yet documented: values → lookup op.
The user (employee) the time belongs to.
Refers to UserRead.UserID.
- UserID is not the caller and the caller lacks RegistrationAllRegistrations — Fails with 300131 (HTTP 500). Department / legal-entity access does not count on this path.
> Not yet documented: values → lookup op.
The date the time is registered on. Time zone: not yet documented. > Not yet documented: constraints → timezone.
Duration in decimal hours. Unit: hours (decimal) Required and non-zero for every GroupType, including absence. TimeTrackingFormat is always Hours on import.
What kind of registration this is.
The validator's NotNull rule cannot fail on a non-nullable enum: an omitted GroupType arrives as 0 and is treated as a non-absence registration.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
| 1 | Project | Time worked on a project task. |
| 3 | Absence | Absence. AbsenceCodeID is required; Project/Contract/Task are not. |
| 6 | TravelTime | Travel time on a project task. |
> Not yet documented: notes.
For absence, the absence code to register.
Conditional. Required (non-zero) when GroupType = Absence (3); must be 0/omitted otherwise (validation error).
Refers to AbsenceCodeRead.AbsenceCodeID.
> Not yet documented: values → lookup op.
Whether the time is billable. Same account-feature overrides as TimeRegistrationWrite.Billable (BillableFlagCoordinator).
Free-text comment on the registration.
Second free-text field, configurable per site. Conditional. Required when the site configures the additional field as mandatory (30048).
Billable duration in decimal hours. Unit: hours (decimal)
Salary month the registration belongs to (MMyyyy). Same rules as TimeRegistrationWrite.MonthlyPeriod (malformed → HTTP 500).
Hourly rate to price this registration with. Unit: currency per hour — the project's currency Contains financial data. - GroupType = Project or TravelTime and value differs from the allocation's current rate — An active project-specific hourly rate on ContractID with the same name (HourlyRateName, case-insensitive) and amount is reused; otherwise a new one is created and persists. The allocation points at it for the save and is pointed back afterwards (also on failure). The value is non-nullable: omitting it means 0, which prices the registration at 0 when HourlyRateName is given and fails with 38037 when it is not (unless the allocation's rate is 0). - GroupType = Project or TravelTime and value equals the allocation's current rate — No rate is created; the allocation's rate applies. - GroupType = Absence — ignored Not evaluated by the manager (only by the 400 validator rule R3 of import).
Name of the project-specific hourly rate to reuse or create for this import. Conditional. Required when GroupType is not Absence and HourlyRate ≠ 0 (400), and when HourlyRate differs from the allocation's current rate (38037, HTTP 500). Only the validator rule applies (required when HourlyRate ≠ 0 for non-absence); the 38037 check of import does not run.
Obsolete. Formerly the employee cost price for this registration. Contains financial data. Ignored (obsolete). > Not yet documented: constraints → unit.
Obsolete. Formerly the name of the employee cost rate to use or create. Ignored (obsolete).
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.
Body
The task the time is registered on. Determines the allocation (and so the contract actually used).
Conditional. Required (non-zero) unless GroupType = Absence (3).
Refers to TaskRead.TaskID.
- GroupType = Project or TravelTime — If the employee is not allocated to the task, the allocation is CREATED (staffing side effect, see operation).
Import does NOT check that TaskID belongs to ContractID/ProjectID (only validate-import does).
Not yet documented: values → lookup op.
The project. On import it is only used to pick the currency of a newly created hourly rate.
Conditional. Required (non-zero) unless GroupType = Absence (3).
Refers to ProjectRead.ProjectID.
Not yet documented: values → lookup op.
The project contract (sub-contract) on which the import's project-specific hourly rate is found or created.
Conditional. Required (non-zero) unless GroupType = Absence (3).
Refers to ContractRead.ContractID.
Not yet documented: values → lookup op.
The user (employee) the time belongs to.
Refers to UserRead.UserID.
- UserID is not the caller and the caller lacks RegistrationAllRegistrations — Fails with 300131 (HTTP 500). Department / legal-entity access does not count on this path.
Not yet documented: values → lookup op.
The date the time is registered on.
Time zone: not yet documented.
Not yet documented: constraints → timezone.
Duration in decimal hours.
Unit: hours (decimal)
Required and non-zero for every GroupType, including absence. TimeTrackingFormat is always Hours on import.
What kind of registration this is.
The validator's NotNull rule cannot fail on a non-nullable enum: an omitted GroupType arrives as 0 and is treated as a non-absence registration.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
1 | Project | Time worked on a project task. |
3 | Absence | Absence. AbsenceCodeID is required; Project/Contract/Task are not. |
6 | TravelTime | Travel time on a project task. |
Not yet documented: notes.
136For absence, the absence code to register.
Conditional. Required (non-zero) when GroupType = Absence (3); must be 0/omitted otherwise (validation error).
Refers to AbsenceCodeRead.AbsenceCodeID.
Not yet documented: values → lookup op.
Whether the time is billable.
Same account-feature overrides as TimeRegistrationWrite.Billable (BillableFlagCoordinator).
Free-text comment on the registration.
Second free-text field, configurable per site.
Conditional. Required when the site configures the additional field as mandatory (30048).
Billable duration in decimal hours.
Unit: hours (decimal)
Salary month the registration belongs to (MMyyyy).
Same rules as TimeRegistrationWrite.MonthlyPeriod (malformed → HTTP 500).
Hourly rate to price this registration with.
Unit: currency per hour — the project's currency
Contains financial data.
-
GroupType = Project or TravelTime and value differs from the allocation's current rate — An active project-specific hourly rate on ContractID with the same name (HourlyRateName, case-insensitive) and amount is reused; otherwise a new one is created and persists. The allocation points at it for the save and is pointed back afterwards (also on failure). The value is non-nullable: omitting it means 0, which prices the registration at 0 when HourlyRateName is given and fails with 38037 when it is not (unless the allocation's rate is 0).
-
GroupType = Project or TravelTime and value equals the allocation's current rate — No rate is created; the allocation's rate applies.
-
GroupType = Absence — ignored
Not evaluated by the manager (only by the 400 validator rule R3 of import).
Name of the project-specific hourly rate to reuse or create for this import.
Conditional. Required when GroupType is not Absence and HourlyRate ≠ 0 (400), and when HourlyRate differs from the allocation's current rate (38037, HTTP 500).
Only the validator rule applies (required when HourlyRate ≠ 0 for non-absence); the 38037 check of import does not run.
Obsolete. Formerly the employee cost price for this registration.
Contains financial data.
Ignored (obsolete).
Not yet documented: constraints → unit.
Obsolete. Formerly the name of the employee cost rate to use or create.
Ignored (obsolete).
Responses
Success without content: HTTP 200 with the literal JSON body null. Body is the literal JSON null: the import would be accepted.