An interview is a set of time slots offered to a candidate on an application, or to a person directly (for example an employee on a development pathway). The candidate picks a slot from the link in their email, and the panel scores the interview in the app. Use these endpoints to schedule interviews from another system, to follow their status, and to propose new times or cancel.
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/interviews | List interviews (cursor pagination, updatedSince, ?applicationId=&personId=&status=) | recruitment.view |
POST /api/v1/interviews | Offer interview slots | recruitment.act |
GET /api/v1/interviews/{id} | Get one interview | recruitment.view |
PATCH /api/v1/interviews/{id} | Propose new times, or cancel | recruitment.act |
recruitment.act includes recruitment.view. An id from another workspace returns 404 not_found. Interviews of the Training module (trainer applicants) are not part of this API.
The interview object
| Field | Type | Notes |
|---|---|---|
id | string | |
status | string | OFFERED (slots sent), CONFIRMED (the candidate picked one), COMPLETED or CANCELLED. |
applicationId | string or null | The application; null for a standalone interview with a person. |
personId | string or null | The candidate (or person) interviewed. |
person | object or null | { id, firstName, lastName, email, phone }. email and phone are masked without recruitment.candidate.contact. |
offeredSlots | array | ISO 8601 start times offered. |
selectedSlot | ISO 8601 or null | The slot the candidate picked. |
durationMinutes | integer | Default 45. |
interviewerIds | array | The panel: staff user ids (see Users). |
location | string or null | A room, an address or a meeting link. |
meetingProvider | string or null | manual or google_meet. |
reproposalRequestedAt | ISO 8601 or null | The candidate asked for new times; cleared when new times are proposed. |
notes | array | Panel notes and scores: [{ id, authorUserId, rating, body, createdAt }]. |
externalRefs | array | [{ system, externalId }] links to your systems. |
createdAt, updatedAt | ISO 8601 |
The candidate's private scheduling link is never returned.
Scheduling
POST /api/v1/interviews takes exactly one of applicationId or personId, plus:
offeredSlots: at least two ISO 8601 date-times with an offset (2026-11-02T09:00:00Z).interviewerIds: at least one active staff user of your workspace (422 unresolved_referencewithdetails.idsotherwise).- optional
durationMinutes(15–480, default 45) andlocation.
As in the app, the candidate on an application receives the interview email with the link to pick a slot, and every interviewer is emailed with the CV attached; a standalone person is notified in the app. A rejected or hired application can't be interviewed (409 conflict, application_not_active). The response is 201 Created with the interview.
Proposing new times, cancelling
- New times:
PATCHwith any ofofferedSlots,durationMinutes,interviewerIds,location. Omitted fields keep their value. Only while the interview isOFFERED(409 conflict,interview_not_offered). The candidate's link stays the same and now shows the new times; the invitation and the panel emails are sent again. - Cancel:
PATCHwith{ "status": "CANCELLED" }alone. The candidate is emailed and any calendar event is removed. Cancelling an interview that is alreadyCANCELLEDreturns it unchanged (200, no email, no event); aCOMPLETEDinterview can't be cancelled (409 conflict,details.reasoninterview_closed). - Completed interviews: a
COMPLETEDinterview is closed to new times and to cancelling (409 conflict,details.reasoninterview_closed). Panel scoring continues in the app.
Webhook events
| Event | When | data.object |
|---|---|---|
interview.scheduled | Slots are offered (app or API) | The interview |
interview.updated | The candidate picks a slot or asks for new times, new times are proposed, or any later scorecard submission or edit after the interview is COMPLETED, including the first panelist editing theirs (changedFields lists what changed; ["evaluation"] for a scorecard) | The interview |
interview.cancelled | The interview is cancelled | The interview |
interview.completed | The first panelist submits their scorecard; the interview becomes COMPLETED (sent once per interview) | The interview |
Each panelist scores separately. The first scorecard sets the interview to COMPLETED; the rest of the panel can still submit theirs, and any later scorecard submission or edit (the first panelist's included) sends interview.updated with changedFields: ["evaluation"]. Every scorecard moves the interview's updatedAt, so an updatedSince sync picks up new notes. Payloads follow the API's masking rule: an endpoint whose integration lacks recruitment.candidate.contact receives a masked person.email and person.phone.
Errors
| Status and code | Meaning |
|---|---|
403 forbidden | The key lacks a permission in the table above. |
404 not_found | No interview with that id in your workspace. |
409 conflict | interview_not_offered, interview_closed or application_not_active. |
422 unresolved_reference | The application, person or an interviewer didn't match. |
422 validation_failed | The body is invalid (fewer than two slots, no interviewer, status combined with schedule fields…). |
Example
curl -X POST https://jadiroon.com/api/v1/interviews \
-H "Authorization: Bearer $JADIROON_KEY" \
-H "Content-Type: application/json" \
-d '{ "applicationId": "cmvikf86m00g4knoqqorh7xj4", "offeredSlots": ["2026-11-02T09:00:00Z", "2026-11-03T10:00:00Z"], "interviewerIds": ["cmvikf7xk0003knoqj7x054vw"], "location": "HQ room 2" }'
The response is 201 Created:
{
"id": "cmvm7rmyv0001kndlb0qdm315",
"status": "OFFERED",
"applicationId": "cmvikf86m00g4knoqqorh7xj4",
"personId": "cmvikf86l00g2knoqdek78fqh",
"person": { "id": "cmvikf86l00g2knoqdek78fqh", "firstName": "Turki", "lastName": "Alanazi", "email": "t•••@example.com", "phone": null },
"offeredSlots": ["2026-11-02T09:00:00.000Z", "2026-11-03T10:00:00.000Z"],
"selectedSlot": null,
"durationMinutes": 45,
"interviewerIds": ["cmvikf7xk0003knoqj7x054vw"],
"location": "HQ room 2",
"meetingProvider": "manual",
"reproposalRequestedAt": null,
"notes": [],
"externalRefs": [],
"createdAt": "2026-10-24T09:46:05.191Z",
"updatedAt": "2026-10-24T09:46:05.191Z"
}