# Aural Developer API — Full Reference > Complete API reference for the Aural REST API. For the OpenAPI spec, see /api/v1/openapi.json This document describes the public v1 HTTP API. All paths are relative to the base URL below unless noted. --- ## Authentication Every request must include a valid API key in the Authorization header: Authorization: Bearer dlv_ Create and manage keys in the Aural web app: Settings > API Keys. Keys are stored and transmitted as secrets. They are prefixed with "dlv_" so they are easy to recognize in code and logs (never log full keys). If the header is missing, malformed, or the key is invalid, revoked, or expired, the API returns 401 with code UNAUTHORIZED. If the key’s user has no organization membership, the API may return 403 FORBIDDEN with an explanatory message. Project scoping: the key resolves to a set of project IDs the user can access. Interview and session operations are limited to resources in those projects. Creating a new interview uses the first accessible project when multiple exist. --- ## Base URL Production: https://aural-ai.com/api/v1 When self-hosting, replace the host with your deployment’s public origin; the path prefix remains /api/v1. --- ## Response Format Success (single resource): { "data": { ... } } Success (list): { "data": [ ... ], "cursor": "" } Only GET /interviews and GET /interviews/{id}/sessions return a pagination cursor. Pass the returned value unchanged as the cursor query parameter; null means no next page. Question and candidate lists return data arrays without cursors. Practice lists accept a limit but do not support cursors. Pagination is not a snapshot of concurrently changing data. Error: { "error": { "code": "", "message": "", ...optional_fields } } Common error codes: UNAUTHORIZED — Missing/invalid API key (401) FORBIDDEN — Authenticated but not allowed (403) NOT_FOUND — Resource missing or not visible (404) BAD_REQUEST — Invalid input or JSON (400) PLAN_LIMIT_EXCEEDED — Organization plan limit hit (403, extra quota fields) RATE_LIMITED — API key rate limit exceeded (429; honor Retry-After) INTERNAL_ERROR — Server or database error (500) --- ## Content type Request bodies must be JSON: Content-Type: application/json --- ## Common Workflow 1. POST /interviews — Create an interview template with context, channels, anti-cheating and invitation CC settings. 2. POST /interviews/{id}/questions — Add one or more questions. 3. Choose access: POST /interviews/{id}/publish enables a public link. For invite-only access, create candidates with POST /interviews/{id}/candidates and use their personal inviteUrl instead. Creating candidates does not send email. 4. To email invitations, preview POST /interviews/{id}/invites with candidate IDs and dryRun:true, show the result for confirmation, then execute with dryRun:false. 5. GET /interviews/{id}/sessions — List interview sessions. To end in-progress sessions, preview and confirm POST /interviews/{id}/sessions/end. 6. GET /sessions/{id} — Fetch transcript and session metadata. GET /sessions/{id}/report adds existing report details and retained media; neither read generates an AI report. 7. GET /interviews/{id}/practices and GET /practices/{id} — Read the API key owner's practice summaries and reports. --- ## Endpoints Paths below omit the base URL prefix for readability (e.g. /interviews means https://aural-ai.com/api/v1/interviews). --- ### POST /interviews Create a new interview template in the API key user’s first accessible project. Request: Content-Type: application/json Authorization: Bearer dlv_xxx Body fields: title string REQUIRED Non-empty interview title description string optional Shown to candidates objective string optional What you want to learn assessmentCriteria array optional Array of { "name": string, "description": string } chatEnabled boolean default true voiceEnabled boolean default false videoEnabled boolean default false aiName string default "Aural" Display name for the AI interviewer aiTone string default "PROFESSIONAL" One of: CASUAL, PROFESSIONAL, FORMAL, FRIENDLY followUpDepth string default "MODERATE" One of: LIGHT, MODERATE, DEEP language string default "en" timeLimitMinutes integer optional Positive integer; session time cap antiCheatingEnabled boolean default false ccSenderOnInvite boolean default true inviteCcEmail string|null optional One valid email address; null clears the override jobDescription string|null optional Job description text resumeText string|null optional Resume text companyName string|null optional roleTitle string|null optional Invitation CC: when ccSenderOnInvite is true, use inviteCcEmail if configured, otherwise the API key owner's email. Setting ccSenderOnInvite false disables CC even when an override is stored. Candidates can see the CC address. This setting applies to individual and bulk invitations. Context fields accept text; these endpoints do not fetch URLs or extract PDFs. New interviews are created with requireInvite true and a generated publicSlug. They are not fully “public” until you call POST .../publish. Example: curl -X POST https://aural-ai.com/api/v1/interviews \ -H "Authorization: Bearer dlv_xxx" \ -H "Content-Type: application/json" \ -d '{"title": "Senior Engineer Interview", "voiceEnabled": true, "followUpDepth": "DEEP"}' Response (200): { "data": { "id": "", "title": "Senior Engineer Interview", "publicSlug": "", "projectId": "", "userId": "", "chatEnabled": true, "voiceEnabled": true, "...": "additional interview columns from database" } } Errors: 400 BAD_REQUEST — Invalid JSON, missing title, invalid assessmentCriteria shape, invalid timeLimitMinutes 400 BAD_REQUEST — No accessible project for this API key 401 UNAUTHORIZED — Invalid or missing API key 403 PLAN_LIMIT_EXCEEDED — Template (interview) limit reached for the organization Extra fields may include: limit: "templates", current, max (null if unlimited), upgrade_url 404 NOT_FOUND — Project not found (unexpected if projects resolved correctly) 500 INTERNAL_ERROR — Database or server error --- ### GET /interviews List interviews across all projects the API key can access, ordered by updatedAt descending. Query parameters: limit integer optional Page size, default 20, max 100 cursor string optional Returned interview id from previous page List items are interview rows with embedded counts; full question text is not included in this list (use GET /interviews/{id} for questions). Each item includes _count: "_count": { "questions": , "sessions": } Example: curl -s "https://aural-ai.com/api/v1/interviews?limit=10" \ -H "Authorization: Bearer dlv_xxx" Response (200): { "data": [ { "id": "...", "title": "...", "_count": { "questions": 3, "sessions": 12 }, "...": "..." } ], "cursor": "last_interview_id_or_null" } If the user has no accessible projects, data is an empty array and cursor is null. Errors: 401 UNAUTHORIZED 500 INTERNAL_ERROR --- ### GET /interviews/{id} Fetch one interview with all questions (ordered by question order) and a session count. Path: id uuid Interview id Response shape merges interview fields with: "questions": [ { ...question rows... } ], "_count": { "sessions": } Example: curl -s "https://aural-ai.com/api/v1/interviews/" \ -H "Authorization: Bearer dlv_xxx" Response (200): { "data": { "id": "", "title": "...", "questions": [ { "id": "...", "text": "...", "order": 0, "type": "OPEN_ENDED", "...": "..." } ], "_count": { "sessions": 5 } } } Errors: 401 UNAUTHORIZED 404 NOT_FOUND — Interview not found or not in an accessible project 500 INTERNAL_ERROR --- ### PATCH /interviews/{id} Partially update an interview. Only fields present in the body are updated. Updatable fields (same semantics as POST where applicable): title, description, objective, assessmentCriteria (array or null to clear), chatEnabled, voiceEnabled, videoEnabled, aiName, aiTone, followUpDepth, language, timeLimitMinutes (positive integer or null to clear), antiCheatingEnabled, ccSenderOnInvite, inviteCcEmail, jobDescription, resumeText, companyName, roleTitle Omission preserves the existing value. Null clears inviteCcEmail and the four context strings. inviteCcEmail must be a valid single email when supplied; ccSenderOnInvite must be boolean. Validation highlights: title — non-empty string when provided assessmentCriteria — array of { name, description } or null aiTone / followUpDepth — same enums as POST timeLimitMinutes — positive integer or null If no recognized fields are supplied, returns 400 BAD_REQUEST "No valid fields to update." Example: curl -X PATCH "https://aural-ai.com/api/v1/interviews/" \ -H "Authorization: Bearer dlv_xxx" \ -H "Content-Type: application/json" \ -d '{"description": "Updated copy for candidates", "voiceEnabled": true}' Response (200): { "data": { ...updated interview row... } } Errors: 400 BAD_REQUEST — Invalid JSON or field validation failure 401 UNAUTHORIZED 404 NOT_FOUND 500 INTERNAL_ERROR --- ### DELETE /interviews/{id} Archive the interview (sets isActive to false). This is a soft delete from the API perspective. Response (200): { "data": { "id": "", "archived": true } } Errors: 401 UNAUTHORIZED 404 NOT_FOUND 500 INTERNAL_ERROR --- ### POST /interviews/{id}/publish Make the interview live for public link access: sets isActive true, requireInvite false, and ensures a publicSlug exists (generates one if missing). Response (200): { "data": { "id": "", "publicSlug": "", "url": "https://aural-ai.com/i/" } } The base URL in "url" uses NEXT_PUBLIC_APP_URL when set, otherwise https://aural-ai.com. Errors: 401 UNAUTHORIZED 404 NOT_FOUND — Interview not in accessible project 500 INTERNAL_ERROR --- ### GET /interviews/{id}/questions List all questions for an interview, ordered by order ascending. Response (200): { "data": [ { "id": "...", "interviewId": "...", "text": "...", "order": 0, "type": "OPEN_ENDED", ... } ] } Errors: 401 UNAUTHORIZED 403 FORBIDDEN — Interview exists but not accessible 404 NOT_FOUND — Interview not found 500 INTERNAL_ERROR --- ### POST /interviews/{id}/questions Add one or more questions. Body may be a single JSON object or an array of objects. Per-question fields: text string REQUIRED Non-empty question text type string optional Default OPEN_ENDED. One of: OPEN_ENDED, SINGLE_CHOICE, MULTIPLE_CHOICE, CODING, WHITEBOARD, RESEARCH order integer optional Non-negative; if omitted, appends after current max order required boolean optional Maps to isRequired; default true options string[] optional For choice-style questions; array of strings followUpEnabled boolean optional Maps to probeOnShort; default true description string|null optional starterCode object|null optional Language-to-source string map validationRules JSON|null optional followUpPrompts JSON|null optional probeThreshold number|null optional showIf JSON|null optional skipIf JSON|null optional timeLimitSeconds integer|null optional Positive integer, or null to clear allowFileUpload boolean optional allowedFileTypes string[] optional Example (batch): curl -X POST "https://aural-ai.com/api/v1/interviews//questions" \ -H "Authorization: Bearer dlv_xxx" \ -H "Content-Type: application/json" \ -d '[{"text": "Describe a complex system you built."},{"text": "Pick your stack","type":"SINGLE_CHOICE","options":["Node","Python","Go"]}]' Response (200): { "data": [ { ...created question rows... } ] } Errors: 400 BAD_REQUEST — Empty array, invalid types, invalid options, invalid order, etc. 401 UNAUTHORIZED 403 FORBIDDEN 404 NOT_FOUND 500 INTERNAL_ERROR --- ### PATCH /questions/{id} Update a single question by id. Body is a JSON object with any subset of: text string non-empty when provided type string one of the QUESTION_TYPES (cannot be null) order integer non-negative (cannot be null) required boolean maps to isRequired options string[] or null followUpEnabled boolean maps to probeOnShort Also accepts the advanced fields listed under POST /interviews/{id}/questions: description, starterCode, validationRules, followUpPrompts, probeThreshold, showIf, skipIf, timeLimitSeconds, allowFileUpload, and allowedFileTypes. Omitted fields are preserved; nullable fields can be cleared with null. Use [] to clear allowedFileTypes. At least one valid field must be present. Example: curl -X PATCH "https://aural-ai.com/api/v1/questions/" \ -H "Authorization: Bearer dlv_xxx" \ -H "Content-Type: application/json" \ -d '{"text": "Clarified wording", "order": 2}' Response (200): { "data": { ...updated question... } } Errors: 400 BAD_REQUEST 401 UNAUTHORIZED 403 FORBIDDEN — Question’s interview is not in an accessible project 404 NOT_FOUND — Question not found 500 INTERNAL_ERROR --- ### DELETE /questions/{id} Permanently delete a question. Response (200): { "data": { "id": "", "deleted": true } } Errors: 401 UNAUTHORIZED 403 FORBIDDEN 404 NOT_FOUND 500 INTERNAL_ERROR --- ### GET /interviews/{id}/sessions Paginated list of sessions for an interview. Ordered by createdAt descending, then id descending. Query parameters: limit integer optional Default 20, max 100 cursor string optional Must be a valid session id from this interview; used for keyset pagination Each session in the list includes summary fields and a message count via _count (full message text is not included here; use GET /sessions/{id}). Response fields per row (typical): id, status, participantName, participantEmail, summary, insights, themes, sentiment, totalDurationSeconds, createdAt, updatedAt, startedAt, completedAt, lastActivityAt, "_count": { "messages": } Session status values (enum): IN_PROGRESS, COMPLETED, ABANDONED Example: curl -s "https://aural-ai.com/api/v1/interviews//sessions?limit=20" \ -H "Authorization: Bearer dlv_xxx" Response (200): { "data": [ { "id": "...", "status": "COMPLETED", "_count": { "messages": 42 }, "...": "..." } ], "cursor": "next_session_id_or_null" } Errors: 400 BAD_REQUEST — Invalid cursor 401 UNAUTHORIZED 403 FORBIDDEN 404 NOT_FOUND — Interview not found 500 INTERNAL_ERROR --- ### GET /sessions/{id} Fetch a single session with ordered messages (transcript) and parent interview metadata. Response (200): { "data": { "id": "", "status": "COMPLETED", "participantName": "...", "participantEmail": "...", "summary": "...", "insights": ..., "themes": ..., "sentiment": ..., "totalDurationSeconds": 1234, "createdAt": "...", "startedAt": "...", "completedAt": "...", "lastActivityAt": "...", "messages": [ { "id": "...", "role": "USER", "content": "...", "timestamp": "..." }, { "id": "...", "role": "ASSISTANT", "content": "...", "timestamp": "..." } ], "interview": { "id": "", "title": "...", "objective": "..." } } } Errors: 401 UNAUTHORIZED 403 FORBIDDEN — Session’s interview project not accessible 404 NOT_FOUND — Session or interview linkage missing 500 INTERNAL_ERROR --- ### GET /interviews/{id}/candidates List candidate invite rows for an interview. Response fields per row typically include: id, name, email, phone, notes, inviteToken, sessionId, createdAt, gender, birthday, education, school, major, graduationYear, workExperience Example: curl -s "https://aural-ai.com/api/v1/interviews//candidates" \ -H "Authorization: Bearer dlv_xxx" Response (200): { "data": [ { ... } ] } Errors: 401 UNAUTHORIZED 403 FORBIDDEN 404 NOT_FOUND 500 INTERNAL_ERROR --- ### POST /interviews/{id}/candidates Create one or more candidate invite records and personal links. This does not send email; use POST /interviews/{id}/invites to send. Body may be a single object or array (max 500 per request). Per-candidate fields: name string optional in schema (empty string allowed); display name for the invite email string optional; normalized to lowercase, empty becomes null phone string optional notes string optional gender string|null optional birthday string|null optional Valid YYYY-MM-DD date education string|null optional school string|null optional major string|null optional graduationYear integer|null optional workExperience string|null optional Each row gets a unique inviteToken. Response includes inviteUrl for each candidate: inviteUrl: https://aural-ai.com/i/invite/ Before insert, the API checks the organization’s billing-period session time allowance. If exceeded: 403 PLAN_LIMIT_EXCEEDED Extra fields may include: limit: "session_hours", used_hours, max_hours, upgrade_url Example: curl -X POST "https://aural-ai.com/api/v1/interviews//candidates" \ -H "Authorization: Bearer dlv_xxx" \ -H "Content-Type: application/json" \ -d '[{"name":"Alex","email":"alex@example.com"}]' Response (200): { "data": [ { "id": "...", "name": "Alex", "email": "alex@example.com", "inviteToken": "...", "inviteUrl": "https://aural-ai.com/i/invite/...", "...": "..." } ] } Errors: 400 BAD_REQUEST — Invalid JSON, empty batch, >500 items, bad object shape 401 UNAUTHORIZED 403 FORBIDDEN 403 PLAN_LIMIT_EXCEEDED — Billing-period session hours exhausted 404 NOT_FOUND 500 INTERNAL_ERROR --- ### PATCH /interviews/{id}/candidates/{candidateId} Update a candidate belonging to this interview. Requires MEMBER or higher effective project role. Body: any subset of name (non-empty string), email (valid single email or null), phone, notes, and the extended profile fields listed under candidate creation. Phone, notes, and extended profile fields accept null to clear; omitted fields retain their values. At least one recognized field is required. Response (200): { "data": { ...updated candidate row... } } Errors: 400 BAD_REQUEST for invalid input, 401 UNAUTHORIZED, 403 FORBIDDEN for insufficient role/project access, 404 NOT_FOUND for a missing candidate in this interview, 500 INTERNAL_ERROR. --- ### POST /interviews/{id}/invites Send personal invitation emails to candidate IDs belonging to this interview. Requires MEMBER or higher effective project role. Does not publish the interview or enable public access. Body: ids string[] REQUIRED 1–100 candidate IDs; duplicates are processed once dryRun boolean default true Preview without sending; false executes Only unstarted candidates with an email address and invite token are eligible. Saved interview CC settings apply: ccSenderOnInvite defaults on, inviteCcEmail overrides the API key owner's email, and disabling CC suppresses both. Preview example: curl -X POST "https://aural-ai.com/api/v1/interviews//invites" \ -H "Authorization: Bearer dlv_xxx" \ -H "Content-Type: application/json" \ -d '{"ids":[""],"dryRun":true}' Show the preview to your user and obtain confirmation before submitting the same IDs with dryRun:false. Review the shared bulk action semantics below before retrying. --- ### POST /interviews/{id}/sessions/end End selected IN_PROGRESS interview sessions. Requires MEMBER or higher effective project role. Uses the same completion and activity-based duration accounting as the app. This action does not generate AI reports. Body: ids string[] REQUIRED 1–100 session IDs; duplicates are processed once dryRun boolean default true Preview without ending sessions; false executes IDs must belong to this interview. Completed, abandoned, and missing sessions are skipped. Preview with {"ids":[""],"dryRun":true}, show the result for confirmation, then execute with dryRun:false. Shared bulk action response (200): { "data": { "dryRun": true, "results": [ { "id": "", "status": "eligible" }, { "id": "", "status": "skipped", "reason": "..." } ] } } Both bulk endpoints return per-item statuses: eligible (preview), skipped, succeeded (execution), or failed, with reason when applicable. HTTP 200 does not mean every item succeeded; inspect every result. Scope and eligibility are checked again during execution. A preview does not reserve eligibility. Batches are non-atomic: earlier successful items are not rolled back if a later item fails. Invitation sending is not idempotent. Do not retry successful items or blindly resend after an uncertain timeout; delivery may already have occurred. No idempotency-key contract is provided. Request-level errors include 400 BAD_REQUEST (invalid JSON, empty IDs, or more than 100 IDs before deduplication), 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND, and 429 RATE_LIMITED. Item-level execution errors are reported in results. --- ### GET /sessions/{id}/report Read the existing interview report, transcript, anti-cheating signals, and available media for a session in an accessible project. Does not generate a new report or analysis. Response: { "data": { ...report... } }. Fields include interviewTitle, interviewObjective, participantName, participantEmail, status, createdAt, summary, insights, themes, sentiment, messages, totalDurationSeconds, audioRecordingUrl, audioRecordings, audioDuration, screenshots, antiCheatingLog, planTier, and mediaRetention. mediaRetention contains retentionDays, daysRemaining, expiresSoon, and expired. Recording and screenshot availability depends on retention and whether media was captured. Signed media URLs expire; fetch this endpoint again for fresh URLs instead of storing URLs as permanent links. Reports and media may be absent when unavailable. Errors: 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND, 500 INTERNAL_ERROR. --- ### GET /interviews/{id}/practices List practice summaries owned by the API key's user for an accessible interview. These are practice sessions, separate from candidate interview sessions; this endpoint does not list other users' practices. Query: limit (integer 1–500, default 100). No cursor pagination. Response (200): { "data": [ ...practice summaries... ] }. Errors: 400 BAD_REQUEST for invalid limit, 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND, 500 INTERNAL_ERROR. --- ### GET /practices/{id} Read a practice report owned by the API key's user. The parent interview must also be in an accessible project. Response (200): { "data": { "session": { ... }, "interview": { ... }, "questions": [ ... ], "attempts": [ ... ] } } Includes available attempts, scores, feedback, and retained audio. Media access is subject to retention and signed URL expiry. Reading a practice does not submit answers, run grading, or consume interview session hours. AI practice operations in the app consume AI tokens. Errors: 401 UNAUTHORIZED, 403 FORBIDDEN, 404 NOT_FOUND (including another user's practice), 500 INTERNAL_ERROR. --- ### GET /usage Return plan and quota snapshot for the API key user’s primary organization. Response (200): { "data": { "plan": "", "templates": { "used": , "limit": }, "session_hours": { "used": , "limit": }, "ai_tokens": { "used": , "limit": }, "seats": { "used": , "limit": } } } Notes: templates.used / templates.limit — lifetime-style template count vs plan cap (null limit means unlimited templates on the plan). session_hours.used — hours consumed in the current subscription billing period using recorded activity-based session duration; idle gaps are excluded. session_hours.limit — included hours per period for the plan. ai_tokens — organization AI token quota and estimated usage against that quota. seats — organization member seats vs plan seat cap. Example: curl -s "https://aural-ai.com/api/v1/usage" \ -H "Authorization: Bearer dlv_xxx" Errors: 401 UNAUTHORIZED 500 INTERNAL_ERROR --- ## Plan Limits Aural ties API capabilities to the organization’s subscription. Limits are enforced server-side; clients should handle 403 PLAN_LIMIT_EXCEEDED gracefully and direct users to billing when upgrade_url is present. ### Templates (interviews) Each interview template counts against the organization’s template allowance (with “unlimited” represented as null in usage responses). Creating an interview via POST /interviews runs checkTemplateLimit; when the cap is reached, the API responds with 403 and code PLAN_LIMIT_EXCEEDED, including limit: "templates", current count, max (or null), and upgrade_url. Archiving or deleting behavior for quota reclamation follows product rules in the database; the API’s DELETE /interviews/{id} archives the row (isActive false). ### Session hours Included interview session time is allocated per billing period (active subscription boundaries are used; idle gaps are excluded). Adding candidates via POST /interviews/{id}/candidates checks remaining billing-period session time; if exhausted, you receive PLAN_LIMIT_EXCEEDED with limit: "session_hours", used_hours, max_hours, and upgrade_url. Listing or reading sessions does not consume session hours; consumption is tied to running/completing interview time according to backend metering. ### AI tokens Plans include an AI token pool for model usage. Token consumption during live interviews and analysis reduces the available balance. GET /usage exposes ai_tokens.used and ai_tokens.limit so integrations can surface remaining quota. Specific write endpoints may return PLAN_LIMIT_EXCEEDED for token-related operations elsewhere in the product; treat 403 + code as authoritative. ### 403 PLAN_LIMIT_EXCEEDED When any limit trips, the error object includes: "code": "PLAN_LIMIT_EXCEEDED", "message": "", "limit": "", ...additional numeric context fields..., "upgrade_url": "" Integrations should log the code and limit kind, not the full API key. ### Checking quota proactively Call GET /usage before bulk operations (batch candidate creation, automated template creation) to avoid partial failures. Values are approximate for rapidly changing systems; the enforcement on each mutating request remains the source of truth. --- ## Public API coverage The endpoints above expose interview configuration and context, advanced questions, candidate profiles and invitations, session completion, existing reports/media, practice reads, and usage. Use GET /usage for resolved allowances rather than hard-coded plan values. AI interview generation/refinement, PDF/URL extraction, browser voice/microphone, practice submission/grading, answer-bank editing, recording uploads, and AI report generation remain web/mobile workflows, not public REST write endpoints. Webhooks and official SDKs are not provided. Do not depend on internal tRPC or voice WebSocket protocols as Developer API contracts. --- ## Operational notes - Rate limits: 60 requests per minute per API key. A 429 RATE_LIMITED response includes error.retry_after and Retry-After (seconds), X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds). Wait at least Retry-After before retrying. Do not assume these headers appear on successful responses. - Retry transient read failures with bounded exponential backoff. For writes, reconcile outcomes first; retries may duplicate creations or invitations. - Idempotency: POST create endpoints are not idempotent; use client-generated deduplication if needed. - Timestamps: ISO-8601 strings as returned by the database. - Self-hosted deployments must configure NEXT_PUBLIC_APP_URL so publish URLs and invite links point to the correct origin. --- ## Quick reference (methods and paths) POST /interviews GET /interviews GET /interviews/{id} PATCH /interviews/{id} DELETE /interviews/{id} POST /interviews/{id}/publish GET /interviews/{id}/questions POST /interviews/{id}/questions PATCH /questions/{id} DELETE /questions/{id} GET /interviews/{id}/sessions GET /sessions/{id} GET /interviews/{id}/candidates POST /interviews/{id}/candidates PATCH /interviews/{id}/candidates/{candidateId} POST /interviews/{id}/invites POST /interviews/{id}/sessions/end GET /sessions/{id}/report GET /interviews/{id}/practices GET /practices/{id} GET /usage End of reference.