Create a user
Creates an active user (employee) with its HR settings and returns it.
Creates the login and the employee profile in one call: name, initials, e-mail, legal entity, department, approval manager, rates, calendar, working time, salary group, licence type and extra roles. The new user is active immediately; time zone, language and front page come from account defaults. Run POST /v1/user/validate-new-user first to get every rule violation without side effects. The response is the User representation, which does not echo most of what was sent.
Use it to:
Not for:
- Changing an existing user →
PUT /v1/user(name, licence, roles, approval manager only)
Rules across fields:
- R1: UserRoleIDs must be null or empty unless UserLicenseType is Standard (2).
- R2: StandardHourlyRateID and CostPriceID must belong to LegalEntityID.
- R3: Neither UserName nor Initials may be used by another user, active or inactive.
Behaviour:
- Not idempotent.
- Safe to retry only under a condition. A retry after a timeout fails with 20022 if the first call succeeded — check user.list for the initials before retrying.
- Duplicates: UserName and Initials must be unused (application check, 20022/20024); nothing else is deduplicated.
- Side effect: Inserts the user, its profile, its personal (employee-specific) role and the licence's system role or the requested roles, default page settings and predefined filter views
- Side effect: Sets an initial login credential for the user
- Side effect: Creates the cost-price drill-down rows and refreshes the platform licence status
- Side effect: Updates the allocation-search denormalisation (a failure here is logged and ignored)
- On failure: When the insert fails after the user row is written, the call returns 500 Code 100 and the partial user may remain
- Changes are visible to the next read immediately.
Call first: GET /v1/legal-entity, GET /v1/department, POST /v1/user/validate-new-user
Next: GET /v1/user/{id}, PUT /v1/user
Not yet documented: behaviour → events emitted; behaviour → side effects; examples.
curl -X POST "https://app3.timelog.com/your-account/api/v1/user/create" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"UserName": "John Doe",
"FirstName": "John Doe",
"LastName": "John Doe",
"Initials": "example_string",
"Email": "user@example.com",
"Title": "example_string",
"EmployeeNo": "example_string",
"EmploymentDate": "2024-12-25T10:00:00Z",
"LegalEntityID": 123,
"DepartmentID": 123,
"ApprovalManagerID": 123,
"EmployeeTypeID": 123,
"StandardHourlyRateID": 123,
"CostPriceID": 123,
"PublicHolidayCalendarID": 123,
"AllowanceLegislationID": 123,
"NormalWorkingTimeID": 123,
"SalaryGroupID": 123,
"UserLicenseType": 42,
"UserRoleIDs": [
"null"
]
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/user/create"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"UserName": "John Doe",
"FirstName": "John Doe",
"LastName": "John Doe",
"Initials": "example_string",
"Email": "user@example.com",
"Title": "example_string",
"EmployeeNo": "example_string",
"EmploymentDate": "2024-12-25T10:00:00Z",
"LegalEntityID": 123,
"DepartmentID": 123,
"ApprovalManagerID": 123,
"EmployeeTypeID": 123,
"StandardHourlyRateID": 123,
"CostPriceID": 123,
"PublicHolidayCalendarID": 123,
"AllowanceLegislationID": 123,
"NormalWorkingTimeID": 123,
"SalaryGroupID": 123,
"UserLicenseType": 42,
"UserRoleIDs": [
"null"
]
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/user/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"UserName": "John Doe",
"FirstName": "John Doe",
"LastName": "John Doe",
"Initials": "example_string",
"Email": "user@example.com",
"Title": "example_string",
"EmployeeNo": "example_string",
"EmploymentDate": "2024-12-25T10:00:00Z",
"LegalEntityID": 123,
"DepartmentID": 123,
"ApprovalManagerID": 123,
"EmployeeTypeID": 123,
"StandardHourlyRateID": 123,
"CostPriceID": 123,
"PublicHolidayCalendarID": 123,
"AllowanceLegislationID": 123,
"NormalWorkingTimeID": 123,
"SalaryGroupID": 123,
"UserLicenseType": 42,
"UserRoleIDs": [
"null"
]
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"UserName": "John Doe",
"FirstName": "John Doe",
"LastName": "John Doe",
"Initials": "example_string",
"Email": "user@example.com",
"Title": "example_string",
"EmployeeNo": "example_string",
"EmploymentDate": "2024-12-25T10:00:00Z",
"LegalEntityID": 123,
"DepartmentID": 123,
"ApprovalManagerID": 123,
"EmployeeTypeID": 123,
"StandardHourlyRateID": 123,
"CostPriceID": 123,
"PublicHolidayCalendarID": 123,
"AllowanceLegislationID": 123,
"NormalWorkingTimeID": 123,
"SalaryGroupID": 123,
"UserLicenseType": 42,
"UserRoleIDs": [
"null"
]
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/user/create", 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/user/create')
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 = '{
"UserName": "John Doe",
"FirstName": "John Doe",
"LastName": "John Doe",
"Initials": "example_string",
"Email": "user@example.com",
"Title": "example_string",
"EmployeeNo": "example_string",
"EmploymentDate": "2024-12-25T10:00:00Z",
"LegalEntityID": 123,
"DepartmentID": 123,
"ApprovalManagerID": 123,
"EmployeeTypeID": 123,
"StandardHourlyRateID": 123,
"CostPriceID": 123,
"PublicHolidayCalendarID": 123,
"AllowanceLegislationID": 123,
"NormalWorkingTimeID": 123,
"SalaryGroupID": 123,
"UserLicenseType": 42,
"UserRoleIDs": [
"null"
]
}'
response = http.request(request)
puts response.body
{
"Properties": {
"UserID": 123,
"ID": "123e4567-e89b-12d3-a456-426614174000",
"FirstName": "John Doe",
"LastName": "John Doe",
"Initials": "example_string",
"IsActive": true,
"UserType": "Employee",
"LegalEntityID": 123,
"Email": "user@example.com"
},
"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": "Internal Server Error",
"message": "An unexpected error occurred on the server",
"code": 500,
"requestId": "req_1234567890"
}
/v1/user/create
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
Login name of the new user. Not returned by any REST v1 read. Can be set on create only. Alternate key, unique per TimeLog site. Contains personal data. - Same user name or initials as an active user — 20022 (business rule, HTTP 500). - Same user name or initials as an inactive user — 20024 (business rule, HTTP 500). Create never reuses an inactive user's name.
First name. Can be set on create only. Contains personal data.
Last name. Can be set on create only. Contains personal data.
Short handle of the user (see User.Initials). Must not be used by another user (active: 20022, inactive: 20024). Can be set on create only. Alternate key, unique per TimeLog site. Contains personal data.
E-mail address. Can be set on create only. Contains personal data. - Empty or not an e-mail address — 400 Code 200 ('Email' must not be empty / The specified condition was not met for 'Email'). - Passes the model check but fails the business-rule format check — 31114 (business rule, HTTP 500).
Job title. Can be set on create only. Contains personal data.
Employee number. Conditional. Required when the account feature ShowEmployeeID is on (31027, business rule, HTTP 500); optional otherwise. Can be set on create only. Key from your own system, unique per TimeLog site. Uniqueness is not enforced. Contains personal data.
First day of employment. Can be set on create only. Time zone: not yet documented. Contains personal data. - Year not valid — 31029 (business rule, HTTP 500). > Not yet documented: constraints → timezone.
Legal entity the user is employed in.
Can be set on create only.
Refers to LegalEntityRead.LegalEntityID. Look up valid values with GET /v1/legal-entity.
- Feature MultiLegalEntities on and the ID does not exist — 37029 (business rule, HTTP 500).
- Feature MultiLegalEntities off and the ID is not the system legal entity — 37921 (business rule, HTTP 500). Use GET /v1/legal-entity/system.
Valid values come from GET /v1/legal-entity.
Department the user belongs to. Inactive departments are accepted.
Can be set on create only.
Refers to DepartmentRead.DepartmentID. Look up valid values with GET /v1/department.
- The ID does not exist — 37039 (business rule, HTTP 500).
Valid values come from GET /v1/department.
User ID (not employee ID) of the manager who approves this user's time sheets. 0 = no approval manager.
Can be set on create only.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Non-zero and not an existing user — 37038 (business rule, HTTP 500).
Valid values come from GET /v1/user.
Employee type (account-defined category). 0 = none.
Can be set on create only.
Refers to EmployeeTypeRead.EmployeeTypeID.
- Non-zero and not an existing employee type — 37028 (business rule, HTTP 500).
Valid values come from GET /v1/role.
> Not yet documented: values → lookup op.
Standard hourly (sales) rate of the user; must belong to the user's legal entity.
Can be set on create only.
Refers to HourlyRateRead.HourlyRateID. Look up valid values with GET /v1/hourly-rate.
- 0 — 400 'Standard Hourly Rate ID' must not be empty.
- Negative — Replaced by the legal entity's system default hourly rate.
- Positive and not an existing rate / not a rate of LegalEntityID — 37030 / 37031 (business rule, HTTP 500).
GET /v1/hourly-rate returns the price-list entries of ALL legal entities (incl. customer lists); pick an entry with CustomerID null and LegalEntityID = the user's legal entity.
Valid values come from GET /v1/hourly-rate.
Cost price group of the user; must belong to the user's legal entity.
Can be set on create only.
Refers to CostPriceRead.CostPriceID. Look up valid values with GET /v1/cost-price.
- Not an existing cost price / not one of LegalEntityID — 37032 / 37051 (business rule, HTTP 500).
GET /v1/cost-price returns the cost price groups of ALL legal entities; pick one with LegalEntityId = the user's legal entity.
Valid values come from GET /v1/cost-price.
Public-holiday calendar that drives the user's non-working days.
Can be set on create only.
Refers to HolidayCalendarRead.HolidayCalendarID.
- Not an existing calendar — 37033 (business rule, HTTP 500).
> Not yet documented: values → lookup op.
Allowance (per-diem) legislation that applies to the user's travel expenses.
Can be set on create only.
Refers to AllowanceLegislationRead.AllowanceLegislationID. Look up valid values with GET /v1/allowance-legislation.
- Not an existing legislation — 37034 (business rule, HTTP 500).
Valid values come from GET /v1/allowance-legislation.
Normal working time (weekly norm) of the user. Also stored as the user's work week.
Can be set on create only.
Refers to NormalWorkingTimeRead.NormalWorkingTimeID.
- Not an existing normal working time — 37035 (business rule, HTTP 500).
- Account on an edition that allows only the default normal working time — 37923 (business rule, HTTP 500).
> Not yet documented: values → lookup op.
Salary group of the user.
Can be set on create only.
Refers to SalaryGroupRead.SalaryGroupID.
- Feature SalaryGroups on and the group does not exist / is inactive — 37036 / 37925 (business rule, HTTP 500).
- Feature SalaryGroups off and the ID is not the system salary group — 37924 (business rule, HTTP 500).
Model validation only checks NotNull (never fails on an int); omitting it sends 0, which the business rule rejects in both feature states.
> Not yet documented: values → lookup op.
Licence the user is billed for; decides which functionality the user can reach.
Can be set on create only.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
| 2 | Standard | Standard user: access to most functionality, depending on the account's licence model. The only value accepted when neither ExternalUserLicense nor TimeExpenseUserLicense is on. |
| 7 | Limited | Limited user. Accepted only when feature ExternalUserLicense is on; the system role 'Limited user' is assigned and UserRoleIDs must be empty. |
| 8 | TimeExpense | Time and expense tracking with reports. Accepted only when feature TimeExpenseUserLicense is on; the system role 'Time and expense registrations' is assigned and UserRoleIDs must be empty. |
| 1 | External | External user (time tracking only). Rejected by this operation. |
| 3 | Advanced | Advanced user of special licence models. Rejected by this operation. |
| 4 | ExternalPlus | External user with external disbursement. Rejected by this operation. |
| 6 | TlpBasicLimited | Limited user of the Basic contract. Rejected by this operation. |
| 0 | NotSpecified | No licence (no access). Rejected by this operation. |
Additional roles to give the user. Only for Standard (2) users.
Conditional. Allowed only when UserLicenseType is Standard (2); must be null or empty otherwise (400).
Can be set on create only.
Refers to RoleRead.RoleID. Look up valid values with GET /v1/role.
> Not yet documented: multiple → max items.
> Not yet documented: multiple → duplicates allowed.
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
Login name of the new user. Not returned by any REST v1 read.
Can be set on create only.
Alternate key, unique per TimeLog site.
Contains personal data.
-
Same user name or initials as an active user — 20022 (business rule, HTTP 500).
-
Same user name or initials as an inactive user — 20024 (business rule, HTTP 500). Create never reuses an inactive user's name.
First name.
Can be set on create only.
Contains personal data.
Last name.
Can be set on create only.
Contains personal data.
Short handle of the user (see User.Initials). Must not be used by another user (active: 20022, inactive: 20024).
Can be set on create only.
Alternate key, unique per TimeLog site.
Contains personal data.
E-mail address.
Can be set on create only.
Contains personal data.
-
Empty or not an e-mail address — 400 Code 200 ('Email' must not be empty / The specified condition was not met for 'Email').
-
Passes the model check but fails the business-rule format check — 31114 (business rule, HTTP 500).
Job title.
Can be set on create only.
Contains personal data.
Employee number.
Conditional. Required when the account feature ShowEmployeeID is on (31027, business rule, HTTP 500); optional otherwise.
Can be set on create only.
Key from your own system, unique per TimeLog site. Uniqueness is not enforced.
Contains personal data.
First day of employment.
Can be set on create only.
Time zone: not yet documented.
Contains personal data.
- Year not valid — 31029 (business rule, HTTP 500).
Not yet documented: constraints → timezone.
Legal entity the user is employed in.
Can be set on create only.
Refers to LegalEntityRead.LegalEntityID. Look up valid values with GET /v1/legal-entity.
-
Feature MultiLegalEntities on and the ID does not exist — 37029 (business rule, HTTP 500).
-
Feature MultiLegalEntities off and the ID is not the system legal entity — 37921 (business rule, HTTP 500). Use
GET /v1/legal-entity/system.
Valid values come from GET /v1/legal-entity.
Department the user belongs to. Inactive departments are accepted.
Can be set on create only.
Refers to DepartmentRead.DepartmentID. Look up valid values with GET /v1/department.
- The ID does not exist — 37039 (business rule, HTTP 500).
Valid values come from GET /v1/department.
User ID (not employee ID) of the manager who approves this user's time sheets. 0 = no approval manager.
Can be set on create only.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- Non-zero and not an existing user — 37038 (business rule, HTTP 500).
Valid values come from GET /v1/user.
Employee type (account-defined category). 0 = none.
Can be set on create only.
Refers to EmployeeTypeRead.EmployeeTypeID.
- Non-zero and not an existing employee type — 37028 (business rule, HTTP 500).
Valid values come from GET /v1/role.
Not yet documented: values → lookup op.
Standard hourly (sales) rate of the user; must belong to the user's legal entity.
Can be set on create only.
Refers to HourlyRateRead.HourlyRateID. Look up valid values with GET /v1/hourly-rate.
-
0 — 400 'Standard Hourly Rate ID' must not be empty.
-
Negative — Replaced by the legal entity's system default hourly rate.
-
Positive and not an existing rate / not a rate of LegalEntityID — 37030 / 37031 (business rule, HTTP 500).
GET /v1/hourly-rate returns the price-list entries of ALL legal entities (incl. customer lists); pick an entry with CustomerID null and LegalEntityID = the user's legal entity.
Valid values come from GET /v1/hourly-rate.
Cost price group of the user; must belong to the user's legal entity.
Can be set on create only.
Refers to CostPriceRead.CostPriceID. Look up valid values with GET /v1/cost-price.
- Not an existing cost price / not one of LegalEntityID — 37032 / 37051 (business rule, HTTP 500).
GET /v1/cost-price returns the cost price groups of ALL legal entities; pick one with LegalEntityId = the user's legal entity.
Valid values come from GET /v1/cost-price.
Public-holiday calendar that drives the user's non-working days.
Can be set on create only.
Refers to HolidayCalendarRead.HolidayCalendarID.
- Not an existing calendar — 37033 (business rule, HTTP 500).
Not yet documented: values → lookup op.
Allowance (per-diem) legislation that applies to the user's travel expenses.
Can be set on create only.
Refers to AllowanceLegislationRead.AllowanceLegislationID. Look up valid values with GET /v1/allowance-legislation.
- Not an existing legislation — 37034 (business rule, HTTP 500).
Valid values come from GET /v1/allowance-legislation.
Normal working time (weekly norm) of the user. Also stored as the user's work week.
Can be set on create only.
Refers to NormalWorkingTimeRead.NormalWorkingTimeID.
-
Not an existing normal working time — 37035 (business rule, HTTP 500).
-
Account on an edition that allows only the default normal working time — 37923 (business rule, HTTP 500).
Not yet documented: values → lookup op.
Salary group of the user.
Can be set on create only.
Refers to SalaryGroupRead.SalaryGroupID.
-
Feature SalaryGroups on and the group does not exist / is inactive — 37036 / 37925 (business rule, HTTP 500).
-
Feature SalaryGroups off and the ID is not the system salary group — 37924 (business rule, HTTP 500).
Model validation only checks NotNull (never fails on an int); omitting it sends 0, which the business rule rejects in both feature states.
Not yet documented: values → lookup op.
Licence the user is billed for; decides which functionality the user can reach.
Can be set on create only.
Allowed values:
| Value | Name | Meaning |
|---|---|---|
2 | Standard | Standard user: access to most functionality, depending on the account's licence model. The only value accepted when neither ExternalUserLicense nor TimeExpenseUserLicense is on. |
7 | Limited | Limited user. Accepted only when feature ExternalUserLicense is on; the system role 'Limited user' is assigned and UserRoleIDs must be empty. |
8 | TimeExpense | Time and expense tracking with reports. Accepted only when feature TimeExpenseUserLicense is on; the system role 'Time and expense registrations' is assigned and UserRoleIDs must be empty. |
1 | External | External user (time tracking only). Rejected by this operation. |
3 | Advanced | Advanced user of special licence models. Rejected by this operation. |
4 | ExternalPlus | External user with external disbursement. Rejected by this operation. |
6 | TlpBasicLimited | Limited user of the Basic contract. Rejected by this operation. |
0 | NotSpecified | No licence (no access). Rejected by this operation. |
27813460Additional roles to give the user. Only for Standard (2) users.
Conditional. Allowed only when UserLicenseType is Standard (2); must be null or empty otherwise (400).
Can be set on create only.
Refers to RoleRead.RoleID. Look up valid values with GET /v1/role.
Not yet documented: multiple → max items.
Not yet documented: multiple → duplicates allowed.
Responses
A TimeLog user as REST v1 returns it: identifiers, name, initials, e-mail, active flag, user type and legal entity. In TimeLog a user and an employee are the same person seen from two sides: the user is the login, the employee record carries the HR data.