MENU navbar-image

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&section_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
    }
}
 

Request   

GET api/v2/employees

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

status   string  optional  

active (default), resigned or all. Example: active

emp_code   string  optional  

One code or several separated by commas. Example: 101,102

search   string  optional  

Part of the name or code. Example: john

branch_id   integer  optional  

Example: 1

department_id   integer  optional  

Example: 1

location_id   integer  optional  

Example: 1

section_id   integer  optional  

Example: 1

designation_id   integer  optional  

Example: 1

updated_since   string  optional  

Only employees changed at or after this time (Y-m-d H:i:s). Example: 2026-01-01 00:00:00

per_page   integer  optional  

1 to 200, default 50. Example: 50

page   integer  optional  

Example: 1

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()

Request   

GET api/v2/employees/{emp_code}

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

emp_code   string   

Example: 101

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()

Request   

POST api/v2/employees

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

emp_code   integer   

Unique. Example: 128

emp_name   string   

Example: John Smith

biomatric_code   integer   

The user id on the devices; unique. Example: 128

emp_branch   integer  optional  

Branch id. Example: 1

department   integer  optional  

Department id. Example: 1

designation   integer  optional  

Designation id. Example: 1

location   integer  optional  

Location id. Example: 1

section   integer  optional  

Section id. Example: 1

team   integer  optional  

Team id. Example: 1

emp_type   integer  optional  

Employment type id. Example: 1

biometric_group   integer  optional  

Biometric group id: its devices get the employee. Example: 1

attendance_setting   integer  optional  

Attendance setting id. Example: 1

shift_group   integer  optional  

Shift group id. Example: 1

holiday_group   integer  optional  

Holiday group id. Example: 1

manager   integer  optional  

Manager group id. Example: 1

work_status   integer  optional  

1 working, 2 resigned. Example: 1

gender   integer  optional  

Example: 1

nationality   integer  optional  

Example: 784

birth_date   string  optional  

Y-m-d. Example: 1990-05-01

joindate   string  optional  

Y-m-d. Example: 2026-01-01

resigning_date   string  optional  

Y-m-d. Example: 2026-12-31

leave_credit_date   string  optional  

Y-m-d. Example: 2026-01-01

email   string  optional  

Example: john@example.com

mobile   string  optional  

With the country code. Example: +971501234567

contact_no   string  optional  

Example: 042345678

address   string  optional  

Example: Dubai

login_name   string  optional  

For the employee app and ESS. Example: john

password   string  optional  

For the employee app and ESS. Example: Secret123

gate_pass_number   string  optional  

Example: GP-001

passport_no   string  optional  

Saved, never returned. Example: N1234567

passport_expiry   string  optional  

Y-m-d. Example: 2030-01-01

id_card_no   string  optional  

Saved, never returned. Example: 784-1990-1234567-1

id_expiry   string  optional  

Y-m-d. Example: 2030-01-01

contract_name   string  optional  

Example: Full time

contract_expiry   string  optional  

Y-m-d. Example: 2027-01-01

custom_field1   string  optional  

Example: Value 1

custom_field2   string  optional  

Example: Value 2

custom_field3   string  optional  

Example: Value 3

custom_field4   string  optional  

Example: Value 4

base64image   file  optional  

The photo: jpg, png, gif, bmp or webp, at most 5 MB (send the request as multipart/form-data).

auto_leave_balance_recalculation   integer  optional  

0 skips the leave balance calculation after the save, 1 forces it. Example: 1

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()

Request   

PATCH api/v2/employees/{emp_code}

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

emp_code   string   

Example: 128

Body Parameters

emp_code   integer  optional  

A new employee code, not used by another employee. Example: 128

emp_name   string  optional  

Example: John Smith

biomatric_code   integer  optional  

Not used by another employee. Example: 128

keep_biometric   boolean  optional  

When resigning: true keeps the employee on the devices. Example: false

emp_branch   integer  optional  

Branch id. Example: 1

department   integer  optional  

Department id. Example: 1

designation   integer  optional  

Designation id. Example: 1

location   integer  optional  

Location id. Example: 1

section   integer  optional  

Section id. Example: 1

team   integer  optional  

Team id. Example: 1

emp_type   integer  optional  

Employment type id. Example: 1

biometric_group   integer  optional  

Biometric group id: its devices get the employee. Example: 1

attendance_setting   integer  optional  

Attendance setting id. Example: 1

shift_group   integer  optional  

Shift group id. Example: 1

holiday_group   integer  optional  

Holiday group id. Example: 1

manager   integer  optional  

Manager group id. Example: 1

work_status   integer  optional  

1 working, 2 resigned. Example: 1

gender   integer  optional  

Example: 1

nationality   integer  optional  

Example: 784

birth_date   string  optional  

Y-m-d. Example: 1990-05-01

joindate   string  optional  

Y-m-d. Example: 2026-01-01

resigning_date   string  optional  

Y-m-d. Example: 2026-12-31

leave_credit_date   string  optional  

Y-m-d. Example: 2026-01-01

email   string  optional  

Example: john@example.com

mobile   string  optional  

With the country code. Example: +971501234567

contact_no   string  optional  

Example: 042345678

address   string  optional  

Example: Dubai

login_name   string  optional  

For the employee app and ESS. Example: john

password   string  optional  

For the employee app and ESS. Example: Secret123

gate_pass_number   string  optional  

Example: GP-001

passport_no   string  optional  

Saved, never returned. Example: N1234567

passport_expiry   string  optional  

Y-m-d. Example: 2030-01-01

id_card_no   string  optional  

Saved, never returned. Example: 784-1990-1234567-1

id_expiry   string  optional  

Y-m-d. Example: 2030-01-01

contract_name   string  optional  

Example: Full time

contract_expiry   string  optional  

Y-m-d. Example: 2027-01-01

custom_field1   string  optional  

Example: Value 1

custom_field2   string  optional  

Example: Value 2

custom_field3   string  optional  

Example: Value 3

custom_field4   string  optional  

Example: Value 4

base64image   file  optional  

The photo: jpg, png, gif, bmp or webp, at most 5 MB (send the request as multipart/form-data).

auto_leave_balance_recalculation   integer  optional  

0 skips the leave balance calculation after the save, 1 forces it. Example: 1

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()

Request   

DELETE api/v2/employees/{emp_code}

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

emp_code   string   

Example: 128

Query Parameters

keep_biometric   boolean  optional  

Example: false

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
            }
        }
    ]
}
 

Request   

GET api/v2/leave-types

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

date   string  optional  

Y-m-d. Example: 2026-10-24

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
        }
    ]
}
 

Request   

GET api/v2/leave-balances

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

emp_code   string   

One code, or up to 50 separated by commas. Example: 101,102

leave_type_id   integer  optional  

Only this leave type. Example: 4

date   string  optional  

Y-m-d. Example: 2026-10-24

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
    }
}
 

Request   

GET api/v2/leaves

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

from   string   

Y-m-d. Example: 2026-10-01

to   string   

Y-m-d, at most 366 days after from. Example: 2026-10-31

emp_code   string  optional  

One code or several separated by commas. Example: 101

status   string  optional  

pending, approved or declined. Example: approved

leave_type_id   integer  optional  

Example: 4

updated_since   string  optional  

Only leaves changed at or after this time (Y-m-d H:i:s). Example: 2026-10-01 00:00:00

per_page   integer  optional  

Example: 50

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()

Request   

GET api/v2/leaves/{id}

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Example: 52

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()

Request   

POST api/v2/leaves

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

emp_code   string   

One code, or several separated by commas. Example: 101,102

leave_type_id   integer   

Example: 4

from   string   

Y-m-d. Example: 2026-10-06

to   string   

Y-m-d. Example: 2026-10-07

day_type   string   

full_day or half_day. Example: full_day

remarks   string  optional  

At most 250 characters. Example: Fever

approved   boolean  optional  

true to add it approved. Example: false

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()

Request   

PATCH api/v2/leaves/{id}

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Example: 52

Body Parameters

emp_code   string  optional  

Example: 101

leave_type_id   integer  optional  

Example: 4

from   string  optional  

Y-m-d. Example: 2026-10-06

to   string  optional  

Y-m-d. Example: 2026-10-08

day_type   string  optional  

full_day or half_day. Example: full_day

remarks   string  optional  

Example: Fever

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()

Request   

POST api/v2/leaves/{id}/approve

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Example: 52

Body Parameters

remarks   string  optional  

Example: OK

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()

Request   

POST api/v2/leaves/{id}/decline

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Example: 52

Body Parameters

remarks   string  optional  

Example: Busy week

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()

Request   

DELETE api/v2/leaves/{id}

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Example: 52

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
    }
}
 

Request   

GET api/v2/attendance/daily

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

from   string   

Y-m-d. Example: 2026-10-01

to   string   

Y-m-d, at most 62 days after from. Example: 2026-10-31

emp_code   string  optional  

One code or several separated by commas. Example: 101

per_page   integer  optional  

1 to 200, default 50. Example: 50

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
    }
}
 

Request   

GET api/v2/punches

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

from   string   

Punch date, Y-m-d. Example: 2026-10-01

to   string   

Y-m-d, at most 62 days after from. Example: 2026-10-31

emp_code   string  optional  

One code or several separated by commas. Example: 101

serial_number   string  optional  

Only this device. Example: ABC123

updated_since   string  optional  

Only punches received at or after this time (Y-m-d H:i:s). Example: 2026-10-24 08:00:00

per_page   integer  optional  

1 to 200, default 50. Example: 50

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"
            }
        ]
    }
}
 

Request   

POST api/v2/punches

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

punches   object[]   

Up to 500 punches.

emp_code   string   

Example: 101

punched_at   string   

Y-m-d H:i:s, not in the future. Example: 2026-10-24 08:01:00

serial_number   string   

A device of the company. Example: ABC123

direction   string  optional  

in, out, break_in, break_out, overtime_in, overtime_out, meal_start, meal_end or unknown (default). Example: in

remarks   string  optional  

At most 100 characters. Example: Turnstile 2

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
    }
}
 

Request   

POST api/v2/attendance/calculate

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

from   string   

Y-m-d. Example: 2026-10-01

to   string   

Y-m-d. Example: 2026-10-31

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
        }
    ]
}
 

Request   

GET api/v2/branches

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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
        }
    ]
}
 

Request   

GET api/v2/departments

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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"
        }
    ]
}
 

Request   

GET api/v2/designations

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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
        }
    ]
}
 

Request   

GET api/v2/locations

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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
        }
    ]
}
 

Request   

GET api/v2/sections

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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"
        }
    ]
}
 

Request   

GET api/v2/teams

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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"
        }
    ]
}
 

Request   

GET api/v2/employment-types

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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"
        }
    ]
}
 

Request   

GET api/v2/biometric-groups

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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
        }
    ]
}
 

Request   

GET api/v2/shifts

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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
    }
}
 

Request   

GET api/v2/shift-assignments

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

from   string   

Y-m-d. Example: 2026-10-01

to   string   

Y-m-d, at most 366 days after from. Example: 2026-10-31

emp_code   string  optional  

One code or several separated by commas. Example: 101

per_page   integer  optional  

Example: 50

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"
        }
    ]
}
 

Request   

GET api/v2/holiday-groups

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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
        }
    ]
}
 

Request   

GET api/v2/holidays

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

year   integer  optional  

The year, default this year. Example: 2026

holiday_group_id   integer  optional  

Example: 1

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
        }
    ]
}
 

Request   

GET api/v2/devices

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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
    }
}
 

Request   

GET api/v2/regularizations

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

from   string   

The day corrected, Y-m-d. Example: 2026-10-01

to   string   

Y-m-d, at most 366 days after from. Example: 2026-10-31

emp_code   string  optional  

One code or several separated by commas. Example: 101

status   string  optional  

pending, approved or declined. Example: pending

per_page   integer  optional  

Example: 50

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
    }
}
 

Request   

GET api/v2/work-requests

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

from   string   

Y-m-d: requests from this day on. Example: 2026-10-01

to   string   

Y-m-d, at most 366 days after from. Example: 2026-10-31

emp_code   string  optional  

Example: 101

type   string  optional  

wfh or comp_off. Example: wfh

status   string  optional  

pending, approved, declined or cancelled. Example: approved

per_page   integer  optional  

Example: 50

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
    }
}
 

Request   

GET api/v2/expense-claims

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Query Parameters

from   string   

The expense date, Y-m-d. Example: 2026-10-01

to   string   

Y-m-d, at most 366 days after from. Example: 2026-10-31

emp_code   string  optional  

Example: 101

status   string  optional  

pending, approved, declined or cancelled. Example: approved

per_page   integer  optional  

Example: 50

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"
        }
    ]
}
 

Request   

GET api/v2/webhooks

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

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_..."
    }
}
 

Request   

POST api/v2/webhooks

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

Body Parameters

url   string   

HTTPS, a public address. Example: https://example.com/timetaag

events   string[]   

The events to receive.

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()

Request   

POST api/v2/webhooks/{id}/enable

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Example: 3

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()

Request   

DELETE api/v2/webhooks/{id}

Headers

Authorization      

Example: Bearer {YOUR_API_KEY}

Content-Type      

Example: application/json

Accept      

Example: application/json

URL Parameters

id   integer   

Example: 3