Introduction
Timetaag API provides secure and flexible endpoints for integrating time and attendance management into your applications. It supports real-time data syncing, employee authentication, shift scheduling, leave management, biometric device integration, and detailed reporting.
This documentation describes the Timetaag REST API for connecting your own software (payroll, ERP, HR, access control) to your attendance data.
Base URL
All endpoints start with https://app.timetaag.com/api/v2/.
Authentication
Send your key as Authorization: Bearer <key> with every request (the biotaag-api-key header works too). Make keys in Timetaag under Master Setup > API Settings (super admin): turn the API On, then create an API key - it is shown once, so copy it then. A key looks like 10500v2... (your company ID first) and identifies your company: you never send a company or database name.
Each key has scopes: read (every GET), write (add, change, delete) and webhooks. A key can be limited to some IP addresses and can expire. Revoke a key on the same page; it stops working at once.
Rate limits
60 requests a minute per company. Above that the API answers 429 Too Many Requests - wait a moment and try again. 20 requests a minute with a wrong key from one IP block that IP for a minute.
Answers
Success: {"data": ..., "meta": {...}} - lists come a page at a time (page, per_page up to 200, default 50) with meta.page, meta.per_page, meta.total and meta.last_page. Error: {"error": {"code": "...", "message": "...", "fields": {...}}}. Dates are Y-m-d, times Y-m-d H:i:s, in the company's time zone.
| Status | Meaning |
|---|---|
| 200 / 201 / 202 / 204 | Done (201 added, 202 queued, 204 deleted) |
| 401 | Missing, wrong, revoked or expired key, or the API is off |
| 403 | The key has not the scope, the IP is not allowed, or the plan stops reports |
| 404 | Not found |
| 422 | Fields not valid (error.fields), or the change was refused (error.message) |
| 429 | Too many requests |
| 500 | Server error - try again later or contact support |
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer {YOUR_API_KEY}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Make a key in Timetaag: Master Setup > API Settings (super admin). Turn the API On, then create an API key with the access it needs - it is shown once. Keep keys secret: a key acts with the super admin's access within its scopes.
v2 Employees
Employees. Adding, changing and deleting work as on the employee page (devices, leave balances, Deleted Employees).
List employees
requires authentication
Scope: read. Working employees unless status is sent; for a sync, send updated_since and keep the newest updated_at you received.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/employees?status=active&emp_code=101%2C102&search=john&branch_id=1&department_id=1&location_id=1§ion_id=1&designation_id=1&updated_since=2026-01-01+00%3A00%3A00&per_page=50&page=1" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/employees"
);
const params = {
"status": "active",
"emp_code": "101,102",
"search": "john",
"branch_id": "1",
"department_id": "1",
"location_id": "1",
"section_id": "1",
"designation_id": "1",
"updated_since": "2026-01-01 00:00:00",
"per_page": "50",
"page": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/employees';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'status' => 'active',
'emp_code' => '101,102',
'search' => 'john',
'branch_id' => '1',
'department_id' => '1',
'location_id' => '1',
'section_id' => '1',
'designation_id' => '1',
'updated_since' => '2026-01-01 00:00:00',
'per_page' => '50',
'page' => '1',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/employees'
params = {
'status': 'active',
'emp_code': '101,102',
'search': 'john',
'branch_id': '1',
'department_id': '1',
'location_id': '1',
'section_id': '1',
'designation_id': '1',
'updated_since': '2026-01-01 00:00:00',
'per_page': '50',
'page': '1',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 12,
"emp_code": "101",
"name": "John Smith",
"biometric_code": "101",
"gender": 1,
"birth_date": "1990-05-01",
"join_date": "2020-01-01",
"resign_date": null,
"leave_credit_date": null,
"email": "john@example.com",
"mobile": "+971501234567",
"contact_no": null,
"address": null,
"login_name": "john",
"photo_url": "https://app.timetaag.com/photos/10000/101.jpg",
"work_status": {
"id": 1,
"name": "Working"
},
"branch": {
"id": 1,
"name": "Head Office"
},
"department": {
"id": 2,
"name": "Sales"
},
"designation": {
"id": 3,
"name": "Manager"
},
"location": {
"id": 1,
"name": "Dubai"
},
"section": null,
"team": null,
"employment_type": {
"id": 1,
"name": "Permanent"
},
"attendance_setting_id": 1,
"shift_group_id": 1,
"holiday_group_id": 1,
"manager_group_id": null,
"biometric_group_id": 1,
"nationality": null,
"gate_pass_number": null,
"passport_expiry": null,
"id_expiry": null,
"contract_name": null,
"contract_expiry": null,
"custom_fields": {
"custom_field1": null,
"custom_field2": null,
"custom_field3": null,
"custom_field4": null
},
"created_at": "2026-01-01 09:00:00",
"updated_at": "2026-02-01 10:30:00"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get an employee
requires authentication
Scope: read.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/employees/101" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/employees/101"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/employees/101';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/employees/101'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add an employee
requires authentication
Scope: write. As on the employee page: with "Add new user in biometric" on (Global Settings) the employee goes to the devices of their biometric group, and the leave balances are calculated. Answers 201 with the employee.
Example request:
curl --request POST \
"https://app.timetaag.com/api/v2/employees" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"emp_code\": 128,
\"emp_name\": \"John Smith\",
\"biomatric_code\": 128,
\"emp_branch\": 1,
\"department\": 1,
\"designation\": 1,
\"location\": 1,
\"section\": 1,
\"team\": 1,
\"emp_type\": 1,
\"biometric_group\": 1,
\"attendance_setting\": 1,
\"shift_group\": 1,
\"holiday_group\": 1,
\"manager\": 1,
\"work_status\": 1,
\"gender\": 1,
\"nationality\": 784,
\"birth_date\": \"1990-05-01\",
\"joindate\": \"2026-01-01\",
\"resigning_date\": \"2026-12-31\",
\"leave_credit_date\": \"2026-01-01\",
\"email\": \"john@example.com\",
\"mobile\": \"+971501234567\",
\"contact_no\": \"042345678\",
\"address\": \"Dubai\",
\"login_name\": \"john\",
\"password\": \"Secret123\",
\"gate_pass_number\": \"GP-001\",
\"passport_no\": \"N1234567\",
\"passport_expiry\": \"2030-01-01\",
\"id_card_no\": \"784-1990-1234567-1\",
\"id_expiry\": \"2030-01-01\",
\"contract_name\": \"Full time\",
\"contract_expiry\": \"2027-01-01\",
\"custom_field1\": \"Value 1\",
\"custom_field2\": \"Value 2\",
\"custom_field3\": \"Value 3\",
\"custom_field4\": \"Value 4\",
\"auto_leave_balance_recalculation\": 1
}"
const url = new URL(
"https://app.timetaag.com/api/v2/employees"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"emp_code": 128,
"emp_name": "John Smith",
"biomatric_code": 128,
"emp_branch": 1,
"department": 1,
"designation": 1,
"location": 1,
"section": 1,
"team": 1,
"emp_type": 1,
"biometric_group": 1,
"attendance_setting": 1,
"shift_group": 1,
"holiday_group": 1,
"manager": 1,
"work_status": 1,
"gender": 1,
"nationality": 784,
"birth_date": "1990-05-01",
"joindate": "2026-01-01",
"resigning_date": "2026-12-31",
"leave_credit_date": "2026-01-01",
"email": "john@example.com",
"mobile": "+971501234567",
"contact_no": "042345678",
"address": "Dubai",
"login_name": "john",
"password": "Secret123",
"gate_pass_number": "GP-001",
"passport_no": "N1234567",
"passport_expiry": "2030-01-01",
"id_card_no": "784-1990-1234567-1",
"id_expiry": "2030-01-01",
"contract_name": "Full time",
"contract_expiry": "2027-01-01",
"custom_field1": "Value 1",
"custom_field2": "Value 2",
"custom_field3": "Value 3",
"custom_field4": "Value 4",
"auto_leave_balance_recalculation": 1
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/employees';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'emp_code' => 128,
'emp_name' => 'John Smith',
'biomatric_code' => 128,
'emp_branch' => 1,
'department' => 1,
'designation' => 1,
'location' => 1,
'section' => 1,
'team' => 1,
'emp_type' => 1,
'biometric_group' => 1,
'attendance_setting' => 1,
'shift_group' => 1,
'holiday_group' => 1,
'manager' => 1,
'work_status' => 1,
'gender' => 1,
'nationality' => 784,
'birth_date' => '1990-05-01',
'joindate' => '2026-01-01',
'resigning_date' => '2026-12-31',
'leave_credit_date' => '2026-01-01',
'email' => 'john@example.com',
'mobile' => '+971501234567',
'contact_no' => '042345678',
'address' => 'Dubai',
'login_name' => 'john',
'password' => 'Secret123',
'gate_pass_number' => 'GP-001',
'passport_no' => 'N1234567',
'passport_expiry' => '2030-01-01',
'id_card_no' => '784-1990-1234567-1',
'id_expiry' => '2030-01-01',
'contract_name' => 'Full time',
'contract_expiry' => '2027-01-01',
'custom_field1' => 'Value 1',
'custom_field2' => 'Value 2',
'custom_field3' => 'Value 3',
'custom_field4' => 'Value 4',
'auto_leave_balance_recalculation' => 1,
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/employees'
payload = {
"emp_code": 128,
"emp_name": "John Smith",
"biomatric_code": 128,
"emp_branch": 1,
"department": 1,
"designation": 1,
"location": 1,
"section": 1,
"team": 1,
"emp_type": 1,
"biometric_group": 1,
"attendance_setting": 1,
"shift_group": 1,
"holiday_group": 1,
"manager": 1,
"work_status": 1,
"gender": 1,
"nationality": 784,
"birth_date": "1990-05-01",
"joindate": "2026-01-01",
"resigning_date": "2026-12-31",
"leave_credit_date": "2026-01-01",
"email": "john@example.com",
"mobile": "+971501234567",
"contact_no": "042345678",
"address": "Dubai",
"login_name": "john",
"password": "Secret123",
"gate_pass_number": "GP-001",
"passport_no": "N1234567",
"passport_expiry": "2030-01-01",
"id_card_no": "784-1990-1234567-1",
"id_expiry": "2030-01-01",
"contract_name": "Full time",
"contract_expiry": "2027-01-01",
"custom_field1": "Value 1",
"custom_field2": "Value 2",
"custom_field3": "Value 3",
"custom_field4": "Value 4",
"auto_leave_balance_recalculation": 1
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Change an employee
requires authentication
Scope: write. Only the fields sent change; send a field empty to clear it. work_status 2 resigns the employee: off the devices of their biometric group when Global Settings say so (keep_biometric true keeps them).
Example request:
curl --request PATCH \
"https://app.timetaag.com/api/v2/employees/128" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"emp_code\": 128,
\"emp_name\": \"John Smith\",
\"biomatric_code\": 128,
\"keep_biometric\": false,
\"emp_branch\": 1,
\"department\": 1,
\"designation\": 1,
\"location\": 1,
\"section\": 1,
\"team\": 1,
\"emp_type\": 1,
\"biometric_group\": 1,
\"attendance_setting\": 1,
\"shift_group\": 1,
\"holiday_group\": 1,
\"manager\": 1,
\"work_status\": 1,
\"gender\": 1,
\"nationality\": 784,
\"birth_date\": \"1990-05-01\",
\"joindate\": \"2026-01-01\",
\"resigning_date\": \"2026-12-31\",
\"leave_credit_date\": \"2026-01-01\",
\"email\": \"john@example.com\",
\"mobile\": \"+971501234567\",
\"contact_no\": \"042345678\",
\"address\": \"Dubai\",
\"login_name\": \"john\",
\"password\": \"Secret123\",
\"gate_pass_number\": \"GP-001\",
\"passport_no\": \"N1234567\",
\"passport_expiry\": \"2030-01-01\",
\"id_card_no\": \"784-1990-1234567-1\",
\"id_expiry\": \"2030-01-01\",
\"contract_name\": \"Full time\",
\"contract_expiry\": \"2027-01-01\",
\"custom_field1\": \"Value 1\",
\"custom_field2\": \"Value 2\",
\"custom_field3\": \"Value 3\",
\"custom_field4\": \"Value 4\",
\"auto_leave_balance_recalculation\": 1
}"
const url = new URL(
"https://app.timetaag.com/api/v2/employees/128"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"emp_code": 128,
"emp_name": "John Smith",
"biomatric_code": 128,
"keep_biometric": false,
"emp_branch": 1,
"department": 1,
"designation": 1,
"location": 1,
"section": 1,
"team": 1,
"emp_type": 1,
"biometric_group": 1,
"attendance_setting": 1,
"shift_group": 1,
"holiday_group": 1,
"manager": 1,
"work_status": 1,
"gender": 1,
"nationality": 784,
"birth_date": "1990-05-01",
"joindate": "2026-01-01",
"resigning_date": "2026-12-31",
"leave_credit_date": "2026-01-01",
"email": "john@example.com",
"mobile": "+971501234567",
"contact_no": "042345678",
"address": "Dubai",
"login_name": "john",
"password": "Secret123",
"gate_pass_number": "GP-001",
"passport_no": "N1234567",
"passport_expiry": "2030-01-01",
"id_card_no": "784-1990-1234567-1",
"id_expiry": "2030-01-01",
"contract_name": "Full time",
"contract_expiry": "2027-01-01",
"custom_field1": "Value 1",
"custom_field2": "Value 2",
"custom_field3": "Value 3",
"custom_field4": "Value 4",
"auto_leave_balance_recalculation": 1
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/employees/128';
$response = $client->patch(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'emp_code' => 128,
'emp_name' => 'John Smith',
'biomatric_code' => 128,
'keep_biometric' => false,
'emp_branch' => 1,
'department' => 1,
'designation' => 1,
'location' => 1,
'section' => 1,
'team' => 1,
'emp_type' => 1,
'biometric_group' => 1,
'attendance_setting' => 1,
'shift_group' => 1,
'holiday_group' => 1,
'manager' => 1,
'work_status' => 1,
'gender' => 1,
'nationality' => 784,
'birth_date' => '1990-05-01',
'joindate' => '2026-01-01',
'resigning_date' => '2026-12-31',
'leave_credit_date' => '2026-01-01',
'email' => 'john@example.com',
'mobile' => '+971501234567',
'contact_no' => '042345678',
'address' => 'Dubai',
'login_name' => 'john',
'password' => 'Secret123',
'gate_pass_number' => 'GP-001',
'passport_no' => 'N1234567',
'passport_expiry' => '2030-01-01',
'id_card_no' => '784-1990-1234567-1',
'id_expiry' => '2030-01-01',
'contract_name' => 'Full time',
'contract_expiry' => '2027-01-01',
'custom_field1' => 'Value 1',
'custom_field2' => 'Value 2',
'custom_field3' => 'Value 3',
'custom_field4' => 'Value 4',
'auto_leave_balance_recalculation' => 1,
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/employees/128'
payload = {
"emp_code": 128,
"emp_name": "John Smith",
"biomatric_code": 128,
"keep_biometric": false,
"emp_branch": 1,
"department": 1,
"designation": 1,
"location": 1,
"section": 1,
"team": 1,
"emp_type": 1,
"biometric_group": 1,
"attendance_setting": 1,
"shift_group": 1,
"holiday_group": 1,
"manager": 1,
"work_status": 1,
"gender": 1,
"nationality": 784,
"birth_date": "1990-05-01",
"joindate": "2026-01-01",
"resigning_date": "2026-12-31",
"leave_credit_date": "2026-01-01",
"email": "john@example.com",
"mobile": "+971501234567",
"contact_no": "042345678",
"address": "Dubai",
"login_name": "john",
"password": "Secret123",
"gate_pass_number": "GP-001",
"passport_no": "N1234567",
"passport_expiry": "2030-01-01",
"id_card_no": "784-1990-1234567-1",
"id_expiry": "2030-01-01",
"contract_name": "Full time",
"contract_expiry": "2027-01-01",
"custom_field1": "Value 1",
"custom_field2": "Value 2",
"custom_field3": "Value 3",
"custom_field4": "Value 4",
"auto_leave_balance_recalculation": 1
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('PATCH', url, headers=headers, json=payload)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete an employee
requires authentication
Scope: write. As on the employee page: the employee moves to Deleted Employees (they can be restored there) and comes off the devices when Global Settings say so (keep_biometric true keeps them). Answers 204.
Example request:
curl --request DELETE \
"https://app.timetaag.com/api/v2/employees/128?keep_biometric=" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/employees/128"
);
const params = {
"keep_biometric": "0",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/employees/128';
$response = $client->delete(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'keep_biometric' => '0',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/employees/128'
params = {
'keep_biometric': '0',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('DELETE', url, headers=headers, params=params)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
v2 Leaves
Leave types, balances and leaves. Leaves go through the leave engine of the Leave Entries page: the leave type's active policy counts the days, checks the balance and rules, and keeps the balances.
Leave types
requires authentication
Scope: read. With the policy in force on the date (default today); policy is null when the type has none.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/leave-types?date=2026-10-24" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/leave-types"
);
const params = {
"date": "2026-10-24",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leave-types';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'date' => '2026-10-24',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leave-types'
params = {
'date': '2026-10-24',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 4,
"name": "Sick Leave",
"code": "SL",
"gender": "All",
"is_active": true,
"color": "#fde68a",
"policy": {
"version": 2,
"effective_from": "2026-08-01",
"allotment": "Monthly",
"credit_balance": 3,
"unlimited": false,
"negative_balance_allowed": false,
"day_types": "full_day",
"paid": true
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Leave balances
requires authentication
Scope: read. The balance of each leave type with a policy, for each employee, on the date (default today): the month's or year's balance, as the policy credits it. available is null for an unlimited type.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/leave-balances?emp_code=101%2C102&leave_type_id=4&date=2026-10-24" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/leave-balances"
);
const params = {
"emp_code": "101,102",
"leave_type_id": "4",
"date": "2026-10-24",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leave-balances';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'emp_code' => '101,102',
'leave_type_id' => '4',
'date' => '2026-10-24',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leave-balances'
params = {
'emp_code': '101,102',
'leave_type_id': '4',
'date': '2026-10-24',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"emp_code": "101",
"leave_type_id": 4,
"leave_type": "Sick Leave",
"unlimited": false,
"credited": 3,
"used": 1,
"reserved": 0.5,
"available": 1.5
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List leaves
requires authentication
Scope: read. Leaves that touch the dates (deleted leaves are left out).
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/leaves?from=2026-10-01&to=2026-10-31&emp_code=101&status=approved&leave_type_id=4&updated_since=2026-10-01+00%3A00%3A00&per_page=50" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/leaves"
);
const params = {
"from": "2026-10-01",
"to": "2026-10-31",
"emp_code": "101",
"status": "approved",
"leave_type_id": "4",
"updated_since": "2026-10-01 00:00:00",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leaves';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'from' => '2026-10-01',
'to' => '2026-10-31',
'emp_code' => '101',
'status' => 'approved',
'leave_type_id' => '4',
'updated_since' => '2026-10-01 00:00:00',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leaves'
params = {
'from': '2026-10-01',
'to': '2026-10-31',
'emp_code': '101',
'status': 'approved',
'leave_type_id': '4',
'updated_since': '2026-10-01 00:00:00',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 52,
"emp_code": "101",
"leave_type": {
"id": 4,
"name": "Sick Leave"
},
"from": "2026-10-06",
"to": "2026-10-07",
"day_type": "full_day",
"days": 2,
"status": "approved",
"remarks": "Fever",
"applied_at": "2026-10-05 09:10:00",
"applied_by": "101",
"policy_version_id": 2,
"group_id": null,
"document_url": null,
"created_at": "2026-10-05 09:10:00",
"updated_at": "2026-10-05 11:00:00"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a leave
requires authentication
Scope: read.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/leaves/52" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/leaves/52"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leaves/52';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leaves/52'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add leave
requires authentication
Scope: write. For one or more employees, as on the Leave Entries page. Answers 201 with the leaves added and, in meta.failed, each employee whose leave was refused and why (balance, overlap, policy rules ...). When no leave could be added: 422 with the reasons.
Example request:
curl --request POST \
"https://app.timetaag.com/api/v2/leaves" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"emp_code\": \"101,102\",
\"leave_type_id\": 4,
\"from\": \"2026-10-06\",
\"to\": \"2026-10-07\",
\"day_type\": \"full_day\",
\"remarks\": \"Fever\",
\"approved\": false
}"
const url = new URL(
"https://app.timetaag.com/api/v2/leaves"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"emp_code": "101,102",
"leave_type_id": 4,
"from": "2026-10-06",
"to": "2026-10-07",
"day_type": "full_day",
"remarks": "Fever",
"approved": false
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leaves';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'emp_code' => '101,102',
'leave_type_id' => 4,
'from' => '2026-10-06',
'to' => '2026-10-07',
'day_type' => 'full_day',
'remarks' => 'Fever',
'approved' => false,
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leaves'
payload = {
"emp_code": "101,102",
"leave_type_id": 4,
"from": "2026-10-06",
"to": "2026-10-07",
"day_type": "full_day",
"remarks": "Fever",
"approved": false
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Change a leave
requires authentication
Scope: write. New employee, dates, type or day type enter the leave again through the leave engine and remove the old one - the answer is the new leave, with a new id. When the new values are refused the old leave stays (also with a new id, in the error's fields.kept_leave_id). remarks alone just changes the remarks.
Example request:
curl --request PATCH \
"https://app.timetaag.com/api/v2/leaves/52" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"emp_code\": \"101\",
\"leave_type_id\": 4,
\"from\": \"2026-10-06\",
\"to\": \"2026-10-08\",
\"day_type\": \"full_day\",
\"remarks\": \"Fever\"
}"
const url = new URL(
"https://app.timetaag.com/api/v2/leaves/52"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"emp_code": "101",
"leave_type_id": 4,
"from": "2026-10-06",
"to": "2026-10-08",
"day_type": "full_day",
"remarks": "Fever"
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leaves/52';
$response = $client->patch(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'emp_code' => '101',
'leave_type_id' => 4,
'from' => '2026-10-06',
'to' => '2026-10-08',
'day_type' => 'full_day',
'remarks' => 'Fever',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leaves/52'
payload = {
"emp_code": "101",
"leave_type_id": 4,
"from": "2026-10-06",
"to": "2026-10-08",
"day_type": "full_day",
"remarks": "Fever"
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('PATCH', url, headers=headers, json=payload)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Approve a leave
requires authentication
Scope: write. As the super admin approving on the Leave Entries page (balances, notifications).
Example request:
curl --request POST \
"https://app.timetaag.com/api/v2/leaves/52/approve" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"remarks\": \"OK\"
}"
const url = new URL(
"https://app.timetaag.com/api/v2/leaves/52/approve"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"remarks": "OK"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leaves/52/approve';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'remarks' => 'OK',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leaves/52/approve'
payload = {
"remarks": "OK"
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Decline a leave
requires authentication
Scope: write. The reserved days go back to the balance.
Example request:
curl --request POST \
"https://app.timetaag.com/api/v2/leaves/52/decline" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"remarks\": \"Busy week\"
}"
const url = new URL(
"https://app.timetaag.com/api/v2/leaves/52/decline"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"remarks": "Busy week"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leaves/52/decline';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'remarks' => 'Busy week',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leaves/52/decline'
payload = {
"remarks": "Busy week"
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a leave
requires authentication
Scope: write. Its days go back to the balance. Answers 204.
Example request:
curl --request DELETE \
"https://app.timetaag.com/api/v2/leaves/52" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/leaves/52"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/leaves/52';
$response = $client->delete(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/leaves/52'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('DELETE', url, headers=headers)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
v2 Attendance
Daily attendance (as calculated), the punches it is calculated from, adding punches from other systems, and starting a calculation.
Daily attendance
requires authentication
Scope: read. One row per employee and day, as last calculated (Daily Attendance / reports). Days not calculated yet are missing: start a calculation first.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/attendance/daily?from=2026-10-01&to=2026-10-31&emp_code=101&per_page=50" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/attendance/daily"
);
const params = {
"from": "2026-10-01",
"to": "2026-10-31",
"emp_code": "101",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/attendance/daily';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'from' => '2026-10-01',
'to' => '2026-10-31',
'emp_code' => '101',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/attendance/daily'
params = {
'from': '2026-10-01',
'to': '2026-10-31',
'emp_code': '101',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"date": "2026-10-01",
"emp_code": "101",
"shift_id": 5,
"shift_start": "2026-10-01 08:00:00",
"shift_end": "2026-10-01 17:00:00",
"in_time": "2026-10-01 07:58:00",
"out_time": "2026-10-01 18:05:00",
"work_mins": 600,
"break_mins": 0,
"late_mins": 0,
"early_mins": 0,
"overtime_mins": 7,
"undertime_mins": 0,
"status_code": "P",
"status": "P",
"present": 1,
"absent": 0,
"leave_type_id": null,
"leave_fraction": null,
"weekly_off": false,
"holiday": false,
"outdoor": false,
"in_punch_missing": false,
"out_punch_missing": false,
"remarks": null,
"updated_at": "2026-10-02 01:00:00"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Punches
requires authentication
Scope: read. The punches received from devices, the mobile app, imports and this API (deleted punches are left out). For a sync, send updated_since and keep the newest received_at you got.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/punches?from=2026-10-01&to=2026-10-31&emp_code=101&serial_number=ABC123&updated_since=2026-10-24+08%3A00%3A00&per_page=50" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/punches"
);
const params = {
"from": "2026-10-01",
"to": "2026-10-31",
"emp_code": "101",
"serial_number": "ABC123",
"updated_since": "2026-10-24 08:00:00",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/punches';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'from' => '2026-10-01',
'to' => '2026-10-31',
'emp_code' => '101',
'serial_number' => 'ABC123',
'updated_since' => '2026-10-24 08:00:00',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/punches'
params = {
'from': '2026-10-01',
'to': '2026-10-31',
'emp_code': '101',
'serial_number': 'ABC123',
'updated_since': '2026-10-24 08:00:00',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 9812,
"emp_code": "101",
"punched_at": "2026-10-01 07:58:00",
"direction": "in",
"direction_code": 0,
"serial_number": "ABC123",
"authentication_mode": "FP",
"marking_type": null,
"approved": true,
"latitude": null,
"longitude": null,
"distance_km": null,
"remarks": null,
"received_at": "2026-10-01 07:58:05"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add punches
requires authentication
Scope: write. Punches from another system (access control, kiosk, ...), as Import Swipe Details adds them: for an employee, at a device of the company (add the system as a device first). A punch already there (same employee, time and device) is skipped. The punches count in the next attendance calculation. Answers 201; meta.failed lists the punches refused and why.
Example request:
curl --request POST \
"https://app.timetaag.com/api/v2/punches" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"punches\": [
{
\"emp_code\": \"101\",
\"punched_at\": \"2026-10-24 08:01:00\",
\"serial_number\": \"ABC123\",
\"direction\": \"in\",
\"remarks\": \"Turnstile 2\"
}
]
}"
const url = new URL(
"https://app.timetaag.com/api/v2/punches"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"punches": [
{
"emp_code": "101",
"punched_at": "2026-10-24 08:01:00",
"serial_number": "ABC123",
"direction": "in",
"remarks": "Turnstile 2"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/punches';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'punches' => [
[
'emp_code' => '101',
'punched_at' => '2026-10-24 08:01:00',
'serial_number' => 'ABC123',
'direction' => 'in',
'remarks' => 'Turnstile 2',
],
],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/punches'
payload = {
"punches": [
{
"emp_code": "101",
"punched_at": "2026-10-24 08:01:00",
"serial_number": "ABC123",
"direction": "in",
"remarks": "Turnstile 2"
}
]
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Example response (201):
{
"data": {
"added": 2,
"skipped_duplicates": 1
},
"meta": {
"failed": [
{
"index": 3,
"reason": "Employee not found"
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Calculate attendance
requires authentication
Scope: write. Calculates the daily attendance of the dates, as Calculate Attendance does. With the queue on (Global Settings) it runs in the background; otherwise at most 31 days at a time. Refused (403) while the plan is expired or over its limits, as the web app's reports.
Example request:
curl --request POST \
"https://app.timetaag.com/api/v2/attendance/calculate" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"from\": \"2026-10-01\",
\"to\": \"2026-10-31\"
}"
const url = new URL(
"https://app.timetaag.com/api/v2/attendance/calculate"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"from": "2026-10-01",
"to": "2026-10-31"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/attendance/calculate';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'from' => '2026-10-01',
'to' => '2026-10-31',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/attendance/calculate'
payload = {
"from": "2026-10-01",
"to": "2026-10-31"
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Example response (202):
{
"data": {
"from": "2026-10-01",
"to": "2026-10-31",
"queued": true
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
v2 Setup
The company's lists that employees point to (branches, departments, ...), shifts, holidays and devices. Scope: read. Each list is returned whole.
Branches
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/branches" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/branches"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/branches';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/branches'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "Head Office",
"code": "HO",
"email": null,
"address": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Departments
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/departments" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/departments"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/departments';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/departments'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "Sales",
"code": "SAL",
"description": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Designations
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/designations" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/designations"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/designations';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/designations'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "Manager"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Locations
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/locations" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/locations"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/locations';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/locations'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "Dubai",
"description": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Sections
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/sections" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/sections"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/sections';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/sections'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "Assembly",
"code": "ASM",
"description": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Teams
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/teams" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/teams"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/teams';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/teams'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "Team A"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Employment types
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/employment-types" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/employment-types"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/employment-types';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/employment-types'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "Permanent"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Biometric groups
requires authentication
The devices of a group get the users of the employees in that group.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/biometric-groups" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/biometric-groups"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/biometric-groups';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/biometric-groups'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "Main gate"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Shifts
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/shifts" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/shifts"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/shifts';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/shifts'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 4,
"name": "General",
"code": "GEN",
"start_time": "09:00",
"end_time": "18:00",
"break_start_time": "13:00",
"break_end_time": "14:00",
"is_flexi": false,
"flexi_duration_mins": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Shift assignments
requires authentication
Which shift each employee works, for assignments that touch the dates.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/shift-assignments?from=2026-10-01&to=2026-10-31&emp_code=101&per_page=50" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/shift-assignments"
);
const params = {
"from": "2026-10-01",
"to": "2026-10-31",
"emp_code": "101",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/shift-assignments';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'from' => '2026-10-01',
'to' => '2026-10-31',
'emp_code' => '101',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/shift-assignments'
params = {
'from': '2026-10-01',
'to': '2026-10-31',
'emp_code': '101',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 7,
"emp_code": "101",
"shift_id": 4,
"shift_code": "GEN",
"from": "2026-10-01",
"to": "2026-10-31"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Holiday groups
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/holiday-groups" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/holiday-groups"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/holiday-groups';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/holiday-groups'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 1,
"name": "UAE"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Holidays
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/holidays?year=2026&holiday_group_id=1" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/holidays"
);
const params = {
"year": "2026",
"holiday_group_id": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/holidays';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'year' => '2026',
'holiday_group_id' => '1',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/holidays'
params = {
'year': '2026',
'holiday_group_id': '1',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 3,
"name": "National Day",
"date": "2026-12-02",
"holiday_group_id": 1,
"branch_id": null,
"description": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Devices
requires authentication
The company's biometric devices. status is online, idle or offline, worked out as on the Biometric Devices page.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/devices" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/devices"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/devices';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/devices'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 3,
"name": "Main gate",
"code": "1811",
"serial_number": "ABC123",
"status": "online",
"last_seen_at": "2026-10-24 08:59:12",
"last_punches_at": "2026-10-24 08:58:40",
"firmware": "6.60",
"users": 120,
"fingerprints": 240,
"faces": 80,
"location_id": 1,
"branch_id": 1,
"biometric_group_id": 1
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
v2 Requests
What employees ask for in the app and ESS: attendance regularizations, and with HRMS on, work-from-home / comp-off requests and expense claims. Scope: read.
Regularizations
requires authentication
Corrections of a day's in / out time an employee asked for.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/regularizations?from=2026-10-01&to=2026-10-31&emp_code=101&status=pending&per_page=50" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/regularizations"
);
const params = {
"from": "2026-10-01",
"to": "2026-10-31",
"emp_code": "101",
"status": "pending",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/regularizations';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'from' => '2026-10-01',
'to' => '2026-10-31',
'emp_code' => '101',
'status' => 'pending',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/regularizations'
params = {
'from': '2026-10-01',
'to': '2026-10-31',
'emp_code': '101',
'status': 'pending',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 4,
"emp_code": "101",
"date": "2026-10-03",
"in_time": "08:00",
"out_time": "17:00",
"reason": "Forgot to punch",
"status": "pending",
"manager_remarks": null,
"decided_by": null,
"decided_at": null,
"created_at": "2026-10-03 18:00:00",
"updated_at": "2026-10-03 18:00:00"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Work requests
requires authentication
Work-from-home and comp-off requests (HRMS).
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/work-requests?from=2026-10-01&to=2026-10-31&emp_code=101&type=wfh&status=approved&per_page=50" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/work-requests"
);
const params = {
"from": "2026-10-01",
"to": "2026-10-31",
"emp_code": "101",
"type": "wfh",
"status": "approved",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/work-requests';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'from' => '2026-10-01',
'to' => '2026-10-31',
'emp_code' => '101',
'type' => 'wfh',
'status' => 'approved',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/work-requests'
params = {
'from': '2026-10-01',
'to': '2026-10-31',
'emp_code': '101',
'type': 'wfh',
'status': 'approved',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 9,
"emp_code": "101",
"type": "wfh",
"from": "2026-10-06",
"to": "2026-10-07",
"days": 2,
"half_day": false,
"worked_date": null,
"worked_mins": null,
"reason": "Visit",
"status": "approved",
"manager_remarks": null,
"decided_by": "1",
"decided_at": "2026-10-05 10:00:00",
"created_at": "2026-10-04 12:00:00",
"updated_at": "2026-10-05 10:00:00"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Expense claims
requires authentication
Expense claims (HRMS), with the payout state.
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/expense-claims?from=2026-10-01&to=2026-10-31&emp_code=101&status=approved&per_page=50" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/expense-claims"
);
const params = {
"from": "2026-10-01",
"to": "2026-10-31",
"emp_code": "101",
"status": "approved",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/expense-claims';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'from' => '2026-10-01',
'to' => '2026-10-31',
'emp_code' => '101',
'status' => 'approved',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/expense-claims'
params = {
'from': '2026-10-01',
'to': '2026-10-31',
'emp_code': '101',
'status': 'approved',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()Example response (200):
{
"data": [
{
"id": 3,
"emp_code": "101",
"category_id": 2,
"date": "2026-10-02",
"amount": 45.5,
"currency": "AED",
"title": "Taxi",
"description": null,
"receipt": true,
"status": "approved",
"manager_remarks": null,
"decided_by": "1",
"decided_at": "2026-10-03 09:00:00",
"payout_status": "pending",
"created_at": "2026-10-02 20:00:00",
"updated_at": "2026-10-03 09:00:00"
}
],
"meta": {
"page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
v2 Webhooks
Instead of asking again and again, your program can be told: TimeTaag POSTs each event to your HTTPS URL as JSON {"id", "event", "company_id", "created_at", "data"}, where data has the fields of the employee or leave as this API returns them (device.offline: {"devices": [...]}). Answer with a 2xx status within 10 seconds; a failed POST is tried again after 1 and 10 minutes, and after 50 failures in a row the webhook is switched off.
Check each POST: the X-TimeTaag-Signature header is "sha256=" followed by the HMAC-SHA256 of the raw body with the webhook's secret. X-TimeTaag-Event is the event, X-TimeTaag-Delivery the id (the same on a retry).
Events: employee.created, employee.updated, employee.deleted (employee page and API), leave.created, leave.approved, leave.declined, leave.deleted (Leave Entries, ESS, mobile app and API), device.offline (when the Offline Biometric task of Task Manager runs). Scope: webhooks.
List webhooks
requires authentication
Example request:
curl --request GET \
--get "https://app.timetaag.com/api/v2/webhooks" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/webhooks"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/webhooks';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/webhooks'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()Example response (200):
{
"data": [
{
"id": 3,
"url": "https://example.com/timetaag",
"events": [
"leave.approved"
],
"active": true,
"failures": 0,
"last_status": 200,
"last_error": null,
"last_sent_at": "2026-10-24 09:00:00",
"created_at": "2026-10-20 10:00:00"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add a webhook
requires authentication
Answers 201 with the webhook and its secret - the secret is shown only here: keep it to check the signatures.
Example request:
curl --request POST \
"https://app.timetaag.com/api/v2/webhooks" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"url\": \"https:\\/\\/example.com\\/timetaag\",
\"events\": [
\"leave.approved\",
\"leave.declined\"
]
}"
const url = new URL(
"https://app.timetaag.com/api/v2/webhooks"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"url": "https:\/\/example.com\/timetaag",
"events": [
"leave.approved",
"leave.declined"
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/webhooks';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'url' => 'https://example.com/timetaag',
'events' => [
'leave.approved',
'leave.declined',
],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/webhooks'
payload = {
"url": "https:\/\/example.com\/timetaag",
"events": [
"leave.approved",
"leave.declined"
]
}
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()Example response (201):
{
"data": {
"id": 3,
"url": "https://example.com/timetaag",
"events": [
"leave.approved",
"leave.declined"
],
"active": true,
"failures": 0,
"last_status": null,
"last_error": null,
"last_sent_at": null,
"created_at": "2026-10-24 09:00:00",
"secret": "whsec_..."
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Switch a webhook on again
requires authentication
After it was switched off for failing. Answers with the webhook.
Example request:
curl --request POST \
"https://app.timetaag.com/api/v2/webhooks/3/enable" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/webhooks/3/enable"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/webhooks/3/enable';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/webhooks/3/enable'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a webhook
requires authentication
Answers 204.
Example request:
curl --request DELETE \
"https://app.timetaag.com/api/v2/webhooks/3" \
--header "Authorization: Bearer {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://app.timetaag.com/api/v2/webhooks/3"
);
const headers = {
"Authorization": "Bearer {YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://app.timetaag.com/api/v2/webhooks/3';
$response = $client->delete(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_KEY}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));import requests
import json
url = 'https://app.timetaag.com/api/v2/webhooks/3'
headers = {
'Authorization': 'Bearer {YOUR_API_KEY}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('DELETE', url, headers=headers)
response.json()Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.