Register an external system
Registers a new external system and returns it with its new ID.
Creates the "other side" an integration links TimeLog objects to. Do this once per integration (per account), store the returned ID, and use it as ExternalSystemID when you write external keys on time registrations, projects and tasks. TimeLog does not deduplicate: every call creates a new system, even with a name that already exists.
Use it to:
- First run of a new integration: register your system, persist Properties.ID in your own configuration.
- One system per counterpart: an integration syncing two ERPs registers two systems so their keys never collide.
Not for:
- Setting up a TimeLog built-in finance/payroll integration — creating its integration handler registers the system automatically.
Behaviour:
- Not idempotent.
- Not safe to retry blindly. A retry after a timeout registers a second system. List first and match on the ID you stored (or, failing that, on name + description) before retrying.
- Duplicates: None — names are not unique and are not checked.
- Changes are visible to the next read immediately.
Call first: GET /v1/external-systems
Next: PUT /v1/time-registration/{timeRegistrationID}/external-keys, PUT /v1/project/{projectID}/external-keys, PUT /v1/task/{taskID}/external-keys
See also: PUT /v1/external-systems/{externalSystemID}
Not yet documented: examples (success).
curl -X POST "https://app3.timelog.com/your-account/api/v1/external-systems" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"Name": "John Doe",
"Description": "example_string"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/external-systems"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"Name": "John Doe",
"Description": "example_string"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/external-systems", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"Name": "John Doe",
"Description": "example_string"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"Name": "John Doe",
"Description": "example_string"
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/external-systems", 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/external-systems')
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 = '{
"Name": "John Doe",
"Description": "example_string"
}'
response = http.request(request)
puts response.body
{
"Properties": {
"ID": 123,
"Name": "John Doe",
"Description": "example_string",
"IsSystemExternalSystem": true
},
"Links": [
{
"Href": "example_string",
"Rel": "example_string"
}
],
"Actions": [
{}
]
}
{
"Code": 200,
"Details": [
"'Name' must not be empty.",
"'Description' must not be empty."
],
"Message": "Invalid model state error.",
"Parameters": "externalSystemCreateModel: {"Name":null,"Description":null}",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/external-systems"
}
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
{
"Code": 100,
"Details": [],
"Message": "Unknown error occur in system, please contact system administrator.",
"Parameters": "externalSystemCreateModel: null",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/external-systems"
}
/v1/external-systems
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
Display name of the external system. Not unique: sending an existing name creates a second system with that name. - null, empty or whitespace-only — 400 "'Name' must not be empty." Built-in TimeLog integrations resolve key mappings by system name. Giving a system the name of another one makes those name-based lookups ambiguous (see ExternalSystemRead.Name). > Not yet documented: behaviour.
Free-text description of the external system. - null, empty or whitespace-only — 400 "'Description' must not be empty."
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
Display name of the external system. Not unique: sending an existing name creates a second system with that name.
- null, empty or whitespace-only — 400 "'Name' must not be empty."
Built-in TimeLog integrations resolve key mappings by system name. Giving a system the name of another one makes those name-based lookups ambiguous (see ExternalSystemRead.Name).
Not yet documented: behaviour.
Free-text description of the external system.
- null, empty or whitespace-only — 400 "'Description' must not be empty."
Responses
An external system registered in the account: a named "other side" (ERP, payroll, calendar, CRM …) that TimeLog objects can be linked to through external keys (ExternalKey.ExternalSystemID) and that integration conflicts are raised against. TimeLog's own built-in integrations create one per integration handler (named after the handler, e.g. "e-conomic <site>_v1"); an integrator can register its own with POST /v1/external-systems.