Import a time registration
Creates a time or absence registration priced with a caller-supplied hourly rate.
Migration-style import: you send the project, contract, task, employee and the hourly rate the registration should carry. The server generates the registration GUID (one new registration per call) and returns the created registration. To price project or travel time, the server temporarily points the employee's allocation at a project-specific hourly rate on ContractID (an existing active rate with the same name and amount is reused, otherwise one is created), saves the registration, then points the allocation back. Cost price is no longer set by this call: CostPrice and CostPriceName are accepted and ignored.
Use it to:
- Migrating historical hours from another system with their original hourly rates
Not for:
- Day-to-day time entry →
POST /v1/time-registration(client GUID, no rate records) - Dry run →
POST /v1/time-registration/validate-import
Rules across fields:
- R1: GroupType ≠ Absence (3) → ProjectID, ContractID and TaskID are required (non-zero) and AbsenceCodeID must be 0.
- R2: GroupType = Absence (3) → AbsenceCodeID is required (non-zero).
- R3: GroupType ≠ Absence and HourlyRate ≠ 0 → HourlyRateName is required (400).
- R4: GroupType = Project or TravelTime and HourlyRate differs from the allocation's current rate → HourlyRateName is required, else 38037 (HTTP 500). This also fires when HourlyRate is omitted (0) and the allocation's rate is not 0; R3 does not catch that case.
- R5: TaskID is NOT checked against ContractID/ProjectID on import. A mismatch creates or reuses the hourly rate on ContractID while the allocation belongs to TaskID's contract.
Behaviour:
- Not idempotent.
- Not safe to retry blindly. The GUID is server-generated, so a retry after a timeout creates a second registration.
- Duplicates: None. Every call creates a new registration. Rate records are de-duplicated by (contract, name, amount).
- Side effect: Allocation is created if the employee is not on the task (as create, before validation).
- Side effect: If HourlyRate differs from the allocation's rate: an active project-specific hourly rate on ContractID with the same name (case-insensitive) and amount is reused; otherwise a NEW one is created (persists, ContractHourlyRateCreated). The allocation is saved pointing at it, the registration is saved, then the allocation is saved back to its original rate in a finally block.
- On failure: A created hourly-rate record persists.
- On failure: Not transactional: the switch and the restore are separate saves whose violations are not checked.
- Read-after-write consistency: not yet documented.
Call first: POST /v1/time-registration/validate-import
Next: GET /v1/time-registration/{timeRegistrationID}
See also: POST /v1/time-registration
Not yet documented: behaviour → consistency; examples (success).
curl -X POST "https://app3.timelog.com/your-account/api/v1/time-registration/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/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/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/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/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
{
"Properties": {
"TimeRegistrationID": 123,
"TimeRegistrationGUID": "123e4567-e89b-12d3-a456-426614174000",
"UserID": 123,
"DepartmentID": 123,
"Date": "2024-12-25T10:00:00Z",
"Hours": 3.14,
"Minutes": 42,
"BillableHours": 3.14,
"BillableMinutes": 42,
"IsBillable": true,
"Factor": 3.14,
"EntityType": 42,
"EntityID": 123,
"AbsenceCode": {
"AbsenceCodeID": 123,
"AbsenceCode": "example_string",
"Name": "John Doe",
"Description": "example_string",
"IsActive": true,
"FlexPositiveFactor": 3.14,
"SalaryAccountFactor": 3.14,
"SalaryAccountFactorEffect": 10,
"SalaryAccountID": 123,
"AbsenceCodeTypeID": 123,
"DeductCapacity": true,
"TimeTrackingFormat": 42,
"RequireApproval": true,
"IsPublicVisible": true,
"IsFavourite": "null",
"ID": "123e4567-e89b-12d3-a456-426614174000"
},
"TimeTrackingFormat": 42,
"Comment": "null",
"AdditionalTextField": "null",
"MonthlyPeriod": "null",
"Status": 42,
"ApprovalStatus": 42,
"InvoiceStatus": 42,
"IsInvoiced": true,
"IsFixedPrice": "null",
"IsInClosedAccountingPeriod": true,
"IsMeeting": true,
"CalendarEventID": 123,
"StartTime": "null",
"EndTime": "null",
"Created": "2024-12-25T10:00:00Z",
"CreatedBy": 42,
"LastModified": "2024-12-25T10:00:00Z",
"LastModifiedBy": 42,
"InterfaceCreated": 42,
"InterfaceModified": 42
},
"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
}
{
"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/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
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).
Obsolete. Formerly the employee cost price for this registration. Contains financial data. > Not yet documented: constraints → unit.
Obsolete. Formerly the name of the employee cost rate to use or create.
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
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).
Obsolete. Formerly the employee cost price for this registration.
Contains financial data.
Not yet documented: constraints → unit.
Obsolete. Formerly the name of the employee cost rate to use or create.