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
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:
POST /api/internal/companies— creates the company, seeds its defaults and creates its first hr-manager. Send your own tenant identifier asexternal_refand the call becomes idempotent: retries, re-installs and double-clicks return the same company instead of a second one.POST /api/internal/companies/{company}/employees— pushes the staff across. Upserts onemployee_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.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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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());Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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 مُستخدم من قبل."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Redeem a link code
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": [
"رمز الربط غير صالح أو منتهي أو مستخدَم من قبل."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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": "تم بلوغ الحد الأقصى لعدد الموظفين في باقتك."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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": [
"لدى الموظف حساب دخول بالفعل."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a 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
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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>"
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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": "لا يمكن أن تتجاوز الغرامات أجر خمسة أيام في الشهر."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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"
}
]
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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": [
"هذا النوع من الإجازات يتطلب مرفقاً."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Approve 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": [
"حقل السبب مطلوب."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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": [
"أنت خارج نطاق موقع العمل."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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"
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
]
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List 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
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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": "سارة القحطاني"
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
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."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.