API reference
API Reference
Welcome to the Okta Partners platform API reference. Once your app is approved and a tenant installs it, this API lets your app read and write Okta data — strictly within the scopes that tenant explicitly granted.
The runtime API is hosted by **okta-web** (the core platform app); every request hits the `/apps` path under the base URL. The partner platform (okta-partners) is where you register your app, pick scopes, and manage versions and webhooks.
The API is REST over HTTPS, exchanges JSON (UTF-8), and uses standard HTTP status codes. Every read and write is isolated per tenant and protected by a named scope.
Your first call
curl https://getokta.io/api/apps/whoami \
-H "Authorization: Bearer $OKTA_PARTNER_TOKEN" \
-H "Accept: application/json"
Base URL
The runtime API is hosted by okta-web under the following path:
https://getokta.io/api/apps
Sandbox environment:
https://dev.getokta.io/api/apps
Authentication
Every request authenticates with a per-installation **Bearer token**. The token is issued automatically when a tenant installs your app; it is bound to a (tenant, app) pair and carries the granted scopes.
Authorization: Bearer <installation_token>
- Never embed the token in front-end code or a public repo — store it as a server-side secret (e.g. `OKTA_PARTNER_TOKEN`).
- The token is rotatable from the partner dashboard; external apps receive the new token over the bridge without a redeploy.
- A missing or invalid token returns `401 Unauthorized`. A valid token lacking the required scope returns `403 Forbidden`.
Verify your token
curl https://getokta.io/api/apps/whoami \
-H "Authorization: Bearer $OKTA_PARTNER_TOKEN"
# → { "tenant_id": 42, "module": "your-app", "scopes": [ ... ] }
Scopes
Every piece of data is protected by a scope shaped `
| Scope | Description |
|---|---|
education.students.read |
Read students |
education.students.write |
Create / update students |
education.guardians.read |
Read guardians |
education.subjects.read |
Read subjects |
education.grades.read |
Read grades |
education.sections.read |
Read sections |
education.academic_years.read |
Read academic years |
education.terms.read |
Read terms |
employees.directory.read |
Read employee directory |
entity.hierarchy.read |
See the entity’s tree: who is beneath, their profiles, who is above — and a condition for reading the children’s data with `?entities=` |
reports.builder.read |
Report catalog / run, list the school’s letterheads and print on them |
reports.builder.write |
Propose an app-authored letterhead (arrives as a draft) |
notifications.providers.send |
Send via a notification provider |
notifications.dispatch.send |
Dispatch a notification from your app’s catalog over HTTP (an Embedded app calls the service in-process, no scope needed) |
notifications.logs.read |
Read the delivery log of your app’s notifications (your installation only) |
realtime.messages.write |
Publish an instant message to your app's surfaces (listening needs no scope) |
Endpoints
All paths are relative to the base URL above. Write endpoints (POST/PATCH) require the matching scope.
Identity
/whoami
no scope
Current installation info (tenant, app, scopes).
https://getokta.io/api/apps/whoami
Current installation info (tenant, app, scopes).
Entity & tree
/entity
no scope
Where am I: the entity’s type, whether it is a container, its parent.
https://getokta.io/api/apps/entity
Where am I: the entity’s type, whether it is a container, its parent.
/entity/children
entity.hierarchy.read
The entities beneath, each with `installed` and `access` (what can be read there), plus `can_read_children` for the current person.
https://getokta.io/api/apps/entity/children
The entities beneath, each with `installed` and `access` (what can be read there), plus `can_read_children` for the current person.
Query Params
Every level instead of the next one only.
/entity/children/{id}
entity.hierarchy.read
One child’s profile: logo (as a path), contact, country, stage, sizes, and `access`.
https://getokta.io/api/apps/entity/children/{id}
One child’s profile: logo (as a path), contact, country, stage, sizes, and `access`.
Path Params
ULID of an entity beneath yours.
/entity/children/{id}/logo
entity.hierarchy.read
The child’s logo as a file — 404 when there is none.
https://getokta.io/api/apps/entity/children/{id}/logo
The child’s logo as a file — 404 when there is none.
Path Params
ULID of an entity beneath yours.
/entity/departments
entity.hierarchy.read
The complex’s departments and each one’s schools, with `viewer` — where the person stands (`reach` what they reach, or null for the whole tree). Every tree read is already fenced to the person’s department.
https://getokta.io/api/apps/entity/departments
The complex’s departments and each one’s schools, with `viewer` — where the person stands (`reach` what they reach, or null for the whole tree). Every tree read is already fenced to the person’s department.
/entity/ancestors
entity.hierarchy.read
The entities above me, nearest first.
https://getokta.io/api/apps/entity/ancestors
The entities above me, nearest first.
Students
/education/students
education.students.read
List students (paginated).
https://getokta.io/api/apps/education/students
List students (paginated).
Query Params
Page number (1-based).
Items per page (max 100).
Optional free-text search.
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/education/students/{id}
education.students.read
A single student.
https://getokta.io/api/apps/education/students/{id}
A single student.
Path Params
The resource id in Okta.
Query Params
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/education/students
education.students.write
Create a student.
https://getokta.io/api/apps/education/students
Create a student.
Body Params
Student's full name.
National / residency id.
Birth date (YYYY-MM-DD).
male or female.
Grade id.
Section id.
Guardian id.
/education/students/{id}
education.students.write
Update a student.
https://getokta.io/api/apps/education/students/{id}
Update a student.
Path Params
The resource id in Okta.
Body Params
Student's full name.
National / residency id.
Birth date (YYYY-MM-DD).
male or female.
Grade id.
Section id.
Guardian id.
Guardians
/education/guardians
education.guardians.read
List guardians.
https://getokta.io/api/apps/education/guardians
List guardians.
Query Params
Page number (1-based).
Items per page (max 100).
Optional free-text search.
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/education/guardians/{id}
education.guardians.read
A single guardian.
https://getokta.io/api/apps/education/guardians/{id}
A single guardian.
Path Params
The resource id in Okta.
Query Params
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/education/guardians
education.guardians.write
Create a guardian.
https://getokta.io/api/apps/education/guardians
Create a guardian.
Body Params
Guardian's full name.
Phone in E.164 format.
Email address.
father | mother | other.
/education/guardians/{id}
education.guardians.write
Update a guardian.
https://getokta.io/api/apps/education/guardians/{id}
Update a guardian.
Path Params
The resource id in Okta.
Body Params
Guardian's full name.
Phone in E.164 format.
Email address.
father | mother | other.
Curriculum
/education/subjects
education.subjects.read
Subjects.
https://getokta.io/api/apps/education/subjects
Subjects.
Query Params
Page number (1-based).
Items per page (max 100).
Optional free-text search.
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/education/subjects
education.subjects.write
Create a subject (idempotent).
https://getokta.io/api/apps/education/subjects
Create a subject (idempotent).
Body Params
Subject name.
Subject code.
/education/grades
education.grades.read
Grades.
https://getokta.io/api/apps/education/grades
Grades.
Query Params
Page number (1-based).
Items per page (max 100).
Optional free-text search.
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/education/sections
education.sections.read
Sections.
https://getokta.io/api/apps/education/sections
Sections.
Query Params
Page number (1-based).
Items per page (max 100).
Optional free-text search.
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/education/academic-years
education.academic_years.read
Academic years.
https://getokta.io/api/apps/education/academic-years
Academic years.
Query Params
Page number (1-based).
Items per page (max 100).
Optional free-text search.
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/education/terms
education.terms.read
Terms.
https://getokta.io/api/apps/education/terms
Terms.
Query Params
Page number (1-based).
Items per page (max 100).
Optional free-text search.
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
Employees
/employees/directory
employees.directory.read
Employee directory.
https://getokta.io/api/apps/employees/directory
Employee directory.
Query Params
Page number (1-based).
Items per page (max 100).
Optional free-text search.
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/employees/directory/{id}
employees.directory.read
A single employee.
https://getokta.io/api/apps/employees/directory/{id}
A single employee.
Path Params
The resource id in Okta.
Query Params
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/employees/directory
employees.directory.write
Add an employee (idempotent).
https://getokta.io/api/apps/employees/directory
Add an employee (idempotent).
Body Params
Employee's full name.
Job title.
Email address.
Reports
/reports/builder
reports.builder.read
Catalog of available reports.
https://getokta.io/api/apps/reports/builder
Catalog of available reports.
Query Params
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/reports/builder/{key}/run
reports.builder.read
Run a report (idempotent).
https://getokta.io/api/apps/reports/builder/{key}/run
Run a report (idempotent).
Path Params
Report key from the catalog.
Body Params
Report parameters (report-specific).
/reports/templates
reports.builder.read
The school’s approved letterheads.
https://getokta.io/api/apps/reports/templates
The school’s approved letterheads.
Query Params
Up to 20 comma-separated ULIDs of entities beneath yours: the answer is {"entities": {"<ulid>": this endpoint’s answer for that entity}}, a refused one carrying error and status under its key. A school is read with its own grant to your app or your entity’s read grant carried down; entity.hierarchy.read is required.
ULID of ONE entity beneath yours: the answer is that entity’s, as is. For a person holding tenants.children.view_members, never an installation token.
/reports/templates/{id}/render
reports.builder.read
Print your content on a letterhead — returns a PDF.
https://getokta.io/api/apps/reports/templates/{id}/render
Print your content on a letterhead — returns a PDF.
Path Params
The opaque template id exactly as the listing returned it — not a number.
Body Params
The document body — blocks: heading, text, table, info-row, divider, spacer, image. An unknown type is refused, not skipped.
Values for the template’s declared variables. An undeclared key is dropped; platform variables cannot be overwritten.
/reports/templates
reports.builder.write
Propose a template (arrives as a draft, idempotent).
https://getokta.io/api/apps/reports/templates
Propose a template (arrives as a draft, idempotent).
Body Params
Template name in Arabic.
Template name in English.
A description shown to whoever picks the template.
administrative | general.
portrait | landscape.
a4 | a3 | letter.
Show the header band.
Show the footer band.
Right header slot — text accepting {{variables}}.
Center header slot.
Left header slot.
Right footer slot.
Center footer slot.
Left footer slot.
Watermark text.
Watermark opacity (0–100).
Top margin in mm (0–40).
Right margin in mm (0–40).
Bottom margin in mm (0–40).
Left margin in mm (0–40).
Print the signature block at the foot.
Signature block columns (1–4).
Signature block title.
Signatories: title_ar (required), title_en, name, show_line. An empty array removes them all; omitting the key leaves them alone.
Template fields: key (required), label_ar (required), label_en, type (text|date|number|currency), required, default_value. A key shadowing a platform variable is dropped silently.
/reports/templates/{id}
reports.builder.write
Edit a template your app authored (revokes approval).
https://getokta.io/api/apps/reports/templates/{id}
Edit a template your app authored (revokes approval).
Path Params
The opaque template id exactly as the listing returned it — not a number.
Body Params
Template name in Arabic.
Template name in English.
A description shown to whoever picks the template.
administrative | general.
portrait | landscape.
a4 | a3 | letter.
Show the header band.
Show the footer band.
Right header slot — text accepting {{variables}}.
Center header slot.
Left header slot.
Right footer slot.
Center footer slot.
Left footer slot.
Watermark text.
Watermark opacity (0–100).
Top margin in mm (0–40).
Right margin in mm (0–40).
Bottom margin in mm (0–40).
Left margin in mm (0–40).
Print the signature block at the foot.
Signature block columns (1–4).
Signature block title.
Signatories: title_ar (required), title_en, name, show_line. An empty array removes them all; omitting the key leaves them alone.
Template fields: key (required), label_ar (required), label_en, type (text|date|number|currency), required, default_value. A key shadowing a platform variable is dropped silently.
/reports/templates/{id}
reports.builder.write
Delete a draft your app authored.
https://getokta.io/api/apps/reports/templates/{id}
Delete a draft your app authored.
Path Params
The opaque template id exactly as the listing returned it — not a number.
Notifications
/notifications/capabilities
notifications.providers.send
Notification channel capabilities.
https://getokta.io/api/apps/notifications/capabilities
Notification channel capabilities.
/notifications/catalog
notifications.providers.send
For a provider: the notification catalogue of every app installed on the tenant — keys, their variables, and the tenant’s state per key.
https://getokta.io/api/apps/notifications/catalog
For a channel provider: every notification key the apps installed on this tenant declare — names, `variables` (name → type), the developer’s default template and channel ceiling — plus the tenant’s state for the key when it has one (`enabled`, `channels`, `template_id`). These are the keys you will see in `metadata.notification_key`; the variables are what fills your template’s slots. This tenant’s apps only; no recipients, no payloads. `installed_apps` lists every active install with its notification count so an empty answer reads: installs without notifications, or a token on another tenant (`tenant_id`).
Query Params
Only the keys that would go out on this channel here (the tenant’s choice when it has one, else the developer’s ceiling) — e.g. `whatsapp`.
/notifications/send
notifications.providers.send
Send a message (idempotent).
https://getokta.io/api/apps/notifications/send
Send a message (idempotent).
Body Params
Recipient (phone or user id).
Message body.
Specific channel (optional).
/notifications/dispatch
notifications.dispatch.send
Dispatch a catalog notification to its recipient — the platform fans out to the channels (idempotent).
https://getokta.io/api/apps/notifications/dispatch
Dispatches a key declared in your app’s catalog to its recipient; the platform picks the channels from the school’s settings. An Embedded app calls `App\Services\PartnerApi\Notifications\DispatchNotification` in-process with the same payload.
Body Params
A key declared in your app’s catalog (`<slug>.<resource>.<event>`). An undeclared key is refused with a 422 that names it.
`{type, id}` — who it is about: `parent_of_student` (student ulid), `host_user` (a user id), `school_admin` (the school). Absent = the school’s configured recipients. No `*_id` key is ever read as an address.
What the templates’ `{{ placeholders }}` resolve to. Flat keys beside the reserved fields are moved here automatically.
A title your app rendered — used when no template applies.
A body your app rendered — third in precedence after the school’s wording and `default_template`.
A link the in-app card opens.
Locale of the text you sent (`ar`/`en`).
Data for your own use; never printed.
/notifications/logs
notifications.logs.read
What became of what you dispatched: each attempt’s status and error.
https://getokta.io/api/apps/notifications/logs
One row per (key, channel) the platform attempted for your installation, with its status and the error the channel job left. `queued` not yet picked up by the worker; `sent` arrived; `failed` did not (the text says why); `skipped_disabled` the school has not enabled the key; `skipped_no_transport` the channel has no transport; `skipped_quiet` inside quiet hours; `skipped_plan_feature` outside the plan.
Query Params
Page number (default 1).
Page size, max 100 (default 50).
Filter by channel: `in_app` `push` `whatsapp` `sms` `email` `webhook_out`.
Filter by status.
Filter by notification key.
From date (ISO 8601).
To date (ISO 8601).
Realtime
/realtime/publish
realtime.messages.write
Push an instant message to your app's own surfaces in this tenant (not idempotent).
https://getokta.io/api/apps/realtime/publish
Pushes a message to your app's channel in this tenant only. Listening needs no scope — the installation is the permission, and there is no `realtime.messages.read`.
Body Params
Your own event name — `^[a-z][a-z0-9_.:-]{0,63}$`. It never becomes the broadcast event name: that is the fixed `app.message`, and yours travels inside the payload.
The message payload, at most 8192 bytes of JSON. Anything needing data is read by the surface from the API with its usual scope.
The full, always-current list lives in the auto-generated OpenAPI 3.1 spec:/docs/openapi.json·/docs/postman_collection.json
Pagination
List endpoints are paginated. Use `?page=2&per_page=50` (max 100 per page).
{
"data": [ ... ],
"total": 1242,
"per_page": 20,
"current_page": 1,
"last_page": 63
}
Idempotency
Write operations (POST/PATCH) accept an optional header. The same key within 24 hours replays the same stored response — so if your side times out, retrying will not create a duplicate record.
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Errors
Errors are returned as a consistent JSON envelope with the appropriate HTTP status code.
{
"message": "The given scope is not granted for this installation.",
"error": "scope_not_granted",
"scope": "education.students.write"
}
| Status | Name | Description |
|---|---|---|
400 |
Bad Request | Malformed or missing request body. |
401 |
Unauthorized | Missing or invalid token. |
403 |
Forbidden | Valid token, but the required scope is not granted. |
404 |
Not Found | Resource not found within the tenant scope. |
409 |
Conflict | Idempotency conflict or inconsistent state. |
422 |
Unprocessable Entity | Field validation failed. |
429 |
Too Many Requests | Rate limit exceeded. |
5xx |
Server Error | Transient error — retry with backoff. |
Rate limits
Every response carries headers telling you the remaining budget. On exceed, you get `429` with `Retry-After` (seconds). Honor exponential backoff on retry.
| Header | Description |
|---|---|
X-RateLimit-Limit |
Ceiling for the window. |
X-RateLimit-Remaining |
Remaining in the current window. |
Retry-After |
Seconds until a new request is allowed (with 429). |
Webhooks
External apps receive platform events via signed webhooks. Each webhook is an HTTP POST with a JSON body, signed with HMAC-SHA256 over the envelope `
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"event": "education.students.created",
"tenant_id": 42,
"created_at": "2026-04-26T10:30:15+03:00",
"data": { "student": { "id": 123, "full_name": "..." } }
}
| Header | Description |
|---|---|
X-Okta-Event |
Event name. |
X-Okta-Delivery-Id |
UUID stable across retries (for dedup). |
X-Okta-Timestamp |
Unix timestamp at signing. |
X-Okta-Signature |
HMAC-SHA256 signature (hex). |
Verify the signature
$expected = hash_hmac(
'sha256',
$request->header('X-Okta-Timestamp').'.'.$request->getContent(),
$YOUR_WEBHOOK_SECRET,
);
if (! hash_equals($expected, $request->header('X-Okta-Signature'))) {
return response('invalid signature', 401);
}
- Check `X-Okta-Timestamp` is within the last 5 minutes, and store `X-Okta-Delivery-Id` for at least 15 minutes to reject replays.
- Respond 2xx within 10 seconds. For long work, accept with 202 immediately and process asynchronously.
- Retry backoff: 30s → 2m → 10m → 1h → 6h, after which the delivery is marked failed and surfaces on the Webhook Deliveries page in the partner dashboard.
More resources
This reference is a practical summary. For the full picture (integration types, app lifecycle, the manifest, the policy scanner) see the developer guide.
- Full developer guide
- Raw markdown source
- llms.txt — AI-friendly site index
- Apply to become a partner