An opening is one round of hiring on a position: when you open a position for hiring, Jadiroon starts an opening for it, and candidates apply to that opening. Use these endpoints to open positions for hiring from your HR or ERP system, to see which openings are live, and to close them. Candidates on an opening are Applications.
All endpoints sit under /api/v1 and need an API key (Authorization: Bearer jdr_live_…) created by your Workspace Admin in Settings → Integrations.
Endpoints
| Method and path | What it does | Permission |
|---|---|---|
GET /api/v1/openings | List openings (cursor pagination, updatedSince, ?positionId=, ?status=) | recruitment.view |
POST /api/v1/openings | Open a position for hiring | recruitment.hiring.open |
GET /api/v1/openings/{id} | Get one opening | recruitment.view |
POST /api/v1/openings/{id}/close | Close an opening | recruitment.hiring.open |
Opening and closing use the same permission as in the app. An opening's id is its own id, not the position's. A position has at most one OPEN opening at a time; earlier rounds stay listed as CLOSED or FILLED. An id from another workspace returns 404 not_found.
The opening object
| Field | Type | Notes |
|---|---|---|
id | string | The opening's id. |
positionId | string | The position being hired for. See Positions. |
title | { ar, en } | The position's title; a missing language falls back to its job description's name. |
status | string | OPEN, CLOSED (closed by hand or by archiving the position) or FILLED (closed by a hire). |
hiringPathway | string or null | While OPEN: INTERNAL_ONLY, INTERNAL_FIRST (employees first, then external) or EXTERNAL. Null otherwise. |
externalAt | ISO 8601 or null | While OPEN: when the opening goes external (now for EXTERNAL, after the internal window for INTERNAL_FIRST, never for INTERNAL_ONLY). |
openedAt, closedAt | ISO 8601 | closedAt is null while open. |
externalRefs | array | [{ system, externalId }] links to your systems. |
createdAt, updatedAt | ISO 8601 |
Opening a position for hiring
POST /api/v1/openings takes the position, by id or by your external id, an optional pathway and an optional posting text:
{ "position": { "externalSystem": "oracle-hcm", "externalId": "POS-2207" }, "hiringPathway": "INTERNAL_FIRST" }
Without hiringPathway, the position's previous pathway is reused (INTERNAL_FIRST the first time). The same rules as the app apply:
- the position must have a job description and a placement in the Organizational Structure (
422 validation_failed,details.reasonnot_hire_ready, withdetails.missing); - it must have a posting text (
422 validation_failed,details.reasonno_job_description). A position created through the API has none: sendpostingwith the request when the position has none,{ "posting": { "ar": "…", "en": "…" } }(at least one language). It is saved as the position's posting text (postingon the position), and a language you leave out or send blank keeps its current text; - its job description must not be archived (
422 validation_failed,details.reasonarchived): applications to it would all fail; - an archived position can't be opened (
409 conflict,position_archived); unarchive it in the app first; - a position that already has an
OPENopening returns409 conflictwithdetails.reasonalready_openanddetails.openingId.
The response is 201 Created with the opening. New positions are created with Positions; the API opens existing ones.
Closing
POST /api/v1/openings/{id}/close closes hiring on the position (its hiring status becomes CLOSED) and the opening. Closing an opening that is not OPEN changes nothing and returns it as is. Candidates on it keep their applications.
Webhook events
| Event | When | data.object |
|---|---|---|
opening.created | A position is created and opened in one step in the app | The opening |
opening.opened | Hiring is opened on a position (app or API) | The opening |
opening.closed | Hiring is closed on a position (app or API), or its job description is archived or deleted | The opening |
opening.archived | The position is archived (its open opening is closed) | The opening |
opening.updated | The opening's title changes, it is made external now, or its position is restored from the archive; data.changedFields lists the fields when known | The opening |
Opening and closing also change the position's hiring fields, so they send position.updated too, and a position created through the openings flow sends position.created. Each event carries the opening it is about; archiving a position that has no open opening sends no opening.archived.
Errors
| Status and code | Meaning |
|---|---|
403 forbidden | The key lacks the permission in the table above. |
404 not_found | No opening with that id in your workspace. |
409 conflict | already_open or position_archived. |
422 unresolved_reference | The position reference didn't match a position in your workspace. |
422 validation_failed | The body is invalid, or the position isn't ready to hire (not_hire_ready, no_job_description). |
Example
curl -X POST https://jadiroon.com/api/v1/openings \
-H "Authorization: Bearer $JADIROON_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: open-POS-2207-2026-10" \
-d '{ "position": { "id": "cmvikf8270086knoq85hjhu3u" }, "hiringPathway": "INTERNAL_ONLY" }'
{
"id": "cmvm6inad0002kncr1pytpf2x",
"positionId": "cmvikf8270086knoq85hjhu3u",
"title": { "ar": "طبيب أخصائي", "en": "Specialist Physician" },
"status": "OPEN",
"hiringPathway": "INTERNAL_ONLY",
"externalAt": null,
"openedAt": "2026-10-24T09:11:06.085Z",
"closedAt": null,
"externalRefs": [],
"createdAt": "2026-10-24T09:11:06.085Z",
"updatedAt": "2026-10-24T09:11:06.085Z"
}