MENU navbar-image
Okta HR Okta HR API Reference

Introduction

REST API for Okta HR — mobile self-service, HR operations and Okta Suite internal integration.

Welcome to the Okta HR API — use it to power the employee mobile app, integrate accounting/ERP systems, or connect other Okta Suite products.

Responses are JSON:API documents: {"data": [{"id", "type", "attributes": {…}}]}.

Two APIs, two kinds of caller

*`/api/v1/— on behalf of a person.** Sanctum bearer tokens. The caller is a user, and everything they can see is what that user is allowed to see. Employee self-service lives under/api/v1/me/*`.

*`/api/internal/— on behalf of a product.** A service key in theX-Service-Keyheader, issued withphp artisan okta:issue-service-key `. This is for other Okta Suite products, which have their own users and cannot obtain a token for any of them.

A service key names a product, not a person, and it is suite-level: it carries no company. So every internal route addresses its company explicitly in the path — /api/internal/companies/{company}/… — and any endpoint that records who did something takes an acting_employee_id alongside the decision. Without it the approval chain would file an approval attributed to nobody.

Money and dates

Money is integer halalas (1 SAR = 100) everywhere, in both directions. Rates are basis points. Dates are stored UTC and displayed Asia/Riyadh; Y-m-d for dates, ISO-8601 for timestamps.

Onboarding a tenant from another suite product

The three calls, in order:

  1. POST /api/internal/companies — creates the company, seeds its defaults and creates its first hr-manager. Send your own tenant identifier as external_ref and the call becomes idempotent: retries, re-installs and double-clicks return the same company instead of a second one.
  2. POST /api/internal/companies/{company}/employees — pushes the staff across. Upserts on employee_number, so a nightly re-sync updates rows rather than growing a duplicate roster. Departments and branches are matched by name and created if new.
  3. POST /api/internal/companies/{company}/employees/{employee}/account — gives an employee a login. Deliberately separate from step 2: an HR record and a person who can sign in are different decisions, and syncing a roster should not silently mint dozens of accounts.

Send each employee's email in step 2. It is the only field both systems hold for the same human, so it is how your product later maps its signed-in user back onto an employee here — and GET /employees?search= matches on it.

Errors

401 no or unknown credentials · 404 the resource does not exist or belongs to another company · 422 your input, with a errors object naming the fields — the message is written to be shown to a user · 429 rate limited (120 req/min on the internal API).

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_TOKEN}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Obtain a Sanctum bearer token via POST /api/v1/auth/login (email, password, device_name), then send it as Authorization: Bearer {token}. The login endpoint itself and /api/internal/* (which uses the X-Service-Key header) do not use bearer tokens.

Endpoints

Issue a Sanctum token for the mobile app / API consumers.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/auth/login" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"email\": \"gbailey@example.net\",
    \"password\": \"|]|{+-\",
    \"device_name\": \"v\"
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/auth/login"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "email": "gbailey@example.net",
    "password": "|]|{+-",
    "device_name": "v"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/auth/login

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

email   string     

يجب أن يكون value بريدًا إلكترونيًا صحيحًا. Example: gbailey@example.net

password   string     

Example: |]|{+-

device_name   string     

يجب ألا يزيد طول value عن 100 حرفًا. Example: v

Revoke the token used for the current request.

requires authentication

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/auth/logout" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/auth/logout"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Request      

POST api/v1/auth/logout

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

The authenticated user's profile (mobile self-service).

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/me" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/me"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/me

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

List employees (paginated, filterable by status and search).

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/employees" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/employees"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/employees

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Create an employee. Money fields are integers in halalas.

requires authentication

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/employees" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"employee_number\": \"bngzmiyvdljnikhw\",
    \"name_ar\": \"a\",
    \"name_en\": \"y\",
    \"nationality\": \"k\",
    \"gender\": \"female\",
    \"birth_date\": \"2026-09-09T13:05:11\",
    \"marital_status\": \"married\",
    \"national_id\": \"cmyuwpwlvqwrsitc\",
    \"mobile\": \"pscqldzsnrwtujwv\",
    \"email\": \"jhaag@example.net\",
    \"national_address\": \"l\",
    \"city\": \"q\",
    \"address\": \"p\",
    \"job_title\": \"p\",
    \"contract_type\": \"remote\",
    \"hire_date\": \"2026-09-09T13:05:11\",
    \"contract_end_date\": \"2026-09-09T13:05:11\",
    \"probation_end_date\": \"2026-09-09T13:05:11\",
    \"status\": \"resigned\",
    \"basic_salary\": 9,
    \"housing_allowance\": 16,
    \"transport_allowance\": 27,
    \"other_allowances\": 4,
    \"iban\": \"w\",
    \"bank_name\": \"t\"
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/employees"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "employee_number": "bngzmiyvdljnikhw",
    "name_ar": "a",
    "name_en": "y",
    "nationality": "k",
    "gender": "female",
    "birth_date": "2026-09-09T13:05:11",
    "marital_status": "married",
    "national_id": "cmyuwpwlvqwrsitc",
    "mobile": "pscqldzsnrwtujwv",
    "email": "jhaag@example.net",
    "national_address": "l",
    "city": "q",
    "address": "p",
    "job_title": "p",
    "contract_type": "remote",
    "hire_date": "2026-09-09T13:05:11",
    "contract_end_date": "2026-09-09T13:05:11",
    "probation_end_date": "2026-09-09T13:05:11",
    "status": "resigned",
    "basic_salary": 9,
    "housing_allowance": 16,
    "transport_allowance": 27,
    "other_allowances": 4,
    "iban": "w",
    "bank_name": "t"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/employees

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

employee_number   string     

يجب ألا يزيد طول value عن 20 حرفًا. Example: bngzmiyvdljnikhw

name_ar   string     

يجب ألا يزيد طول value عن 255 حرفًا. Example: a

name_en   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: y

nationality   string  optional    

يجب ألا يزيد طول value عن 100 حرفًا. Example: k

gender   string  optional    

Example: female

Must be one of:
  • male
  • female
birth_date   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

marital_status   string  optional    

Example: married

Must be one of:
  • single
  • married
  • divorced
  • widowed
national_id   string  optional    

يجب ألا يزيد طول value عن 20 حرفًا. Example: cmyuwpwlvqwrsitc

mobile   string  optional    

يجب ألا يزيد طول value عن 20 حرفًا. Example: pscqldzsnrwtujwv

email   string  optional    

يجب أن يكون value بريدًا إلكترونيًا صحيحًا. يجب ألا يزيد طول value عن 255 حرفًا. Example: jhaag@example.net

national_address   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: l

city   string  optional    

يجب ألا يزيد طول value عن 100 حرفًا. Example: q

address   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: p

job_title   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: p

department_id   string  optional    

Must match an existing stored value.

branch_id   string  optional    

Must match an existing stored value.

manager_id   string  optional    

Must match an existing stored value.

contract_type   string  optional    

Example: remote

Must be one of:
  • full_time
  • part_time
  • temporary
  • remote
hire_date   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

contract_end_date   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

probation_end_date   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

status   string  optional    

Example: resigned

Must be one of:
  • active
  • suspended
  • resigned
  • terminated
basic_salary   integer     

يجب ألا يقل value عن 0. Example: 9

housing_allowance   integer  optional    

يجب ألا يقل value عن 0. Example: 16

transport_allowance   integer  optional    

يجب ألا يقل value عن 0. Example: 27

other_allowances   integer  optional    

يجب ألا يقل value عن 0. Example: 4

iban   string  optional    

يجب ألا يزيد طول value عن 34 حرفًا. Example: w

bank_name   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: t

Show a single employee.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/employees/16" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/employees/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/employees/{id}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The ID of the employee. Example: 16

Update an employee.

requires authentication

Example request:
curl --request PUT \
    "https://hr.getokta.io/api/v1/employees/16" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"employee_number\": \"bngzmiyvdljnikhw\",
    \"name_ar\": \"a\",
    \"name_en\": \"y\",
    \"nationality\": \"k\",
    \"gender\": \"male\",
    \"birth_date\": \"2026-09-09T13:05:11\",
    \"marital_status\": \"single\",
    \"national_id\": \"cmyuwpwlvqwrsitc\",
    \"mobile\": \"pscqldzsnrwtujwv\",
    \"email\": \"jhaag@example.net\",
    \"national_address\": \"l\",
    \"city\": \"q\",
    \"address\": \"p\",
    \"job_title\": \"p\",
    \"contract_type\": \"full_time\",
    \"hire_date\": \"2026-09-09T13:05:11\",
    \"contract_end_date\": \"2026-09-09T13:05:11\",
    \"probation_end_date\": \"2026-09-09T13:05:11\",
    \"status\": \"suspended\",
    \"basic_salary\": 9,
    \"housing_allowance\": 16,
    \"transport_allowance\": 27,
    \"other_allowances\": 4,
    \"iban\": \"w\",
    \"bank_name\": \"t\"
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/employees/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "employee_number": "bngzmiyvdljnikhw",
    "name_ar": "a",
    "name_en": "y",
    "nationality": "k",
    "gender": "male",
    "birth_date": "2026-09-09T13:05:11",
    "marital_status": "single",
    "national_id": "cmyuwpwlvqwrsitc",
    "mobile": "pscqldzsnrwtujwv",
    "email": "jhaag@example.net",
    "national_address": "l",
    "city": "q",
    "address": "p",
    "job_title": "p",
    "contract_type": "full_time",
    "hire_date": "2026-09-09T13:05:11",
    "contract_end_date": "2026-09-09T13:05:11",
    "probation_end_date": "2026-09-09T13:05:11",
    "status": "suspended",
    "basic_salary": 9,
    "housing_allowance": 16,
    "transport_allowance": 27,
    "other_allowances": 4,
    "iban": "w",
    "bank_name": "t"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

PUT api/v1/employees/{id}

PATCH api/v1/employees/{id}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The ID of the employee. Example: 16

Body Parameters

employee_number   string  optional    

يجب ألا يزيد طول value عن 20 حرفًا. Example: bngzmiyvdljnikhw

name_ar   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: a

name_en   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: y

nationality   string  optional    

يجب ألا يزيد طول value عن 100 حرفًا. Example: k

gender   string  optional    

Example: male

Must be one of:
  • male
  • female
birth_date   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

marital_status   string  optional    

Example: single

Must be one of:
  • single
  • married
  • divorced
  • widowed
national_id   string  optional    

يجب ألا يزيد طول value عن 20 حرفًا. Example: cmyuwpwlvqwrsitc

mobile   string  optional    

يجب ألا يزيد طول value عن 20 حرفًا. Example: pscqldzsnrwtujwv

email   string  optional    

يجب أن يكون value بريدًا إلكترونيًا صحيحًا. يجب ألا يزيد طول value عن 255 حرفًا. Example: jhaag@example.net

national_address   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: l

city   string  optional    

يجب ألا يزيد طول value عن 100 حرفًا. Example: q

address   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: p

job_title   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: p

department_id   string  optional    

Must match an existing stored value.

branch_id   string  optional    

Must match an existing stored value.

manager_id   string  optional    

Must match an existing stored value.

contract_type   string  optional    

Example: full_time

Must be one of:
  • full_time
  • part_time
  • temporary
  • remote
hire_date   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

contract_end_date   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

probation_end_date   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

status   string  optional    

Example: suspended

Must be one of:
  • active
  • suspended
  • resigned
  • terminated
basic_salary   integer  optional    

يجب ألا يقل value عن 0. Example: 9

housing_allowance   integer  optional    

يجب ألا يقل value عن 0. Example: 16

transport_allowance   integer  optional    

يجب ألا يقل value عن 0. Example: 27

other_allowances   integer  optional    

يجب ألا يقل value عن 0. Example: 4

iban   string  optional    

يجب ألا يزيد طول value عن 34 حرفًا. Example: w

bank_name   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: t

List all departments of the current company.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/departments" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/departments"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/departments

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

List all branches of the current company.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/branches" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/branches"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/branches

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

List company attendance records, filterable by date range.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/attendance-records" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"2026-09-09T13:05:11\",
    \"to\": \"2026-09-09T13:05:11\",
    \"employee_id\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/attendance-records"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "2026-09-09T13:05:11",
    "to": "2026-09-09T13:05:11",
    "employee_id": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/attendance-records

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

from   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

to   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:11

employee_id   integer  optional    

Example: 16

List company leave requests (HR/manager).

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/leave-requests" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/leave-requests"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/leave-requests

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Act on the current approval step (approve|reject).

requires authentication

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/leave-requests/16/act" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"decision\": \"reject\",
    \"notes\": \"b\"
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/leave-requests/16/act"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "decision": "reject",
    "notes": "b"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/leave-requests/{leaveRequest_id}/act

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

leaveRequest_id   integer     

The ID of the leaveRequest. Example: 16

Body Parameters

decision   string     

Example: reject

Must be one of:
  • approve
  • reject
notes   string  optional    

A rejection must say why; the note is optional when approving. يجب ألا يزيد طول value عن 255 حرفًا. Example: b

List payroll runs (HR only).

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/payroll-runs" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/payroll-runs"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/payroll-runs

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

A run with its employee lines.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/payroll-runs/16" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/payroll-runs/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/payroll-runs/{payrollRun_id}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

payrollRun_id   integer     

The ID of the payrollRun. Example: 16

List company assets with their current holder.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/assets" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/assets"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/assets

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Published company announcements.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/announcements" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/announcements"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/announcements

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

The authenticated user's attendance records for a month (?month=YYYY-MM).

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/me/attendance" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/attendance"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/me/attendance

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Mobile check-in with geofence validation against the branch.

requires authentication

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/me/attendance/check-in" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"latitude\": -89,
    \"longitude\": -179
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/attendance/check-in"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "latitude": -89,
    "longitude": -179
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/me/attendance/check-in

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

latitude   number  optional    

يجب أن يكون value بين -90 و 90. Example: -89

longitude   number  optional    

يجب أن يكون value بين -180 و 180. Example: -179

Mobile check-out.

requires authentication

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/me/attendance/check-out" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"latitude\": -89,
    \"longitude\": -179
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/attendance/check-out"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "latitude": -89,
    "longitude": -179
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/me/attendance/check-out

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

latitude   number  optional    

يجب أن يكون value بين -90 و 90. Example: -89

longitude   number  optional    

يجب أن يكون value بين -180 و 180. Example: -179

The authenticated employee's leave requests.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/me/leaves" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/leaves"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/me/leaves

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Submit a leave request (enters the approval chain).

requires authentication

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/me/leaves" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "leave_type_id=architecto"\
    --form "start_date=2052-10-02"\
    --form "end_date=2052-10-02"\
    --form "reason=n"\
    --form "attachments[]=@/tmp/phphfb0u528md0mclFl8A2" 
const url = new URL(
    "https://hr.getokta.io/api/v1/me/leaves"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('leave_type_id', 'architecto');
body.append('start_date', '2052-10-02');
body.append('end_date', '2052-10-02');
body.append('reason', 'n');
body.append('attachments[]', document.querySelector('input[name="attachments[]"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Request      

POST api/v1/me/leaves

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

Body Parameters

leave_type_id   string     

Example: architecto

start_date   string     

value ليس تاريخًا صحيحًا. يجب أن يكون value تاريخًا بعد أو يساوي today. Example: 2052-10-02

end_date   string     

value ليس تاريخًا صحيحًا. يجب أن يكون value تاريخًا بعد أو يساوي start_date. Example: 2052-10-02

reason   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: n

attachments   file[]  optional    

Up to 5 supporting documents, 10MB each, as multipart/form-data. Required when the type says requires_attachment.

Current-year leave balances.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/me/leave-balances" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/leave-balances"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/me/leave-balances

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

The authenticated employee's payslips from closed runs.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/me/payslips" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/payslips"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/me/payslips

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

The user's in-app notifications.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/v1/me/notifications" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/notifications"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/me/notifications

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Mark one (or all) notifications as read.

requires authentication

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/me/notifications/read" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"id\": \"architecto\"
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/notifications/read"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "id": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/me/notifications/read

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

id   string  optional    

Example: architecto

Register (or refresh) an FCM device token for the authenticated user.

requires authentication

A token is globally unique: if the same device was previously bound to another user it is reassigned here, never duplicated. Registration is idempotent — the app calls it on every launch and token rotation.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/v1/me/devices" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token\": \"b\",
    \"platform\": \"unknown\"
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/devices"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "b",
    "platform": "unknown"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

POST api/v1/me/devices

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

يجب ألا يزيد طول value عن 512 حرفًا. Example: b

platform   string  optional    

Example: unknown

Must be one of:
  • android
  • ios
  • web
  • unknown

Revoke a device token (logout / uninstall). Only the owner may revoke.

requires authentication

Example request:
curl --request DELETE \
    "https://hr.getokta.io/api/v1/me/devices" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token\": \"b\"
}"
const url = new URL(
    "https://hr.getokta.io/api/v1/me/devices"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "b"
};

fetch(url, {
    method: "DELETE",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Request      

DELETE api/v1/me/devices

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

يجب ألا يزيد طول value عن 512 حرفًا. Example: b

Internal (suite)

List companies

requires authentication

Every company the service key can see — ids, names and status, nothing more.

A service key is suite-level, so unfiltered this returns every company in the suite. That is fine for an operator's own tooling and wrong for anything a tenant looks at: one school's screen must never be built out of a list of every other school. Pass external_ref to ask the only question a consuming product actually has — "do you already hold a company for MY tenant?" — and get back nought or one row instead of the whole suite. Answering that question by downloading everything and filtering client-side works, and quietly puts every other tenant's name inside the asking tenant's process.

has_users is what tells a caller its onboarding is unfinished. A company with staff and nobody who can log in looks provisioned from every other angle, and the caller has no way to notice — provisioning answers "already done" forever once the company exists.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies?external_ref=okta%3Atenant-42" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"external_ref\": \"b\"
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies"
);

const params = {
    "external_ref": "okta:tenant-42",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "external_ref": "b"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "type": "companies",
            "attributes": {
                "name_ar": "مدرسة النور",
                "name_en": "Al Noor School",
                "status": "active",
                "external_ref": "okta:tenant-42",
                "has_users": true
            }
        }
    ]
}
 

Request      

GET api/internal/companies

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

external_ref   string  optional    

Return only the company carrying this identifier — the one you sent when provisioning. Example: okta:tenant-42

Body Parameters

external_ref   string  optional    

يجب ألا يزيد طول value عن 255 حرفًا. Example: b

Provision a company

requires authentication

Creates a company, seeds its defaults (leave types, asset categories, onboarding checklist) and creates its first hr-manager — the whole set, in one transaction. This is step one of onboarding a tenant from another Okta Suite product.

This call is idempotent on external_ref. Send your own tenant identifier and you can call it on every install, every retry and every double-clicked button: the first call creates, the rest return the same company untouched with created: false. Without it, a retry silently creates a second company for the same school and half the staff end up in each.

The admin's password is returned once, and only when we generated it. There is no way to read it back.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name_ar\": \"مدرسة النور\",
    \"name_en\": \"Al Noor School\",
    \"commercial_register\": \"1010101010\",
    \"external_ref\": \"okta:tenant-42\",
    \"admin_name\": \"خالد العتيبي\",
    \"admin_email\": \"khalid@alnoor.test\",
    \"admin_password\": \"s3cret-passphrase\"
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name_ar": "مدرسة النور",
    "name_en": "Al Noor School",
    "commercial_register": "1010101010",
    "external_ref": "okta:tenant-42",
    "admin_name": "خالد العتيبي",
    "admin_email": "khalid@alnoor.test",
    "admin_password": "s3cret-passphrase"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, already provisioned):


{
    "data": {
        "id": 7,
        "type": "companies",
        "attributes": {
            "name_ar": "مدرسة النور",
            "name_en": "Al Noor School",
            "status": "active",
            "external_ref": "okta:tenant-42",
            "created": false,
            "admin": null
        }
    }
}
 

Example response (201):


{
    "data": {
        "id": 7,
        "type": "companies",
        "attributes": {
            "name_ar": "مدرسة النور",
            "name_en": "Al Noor School",
            "status": "active",
            "external_ref": "okta:tenant-42",
            "created": true,
            "admin": {
                "id": 31,
                "name": "خالد العتيبي",
                "email": "khalid@alnoor.test",
                "password": "Xk29fBq1zLmA"
            }
        }
    }
}
 

Example response (422, admin email already belongs to someone):


{
    "message": "The given data was invalid.",
    "errors": {
        "admin_email": [
            "حقل admin email مُستخدم من قبل."
        ]
    }
}
 

Request      

POST api/internal/companies

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name_ar   string     

The company's Arabic name. Example: مدرسة النور

name_en   string  optional    

The company's English name. Example: Al Noor School

commercial_register   string  optional    

Commercial register number. Example: 1010101010

external_ref   string  optional    

Your own identifier for this tenant. Makes the call idempotent — send it. Example: okta:tenant-42

admin_name   string  optional    

The first hr-manager's name. Example: خالد العتيبي

admin_email   string  optional    

The first hr-manager's email. Omit to create a company with no users yet. Example: khalid@alnoor.test

admin_password   string  optional    

Their password. Omit and one is generated and returned once. Example: s3cret-passphrase

requires authentication

Answers "which company is this?" for a consuming product, without letting it choose. A service key is suite-level, so a company listing shows every company in the suite; letting a product pick from that does not error when it picks wrong, it quietly serves one tenant another tenant's employees and payroll.

So the authority runs the other way. The company's own hr-manager generates a code inside their company (Settings → link code) and hands it over; presenting it here is the proof. The code is single-use and expires within the hour.

Unknown, expired and already-used all return the same 422. Saying which one it was only helps somebody probing for a valid code.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/link-codes/redeem" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"okhr_A7KQ2M-9XTBVC\",
    \"used_by\": \"okta:tenant-42\"
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/link-codes/redeem"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "okhr_A7KQ2M-9XTBVC",
    "used_by": "okta:tenant-42"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 7,
        "type": "companies",
        "attributes": {
            "name_ar": "مدرسة النور",
            "name_en": "Al Noor School",
            "status": "active",
            "external_ref": "okta:tenant-42"
        }
    }
}
 

Example response (422, unknown, expired or already used):


{
    "message": "...",
    "errors": {
        "code": [
            "رمز الربط غير صالح أو منتهي أو مستخدَم من قبل."
        ]
    }
}
 

List the company's employees.

requires authentication

Filterable so a consuming product does not have to page the whole roster to find the active staff of one department.

Every row carries its manager block (id + bilingual names), so a consumer can resolve a reporting line — "who is on this manager's team" — without one extra call per person. manager_id narrows to one manager's direct reports; a consumer should still verify each returned row's manager.id, so that running against a platform predating this filter degrades to an empty team rather than to the whole roster.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees?manager_id=12" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status\": \"architecto\",
    \"department_id\": 16,
    \"branch_id\": 16,
    \"manager_id\": 16,
    \"search\": \"n\",
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees"
);

const params = {
    "manager_id": "12",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status": "architecto",
    "department_id": 16,
    "branch_id": 16,
    "manager_id": 16,
    "search": "n",
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
access-control-allow-origin: *
 

{
    "message": "No query results for model [App\\Models\\Company] 16"
}
 

Request      

GET api/internal/companies/{company_id}/employees

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

manager_id   integer  optional    

Only this manager's direct reports. Example: 12

Body Parameters

status   string  optional    

Example: architecto

department_id   integer  optional    

Example: 16

branch_id   integer  optional    

Example: 16

manager_id   integer  optional    

Example: 16

search   string  optional    

يجب ألا يزيد طول value عن 120 حرفًا. Example: n

per_page   integer  optional    

Example: 16

One employee.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
access-control-allow-origin: *
 

{
    "message": "No query results for model [App\\Models\\Company] 16"
}
 

Request      

GET api/internal/companies/{company_id}/employees/{id}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

id   integer     

The ID of the employee. Example: 16

List the company's departments.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/departments" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/departments"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
access-control-allow-origin: *
 

{
    "message": "No query results for model [App\\Models\\Company] 16"
}
 

Request      

GET api/internal/companies/{company_id}/departments

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

List the company's branches.

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/branches" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/branches"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
access-control-allow-origin: *
 

{
    "message": "No query results for model [App\\Models\\Company] 16"
}
 

Request      

GET api/internal/companies/{company_id}/branches

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Every employee number on file

requires authentication

One flat list of employee_number, unpaginated, and nothing else. It answers a single question — which of my people does HR already hold? — that a consuming product otherwise has to answer by downloading the whole directory and comparing it row by row.

That download is not a fair way to ask it. [employees] is paginated and capped at 200 rows a page, so a product diffing its roster against one page reports everyone past row 200 as missing — and a sync built on that answer files them all a second time. Here the whole set comes back in one call: a number is a few dozen bytes, a directory row is a few hundred with department, branch, manager and wage hanging off it.

Every status is included. Someone who resigned still has a file, and leaving them out would report them as never synced at all.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employee-numbers" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employee-numbers"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        "01K9RS4X0J7Q2ZC3VH8T5NB1AE",
        "E-1042"
    ],
    "meta": {
        "count": 2
    }
}
 

Request      

GET api/internal/companies/{company_id}/employee-numbers

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Create or update an employee

requires authentication

The third step of onboarding a tenant: pushing its staff across from the consuming product into HR.

Upserts on employee_number, which is the only stable key the two systems can agree on. That makes a re-sync safe — run it nightly, run it twice, and you update the same rows rather than growing a duplicate roster. Department and branch are matched by NAME and created if they are new, because the calling product has its own ids for them and cannot know ours.

Money is in halalas (1 SAR = 100), the same as everywhere else here.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/employees" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"employee_number\": \"E-1042\",
    \"name_ar\": \"سعيد الغامدي\",
    \"name_en\": \"Saeed Alghamdi\",
    \"email\": \"saeed@alnoor.test\",
    \"mobile\": \"0551234567\",
    \"national_id\": \"1012345678\",
    \"nationality\": \"سعودي\",
    \"job_title\": \"معلم رياضيات\",
    \"department\": \"الشؤون التعليمية\",
    \"branch\": \"الفرع الرئيسي\",
    \"hire_date\": \"2024-09-01\",
    \"contract_end_date\": \"2027-08-31\",
    \"contract_type\": \"unlimited\",
    \"status\": \"active\",
    \"basic_salary\": 800000,
    \"housing_allowance\": 200000,
    \"transport_allowance\": 50000,
    \"iban\": \"SA0380000000608010167519\",
    \"bank_name\": \"الأهلي\"
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "employee_number": "E-1042",
    "name_ar": "سعيد الغامدي",
    "name_en": "Saeed Alghamdi",
    "email": "saeed@alnoor.test",
    "mobile": "0551234567",
    "national_id": "1012345678",
    "nationality": "سعودي",
    "job_title": "معلم رياضيات",
    "department": "الشؤون التعليمية",
    "branch": "الفرع الرئيسي",
    "hire_date": "2024-09-01",
    "contract_end_date": "2027-08-31",
    "contract_type": "unlimited",
    "status": "active",
    "basic_salary": 800000,
    "housing_allowance": 200000,
    "transport_allowance": 50000,
    "iban": "SA0380000000608010167519",
    "bank_name": "الأهلي"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, updated):


{
    "data": {
        "id": 88,
        "type": "employees",
        "attributes": {
            "employee_number": "E-1042",
            "name_ar": "سعيد الغامدي",
            "email": "saeed@alnoor.test",
            "status": "active"
        }
    }
}
 

Example response (201, created):


{
    "data": {
        "id": 88,
        "type": "employees",
        "attributes": {
            "employee_number": "E-1042",
            "name_ar": "سعيد الغامدي",
            "email": "saeed@alnoor.test",
            "status": "active"
        }
    }
}
 

Example response (422, plan limit reached):


{
    "message": "تم بلوغ الحد الأقصى لعدد الموظفين في باقتك."
}
 

Request      

POST api/internal/companies/{company_id}/employees

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Body Parameters

employee_number   string     

Your stable identifier for this person. Example: E-1042

name_ar   string     

Arabic full name. Example: سعيد الغامدي

name_en   string  optional    

English full name. Example: Saeed Alghamdi

email   string  optional    

Work email. This is how a suite product later maps its signed-in user back to this employee — send it. Example: saeed@alnoor.test

mobile   string  optional    

Mobile number. Example: 0551234567

national_id   string  optional    

National / Iqama id. Example: 1012345678

nationality   string  optional    

Example: سعودي

job_title   string  optional    

Example: معلم رياضيات

department   string  optional    

Department name — matched, then created if new. Example: الشؤون التعليمية

branch   string  optional    

Branch name — matched, then created if new. Example: الفرع الرئيسي

hire_date   string  optional    

Y-m-d. Example: 2024-09-01

contract_end_date   string  optional    

Y-m-d. Example: 2027-08-31

contract_type   string  optional    

One of the contract type enum values. Example: unlimited

status   string  optional    

Employment status. Defaults to active on create. Example: active

basic_salary   integer  optional    

Basic salary in halalas. Example: 800000

housing_allowance   integer  optional    

Housing allowance in halalas. Example: 200000

transport_allowance   integer  optional    

Transport allowance in halalas. Example: 50000

iban   string  optional    

Example: SA0380000000608010167519

bank_name   string  optional    

Example: الأهلي

Provision a login account for an employee

requires authentication

Creates the user, assigns the employee role and links it to the employee record. The password is returned once — it is stored hashed and there is no way to read it back.

Deliberately separate from creating the employee: an HR record and a person who can log in are different decisions, and syncing a roster should not silently mint dozens of accounts.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/employees/16/account" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/account"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 31,
        "type": "users",
        "attributes": {
            "name": "سعيد الغامدي",
            "email": "saeed@alnoor.test",
            "password": "Xk29fBq1zLmA",
            "employee_id": 88
        }
    }
}
 

Example response (422, already has one):


{
    "message": "...",
    "errors": {
        "account": [
            "لدى الموظف حساب دخول بالفعل."
        ]
    }
}
 

Request      

POST api/internal/companies/{company_id}/employees/{employee}/account

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

List an employee's documents

requires authentication

Contracts, iqamas, licences and the rest, with their expiry dates so a consuming product can show the same early warning HR sees.

The rows are metadata; has_file says whether a scan is attached, and the file itself is served by its own endpoint (.../documents/{id}/file) rather than inline. A list is read to see what exists and when it expires — attaching the images of everybody's identity papers to that answer would move megabytes nobody asked for.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/documents?expiring_within_days=60" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"expiring_within_days\": 1
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/documents"
);

const params = {
    "expiring_within_days": "60",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "expiring_within_days": 1
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 12,
            "type": "employee-documents",
            "attributes": {
                "employee_id": 88,
                "type": "iqama",
                "number": "2412345678",
                "issue_date": "2024-02-01",
                "expiry_date": "2027-01-31",
                "notify_before_days": 60,
                "days_until_expiry": 172,
                "is_expired": false,
                "has_file": true
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/documents

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Query Parameters

expiring_within_days   integer  optional    

Only documents expiring within this many days. Example: 60

Body Parameters

expiring_within_days   integer  optional    

يجب ألا يقل value عن 0. يجب ألا يكون value أكبر من 3650. Example: 1

Record a document

requires authentication

An iqama, a passport, a contract or a licence — its number, its dates and optionally the scan itself — so the platform's expiry warnings cover it.

Send the file as multipart under file to attach it; the ceiling is 10 MB, the same one the platform's own form uses. One rule for one thing: a scan the web form accepts and the API refuses is a difference nobody could explain to the person holding the passport.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/employees/16/documents" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "type=iqama"\
    --form "number=2412345678"\
    --form "issue_date=2024-02-01"\
    --form "expiry_date=2027-01-31"\
    --form "notify_before_days=60"\
    --form "file=@/tmp/php01io8vtnjggs0kU65Wb" 
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/documents"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('type', 'iqama');
body.append('number', '2412345678');
body.append('issue_date', '2024-02-01');
body.append('expiry_date', '2027-01-31');
body.append('notify_before_days', '60');
body.append('file', document.querySelector('input[name="file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 12,
        "type": "employee-documents",
        "attributes": {
            "employee_id": 88,
            "type": "iqama",
            "number": "2412345678",
            "issue_date": "2024-02-01",
            "expiry_date": "2027-01-31",
            "notify_before_days": 60,
            "days_until_expiry": 172,
            "is_expired": false,
            "has_file": false
        }
    }
}
 

Request      

POST api/internal/companies/{company_id}/employees/{employee}/documents

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Body Parameters

type   string     

One of: iqama, passport, contract, certificate, driving_license, other. Example: iqama

number   string  optional    

The document's number. Example: 2412345678

issue_date   date  optional    

Example: 2024-02-01

expiry_date   date  optional    

Must not precede the issue date. Example: 2027-01-31

notify_before_days   integer  optional    

How many days ahead HR is warned. Default 30. Example: 60

file   file  optional    

The scan, up to 10 MB. Multipart only. Example: /tmp/php01io8vtnjggs0kU65Wb

Update a document

requires authentication

A partial update: only the fields you send are written, so correcting an expiry date does not blank the number beside it. A file REPLACES whatever scan is attached — the collection is the document's single file, and two copies of an iqama with no way to tell which is current is worse than the one that was wrong.

Example request:
curl --request PATCH \
    "https://hr.getokta.io/api/internal/companies/16/employees/16/documents/architecto" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "type=iqama"\
    --form "number=2412345678"\
    --form "issue_date=2024-02-01"\
    --form "expiry_date=2027-01-31"\
    --form "notify_before_days=60"\
    --form "file=@/tmp/php1lhp0p3ocbej77CVien" 
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/documents/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('type', 'iqama');
body.append('number', '2412345678');
body.append('issue_date', '2024-02-01');
body.append('expiry_date', '2027-01-31');
body.append('notify_before_days', '60');
body.append('file', document.querySelector('input[name="file"]').files[0]);

fetch(url, {
    method: "PATCH",
    headers,
    body,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 12,
        "type": "employee-documents",
        "attributes": {
            "employee_id": 88,
            "type": "iqama",
            "number": "2412345678",
            "issue_date": "2024-02-01",
            "expiry_date": "2027-01-31",
            "notify_before_days": 60,
            "days_until_expiry": 172,
            "is_expired": false,
            "has_file": false
        }
    }
}
 

Request      

PATCH api/internal/companies/{company_id}/employees/{employee}/documents/{document}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

document   string     

The document. Example: architecto

Body Parameters

type   string  optional    

One of: iqama, passport, contract, certificate, driving_license, other. Example: iqama

number   string  optional    

Example: 2412345678

issue_date   date  optional    

Example: 2024-02-01

expiry_date   date  optional    

Example: 2027-01-31

notify_before_days   integer  optional    

Example: 60

file   file  optional    

A new scan, replacing the current one. Multipart only. Example: /tmp/php1lhp0p3ocbej77CVien

Delete a document

requires authentication

Deletes the RECORD and whatever file is attached to it. A document added by mistake has to be removable by whoever added it, or the only way out of a typo is another product.

Example request:
curl --request DELETE \
    "https://hr.getokta.io/api/internal/companies/16/employees/16/documents/architecto" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/documents/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "deleted": true
}
 

Request      

DELETE api/internal/companies/{company_id}/employees/{employee}/documents/{document}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

document   string     

The document. Example: architecto

Download a document's file

requires authentication

The scan itself, streamed — for a consuming product that already shows the employee's file to the same HR people who see it on the platform.

This is the one endpoint in the suite API that serves a FILE, and it is addressed through both the company and the employee, so an id from another employee 404s rather than resolving.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/documents/architecto/file" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/documents/architecto/file"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (404, no file attached):


{
    "message": "No file attached to this document."
}
 

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
access-control-allow-origin: *
 

{
    "message": "No query results for model [App\\Models\\Company] 16"
}
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/documents/{document}/file

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

document   string     

The document. Example: architecto

The employee's detailed report (PDF)

requires authentication

The same التقرير المفصل the platform's own file offers — identity, pay, a year of attendance, requests, leaves and letters — rendered by the same builder, so the two cannot describe one employee differently.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/report" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/report"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, a PDF):


"<binary>"
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/report

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

List an employee's letters

requires authentication

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/letters" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/letters"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 5,
            "type": "official-letters",
            "attributes": {
                "employee_id": 88,
                "reference": "A1B2-C3D4E5F6",
                "letter_type": "salary_certificate",
                "language": "ar",
                "addressed_to": null,
                "status": "approved",
                "is_releasable": true
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/letters

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Issue an official letter

requires authentication

A salary certificate, an employment certificate or a salary-transfer letter. The letter enters the company's configured approval chain exactly as one raised on the platform does — a letter issued from another product must not be a way around an approval the company set up — so a company with a chain gets a pending letter, and one without gets a releasable one.

acting_employee_id names the PERSON issuing it: a service key identifies a product, and a letter has to record who signed it off.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/employees/16/letters" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"type\": \"salary_certificate\",
    \"language\": \"ar\",
    \"addressed_to\": \"البنك الأهلي\",
    \"acting_employee_id\": 88
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/letters"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "type": "salary_certificate",
    "language": "ar",
    "addressed_to": "البنك الأهلي",
    "acting_employee_id": 88
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 5,
        "type": "official-letters",
        "attributes": {
            "employee_id": 88,
            "reference": "A1B2-C3D4E5F6",
            "letter_type": "salary_certificate",
            "language": "ar",
            "addressed_to": "البنك الأهلي",
            "status": "pending",
            "is_releasable": false
        }
    }
}
 

Request      

POST api/internal/companies/{company_id}/employees/{employee}/letters

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Body Parameters

type   string     

salary_certificate, employment_certificate or salary_transfer. Example: salary_certificate

language   string     

ar or en. Example: ar

addressed_to   string  optional    

Who the letter is addressed to. Example: البنك الأهلي

acting_employee_id   integer     

The employee issuing it. Example: 88

Download an issued letter (PDF)

requires authentication

403 until the approval chain has cleared it: an unapproved letter is a draft, and a draft that prints on letterhead is the thing the chain exists to prevent.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/letters/architecto/file" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/letters/architecto/file"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (403, not approved yet):


{
    "message": "الخطاب لم يُعتمد بعد."
}
 

Example response (404):

Show headers
cache-control: no-cache, private
content-type: application/json
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
access-control-allow-origin: *
 

{
    "message": "No query results for model [App\\Models\\Company] 16"
}
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/letters/{letter}/file

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

letter   string     

The letter. Example: architecto

List an employee's custody

requires authentication

The assets currently in their hands, and optionally everything ever handed to them. An open assignment is one that has not been returned — which is what a clearance blocks on when someone leaves.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/custody?include_returned=1" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"include_returned\": true
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/custody"
);

const params = {
    "include_returned": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "include_returned": true
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 9,
            "type": "asset-assignments",
            "attributes": {
                "employee_id": 88,
                "asset_id": 4,
                "asset_name": "لابتوب Dell 5540",
                "asset_serial": "SN-99182",
                "assigned_at": "2025-03-02",
                "returned_at": null,
                "is_open": true
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/custody

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Query Parameters

include_returned   boolean  optional    

Include closed (returned) assignments. Example: true

Body Parameters

include_returned   boolean  optional    

Example: true

List an employee's violations

requires authentication

The disciplinary file (Saudi labour law art. 66–71): what the employee is said to have done and the ONE penalty imposed for it. A fine carries the payroll line it created, so what the payslip withholds and what the file claims cannot drift apart.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/violations" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/violations"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 3,
            "type": "employee-violations",
            "attributes": {
                "employee_id": 88,
                "date": "2026-08-02",
                "violation_type": "lateness",
                "penalty": "deduction",
                "description": "تأخر ساعتين",
                "deduction_amount": 15000,
                "salary_adjustment_id": 41,
                "recorded_on": "2026-08-02"
            }
        }
    ],
    "meta": {
        "monthly_cap": 50000,
        "remaining_this_month": 35000
    }
}
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/violations

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Record a violation

requires authentication

ONE penalty per violation (art. 71), and a deduction penalty must carry its amount in halalas — it writes a real payroll deduction for that month, with its own approval chain, so the file and the payslip say the same thing.

The art. 68 ceiling (five days' wage a month in fines) is enforced by the platform: an amount that would breach it is refused with a 422 naming what is left, not silently clipped.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/employees/16/violations" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"date\": \"2026-08-02\",
    \"type\": \"lateness\",
    \"penalty\": \"deduction\",
    \"description\": \"تأخر ساعتين عن الدوام\",
    \"deduction_amount\": 15000,
    \"acting_employee_id\": 88
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/violations"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "date": "2026-08-02",
    "type": "lateness",
    "penalty": "deduction",
    "description": "تأخر ساعتين عن الدوام",
    "deduction_amount": 15000,
    "acting_employee_id": 88
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 3,
        "type": "employee-violations",
        "attributes": {
            "employee_id": 88,
            "date": "2026-08-02",
            "violation_type": "lateness",
            "penalty": "deduction",
            "description": "تأخر ساعتين",
            "deduction_amount": 15000,
            "salary_adjustment_id": 41,
            "recorded_on": "2026-08-02"
        }
    }
}
 

Example response (422, over the monthly cap):


{
    "message": "لا يمكن أن تتجاوز الغرامات أجر خمسة أيام في الشهر."
}
 

Request      

POST api/internal/companies/{company_id}/employees/{employee}/violations

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Body Parameters

date   date     

When it happened. Example: 2026-08-02

type   string     

lateness, absence, conduct, negligence, safety or other. Example: lateness

penalty   string     

verbal_warning, written_warning, deduction, suspension or dismissal. Example: deduction

description   string  optional    

What happened. Example: تأخر ساعتين عن الدوام

deduction_amount   integer  optional    

Halalas. Required for a deduction. Example: 15000

acting_employee_id   integer  optional    

The employee recording it. Example: 88

Withdraw a violation

requires authentication

Deletes the record AND the payroll line it created: a penalty that was withdrawn must not keep taking money off the payslip.

Example request:
curl --request DELETE \
    "https://hr.getokta.io/api/internal/companies/16/employees/16/violations/architecto" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/violations/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (200):


{
    "deleted": true
}
 

Request      

DELETE api/internal/companies/{company_id}/employees/{employee}/violations/{violation}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

violation   string     

The violation. Example: architecto

List the company's custody

requires authentication

Every handover in the company, newest first — the same rows the per-employee call serves, without having to know whose file to open. A consuming product building a custody screen needs "who holds what across the school", and asking that one employee at a time is one request per employee for a page that fits on one screen.

Each row carries its holder inline (id, number and BOTH names) so the list can be read without a second call, and the employee name follows the suite rule: never resolved server-side, because the caller is a server and the locale on the request is the browser's, not the tenant's.

Open assignments only unless include_returned is set — an open one is what a clearance blocks on when somebody leaves, and a list mixing the two cannot be read for that at a glance.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/custody?include_returned=1&employee_id=88&search=%D9%84%D8%A7%D8%A8%D8%AA%D9%88%D8%A8&per_page=50" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"include_returned\": true,
    \"employee_id\": 16,
    \"search\": \"n\",
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/custody"
);

const params = {
    "include_returned": "1",
    "employee_id": "88",
    "search": "لابتوب",
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "include_returned": true,
    "employee_id": 16,
    "search": "n",
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 9,
            "type": "asset-assignments",
            "attributes": {
                "employee_id": 88,
                "employee_name_ar": "سعيد الغامدي",
                "employee_name_en": "Saeed Alghamdi",
                "employee_number": "E-1042",
                "asset_id": 4,
                "asset_name": "لابتوب Dell 5540",
                "asset_serial": "SN-99182",
                "assigned_at": "2025-03-02",
                "returned_at": null,
                "is_open": true
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/custody

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

include_returned   boolean  optional    

Include closed (returned) assignments. Example: true

employee_id   integer  optional    

Only this employee's custody. Example: 88

search   string  optional    

Matches the asset name or its serial number. Example: لابتوب

per_page   integer  optional    

Default 50, max 200. Example: 50

Body Parameters

include_returned   boolean  optional    

Example: true

employee_id   integer  optional    

Example: 16

search   string  optional    

يجب ألا يزيد طول value عن 120 حرفًا. Example: n

per_page   integer  optional    

Example: 16

List self-service requests

requires authentication

Every request type the platform models — resignation, attendance correction, overtime, expense claim, exit/re-entry, data change — as one list, because they share one approval chain.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/requests?employee_id=88&status=pending&type=overtime&per_page=50" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"employee_id\": 16,
    \"status\": \"n\",
    \"type\": \"g\",
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/requests"
);

const params = {
    "employee_id": "88",
    "status": "pending",
    "type": "overtime",
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "employee_id": 16,
    "status": "n",
    "type": "g",
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 41,
            "type": "employee-requests",
            "attributes": {
                "employee_id": 88,
                "employee_name_ar": "سعيد الغامدي",
                "employee_name_en": "Saeed Alghamdi",
                "request_type": "overtime",
                "status": "pending",
                "payload": {
                    "date": "2026-08-02",
                    "hours": 3
                },
                "submitted_at": "2026-08-02T09:12:00+00:00",
                "decided_at": null,
                "rejection_reason": null
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/requests

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

employee_id   integer  optional    

Only this employee's requests. Example: 88

status   string  optional    

pending, approved, rejected, returned or cancelled. Example: pending

type   string  optional    

A request type key. Example: overtime

per_page   integer  optional    

Default 50, max 200. Example: 50

Body Parameters

employee_id   integer  optional    

Example: 16

status   string  optional    

يجب ألا يزيد طول value عن 30 حرفًا. Example: n

type   string  optional    

يجب ألا يزيد طول value عن 50 حرفًا. Example: g

per_page   integer  optional    

Example: 16

Submit a self-service request

requires authentication

Goes through the same service the platform's own صفحة الطلبات uses, so it starts the company's configured approval chain and, on final approval, applies the same effects — a resignation really does change employment status, overtime really does become a salary adjustment.

The payload is type-specific and validated by that service; see the request types documented on GET /requests.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/requests" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"employee_id\": 88,
    \"type\": \"overtime\",
    \"payload\": {
        \"date\": \"2026-08-02\",
        \"hours\": 3,
        \"reason\": \"تغطية اختبارات\"
    }
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/requests"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "employee_id": 88,
    "type": "overtime",
    "payload": {
        "date": "2026-08-02",
        "hours": 3,
        "reason": "تغطية اختبارات"
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 41,
        "type": "employee-requests",
        "attributes": {
            "employee_id": 88,
            "request_type": "overtime",
            "status": "pending"
        }
    }
}
 

Example response (422, no approval chain configured):


{
    "message": "Approver chain is empty"
}
 

Request      

POST api/internal/companies/{company_id}/requests

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Body Parameters

employee_id   integer     

Whose request this is. Example: 88

type   string     

The request type key. Example: overtime

payload   object  optional    

The type-specific fields.

List announcements

requires authentication

The company's announcements, newest first, with their audience and importance so a consuming product can surface the important ones the way the platform does.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/announcements?per_page=25" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/announcements"
);

const params = {
    "per_page": "25",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 5,
            "type": "announcements",
            "attributes": {
                "title": "إجازة اليوم الوطني",
                "body": "...",
                "importance": "important",
                "audience_type": "all",
                "audience_id": null,
                "published_at": "2026-09-20T07:00:00+00:00",
                "has_attachment": false
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/announcements

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

per_page   integer  optional    

Default 25, max 200. Example: 25

Body Parameters

per_page   integer  optional    

Example: 16

List leave types

requires authentication

tracks_balance says whether the type draws down an annual balance — sick and unpaid leave do not, so showing a "remaining days" figure against them tells the employee something untrue.

requires_attachment says the type cannot be filed without a document (a medical certificate, a marriage contract). store REFUSES such a request when nothing is attached, so read this before you submit: a rule the consumer cannot see until the server rejects them is a trap, and the employee filling the form is the one who pays for it.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/leave-types" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/leave-types"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 1,
            "type": "leave-types",
            "attributes": {
                "code": "annual",
                "name_ar": "إجازة سنوية",
                "name_en": "Annual leave",
                "is_active": true,
                "tracks_balance": true,
                "requires_attachment": false
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/leave-types

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

List leave balances

requires authentication

Defaults to the current year. pending_days is already deducted: balances are debited on SUBMISSION, not approval, or five overlapping requests each pass the balance check individually and overdraw.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/leave-balances?employee_id=88&year=2026&per_page=50" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"employee_id\": 16,
    \"year\": 22,
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/leave-balances"
);

const params = {
    "employee_id": "88",
    "year": "2026",
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "employee_id": 16,
    "year": 22,
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 3,
            "type": "leave-balances",
            "attributes": {
                "employee_id": 88,
                "year": 2026,
                "entitled_days": 21,
                "used_days": 5,
                "pending_days": 2,
                "remaining_days": 14
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/leave-balances

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

employee_id   integer  optional    

Only this employee's balances. Example: 88

year   integer  optional    

Defaults to the current year. Example: 2026

per_page   integer  optional    

Default 50, max 200. Example: 50

Body Parameters

employee_id   integer  optional    

Example: 16

year   integer  optional    

يجب ألا يقل value عن 2000. يجب ألا يكون value أكبر من 2100. Example: 22

per_page   integer  optional    

Example: 16

List leave requests

requires authentication

from and to together return requests that OVERLAP that window, not only ones fully inside it — a leave spanning the month boundary is the one you most need to see.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/leave-requests?status=pending&employee_id=88&from=2026-08-01&to=2026-08-31&per_page=20" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status\": \"architecto\",
    \"employee_id\": 16,
    \"from\": \"2026-09-09T13:05:12\",
    \"to\": \"2026-09-09T13:05:12\",
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/leave-requests"
);

const params = {
    "status": "pending",
    "employee_id": "88",
    "from": "2026-08-01",
    "to": "2026-08-31",
    "per_page": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status": "architecto",
    "employee_id": 16,
    "from": "2026-09-09T13:05:12",
    "to": "2026-09-09T13:05:12",
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 42,
            "type": "leave-requests",
            "attributes": {
                "employee_id": 88,
                "employee": {
                    "id": 88,
                    "name_ar": "سعيد الغامدي",
                    "name_en": "Saeed Alghamdi",
                    "employee_number": "E-100"
                },
                "start_date": "2026-08-10",
                "end_date": "2026-08-14",
                "days": 5,
                "reason": "سفر عائلي",
                "status": "pending",
                "leave_type": {
                    "id": 1,
                    "name_ar": "إجازة سنوية",
                    "name_en": "Annual leave",
                    "code": "annual",
                    "is_paid": true
                },
                "attachments": [
                    {
                        "id": 7,
                        "name": "تقرير-طبي.pdf",
                        "size": 48213,
                        "mime_type": "application/pdf"
                    }
                ]
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/leave-requests

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

status   string  optional    

pending, approved, rejected or cancelled. Example: pending

employee_id   integer  optional    

Example: 88

from   string  optional    

Y-m-d. Requires to. Example: 2026-08-01

to   string  optional    

Y-m-d. Requires from. Example: 2026-08-31

per_page   integer  optional    

Default 20, max 200. Example: 20

Body Parameters

status   string  optional    

Example: architecto

employee_id   integer  optional    

Example: 16

from   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:12

to   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:12

per_page   integer  optional    

Example: 16

Submit a leave request

requires authentication

Enters the ordinary approval chain — a request created here is indistinguishable downstream from one the employee filed themselves, which is the point: one queue, one set of rules, one audit trail.

The same balance and overlap checks apply. A request that would overdraw the balance or double-book dates is refused with a 422 whose message names the real numbers; show that message rather than writing your own, or the employee sees two different explanations for one refusal.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/leave-requests" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "employee_id=88"\
    --form "leave_type_id=1"\
    --form "start_date=2026-08-10"\
    --form "end_date=2026-08-14"\
    --form "reason=سفر عائلي"\
    --form "attachments[]=@/tmp/php5gesvvgh9vbc56po1fL" 
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/leave-requests"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('employee_id', '88');
body.append('leave_type_id', '1');
body.append('start_date', '2026-08-10');
body.append('end_date', '2026-08-14');
body.append('reason', 'سفر عائلي');
body.append('attachments[]', document.querySelector('input[name="attachments[]"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());

Example response (201):


{
    "data": {
        "id": 42,
        "type": "leave-requests",
        "attributes": {
            "employee_id": 88,
            "employee": {
                "id": 88,
                "name_ar": "سعيد الغامدي",
                "name_en": "Saeed Alghamdi",
                "employee_number": "E-100"
            },
            "start_date": "2026-08-10",
            "end_date": "2026-08-14",
            "days": 5,
            "reason": "سفر عائلي",
            "status": "pending",
            "leave_type": {
                "id": 1,
                "name_ar": "إجازة سنوية",
                "name_en": "Annual leave",
                "code": "annual",
                "is_paid": true
            },
            "attachments": [
                {
                    "id": 7,
                    "name": "تقرير-طبي.pdf",
                    "size": 48213,
                    "mime_type": "application/pdf"
                }
            ]
        }
    }
}
 

Example response (422, not enough balance):


{
    "message": "...",
    "errors": {
        "start_date": [
            "رصيدك 3.5 يوم وطلبت 5 أيام."
        ]
    }
}
 

Example response (422, overlaps an existing request):


{
    "message": "...",
    "errors": {
        "start_date": [
            "يوجد طلب إجازة آخر في نفس الفترة."
        ]
    }
}
 

Example response (422, the type requires a document):


{
    "message": "...",
    "errors": {
        "attachments": [
            "هذا النوع من الإجازات يتطلب مرفقاً."
        ]
    }
}
 

Request      

POST api/internal/companies/{company_id}/leave-requests

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Body Parameters

employee_id   integer     

Example: 88

leave_type_id   integer     

Must be an active type of this company. Example: 1

start_date   string     

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

end_date   string     

Y-m-d, on or after start_date. Example: 2026-08-14

reason   string  optional    

Example: سفر عائلي

attachments   file[]  optional    

Up to 5 supporting documents, 10MB each. Send as multipart/form-data. REQUIRED when the leave type says requires_attachment — read that off the types endpoint first.

Approve or reject a leave request

requires authentication

Acts on the current step of the chain — earlier steps are already decided and later ones are not actionable yet.

acting_employee_id is required and is checked against the step's approver: a service key says which PRODUCT is calling, never which person decided, and an approval attributed to a product is not one anyone can stand behind later. An employee who is not the pending approver gets a 403, not a silent no-op.

A rejection requires notes. The requester is shown that sentence as the reason, and "rejected, no reason given" is the single most complained-about thing an approval system can do.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/leave-requests/16/act" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"acting_employee_id\": 12,
    \"decision\": \"reject\",
    \"notes\": \"الفترة مزدحمة بالاختبارات\"
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/leave-requests/16/act"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "acting_employee_id": 12,
    "decision": "reject",
    "notes": "الفترة مزدحمة بالاختبارات"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 42,
        "type": "leave-requests",
        "attributes": {
            "status": "rejected"
        }
    }
}
 

Example response (403, not the pending approver):


{
    "message": "This action is unauthorized."
}
 

Example response (422, rejected without a reason):


{
    "message": "...",
    "errors": {
        "notes": [
            "حقل السبب مطلوب."
        ]
    }
}
 

Request      

POST api/internal/companies/{company_id}/leave-requests/{leaveRequest}/act

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

leaveRequest   integer     

Example: 16

Body Parameters

acting_employee_id   integer     

The employee making the decision. Example: 12

decision   string     

approve or reject. Example: reject

notes   string  optional    

Required when rejecting. Example: الفترة مزدحمة بالاختبارات

List attendance records

requires authentication

Minutes, not times, are the numbers to trust here: late_minutes, early_leave_minutes and worked_minutes are computed against the employee's shift and its grace window, which a consumer cannot rederive from the raw punches without reimplementing the same rules.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/attendance-records?employee_id=88&from=2026-08-01&to=2026-08-31&per_page=50" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"2026-09-09T13:05:12\",
    \"to\": \"2026-09-09T13:05:12\",
    \"employee_id\": 16,
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/attendance-records"
);

const params = {
    "employee_id": "88",
    "from": "2026-08-01",
    "to": "2026-08-31",
    "per_page": "50",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "2026-09-09T13:05:12",
    "to": "2026-09-09T13:05:12",
    "employee_id": 16,
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 501,
            "type": "attendance-records",
            "attributes": {
                "employee_id": 88,
                "employee": {
                    "id": 88,
                    "name_ar": "سعيد الغامدي",
                    "name_en": "Saeed Alghamdi",
                    "employee_number": "E-100"
                },
                "date": "2026-08-03",
                "check_in_at": "2026-08-03T04:58:00+00:00",
                "check_out_at": "2026-08-03T12:04:00+00:00",
                "check_in_2_at": null,
                "check_out_2_at": null,
                "status": "present",
                "late_minutes": 0,
                "early_leave_minutes": 0,
                "worked_minutes": 426
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/attendance-records

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

employee_id   integer  optional    

Example: 88

from   string  optional    

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

to   string  optional    

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

per_page   integer  optional    

Default 50, max 200. Example: 50

Body Parameters

from   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:12

to   string  optional    

value ليس تاريخًا صحيحًا. Example: 2026-09-09T13:05:12

employee_id   integer  optional    

Example: 16

per_page   integer  optional    

Example: 16

Check an employee in

requires authentication

Times are computed in Asia/Riyadh and stored UTC. Lateness is measured against the shift's start plus its grace window, so an on-time arrival inside grace records zero late minutes rather than a small positive number.

latitude/longitude are validated against the branch geofence when that branch has geofencing switched on; a check-in outside it is refused with a 422. Send them whenever the device can supply them — a branch may have enforcement enabled without your product knowing.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/attendance/check-in" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"employee_id\": 88,
    \"latitude\": 24.7136,
    \"longitude\": 46.6753
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/attendance/check-in"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "employee_id": 88,
    "latitude": 24.7136,
    "longitude": 46.6753
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 501,
        "type": "attendance-records",
        "attributes": {
            "employee_id": 88,
            "date": "2026-08-03",
            "check_in_at": "2026-08-03T04:58:00+00:00",
            "check_out_at": null,
            "check_in_2_at": null,
            "check_out_2_at": null,
            "status": "present",
            "late_minutes": 0,
            "early_leave_minutes": 0,
            "worked_minutes": 0
        }
    }
}
 

Example response (422, outside the branch geofence):


{
    "message": "...",
    "errors": {
        "latitude": [
            "أنت خارج نطاق موقع العمل."
        ]
    }
}
 

Request      

POST api/internal/companies/{company_id}/attendance/check-in

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Body Parameters

employee_id   integer     

Example: 88

latitude   number  optional    

Example: 24.7136

longitude   number  optional    

Example: 46.6753

Check an employee out

requires authentication

Closes the day's record and computes worked_minutes and any early leave. Checking out with no open check-in is a 422 rather than a silently created half-record.

Example request:
curl --request POST \
    "https://hr.getokta.io/api/internal/companies/16/attendance/check-out" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"employee_id\": 88
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/attendance/check-out"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "employee_id": 88
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 501,
        "type": "attendance-records",
        "attributes": {
            "employee_id": 88,
            "date": "2026-08-03",
            "check_in_at": "2026-08-03T04:58:00+00:00",
            "check_out_at": "2026-08-03T12:04:00+00:00",
            "check_in_2_at": null,
            "check_out_2_at": null,
            "status": "present",
            "late_minutes": 0,
            "early_leave_minutes": 0,
            "worked_minutes": 426
        }
    }
}
 

Request      

POST api/internal/companies/{company_id}/attendance/check-out

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Body Parameters

employee_id   integer     

Example: 88

List payroll runs

requires authentication

Read-only, deliberately: creating or closing a run from a service key would be a financial write attributed to no person. Closing stays in the platform, where the permissions and the activity log are.

status moves draft → review → approved → closed, and a closed run is immutable. Totals are in halalas.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/payroll-runs?status=closed&year=2026&per_page=12" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"status\": \"architecto\",
    \"year\": 22,
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/payroll-runs"
);

const params = {
    "status": "closed",
    "year": "2026",
    "per_page": "12",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "status": "architecto",
    "year": 22,
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 9,
            "type": "payroll-runs",
            "attributes": {
                "year": 2026,
                "month": 7,
                "status": "closed",
                "employees_count": 42,
                "total_net": 153400000,
                "closed_at": "2026-07-27T09:00:00+00:00"
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/payroll-runs

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

status   string  optional    

draft, review, approved or closed. Example: closed

year   integer  optional    

Example: 2026

per_page   integer  optional    

Default 12, max 200. Example: 12

Body Parameters

status   string  optional    

Example: architecto

year   integer  optional    

يجب ألا يقل value عن 2000. يجب ألا يكون value أكبر من 2100. Example: 22

per_page   integer  optional    

Example: 16

Show a payroll run

requires authentication

The run with a line per employee: gross, the GOSI deduction, other earnings and deductions, and net — all in halalas.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/payroll-runs/16" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/payroll-runs/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": 9,
        "type": "payroll-runs",
        "attributes": {
            "year": 2026,
            "month": 7,
            "status": "closed",
            "items": [
                {
                    "employee_id": 88,
                    "basic_salary": 800000,
                    "housing_allowance": 200000,
                    "gosi_deduction": 97500,
                    "total_earnings": 1050000,
                    "total_deductions": 97500,
                    "net_salary": 952500
                }
            ]
        }
    }
}
 

Request      

GET api/internal/companies/{company_id}/payroll-runs/{payrollRun}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

payrollRun   integer     

Example: 16

List an employee's payslips

requires authentication

Only lines from closed runs. A draft or in-review line is still moving, and a consuming product that shows it invites "why is my salary different from what the app said last week" — to which the honest answer is that it was never final.

Amounts are halalas.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/payslips" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"per_page\": 16
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/payslips"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "per_page": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "id": 310,
            "type": "payslips",
            "attributes": {
                "period": "2026-07",
                "basic_salary": 800000,
                "housing_allowance": 200000,
                "transport_allowance": 50000,
                "other_allowances": 0,
                "overtime_amount": 0,
                "extra_earnings": 0,
                "absence_deduction": 0,
                "late_deduction": 0,
                "unpaid_leave_deduction": 0,
                "extra_deductions": 0,
                "gosi_employee": 97500,
                "gross": 1050000,
                "total_deductions": 97500,
                "net": 952500
            }
        }
    ]
}
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/payslips

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Body Parameters

per_page   integer  optional    

Example: 16

The employee's full personal cost

requires authentication

One answer to "what does this person cost?": the contractual wage, the current month's movement as payroll computed it (or a contract-only estimate when no run has touched the month — month.source says which), the employer's additions (GOSI share, the month's end-of-service accrual per art. 84), the accrued end-of-service liability to date, and the last twelve months of employer cost. A month with no run is cost: null, not an invented figure.

Money in halalas everywhere, like the rest of this API.

month re-anchors the whole report to a past month — the consuming app's month arrows send the period.key shape straight back, so the device never does date arithmetic of its own. A future month is refused: there is nothing honest to report about it.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/employees/16/cost?month=2026-07" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"month\": \"6425-(03\"
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/employees/16/cost"
);

const params = {
    "month": "2026-07",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "month": "6425-(03"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "as_of": "2026-08-27",
        "period": {
            "year": 2026,
            "month": 8,
            "key": "2026-08"
        },
        "contract": {
            "basic": 900000,
            "housing": 225000,
            "transport": 80000,
            "other": 40000,
            "total": 1245000,
            "contributory": 1125000,
            "is_saudi": true
        },
        "month": {
            "source": "run",
            "run_id": 12,
            "run_status": "closed",
            "overtime_amount": 33750,
            "extra_earnings": 18000,
            "absence_deduction": 41500,
            "late_deduction": 8213,
            "unpaid_leave_deduction": 0,
            "extra_deductions": 30000,
            "gosi_employee": 109688,
            "gosi_employer": 132188,
            "gross": 1296750,
            "total_deductions": 189401,
            "net": 1107349,
            "absent_days": 1,
            "late_minutes": 95,
            "overtime_minutes": 360
        },
        "employer": {
            "gosi_employer": 132188,
            "eos_monthly_accrual": 51875,
            "monthly_cost": 1480813,
            "annual_cost": 17769756,
            "day_cost": 49360,
            "burden_ratio": 0.1419,
            "wedge_ratio": 0.3372
        },
        "eos": {
            "service_years": 3.99,
            "accrued": 2483775,
            "first_five": 2483775,
            "beyond_five": 0
        },
        "months": [
            {
                "key": "2025-09",
                "year": 2025,
                "month": 9,
                "cost": 1394000,
                "gross": 1230000,
                "net": 1080000
            }
        ]
    }
}
 

Request      

GET api/internal/companies/{company_id}/employees/{employee}/cost

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

employee   integer     

The employee. Example: 16

Query Parameters

month   string  optional    

A past or current month, YYYY-MM. Example: 2026-07

Body Parameters

month   string  optional    

Must match the regex /^\d{4}-(0[1-9]. Example: 6425-(03

Company statistics

requires authentication

Everything a consuming product's landing screen needs, in one call — so a dashboard is one request rather than eight, and every product shows the same numbers because they are counted in one place.

Counts only: anything a user can click through to has its own endpoint.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/stats?expiry_horizon_days=60" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"expiry_horizon_days\": 1
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/stats"
);

const params = {
    "expiry_horizon_days": "60",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "expiry_horizon_days": 1
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "id": "7",
        "type": "company-stats",
        "attributes": {
            "headcount": 42,
            "employees_by_status": {
                "active": 40,
                "resigned": 2
            },
            "attendance_today": {
                "present": 38,
                "absent": 1,
                "on_leave": 1
            },
            "pending_leave_requests": 3,
            "on_leave_today": 1,
            "documents_expiring": 4,
            "documents_expired": 1,
            "contracts_ending": 2,
            "last_closed_payroll": {
                "year": 2026,
                "month": 7,
                "total_net": 153400000
            },
            "expiry_horizon_days": 60
        }
    }
}
 

Request      

GET api/internal/companies/{company_id}/stats

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Query Parameters

expiry_horizon_days   integer  optional    

How far ahead to look for expiring documents and contracts. Default 60, max 365. Example: 60

Body Parameters

expiry_horizon_days   integer  optional    

يجب ألا يقل value عن 1. يجب ألا يكون value أكبر من 365. Example: 1

Suite API — approval workflows

Read and set the per-request-type approval chains a company routes its requests through — the same rows /settings/approvals edits, and the same ones ApproverChainResolver prefers over the default manager → HR fallback.

WHY THIS EXISTS ON THE API AT ALL. Every request the suite files through POST /requests and POST /leave-requests enters a chain, and a company with none configured falls back to the default — which fails outright when the employee has no manager, refusing the request with «لا يوجد معتمِد لهذا الموظف». A consuming product could therefore file requests it had no way to make routable, and no way to see why. The vocabulary needed to fix that — which types exist, which roles and people may approve — was only ever on a Livewire page.

List the chains, and the vocabulary for editing them

requires authentication

Every request type is returned, configured or not — a caller building an editor needs the whole list, and «this type has no chain» is an answer, not an absence. uses_default says which it is: with no row the platform falls back to direct manager → HR, and that fallback is not a chain anybody chose.

Example request:
curl --request GET \
    --get "https://hr.getokta.io/api/internal/companies/16/approval-workflows" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/approval-workflows"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "data": [
        {
            "request_type": "leave_request",
            "label_ar": "طلب إجازة",
            "label_en": "Leave request",
            "is_live": true,
            "uses_default": false,
            "steps": [
                {
                    "type": "direct_manager",
                    "label_ar": "المدير المباشر",
                    "label_en": "Direct manager"
                },
                {
                    "type": "role",
                    "role": "hr-manager",
                    "label_ar": "صاحب دور: hr-manager",
                    "label_en": "Role: hr-manager"
                }
            ]
        }
    ],
    "meta": {
        "step_types": [
            "direct_manager",
            "role",
            "user"
        ],
        "roles": [
            "hr-manager",
            "manager",
            "super-admin"
        ],
        "users": [
            {
                "id": 7,
                "name": "سارة القحطاني"
            }
        ]
    }
}
 

Request      

GET api/internal/companies/{company_id}/approval-workflows

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

Replace one type's chain

requires authentication

WHOLE-LIST REPLACE, like the settings page's save: the steps you send become the chain, in the order you send them. An EMPTY list deletes the row, which restores the default fallback rather than leaving a chain with nobody in it — a request routed to nobody is pending forever, in no queue, and looks submitted to the person who filed it.

Example request:
curl --request PUT \
    "https://hr.getokta.io/api/internal/companies/16/approval-workflows/architecto" \
    --header "Authorization: Bearer {YOUR_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"steps\": [
        {
            \"type\": \"role\",
            \"role\": \"hr-manager\",
            \"user_id\": 7
        }
    ]
}"
const url = new URL(
    "https://hr.getokta.io/api/internal/companies/16/approval-workflows/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_TOKEN}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "steps": [
        {
            "type": "role",
            "role": "hr-manager",
            "user_id": 7
        }
    ]
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "data": {
        "request_type": "leave_request",
        "uses_default": false,
        "steps": [
            {
                "type": "direct_manager",
                "label_ar": "المدير المباشر",
                "label_en": "Direct manager"
            }
        ]
    }
}
 

Example response (422, unknown approver):


{
    "message": "That user does not belong to this company."
}
 

Request      

PUT api/internal/companies/{company_id}/approval-workflows/{type}

Headers

Authorization        

Example: Bearer {YOUR_TOKEN}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

company_id   integer     

The ID of the company. Example: 16

type   string     

Example: architecto

Body Parameters

steps   object[]     

The ordered steps. Send [] to restore the default chain.

type   string     

One of: direct_manager, role, user. Example: role

role   string  optional    

Required when type is role. Example: hr-manager

user_id   integer  optional    

Required when type is user. Must be a user of this company. Example: 7