curl --request PUT \
--url https://api.teambuilder.ge/v1/employees/sync \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"employees": [
{
"external_id": "EMP-00042",
"email": "nino.beridze@example.com",
"first_name": "Nino",
"last_name": "Beridze",
"department_external_id": "D-ENG",
"department_name": "Engineering",
"job_title": "Front-End Developer"
}
]
}
'const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
employees: [
{
external_id: 'EMP-00042',
email: 'nino.beridze@example.com',
first_name: 'Nino',
last_name: 'Beridze',
department_external_id: 'D-ENG',
department_name: 'Engineering',
job_title: 'Front-End Developer'
}
]
})
};
fetch('https://api.teambuilder.ge/v1/employees/sync', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.teambuilder.ge/v1/employees/sync"
payload = { "employees": [
{
"external_id": "EMP-00042",
"email": "nino.beridze@example.com",
"first_name": "Nino",
"last_name": "Beridze",
"department_external_id": "D-ENG",
"department_name": "Engineering",
"job_title": "Front-End Developer"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text){
"created": 3,
"updated": 5,
"unchanged": 205,
"reactivated": 1,
"deactivated": 2,
"departments_created": 1,
"departments_restored": 0,
"deactivation_candidates": [
{
"employee_id": "2a9c4e6b-7d1f-4b3a-9e5c-8f0d2b4a6c13",
"external_id": "EMP-00017",
"email": "luka.tsiklauri@example.com"
},
{
"employee_id": "6f1b3d5a-9c2e-4a7b-8d4f-0e3c5a7b9d26",
"external_id": "EMP-00093",
"email": "mariam.lomidze@example.com"
}
],
"deactivation_candidates_total": 2,
"unlinked_members": 4,
"deactivation_limit": 25,
"deactivation_skipped_reason": null,
"errors": [
{
"index": 41,
"external_id": "EMP-00107",
"code": "external_id_mismatch",
"message": "this email belongs to an employee of your company with a different external_id"
}
],
"warnings": [
{
"index": 7,
"external_id": "EMP-00058",
"code": "department_moved_to_top_level",
"message": "the department's parent is archived, so it was restored at the top level"
}
]
}{
"error": {
"code": "validation_failed",
"message": "employees must contain no more than 5000 elements",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "unauthorized",
"message": "Invalid API key",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "forbidden",
"message": "This API key lacks the required scope: employees:write",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "conflict",
"message": "Another employee sync for your company is running; retry when it has finished",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "length_required",
"message": "Send the body with a Content-Length header",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "payload_too_large",
"message": "Request body is too large",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many employee syncs. Retry after 1800 seconds.",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}Sync your employee roster
Upsert your HR roster in one idempotent request, optionally deactivating employees who have left.
curl --request PUT \
--url https://api.teambuilder.ge/v1/employees/sync \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"employees": [
{
"external_id": "EMP-00042",
"email": "nino.beridze@example.com",
"first_name": "Nino",
"last_name": "Beridze",
"department_external_id": "D-ENG",
"department_name": "Engineering",
"job_title": "Front-End Developer"
}
]
}
'const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
employees: [
{
external_id: 'EMP-00042',
email: 'nino.beridze@example.com',
first_name: 'Nino',
last_name: 'Beridze',
department_external_id: 'D-ENG',
department_name: 'Engineering',
job_title: 'Front-End Developer'
}
]
})
};
fetch('https://api.teambuilder.ge/v1/employees/sync', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.teambuilder.ge/v1/employees/sync"
payload = { "employees": [
{
"external_id": "EMP-00042",
"email": "nino.beridze@example.com",
"first_name": "Nino",
"last_name": "Beridze",
"department_external_id": "D-ENG",
"department_name": "Engineering",
"job_title": "Front-End Developer"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text){
"created": 3,
"updated": 5,
"unchanged": 205,
"reactivated": 1,
"deactivated": 2,
"departments_created": 1,
"departments_restored": 0,
"deactivation_candidates": [
{
"employee_id": "2a9c4e6b-7d1f-4b3a-9e5c-8f0d2b4a6c13",
"external_id": "EMP-00017",
"email": "luka.tsiklauri@example.com"
},
{
"employee_id": "6f1b3d5a-9c2e-4a7b-8d4f-0e3c5a7b9d26",
"external_id": "EMP-00093",
"email": "mariam.lomidze@example.com"
}
],
"deactivation_candidates_total": 2,
"unlinked_members": 4,
"deactivation_limit": 25,
"deactivation_skipped_reason": null,
"errors": [
{
"index": 41,
"external_id": "EMP-00107",
"code": "external_id_mismatch",
"message": "this email belongs to an employee of your company with a different external_id"
}
],
"warnings": [
{
"index": 7,
"external_id": "EMP-00058",
"code": "department_moved_to_top_level",
"message": "the department's parent is archived, so it was restored at the top level"
}
]
}{
"error": {
"code": "validation_failed",
"message": "employees must contain no more than 5000 elements",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "unauthorized",
"message": "Invalid API key",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "forbidden",
"message": "This API key lacks the required scope: employees:write",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "conflict",
"message": "Another employee sync for your company is running; retry when it has finished",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "length_required",
"message": "Send the body with a Content-Length header",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "payload_too_large",
"message": "Request body is too large",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many employee syncs. Retry after 1800 seconds.",
"request_id": "req_7Hq2LmX9vT4kPz1RbN8sWc"
}
}Authorizations
API key — tb_live_… for production data, tb_test_… for the sandbox.
Body
Your employee roster, or one part of it.
The employees to sync, at most 5,000 per request. Each row is checked on its own: an invalid row is reported in errors and does not stop the others.
1 - 5000 elementsShow child attributes
Show child attributes
Set to true to deactivate the employees this request does not name. Send it only when the request carries your complete roster; leave it false when you split a roster across requests. Only employees linked to an external_id can be deactivated (the others are counted in unlinked_members), and never admins. Nobody is deactivated when any row is rejected as malformed, duplicated or outside your SSO domains, when the sync stopped part-way (batch_aborted), or when more employees would be deactivated than deactivation_limit allows (see max_deactivations). Deactivation is soft: history is kept, and a later sync that names the employee again reactivates them.
true
Confirms a large deactivation. Without it, one sync deactivates at most 10 employees or 20% of your active employees linked to an external_id (admins excluded), whichever is more; with it, at most this many. Send it only for a deliberate reorganisation. If more employees would be deactivated, nobody is, and deactivation_skipped_reason says why.
1 <= x <= 10000025
Response
The sync ran. Check errors for rows that were not applied.
What the sync did. created + updated + unchanged plus the number of errors equals the rows sent.
Employees added to your company.
x >= 03
Existing employees whose external_id, department or job title changed, or who were reactivated.
x >= 05
Rows that already matched TeamBuilder. Sending the same roster again reports every row here.
x >= 0205
Of updated: deactivated employees this roster named again, now active.
x >= 01
Employees deactivated because deactivate_missing was true. 0 otherwise.
x >= 02
Departments created, at the top level, because a row named an unknown one.
x >= 01
Archived departments restored because a row named their department_external_id.
x >= 00
Active employees linked to an external_id that this roster does not name, ordered by employee_id, at most 1,000. They were deactivated if deactivate_missing was true and deactivation_skipped_reason is null; otherwise they are only reported. Admins and employees with an admin-portal role are never candidates.
Show child attributes
Show child attributes
[
{
"employee_id": "2a9c4e6b-7d1f-4b3a-9e5c-8f0d2b4a6c13",
"external_id": "EMP-00017",
"email": "luka.tsiklauri@example.com"
},
{
"employee_id": "6f1b3d5a-9c2e-4a7b-8d4f-0e3c5a7b9d26",
"external_id": "EMP-00093",
"email": "mariam.lomidze@example.com"
}
]
All deactivation candidates, including any beyond the ones listed.
x >= 02
Active employees without an external_id (added in TeamBuilder, never synced) that this roster does not name. A sync never deactivates them. To link one, send their email with an external_id.
x >= 04
The most employees this sync could deactivate: max_deactivations if you sent it, else the default limit.
x >= 025
Why deactivate_missing deactivated nobody. Null when it was not requested or nothing stopped it.
rejected_rows: some rows were malformed, duplicated or outside your SSO domains, or the sync stopped part-way, so the roster may be incomplete.limit_exceeded: more employees would have been deactivated thandeactivation_limitallows; sendmax_deactivationsto confirm.
rejected_rows, limit_exceeded null
Rows that were not applied, in request order.
Show child attributes
Show child attributes
Rows that were applied but deserve a look, in request order.
Show child attributes
Show child attributes
