REST API
Programmatic access to interviews. Schedule, retrieve, update, and delete interviews from your ATS or other internal tools.
Overview
All endpoints are versioned under /v1 and return JSON. Requests and responses use UTF-8. Timestamps are ISO 8601 strings in UTC.
Base URL
The API base URL is specific to your deployment. You can find it under Settings → Integrations. Examples in this document use {base_url} as a placeholder.
{base_url}/v1/interviewsAuthentication
The API uses bearer tokens. Generate one under Settings → Integrations → API tokens and pass it in the Authorization header on every request. Treat tokens like passwords — the secret is shown only once.
Authorization: Bearer qlo_live_xxxxxxxxxxxxxxxxxxxxxxxxTokens act on behalf of the user who created them and inherit that user's billing balance and interview ownership. Revoking a token in the settings disables it immediately.
Errors
Errors use standard HTTP status codes with a JSON body containing a stable code and a human-readable message.
{
"error": {
"code": "validation_failed",
"message": "Field \"title\" is required and must be a non-empty string"
}
}| Name | Type | Description |
|---|---|---|
400 validation_failed | client error | Malformed JSON, missing required field, or unknown field for the endpoint. |
401 unauthenticated | client error | Missing or malformed Authorization header. |
401 invalid_token | client error | Token does not exist or has been revoked. |
402 insufficient_balance | client error | Not enough interview credits to schedule a new interview. |
404 not_found | client error | Interview does not exist or is owned by a different user. |
405 method_not_allowed | client error | HTTP method not supported for this path. |
500 internal_error | server error | Unexpected server-side failure. Safe to retry with backoff. |
Create interview
Schedules a new interview against an existing plan. Debits one interview credit from the caller's balance. The response contains the interview id and the 4-digit pin the candidate uses to join the room at /i/{id}.
/v1/interviewsBody
| Name | Type | Description |
|---|---|---|
titlerequired | string | Display title for the interview. Max 200 characters. |
planIdrequired | string | ID of the Plan to use as the interview script. Must belong to the caller. |
intervieweeNamerequired | string | Candidate's name shown in the room and reports. Max 200 characters. |
Example
curl -X POST {base_url}/v1/interviews \
-H "Authorization: Bearer $QUOLLO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Backend engineer — final round",
"planId": "plan_01HZK...",
"intervieweeName": "Jane Doe"
}'{
"id": "9f1b6e8c-...",
"pin": "4821",
"balanceAfter": 12
}List interviews
Returns interviews owned by the caller. Supports cursor-based pagination — pass nextCursor from the previous response back as the cursor query parameter to fetch the next page.
/v1/interviewsQuery parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Page size. Default 20, max 100. |
status | string | Filter by status. One of PENDING, IN_PROGRESS, FINISHED. |
cursor | string | Opaque pagination cursor returned as nextCursor in the previous response. |
Example
curl "{base_url}/v1/interviews?status=FINISHED&limit=50" \
-H "Authorization: Bearer $QUOLLO_TOKEN"{
"items": [
{
"id": "9f1b6e8c-...",
"title": "Backend engineer — final round",
"status": "FINISHED",
"planId": "plan_01HZK...",
"pin": "4821",
"intervieweeName": "Jane Doe",
"startedAt": "2026-05-18T10:02:14.000Z",
"finishedAt": "2026-05-18T10:48:51.000Z",
"createdAt": "2026-05-15T08:30:00.000Z",
"updatedAt": "2026-05-18T10:48:51.000Z"
}
],
"nextCursor": "eyJpZCI6eyJTIjoiOWYxYjZlOGMtLi4uIn19"
}Get interview
Returns the interview and, once the interview has finished and post-processing has run, the associated record with results, summary, participants, and a pre-signed video URL that is valid for one hour.
/v1/interviews/{id}Path parameters
| Name | Type | Description |
|---|---|---|
idrequired | string | Interview ID returned from create. |
Response shape
For non-finished interviews the record field is null. For finished interviews record.status moves through IDLE → PROCESSING → READY; the videoUrl is only populated when status is READY.
{
"interview": {
"id": "9f1b6e8c-...",
"title": "Backend engineer — final round",
"status": "FINISHED",
"planId": "plan_01HZK...",
"pin": "4821",
"intervieweeName": "Jane Doe",
"startedAt": "2026-05-18T10:02:14.000Z",
"finishedAt": "2026-05-18T10:48:51.000Z",
"createdAt": "2026-05-15T08:30:00.000Z",
"updatedAt": "2026-05-18T10:48:51.000Z"
},
"record": {
"status": "READY",
"summary": "Strong on systems design, weaker on...",
"results": [
{ "sectionId": "...", "questionId": "...", "score": 4, "notes": "..." }
],
"participants": [
{ "name": "Jane Doe", "role": "INTERVIEWEE" }
],
"videoUrl": "https://...s3.amazonaws.com/records/.../9f1b6e8c-....mp4?..."
}
}Update interview
Partial update. Only the fields listed below are writable. Sending any other field returns 400 validation_failed.
/v1/interviews/{id}Body
| Name | Type | Description |
|---|---|---|
title | string | New title. Max 200 characters. |
intervieweeName | string | New candidate name. Max 200 characters. |
planId | string | Reassign the interview to a different plan. |
At least one of the fields above must be present in the body.
Example
curl -X PATCH {base_url}/v1/interviews/9f1b6e8c-... \
-H "Authorization: Bearer $QUOLLO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "title": "Backend engineer — round 2" }'Delete interview
Deletes the interview. If the interview was still PENDING and a credit was charged when it was created, the credit is refunded and refunded: true with the new balanceAfter is returned. Interviews that have already started or finished are deleted without a refund.
/v1/interviews/{id}Response
{
"deleted": true,
"refunded": true,
"balanceAfter": 13
}List plans
Returns plans owned by the caller. Use the resulting id as the planId when creating an interview. Only summary fields are returned — sections and questions live in the dashboard.
/v1/plansQuery parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Page size. Default 20, max 100. |
cursor | string | Opaque pagination cursor returned as nextCursor in the previous response. |
Response shape
| Name | Type | Description |
|---|---|---|
id | string | Plan ID. Pass this as planId when creating an interview. |
name | string | Display name. |
tags | string[] | Free-form tags assigned to the plan. |
seniority | string | null | One of JUNIOR, MIDDLE, SENIOR. Null when not set on the plan. |
language | string | Plan language as a 2-letter code (EN, DE, FR, ES, IT, NL, PL, PT, RU, UA, and other EU languages). |
Example
curl "{base_url}/v1/plans?limit=50" \
-H "Authorization: Bearer $QUOLLO_TOKEN"{
"items": [
{
"id": "plan_01HZK...",
"name": "Backend engineer — interview",
"tags": ["backend", "node", "aws"],
"seniority": "SENIOR",
"language": "EN"
}
],
"nextCursor": null
}