{"openapi":"3.1.0","info":{"title":"CodeArena API","version":"1.0.0","summary":"Enterprise live coding interview & assessment platform API","description":"## Overview\n\nThe CodeArena API powers live coding interviews, technical assessments, sandboxed code execution, AI-assisted evaluation and interview reporting.\n\n## Conventions\n\n- **Envelope** — every response is wrapped: success responses return `{ \"success\": true, \"data\": … }`; failures return `{ \"success\": false, \"error\": { \"code\", \"message\", \"details?\" } }`.\n- **Validation** — all request bodies are validated with Zod. Invalid payloads return `422 VALIDATION_ERROR` with the individual issues in `error.details`.\n- **Authentication** — requests are authenticated via an HttpOnly session cookie (`ca_session`). In production deployments this seam is backed by Clerk; candidates authenticate implicitly through invitation tokens.\n- **Authorization** — RBAC with seven roles (`super_admin`, `org_admin`, `recruiter`, `hiring_manager`, `interviewer`, `candidate`, `viewer`). Endpoints document the permission they enforce; violations return `403 FORBIDDEN`.\n- **Rate limiting** — write-heavy endpoints are rate limited per user. Exceeding a limit returns `429 RATE_LIMITED`.\n- **Pagination** — list endpoints accept `page` (1-based) and `limit` (max 100) and return `{ items, page, limit, total }`.\n- **Multi-tenancy** — all data is scoped to the caller's organization. Hidden test cases and staff solutions are redacted at the API layer for candidate roles.\n\n## Realtime collaboration\n\nThe sync endpoint uses a stateless polling transport whose payloads mirror a Socket.IO event contract one-to-one (`code:update`, `cursor:move`, `chat:message`, `presence:heartbeat`, `activity:event`), so a WebSocket gateway can replace it without changing shapes. Shared-document writes use optimistic version gating (last-writer-wins with version fencing).","contact":{"name":"CodeArena Platform Team","email":"api@codearena.dev"},"license":{"name":"Proprietary"}},"servers":[{"url":"/","description":"Current deployment"}],"tags":[{"name":"System","description":"Health and service metadata."},{"name":"Session","description":"Session identity and role switching (Clerk seam in production)."},{"name":"Questions","description":"Organization question bank with visible and hidden test cases, starter code and staff-only solutions."},{"name":"Interviews","description":"Interview lifecycle: `waiting → joined → started → paused ⇄ resumed → completed → report_generated`. Invalid transitions return 409."},{"name":"Collaboration","description":"Realtime room synchronization: shared code document, cursors, presence, chat and anti-cheat activity events."},{"name":"Execution","description":"Sandboxed code execution and test-suite judging. The runner is provider-abstracted (local sandbox, Judge0 CE, Piston, or a Docker fleet)."},{"name":"AI","description":"Provider-abstracted AI evaluation: code quality scoring, complexity estimation, plagiarism risk and hiring recommendations."}],"components":{"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"ca_session","description":"HttpOnly, SameSite=Lax session cookie carrying the authenticated persona (name, email, role). Set via POST /api/v1/session."}},"parameters":{"Page":{"name":"page","in":"query","description":"1-based page number","schema":{"type":"integer","minimum":1,"default":1}},"Limit":{"name":"limit","in":"query","description":"Page size (max 100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"InterviewId":{"name":"id","in":"path","required":true,"description":"Interview UUID","schema":{"type":"string","format":"uuid"}},"QuestionId":{"name":"id","in":"path","required":true,"description":"Question UUID","schema":{"type":"string","format":"uuid"}}},"responses":{"ValidationError":{"description":"Request body failed Zod validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Validation failed","details":[{"path":["title"],"message":"String must contain at least 3 character(s)"}]}}}}},"Forbidden":{"description":"Caller's role lacks the required permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"success":false,"error":{"code":"FORBIDDEN","message":"Your role cannot schedule interviews"}}}}},"NotFound":{"description":"Resource does not exist or is soft-deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"success":false,"error":{"code":"NOT_FOUND","message":"Interview not found"}}}}},"RateLimited":{"description":"Per-user rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"success":false,"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded — max 30 executions/minute"}}}}}},"schemas":{"ErrorEnvelope":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","const":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code","enum":["BAD_REQUEST","INVALID_JSON","VALIDATION_ERROR","FORBIDDEN","NOT_FOUND","RATE_LIMITED","INVALID_TRANSITION","NO_TEST_CASES","NO_ORG"]},"message":{"type":"string","description":"Human-readable explanation"},"details":{"description":"Optional structured details (e.g. Zod issues for VALIDATION_ERROR)"}}}}},"Role":{"type":"string","enum":["super_admin","org_admin","recruiter","hiring_manager","interviewer","candidate","viewer"]},"LanguageId":{"type":"string","description":"Registered execution language. `javascript`, `typescript`, `python` and `bash` run on the local sandbox; the rest route to the containerized runner fleet.","enum":["javascript","typescript","python","java","c","cpp","csharp","go","rust","php","ruby","kotlin","swift","dart","scala","bash","sql"]},"InterviewStatus":{"type":"string","enum":["waiting","joined","started","paused","resumed","completed","report_generated","cancelled"]},"Difficulty":{"type":"string","enum":["easy","medium","hard"]},"Recommendation":{"type":"string","enum":["strong_hire","hire","no_hire","strong_no_hire"]},"Example":{"type":"object","properties":{"input":{"type":"string"},"output":{"type":"string"},"explanation":{"type":"string"}}},"TestCase":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"questionId":{"type":"string","format":"uuid"},"label":{"type":"string","examples":["Example 1"]},"input":{"type":"string","description":"stdin passed to the program. Returned as `[hidden]` to candidate roles for hidden cases."},"expectedOutput":{"type":"string","description":"Expected stdout (trimmed comparison)."},"isHidden":{"type":"boolean"},"weight":{"type":"integer","default":1},"orderIndex":{"type":"integer"}}},"Question":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"type":{"type":"string","enum":["algorithm","data_structures","sql","frontend","backend","system_design","debugging","api_design","javascript","react","nodejs"]},"difficulty":{"$ref":"#/components/schemas/Difficulty"},"description":{"type":"string","description":"Markdown problem statement"},"constraints":{"type":"string","nullable":true},"examples":{"type":"array","items":{"$ref":"#/components/schemas/Example"}},"tags":{"type":"array","items":{"type":"string"}},"timeLimitMs":{"type":"integer","default":5000},"memoryLimitMb":{"type":"integer","default":256},"starterCode":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of language id → starter source"},"solution":{"type":"string","nullable":true,"description":"Staff-only; null for candidate/viewer roles"},"editorial":{"type":"string","nullable":true,"description":"Staff-only"},"version":{"type":"integer","description":"Optimistic-locking version"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Interview":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organizationId":{"type":"string","format":"uuid"},"title":{"type":"string"},"kind":{"type":"string","enum":["scheduled","instant","assessment","pair_programming","practice"]},"status":{"$ref":"#/components/schemas/InterviewStatus"},"scheduledAt":{"type":"string","format":"date-time","nullable":true},"startedAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"durationMinutes":{"type":"integer"},"questionId":{"type":"string","format":"uuid","nullable":true},"language":{"$ref":"#/components/schemas/LanguageId"},"codeDocument":{"type":"string","description":"Authoritative shared editor document"},"codeVersion":{"type":"integer","description":"Monotonic version for optimistic concurrency"},"inviteToken":{"type":"string","description":"Token for the candidate join link: /join/{inviteToken}"},"candidateName":{"type":"string","nullable":true},"candidateEmail":{"type":"string","nullable":true},"finalScore":{"type":"integer","nullable":true,"minimum":0,"maximum":100},"recommendation":{"$ref":"#/components/schemas/Recommendation"},"aiSummary":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Participant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"interviewId":{"type":"string","format":"uuid"},"name":{"type":"string"},"role":{"type":"string","enum":["interviewer","candidate","observer"]},"color":{"type":"string","examples":["#22d3ee"]},"cursorLine":{"type":"integer"},"cursorColumn":{"type":"integer"},"lastSeenAt":{"type":"string","format":"date-time"}}},"ChatMessage":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"interviewId":{"type":"string","format":"uuid"},"authorName":{"type":"string"},"authorRole":{"type":"string"},"body":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"ActivityEvent":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"interviewId":{"type":"string","format":"uuid"},"actorName":{"type":"string"},"type":{"type":"string","description":"e.g. join, leave, run, submit, tab_blur, tab_focus, paste, copy, fullscreen_exit, status_change"},"severity":{"type":"string","enum":["info","warning","critical"]},"metadata":{"type":"object","additionalProperties":true},"createdAt":{"type":"string","format":"date-time"}}},"TestCaseOutcome":{"type":"object","properties":{"label":{"type":"string"},"passed":{"type":"boolean"},"hidden":{"type":"boolean"},"expected":{"type":"string","description":"Omitted for hidden cases"},"actual":{"type":"string","description":"Omitted for hidden cases"},"error":{"type":"string","description":"Runtime/compile error excerpt when the case failed abnormally"},"timeMs":{"type":"integer"}}},"Submission":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"interviewId":{"type":"string","format":"uuid","nullable":true},"questionId":{"type":"string","format":"uuid"},"language":{"$ref":"#/components/schemas/LanguageId"},"code":{"type":"string"},"status":{"type":"string","enum":["queued","running","passed","failed","error"]},"passedCount":{"type":"integer"},"totalCount":{"type":"integer"},"runtimeMs":{"type":"integer","nullable":true},"results":{"type":"array","items":{"$ref":"#/components/schemas/TestCaseOutcome"}},"createdAt":{"type":"string","format":"date-time"}}},"ExecutionResult":{"type":"object","properties":{"executionId":{"type":"string","format":"uuid"},"runner":{"type":"string","examples":["local-sandbox"]},"status":{"type":"string","enum":["completed","timeout","error","unsupported"]},"stdout":{"type":"string"},"stderr":{"type":"string"},"exitCode":{"type":"integer","nullable":true},"runtimeMs":{"type":"integer"}}},"AiReview":{"type":"object","properties":{"provider":{"type":"string","examples":["heuristic-local"]},"qualityScore":{"type":"integer","minimum":0,"maximum":100},"complexity":{"type":"string","examples":["O(n) — single-pass iteration"]},"strengths":{"type":"array","items":{"type":"string"}},"issues":{"type":"array","items":{"type":"string"}},"optimizations":{"type":"array","items":{"type":"string"}},"plagiarismRisk":{"type":"string","enum":["low","medium","high"]},"recommendation":{"$ref":"#/components/schemas/Recommendation"},"summary":{"type":"string"}}}}},"security":[{"sessionCookie":[]}],"paths":{"/api/health":{"get":{"tags":["System"],"operationId":"getHealth","summary":"Health check","description":"Verifies API liveness and database connectivity (`SELECT 1` against Neon PostgreSQL).","security":[],"responses":{"200":{"description":"Service healthy","content":{"application/json":{"example":{"ok":true}}}},"500":{"description":"Database unreachable","content":{"application/json":{"example":{"ok":false}}}}}}},"/api/v1/session":{"get":{"tags":["Session"],"operationId":"getSession","summary":"Get current session","description":"Returns the authenticated persona and its effective RBAC permission set.","responses":{"200":{"description":"Current session","content":{"application/json":{"example":{"success":true,"data":{"name":"Alex Morgan","email":"alex.morgan@acme.dev","role":"interviewer","permissions":["questions:view","interviews:create","interviews:manage","code:execute","ai:evaluate"]}}}}}}},"post":{"tags":["Session"],"operationId":"setSession","summary":"Switch persona / sign in","description":"Sets the session cookie. This is the Clerk integration seam — in production the IdP issues the session and this endpoint is replaced by the OAuth/OIDC flow.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","role"],"properties":{"name":{"type":"string","maxLength":80},"email":{"type":"string","format":"email"},"role":{"$ref":"#/components/schemas/Role"}}},"example":{"name":"Priya Shah","email":"priya.shah@acme.dev","role":"hiring_manager"}}}},"responses":{"200":{"description":"Session updated","content":{"application/json":{"example":{"success":true,"data":{"name":"Priya Shah","email":"priya.shah@acme.dev","role":"hiring_manager"}}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/questions":{"get":{"tags":["Questions"],"operationId":"listQuestions","summary":"List questions","description":"Paginated, filterable question list scoped to the caller's organization. Soft-deleted questions are excluded.","parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"name":"q","in":"query","description":"Case-insensitive title search","schema":{"type":"string"}},{"name":"difficulty","in":"query","schema":{"$ref":"#/components/schemas/Difficulty"}},{"name":"type","in":"query","description":"Question category filter","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated question list","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Question"}},"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"}}}}},"example":{"success":true,"data":{"items":[{"id":"b9cce5e5-9ed9-4180-8f06-d9887c6dfee6","title":"Two Sum","slug":"two-sum","type":"algorithm","difficulty":"easy","tags":["arrays","hash-map"],"timeLimitMs":5000}],"page":1,"limit":20,"total":5}}}}}}},"post":{"tags":["Questions"],"operationId":"createQuestion","summary":"Create question","description":"Creates a question with test cases. **Requires** `questions:create` (interviewer, org_admin, super_admin). Rate limited.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","description"],"properties":{"title":{"type":"string","minLength":3,"maxLength":200},"type":{"type":"string","default":"algorithm"},"difficulty":{"$ref":"#/components/schemas/Difficulty"},"description":{"type":"string","minLength":10,"description":"Markdown problem statement"},"constraints":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"timeLimitMs":{"type":"integer","minimum":500,"maximum":10000,"default":5000},"starterCode":{"type":"object","additionalProperties":{"type":"string"}},"solution":{"type":"string"},"testCases":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"input":{"type":"string"},"expectedOutput":{"type":"string"},"isHidden":{"type":"boolean","default":false}}}}}},"example":{"title":"Reverse Words","type":"algorithm","difficulty":"easy","description":"Read a line from stdin and print its words in reverse order.","tags":["strings"],"starterCode":{"javascript":"const line = require('fs').readFileSync(0,'utf8').trim();\n// TODO\n"},"testCases":[{"label":"Example 1","input":"hello world","expectedOutput":"world hello","isHidden":false},{"label":"Hidden 1","input":"a b c","expectedOutput":"c b a","isHidden":true}]}}}},"responses":{"201":{"description":"Question created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Question"}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/questions/{id}":{"get":{"tags":["Questions"],"operationId":"getQuestion","summary":"Get question detail","description":"Returns the question with its test cases. Hidden-case inputs/outputs, `solution` and `editorial` are **redacted** unless the caller has `questions:view_solutions`.","parameters":[{"$ref":"#/components/parameters/QuestionId"}],"responses":{"200":{"description":"Question with test cases","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"allOf":[{"$ref":"#/components/schemas/Question"},{"type":"object","properties":{"testCases":{"type":"array","items":{"$ref":"#/components/schemas/TestCase"}}}}]}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Questions"],"operationId":"deleteQuestion","summary":"Soft-delete question","description":"Marks the question deleted (sets `deletedAt`). **Requires** `questions:delete`.","parameters":[{"$ref":"#/components/parameters/QuestionId"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"example":{"success":true,"data":{"deleted":true}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/v1/interviews":{"get":{"tags":["Interviews"],"operationId":"listInterviews","summary":"List interviews","parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/InterviewStatus"}}],"responses":{"200":{"description":"Paginated interview list (each item includes `questionTitle`)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Interview"}},"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"}}}}}}}}}},"post":{"tags":["Interviews"],"operationId":"createInterview","summary":"Schedule interview","description":"Creates a session, seeds the shared editor with the question's starter code for the chosen language, and returns an `inviteToken` for the candidate link `/join/{inviteToken}`. **Requires** `interviews:create`. Audit-logged.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","candidateName"],"properties":{"title":{"type":"string","minLength":3,"maxLength":200},"kind":{"type":"string","enum":["scheduled","instant","assessment","pair_programming","practice"],"default":"scheduled"},"questionId":{"type":"string","format":"uuid","nullable":true},"language":{"$ref":"#/components/schemas/LanguageId"},"candidateName":{"type":"string","maxLength":120},"candidateEmail":{"type":"string","format":"email","nullable":true},"durationMinutes":{"type":"integer","minimum":10,"maximum":240,"default":60},"scheduledAt":{"type":"string","format":"date-time","nullable":true}}},"example":{"title":"Backend Engineer — Round 1","kind":"instant","questionId":"b9cce5e5-9ed9-4180-8f06-d9887c6dfee6","language":"javascript","candidateName":"Jordan Rivera","candidateEmail":"jordan@example.com","durationMinutes":60}}}},"responses":{"201":{"description":"Interview created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Interview"}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/interviews/{id}":{"get":{"tags":["Interviews"],"operationId":"getInterview","summary":"Get interview detail","description":"Returns the interview with its question, test cases, participants and recent submissions. Staff (`interviews:manage`) additionally receive private notes, feedback and the anti-cheat activity log; candidates receive redacted hidden cases and no staff data.","parameters":[{"$ref":"#/components/parameters/InterviewId"}],"responses":{"200":{"description":"Interview aggregate","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"interview":{"$ref":"#/components/schemas/Interview"},"question":{"$ref":"#/components/schemas/Question"},"testCases":{"type":"array","items":{"$ref":"#/components/schemas/TestCase"}},"participants":{"type":"array","items":{"$ref":"#/components/schemas/Participant"}},"submissions":{"type":"array","items":{"$ref":"#/components/schemas/Submission"}},"activityEvents":{"type":"array","items":{"$ref":"#/components/schemas/ActivityEvent"}}}}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Interviews"],"operationId":"updateInterview","summary":"Update interview (lifecycle, scoring, notes, feedback)","description":"Multi-purpose update endpoint:\n- **Lifecycle** — `status` must follow `waiting → joined → started → paused ⇄ resumed → completed → report_generated`; anything else returns `409 INVALID_TRANSITION`. Staff-only for privileged transitions.\n- **Language switch** — resets the shared document to the question's starter code (staff only).\n- **Scoring** — `finalScore` (0–100) and `recommendation` require `interviews:score`.\n- **Notes** — `note` appends a private interviewer note.\n- **Feedback** — `feedback` records a structured scorecard (1–5 per dimension).","parameters":[{"$ref":"#/components/parameters/InterviewId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/InterviewStatus"},"language":{"$ref":"#/components/schemas/LanguageId"},"questionId":{"type":"string","format":"uuid","nullable":true},"finalScore":{"type":"integer","minimum":0,"maximum":100},"recommendation":{"$ref":"#/components/schemas/Recommendation"},"note":{"type":"string"},"feedback":{"type":"object","required":["problemSolving","codeQuality","communication","technicalDepth","overall","recommendation"],"properties":{"problemSolving":{"type":"integer","minimum":1,"maximum":5},"codeQuality":{"type":"integer","minimum":1,"maximum":5},"communication":{"type":"integer","minimum":1,"maximum":5},"technicalDepth":{"type":"integer","minimum":1,"maximum":5},"overall":{"type":"integer","minimum":1,"maximum":5},"recommendation":{"$ref":"#/components/schemas/Recommendation"},"comments":{"type":"string"}}}}},"examples":{"startInterview":{"summary":"Start the interview","value":{"status":"started"}},"scoreCandidate":{"summary":"Score and recommend","value":{"finalScore":84,"recommendation":"hire"}},"addFeedback":{"summary":"Submit a scorecard","value":{"feedback":{"problemSolving":5,"codeQuality":4,"communication":4,"technicalDepth":4,"overall":4,"recommendation":"hire","comments":"Strong hash-map intuition."}}}}}}},"responses":{"200":{"description":"Updated interview","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Interview"}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Invalid lifecycle transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"success":false,"error":{"code":"INVALID_TRANSITION","message":"Invalid lifecycle transition waiting → completed"}}}}},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/interviews/{id}/sync":{"post":{"tags":["Collaboration"],"operationId":"syncInterviewRoom","summary":"Realtime room sync (code · cursors · chat · presence · anti-cheat)","description":"Batched realtime frame. Clients POST every ~1.6s with their heartbeat plus optional deltas, and receive the authoritative room snapshot.\n\n| Field | Socket.IO equivalent | Semantics |\n|---|---|---|\n| `code` | `code:update` | Applied only if `baseVersion` matches the server version (optimistic fencing) |\n| `cursor` | `cursor:move` | Updates the participant's live cursor position |\n| `chat` | `chat:message` | Appends a chat message |\n| `events` | `activity:event` | Batched anti-cheat events (`tab_blur`, `paste`, `fullscreen_exit`, …) |\n| heartbeat | `presence:heartbeat` | Upserts presence; participants idle >15s drop from the roster |\n\nFirst call without `participantId` registers the participant and (for candidates) advances a `waiting` interview to `joined`.","parameters":[{"$ref":"#/components/parameters/InterviewId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"participantId":{"type":"string","format":"uuid","nullable":true},"name":{"type":"string","maxLength":80},"role":{"type":"string","enum":["interviewer","candidate","observer"],"default":"candidate"},"cursor":{"type":"object","properties":{"line":{"type":"integer","minimum":1},"column":{"type":"integer","minimum":1}}},"code":{"type":"object","required":["content","baseVersion"],"properties":{"content":{"type":"string","maxLength":200000},"baseVersion":{"type":"integer"}}},"chat":{"type":"string","maxLength":2000},"events":{"type":"array","maxItems":20,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","maxLength":40},"severity":{"type":"string","enum":["info","warning","critical"],"default":"info"},"metadata":{"type":"object","additionalProperties":true}}}},"chatSince":{"type":"string","format":"date-time","description":"Watermark — only newer chat messages are returned"}}},"example":{"participantId":null,"name":"Jordan Rivera","role":"candidate","cursor":{"line":4,"column":18},"code":{"content":"function twoSum(nums, target) { /* … */ }","baseVersion":12},"events":[{"type":"tab_blur","severity":"warning"}]}}}},"responses":{"200":{"description":"Authoritative room snapshot","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"participantId":{"type":"string","format":"uuid"},"interview":{"$ref":"#/components/schemas/Interview"},"participants":{"type":"array","items":{"$ref":"#/components/schemas/Participant"}},"chat":{"type":"array","items":{"$ref":"#/components/schemas/ChatMessage"}},"executions":{"type":"array","items":{"$ref":"#/components/schemas/ExecutionResult"}},"submissions":{"type":"array","items":{"$ref":"#/components/schemas/Submission"}},"integrityWarnings":{"type":"integer"},"serverTime":{"type":"string","format":"date-time"}}}}}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/api/v1/execute":{"post":{"tags":["Execution"],"operationId":"executeCode","summary":"Execute code with custom stdin","description":"Runs code in the sandboxed execution engine (isolated temp dir, minimal environment, hard time limit ≤10s, output caps). Runner selection is provider-abstracted via `EXECUTION_PROVIDER` (local sandbox default; Judge0 CE / Piston / Docker fleet in production). Rate limited to 30 executions/minute per user.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["language","code"],"properties":{"language":{"$ref":"#/components/schemas/LanguageId"},"code":{"type":"string","minLength":1,"maxLength":200000},"stdin":{"type":"string","maxLength":10000,"default":""},"interviewId":{"type":"string","format":"uuid","nullable":true,"description":"Associates the execution with an interview for the activity log"},"timeLimitMs":{"type":"integer","minimum":500,"maximum":10000,"default":5000}}},"example":{"language":"python","code":"import sys\nprint(sys.stdin.read().strip().upper())","stdin":"hello","timeLimitMs":5000}}}},"responses":{"200":{"description":"Execution result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ExecutionResult"}}},"example":{"success":true,"data":{"executionId":"5155b187-d3de-4226-b973-0fad567816d4","runner":"local-sandbox","status":"completed","stdout":"HELLO\n","stderr":"","exitCode":0,"runtimeMs":72}}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/submissions":{"post":{"tags":["Execution"],"operationId":"submitSolution","summary":"Submit solution for judging","description":"Runs the code against **all** of the question's test cases (visible and hidden) with per-case timing, persists the graded submission, and logs a `submit` activity event. Hidden-case expected/actual values are omitted from the response. Rate limited to 12 submissions/minute per user.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["questionId","language","code"],"properties":{"questionId":{"type":"string","format":"uuid"},"interviewId":{"type":"string","format":"uuid","nullable":true},"language":{"$ref":"#/components/schemas/LanguageId"},"code":{"type":"string","minLength":1,"maxLength":200000}}}}}},"responses":{"200":{"description":"Verdict with per-case outcomes","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"submissionId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["passed","failed"]},"passed":{"type":"integer"},"total":{"type":"integer"},"runtimeMs":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/TestCaseOutcome"}}}}}},"example":{"success":true,"data":{"submissionId":"75e8f51b-22c7-4e80-ac14-5834a1472e89","status":"passed","passed":4,"total":4,"runtimeMs":312,"results":[{"label":"Example 1","passed":true,"hidden":false,"expected":"[0,1]","actual":"[0,1]","timeMs":74},{"label":"Hidden — negatives","passed":true,"hidden":true,"timeMs":82}]}}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Validation error, or the question has no test cases (`NO_TEST_CASES`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/ai/review":{"post":{"tags":["AI"],"operationId":"aiReviewCode","summary":"AI code review & hiring recommendation","description":"Runs the provider-abstracted AI evaluation over the submitted code. When `interviewId` is supplied, the review is enriched with the latest submission's test outcomes and the session's integrity signals (paste count, tab switches) for plagiarism-risk scoring. With `persist: true`, the summary, score and recommendation are written to the interview. **Requires** `ai:evaluate`. Providers (OpenAI, Anthropic, Gemini, local heuristic) are selected via `AI_PROVIDER`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["language","code"],"properties":{"interviewId":{"type":"string","format":"uuid","nullable":true},"language":{"$ref":"#/components/schemas/LanguageId"},"code":{"type":"string","minLength":1,"maxLength":200000},"persist":{"type":"boolean","default":false,"description":"Write summary/score/recommendation to the interview"}}}}}},"responses":{"200":{"description":"Structured review","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AiReview"}}},"example":{"success":true,"data":{"provider":"heuristic-local","qualityScore":84,"complexity":"O(n) — single-pass iteration","strengths":["Linear-time approach with no nested iteration.","Uses hash-based structures for efficient lookups."],"issues":[],"optimizations":[],"plagiarismRisk":"low","recommendation":"hire","summary":"Automated review of the javascript solution for “Two Sum”. Estimated complexity: O(n). Quality score 84/100…"}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/openapi":{"get":{"tags":["System"],"operationId":"getOpenApiSpec","summary":"This OpenAPI document","security":[],"responses":{"200":{"description":"OpenAPI 3.1 specification (JSON)"}}}}}}