Crosschq Interview Partner API
Enabling Crosschq Partner API for Interview Agent
Configure App settingsThe Crosschq Interview Partner APIs allow third-party platforms to programmatically integrate with Crosschq's AI Interview product. This API exposes the following capabilities:
- List the AI interview plans configured for their organization.
- Request a new AI interview for a candidate.
- List interviews with filters and retrieve rich interview details and transcripts.
- Cancel an interview that is still in progress.
- Receive webhook notifications when an interview’s status changes.
Sandbox testing is available — please reach out to support@crosschq.com to request access or to enable the Partner API on your organization.
The Partner API base URL is provisioned during partner onboarding — reach out to your Crosschq contact to retrieve it. Throughout this document, <URL> represents the base URL you receive at onboarding.
Table of Contents
Authentication
Navigate to Settings > Connectors and enable the Crosschq Partner API toggle. Once enabled, the API Settings panel will expand.

General
The Partner API Key is the token that must be sent on an AUTHORIZATION header with each API request.

Webhook Configuration
The API Settings panel includes webhook configuration for both the 360 app and the Interview app. Each app has the following fields:
- Webhook URL — the endpoint URL where Crosschq will send event notifications.
- Request method —
POST(default) orPUT. UsePOSTto create a new record andPUTto update an existing one. - Auth method (optional) — the authentication method used to call your endpoint. Available options:
- No Auth (default) — no authentication is required.
- Basic — HTTP Basic authentication.
- Token — token-based authentication.
- Token (optional) — required only when Auth method is set to Basic or Token.
Configure both apps independently, then click Save Changes.

Configure app settings
Once your API settings are saved, you can configure additional settings for specific Crosschq Apps.
.png?width=670&height=149&name=image%20(10).png)
Endpoints
List interview agents
GET /api/v1/interview/interview-plans
Returns the active AI interview agents configured for your organization. Results are paginated using an opaque cursor.
Query Parameters:
- limit: (optional) number of results per page. Default
25, maximum100. - cursor: (optional) opaque pagination token returned by a previous response in
next_cursor. Omit on the first call. - search: (optional) case-insensitive substring filter applied to the plan's
name.
Response:
{
"data": [
{
"id": "6f3a9b2c-1d4e-4a2f-9b8a-2c5d4e6f7a8b",
"name": "Software Engineer Screen",
"job_title": "Software Engineer",
"created": "2026-01-04T14:22:10Z",
"modified": "2026-01-12T09:05:33Z"
}
],
"next_cursor": "eyJjcmVhdGVkIjoiMjAyNi0wMS0wNFQxNDoyMjoxMFoifQ=="
}
When there are no more pages, next_cursor is null.
Create AI Interview
POST /api/v1/interview/ai-interviews
Creates a new AI interview for a candidate against one of your organization's active interview agents. The candidate is notified by email and/or SMS based on the agent's configuration.
Request Headers:
- Authorization: (required) your partner API key (UUID).
- Idempotency-Key: (optional, strongly recommended) unique client-supplied string used to safely retry this request. See Idempotency.
- Content-Type:
application/json
Request Body:
- interview_plan_id: (required) UUID of the interview agent to use. The agent must have status
activeand be fully configured. - candidate: (required) candidate dictionary.
- first_name: (required) candidate's first name.
- last_name: (required) candidate's last name.
- email: (required) candidate's email address.
- phone: (optional) candidate's phone number in E.164 format (for SMS-enabled plans).
- external_id: (optional) your system's identifier for the candidate. Used for deduplication.
- external_id: (optional) your system's identifier for this interview request. Used for deduplication and returned on all responses.
- metadata: (optional) free-form JSON object that is stored with the interview and returned on webhook payloads.
- skip_duplicate_check: (optional) boolean. When
true, a new interview is created even if an interview already exists for the same candidate and plan. Defaults tofalse.
{
"interview_plan_id": "6f3a9b2c-1d4e-4a2f-9b8a-2c5d4e6f7a8b",
"candidate": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"phone": "+15551234567",
"external_id": "candidate-ext-123"
},
"external_id": "interview-ext-789",
"metadata": {
"requisition_id": "REQ-4421",
"source": "partner-ats"
},
"skip_duplicate_check": false
}
Response (201 Created):
{
"interview_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"status": "requested",
"interview_url": "https://app.crosschq.com/interview/a1b2c3d4-e5f6-4789-9abc-def012345678",
"created_at": "2026-05-13T17:42:08Z",
"external_id": "interview-ext-789",
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123"
}
}
Notes:
- The
interview_plan_idmust reference an agent that is active and fully configured. Requests targeting unconfigured or archived plans return404. external_idon both the interview and candidate is stored and is used to detect duplicate submissions whenskip_duplicate_checkisfalse.
Error Responses:
400—validation_error: missing required field, invalid value, plan not fully configured, or a duplicate interview already exists for this candidate/plan combination (whenskip_duplicate_checkisfalse).404—not_found: the interview agent does not exist or is not active.
List Interviews
GET /api/v1/interview/interviews
Returns a paginated list of interviews scoped to your organization. Surfaces AI signals (score, recommendation, language_proficiency) at the root; the rich report prose is available via the PDF (see webhook report_url).
Query Parameters:
| Param | Type | Default | Notes |
|---|---|---|---|
status |
str (CSV) | all | e.g. ?status=completed,in_progress |
created_from / created_to |
ISO datetime | — | Interview.created window |
completed_from / completed_to |
ISO datetime | — | Interview.completed_at window |
candidate_id |
UUID | — | Exact match |
candidate_external_id |
str | — | Exact match |
candidate_email |
str | — | Lowercased exact match |
candidate_ats_id |
str | — | Matches Candidate.tw_id |
job_id |
UUID | — | Matches JobApplication.job_id |
job_external_id |
str | — | Matches Job.source_id |
interview_type |
str | — | ai or human |
score_min / score_max |
float | — | Filters by InterviewSummary.score. score_min > score_max returns 400 invalid_filter_combination. |
cursor |
str | — | Opaque base64 cursor from previous page |
limit |
int | 25 | Max 100 |
Response: {"items": [...], "next_cursor": str|null, "limit": int}. Each item is the slim list shape (id, status, candidate slim, job slim, AI signals, candidate feedback).
Response (200 OK):
{
"items": [
{
"id": "b3c4d5e6-f7a8-4b9c-0d1e-2f3a4b5c6d7e",
"external_id": "interview-ext-789",
"ats_id": null,
"status": "completed",
"interview_type": "ai",
"source": "partner_api",
"created_at": "2026-05-13T17:42:08Z",
"started_at": "2026-05-13T18:01:15Z",
"completed_at": "2026-05-13T18:28:47Z",
"duration_minutes": 27.5,
"completion_percentage": 100.0,
"language": "en",
"interview_url": "https://app.crosschq.com/interviews/<signed-token>/",
"score": 4.2,
"recommendation": "recommended",
"language_proficiency": "Proficient",
"device": {
"is_mobile": false,
"operating_system": "macOS",
"browser": "Chrome"
},
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123",
"ats_id": null,
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com"
},
"job": {
"id": "a9b8c7d6-e5f4-4a3b-2c1d-0e9f8a7b6c5d",
"name": "Senior Software Engineer",
"external_id": "job-ext-456",
"ats_id": null
},
"candidate_feedback": {
"sentiment": "Great",
"feedback_comment": "Really enjoyed the format!",
"submitted_at": "2026-05-13T18:35:02Z"
}
}
],
"next_cursor": "eyJjcmVhdGVkIjoiMjAyNi0wNS0xM1QxNzo0MjowOFoiLCJwayI6ImIzYzRkNWU2LWY3YTgtNGI5Yy0wZDFlLTJmM2E0YjVjNmQ3ZSJ9",
"limit": 25
}
Error Responses:
400--invalid_filter_combination: e.g.score_min > score_max.400--invalid_filter_value: unknown enum value forstatusorinterview_type, or unparseable datetime. Thedetailsobject contains{param: str, invalid_values: list[str], valid_values: list[str]}. Example:
{
"error": "invalid_filter_value",
"message": "Invalid value(s) for 'status': ['potato']. Valid values: ['accepted', 'auto_canceled', 'canceled', 'completed', 'declined', 'in_progress', 'partial', 'pending_confirmation', 'generating_report', 'requested', 'scheduled'].",
"details": {
"param": "status",
"invalid_values": ["potato"],
"valid_values": ["accepted", "auto_canceled", "canceled", "completed", "declined", "generating_report", "in_progress", "partial", "pending_confirmation", "requested", "scheduled"]
}
}
Get Interview Details
GET /api/v1/interview/interviews/{id}
Breaking change (effective 2026-05-15): The response shape was redesigned for AI agent consumers. Existing GET callers must update their parsers. Note: While webhook delivery was unaffected by the 2026-05-15 GET redesign, the webhook payload was extended additively on 2026-05-15 (PR #1124) and again on 2026-05-18 — see the Webhooks section for the current shape.
Breaking change (effective 2026-05-18): The
reportblock was removed; AI signals (score,recommendation,language_proficiency) now live at the root, and a newquestions_and_answerslist anddeviceobject are returned. Webhook listeners are also updated additively (see Webhooks).
Returns rich interview detail including nested candidate, job, AI signals at the root (score, recommendation, language_proficiency), device info, questions_and_answers (PARTIAL/COMPLETED), candidate feedback, and inline transcript chunks (when the count is <= 500; for larger transcripts call /transcriptions).
Response (200 OK):
{
"id": "b3c4d5e6-f7a8-4b9c-0d1e-2f3a4b5c6d7e",
"external_id": "interview-ext-789",
"ats_id": null,
"status": "completed",
"interview_type": "ai",
"source": "partner_api",
"created_at": "2026-05-13T17:42:08Z",
"started_at": "2026-05-13T18:01:15Z",
"completed_at": "2026-05-13T18:28:47Z",
"duration_minutes": 27.5,
"completion_percentage": 100.0,
"language": "en",
"interview_url": "https://app.crosschq.com/interviews/<signed-token>/",
"score": 4.2,
"recommendation": "recommended",
"language_proficiency": "Proficient",
"device": {
"is_mobile": false,
"operating_system": "macOS",
"browser": "Chrome"
},
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123",
"ats_id": null,
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"phone_number": "+15551234567"
},
"job": {
"id": "a9b8c7d6-e5f4-4a3b-2c1d-0e9f8a7b6c5d",
"name": "Senior Software Engineer",
"external_id": "job-ext-456",
"ats_id": null,
"description": "Build and maintain scalable backend services.",
"application_status": "applied",
"applied_at": "2026-05-01T09:00:00Z",
"ats_link": null
},
"transcript": [
{
"speaker": "Interviewer",
"text": "Welcome to your interview with Crosschq. Thanks for making time today. To start, can you briefly introduce yourself?",
"start_seconds": 0.0,
"end_seconds": 6.8
},
{
"speaker": "Jane Doe",
"text": "Thanks for having me. I'm a backend engineer with six years of experience, mostly in distributed systems.",
"start_seconds": 7.0,
"end_seconds": 13.4
},
{
"speaker": "Interviewer",
"text": "Great. Describe a time you improved API performance.",
"start_seconds": 14.0,
"end_seconds": 17.2
},
{
"speaker": "Jane Doe",
"text": "In my last role, we cut p95 latency from 800ms to 120ms by introducing a Redis cache for hot reads and batching downstream DB writes.",
"start_seconds": 17.5,
"end_seconds": 28.9
},
{
"speaker": "Interviewer",
"text": "Thanks Jane, that wraps up our questions for today. We'll be in touch with next steps soon.",
"start_seconds": 29.5,
"end_seconds": 34.1
}
],
"questions_and_answers": [
{
"question": "Describe a time you improved API performance.",
"answer": "In my last role, we cut p95 latency from 800ms to 120ms by introducing a Redis cache for hot reads and batching downstream DB writes.",
"was_asked": true,
"was_answered": true,
"confidence": 0.92
}
],
"candidate_feedback": {
"sentiment": "Great",
"feedback_comment": "Really enjoyed the format!",
"submitted_at": "2026-05-13T18:35:02Z"
}
}
Error Responses:
404--not_found: the interview does not exist or does not belong to your organization.
Get Interview Transcript
GET /api/v1/interview/interviews/{id}/transcriptions
Returns the full transcript (chunks + speakers). PII exposure is raw per the 2026-05-15 policy (A5 DPA scope). The endpoint is unrestricted by status — it returns whatever chunks are available for the interview.
Status codes:
| Status | HTTP | Body |
|---|---|---|
| Success | 200 | Full transcript |
| Not found | 404 | not_found — wrong org or wrong UUID |
Response fields:
| Field | Type | Description |
|---|---|---|
interview_id |
UUID | Interview ID. |
interview_status |
string | Current interview status (e.g. completed, partial). |
confirmation |
object | null | Transcript confirmation state. null for non-AI interviews where the concept does not apply. |
confirmation.required |
bool | Whether the org configured the interview to require candidate transcript confirmation. When false, the candidate was never asked to confirm and confirmation.confirmed is N/A. |
confirmation.confirmed |
bool | Whether the candidate actually confirmed the transcript. Only meaningful when required=true. |
confirmation.confirmed_at |
datetime | null | Timestamp of the candidate's confirmation. |
transcription_note |
string | null | Optional note attached to the transcription. |
transcription |
array | Ordered list of transcript chunks. |
speakers |
array | Speaker registry: label (display name) + role (raw role: ai / user / etc.). |
Response (200 OK):
{
"interview_id": "b3c4d5e6-f7a8-4b9c-0d1e-2f3a4b5c6d7e",
"interview_status": "completed",
"confirmation": {
"required": true,
"confirmed": true,
"confirmed_at": "2026-05-13T18:29:10Z"
},
"transcription_note": null,
"transcription": [
{
"speaker": "Interviewer",
"text": "Welcome to the interview. Please introduce yourself.",
"start_seconds": 0.0,
"end_seconds": 4.8
},
{
"speaker": "Jane Doe",
"text": "Thanks for having me. I am a backend engineer with six years of experience.",
"start_seconds": 5.0,
"end_seconds": 8.1
}
],
"speakers": [
{ "label": "Interviewer", "role": "ai" },
{ "label": "Jane Doe", "role": "user" }
]
}
Cancel Interview
POST /api/v1/interview/interviews/{id}/cancel
Cancels an interview in requested, in_progress, or accepted status. Only interviews in requested, in_progress, or accepted status can be cancelled.
Path Parameters:
- interview_id: (required) UUID of the interview to cancel.
Request Body (optional):
- reason: (optional) free-text reason describing why the interview is being cancelled. Stored with the interview and included in the cancellation webhook.
{
"reason": "Position filled"
}
Response (200 OK):
{
"interview_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"status": "canceled",
"cancelled_at": "2026-05-13T19:12:04Z",
"reason": "Position filled"
}
Error Responses:
400—interview_not_cancellable: the interview is in a status that does not allow cancellation (for example, alreadycompletedorcanceled).404—not_found: the interview does not exist or does not belong to your organization.
Webhooks
Crosschq delivers a POST or PUT request to your configured endpoint on each interview status transition. Webhook URLs and authentication are configured per-organization — contact support@crosschq.com to enable a webhook endpoint.
Supported configuration:
- HTTP method:
POST(default) orPUT. - Authentication: none, HTTP Basic, or a raw
Authorizationheader value, depending on what your endpoint expects. - Timeout: 10 seconds per delivery attempt.
- Retries: failed deliveries are retried up to 3 times with exponential backoff, up to a maximum of 300 seconds between attempts.
Event: interview.status_changed
A single event type is delivered each time an interview transitions to one of the statuses listed below. The data block is intentionally decoupled from the rich InterviewDetailSchema returned by GET /interviews/{id} so the webhook wire format stays stable. It carries the slim envelope (interview ID, status, lifecycle timestamps, candidate ref, AI signals at root) and was extended additively on 2026-05-15 (status_note, recommendation, transcript, etc.) and 2026-05-18 (language_proficiency, device, questions_and_answers).
Webhook payload shape:
| Field | Type | Description |
|---|---|---|
interview_id | UUID | Interview ID. |
external_id | string | null | Partner-supplied reference passed on creation. |
status | string | New status value triggering the event. |
status_note | string | null | Human-readable note describing the current status. |
score | float | null | Overall interview score (1.0–5.0). null until the AI report is available. |
recommendation | string | null | recommended / not_recommended / neutral; null until the report is available or when the org disables the recommendation score. |
language_proficiency | string | null | Poor / Proficient / null; only set when the plan has assess_language_proficiency=true. |
screening | string | null | Reserved for a future screening result. Always null today. |
skills_competencies_avg | float | null | Average skill rating (1.0–5.0). null when no skills are evaluated. |
report_url | string | null | PDF scorecard URL (populated for completed / partial). |
transcription_url | string | null | URL to the transcription view in the Crosschq web app. Populated for completed / partial. |
interview_url | string | Candidate-facing interview URL. |
created_at / started_at / completed_at | datetime | null | Lifecycle timestamps. |
completion_percentage | float | null | 0–100, most useful for partial interviews. |
candidate | object | Slim candidate reference: {id, external_id}. |
device | object | null | {is_mobile, operating_system, browser}. Null when no telemetry was captured. |
transcript | array | null | PII-redacted transcript chunks. Present only for completed and partial. |
questions_and_answers | array | null | Per-question items with question, answer, was_asked, was_answered, confidence (0.0–1.0). Populated for completed and partial. |
Below are example payloads for each supported status transition.
Status: requested
Fires immediately after the interview is created and the candidate is notified.
{
"event": "interview.status_changed",
"timestamp": "2026-05-13T17:42:09Z",
"data": {
"interview_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"external_id": "interview-ext-789",
"status": "requested",
"status_note": "Requested: The Interview was created, and an invitation was sent to the candidate.",
"score": null,
"recommendation": null,
"language_proficiency": null,
"screening": null,
"skills_competencies_avg": null,
"report_url": null,
"transcription_url": null,
"interview_url": "https://app.crosschq.com/interviews/<signed-token>/",
"created_at": "2026-05-13T17:42:08Z",
"started_at": null,
"completed_at": null,
"completion_percentage": null,
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123"
},
"device": null,
"transcript": null,
"questions_and_answers": null
}
}
Status: in_progress
Fires when the candidate begins the interview.
{
"event": "interview.status_changed",
"timestamp": "2026-05-13T18:01:15Z",
"data": {
"interview_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"external_id": "interview-ext-789",
"status": "in_progress",
"status_note": "In Progress: The candidate started the interview session.",
"score": null,
"recommendation": null,
"language_proficiency": null,
"screening": null,
"skills_competencies_avg": null,
"report_url": null,
"transcription_url": null,
"interview_url": "https://app.crosschq.com/interviews/<signed-token>/",
"created_at": "2026-05-13T17:42:08Z",
"started_at": "2026-05-13T18:01:15Z",
"completed_at": null,
"completion_percentage": null,
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123"
},
"device": {
"is_mobile": false,
"operating_system": "macOS",
"browser": "Chrome"
},
"transcript": null,
"questions_and_answers": null
}
}
Status: completed
Triggered when the interview reaches completed status and the AI report has been generated score, recommendation, report_url, transcription_url, and transcript are populated.
{
"event": "interview.status_changed",
"timestamp": "2026-05-13T18:28:47Z",
"data": {
"interview_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"external_id": "interview-ext-789",
"status": "completed",
"status_note": "Completed: The Interview has been completed. The scorecard report is available. Score: 4.2",
"score": 4.2,
"recommendation": "recommended",
"language_proficiency": "Proficient",
"screening": null,
"skills_competencies_avg": 4.1,
"report_url": "https://interview-app.crosschq.com/interview/a1b2c3d4-e5f6-4789-9abc-def012345678/report",
"transcription_url": "https://interview-app.crosschq.com/interview/a1b2c3d4-e5f6-4789-9abc-def012345678/report#transcription",
"interview_url": "https://app.crosschq.com/interviews/<signed-token>/",
"created_at": "2026-05-13T17:42:08Z",
"started_at": "2026-05-13T18:01:15Z",
"completed_at": "2026-05-13T18:28:47Z",
"completion_percentage": 100.0,
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123"
},
"device": {
"is_mobile": false,
"operating_system": "macOS",
"browser": "Chrome"
},
"transcript": [
{
"id": "f8a9b0c1-d2e3-4f5a-9b8c-1d2e3f4a5b6c",
"role": "Assistant",
"text": "Welcome to your interview with Crosschq. Thanks for making time today. To start, can you briefly introduce yourself?",
"timestamp": "2026-05-13T18:01:15Z",
"start_seconds": 0.0,
"end_seconds": 6.8,
"is_assistant": true,
"duration": "00:07",
"agent_could_not_answer": false,
"agent_could_not_answer_tooltip": null
},
{
"id": "a1b2c3d4-e5f6-4789-9abc-def012345679",
"role": "Jane Doe",
"text": "Thanks for having me. I'm a backend engineer with six years of experience, mostly in distributed systems.",
"timestamp": "2026-05-13T18:01:22Z",
"start_seconds": 7.0,
"end_seconds": 13.4,
"is_assistant": false,
"duration": "00:06",
"agent_could_not_answer": false,
"agent_could_not_answer_tooltip": null
},
{
"id": "b2c3d4e5-f6a7-4890-abcd-ef0123456780",
"role": "Assistant",
"text": "Great. Describe a time you improved API performance.",
"timestamp": "2026-05-13T18:01:29Z",
"start_seconds": 14.0,
"end_seconds": 17.2,
"is_assistant": true,
"duration": "00:03",
"agent_could_not_answer": false,
"agent_could_not_answer_tooltip": null
},
{
"id": "c3d4e5f6-a7b8-4901-bcde-f01234567891",
"role": "Jane Doe",
"text": "In my last role, we cut p95 latency from 800ms to 120ms by introducing a Redis cache for hot reads and batching downstream DB writes.",
"timestamp": "2026-05-13T18:01:32Z",
"start_seconds": 17.5,
"end_seconds": 28.9,
"is_assistant": false,
"duration": "00:11",
"agent_could_not_answer": false,
"agent_could_not_answer_tooltip": null
},
{
"id": "d4e5f6a7-b8c9-4012-cdef-012345678902",
"role": "Assistant",
"text": "Thanks Jane, that wraps up our questions for today. We'll be in touch with next steps soon.",
"timestamp": "2026-05-13T18:01:44Z",
"start_seconds": 29.5,
"end_seconds": 34.1,
"is_assistant": true,
"duration": "00:05",
"agent_could_not_answer": false,
"agent_could_not_answer_tooltip": null
}
],
"questions_and_answers": [
{
"question": "Describe a time you improved API performance.",
"answer": "In my last role, we cut p95 latency from 800ms to 120ms by introducing a Redis cache for hot reads and batching downstream DB writes.",
"was_asked": true,
"was_answered": true,
"confidence": 0.92
}
]
}
}
Status: canceled
Fires when the interview is cancelled by the partner (via POST /interviews/{id}/cancel) or by the candidate.
{
"event": "interview.status_changed",
"timestamp": "2026-05-13T19:12:04Z",
"data": {
"interview_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"external_id": "interview-ext-789",
"status": "canceled",
"status_note": "Canceled: Interview was canceled by recruiter/admin/system action.",
"score": null,
"recommendation": null,
"language_proficiency": null,
"screening": null,
"skills_competencies_avg": null,
"report_url": null,
"transcription_url": null,
"interview_url": "https://app.crosschq.com/interviews/<signed-token>/",
"created_at": "2026-05-13T17:42:08Z",
"started_at": null,
"completed_at": null,
"completion_percentage": null,
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123"
},
"device": null,
"transcript": null,
"questions_and_answers": null
}
}
Status: auto_canceled
Fires when Crosschq automatically cancels the interview (for example, after the candidate fails to respond within the configured window).
{
"event": "interview.status_changed",
"timestamp": "2026-05-15T17:42:08Z",
"data": {
"interview_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"external_id": "interview-ext-789",
"status": "auto_canceled",
"status_note": "Auto-canceled: The interview was automatically canceled based on your organization's auto-cancellation rules.",
"score": null,
"recommendation": null,
"language_proficiency": null,
"screening": null,
"skills_competencies_avg": null,
"report_url": null,
"transcription_url": null,
"interview_url": "https://app.crosschq.com/interviews/<signed-token>/",
"created_at": "2026-05-13T17:42:08Z",
"started_at": null,
"completed_at": null,
"completion_percentage": null,
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123"
},
"device": null,
"transcript": null,
"questions_and_answers": null
}
}
Status: declined
Fires when the candidate explicitly declines to take the interview.
{
"event": "interview.status_changed",
"timestamp": "2026-05-13T20:05:11Z",
"data": {
"interview_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"external_id": "interview-ext-789",
"status": "declined",
"status_note": "Declined: The candidate declined to take the interview.",
"score": null,
"recommendation": null,
"language_proficiency": null,
"screening": null,
"skills_competencies_avg": null,
"report_url": null,
"transcription_url": null,
"interview_url": "https://app.crosschq.com/interviews/<signed-token>/",
"created_at": "2026-05-13T17:42:08Z",
"started_at": null,
"completed_at": null,
"completion_percentage": null,
"candidate": {
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"external_id": "candidate-ext-123"
},
"device": null,
"transcript": null,
"questions_and_answers": null
}
}
Idempotency
The POST /ai-interviews endpoint supports the Idempotency-Key header to allow safe retries of interview-creation requests. Supply a unique string per request (e.g. a UUID v4). If the same key is sent again within your organization's request log, the original response is returned without creating a new interview.
curl -X POST <URL>/ai-interviews \
-H 'Authorization: 00000000-0000-0000-0000-000000000000' \
-H 'Idempotency-Key: 8a7c2f0e-9b1d-4c3e-b6a7-5e4d3c2b1a09' \
-H 'Content-Type: application/json' \
-d '{
"interview_plan_id": "6f3a9b2c-1d4e-4a2f-9b8a-2c5d4e6f7a8b",
"candidate": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com"
}
}'
We recommend always sending an Idempotency-Key when creating interviews so that network failures or retries cannot accidentally produce duplicates.
Rate Limiting
Requests are rate-limited per API key on a fixed one-hour window. The default limit is 3,600 requests per hour. Higher limits are available on request — contact support@crosschq.com.
When the limit is exceeded, the API responds with 429 Too Many Requests and a Retry-After header indicating how many seconds to wait before retrying.
HTTP/1.1 429 Too Many Requests
Retry-After: 1800
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded.",
"details": {}
}
Errors
All error responses follow the same JSON structure:
{
"error": "error_code",
"message": "Human-readable description.",
"details": {}
}
- error: machine-readable error code suitable for branching logic.
- message: human-readable description for logs and support tickets.
- details: optional object with extra context (for example, per-field validation errors).
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | validation_error | The request body is missing a required field or contains an invalid value. |
| 400 | interview_not_cancellable | The interview cannot be cancelled in its current status. |
| 400 | invalid_filter_combination | e.g. score_min > score_max on GET /interviews. |
| 400 | invalid_filter_value | Unknown enum value for status or interview_type, or unparseable filter input. details shape: {param, invalid_values, valid_values}. |
| 400 | invalid_cursor | The pagination cursor is malformed. |
| 401 | authentication_failed | The API key header (Authorization or X-API-Key) is missing, invalid, or disabled. |
| 404 | not_found | The requested resource does not exist or does not belong to your organization. |
| 422 | validation_error | The request payload failed schema validation. The details.errors array lists the offending fields. |
| 429 | rate_limit_exceeded | The hourly request limit has been exceeded. Honor the Retry-After header. |
| 500 | internal_error | An unexpected error occurred on Crosschq's side. Retry with backoff and contact support if it persists. |
Support
For API enablement, sandbox access, webhook configuration, custom rate limits, or any other questions about the Crosschq Interview Partner API, please contact support@crosschq.com.