{
  "info": {
    "_postman_id": "tcv2-integration-001",
    "name": "Teamcast v2 — Interview Integration APIs",
    "description": "All endpoints for ATS/HRIS integration with Teamcast v2. Covers the 15 REST integration routes, JSON-RPC 2.0 A2A interface, candidate-access flow, OAuth discovery, and webhook config.\n\n**Auth:** Set `apiKey` (sk_live_...) and `tenantId` in the Teamcast v2 environment before running requests.\n\n**Base URL:** `{{baseUrl}}` defaults to `http://localhost:4100/api/v2`. Change to `https://devapi.teamcast.ai/api/v2` for the dev cloud or `https://prodapi.teamcast.ai/api/v2` for production.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      { "key": "key",   "value": "X-API-Key",   "type": "string" },
      { "key": "value", "value": "{{apiKey}}",   "type": "string" },
      { "key": "in",    "value": "header",       "type": "string" }
    ]
  },
  "variable": [
    { "key": "baseUrl", "value": "http://localhost:4100/api/v2", "type": "string" }
  ],
  "item": [
    {
      "name": "Interview Lifecycle (REST)",
      "description": "The 15 integration REST endpoints. Run in order 1 → 9 for the golden path.",
      "item": [
        {
          "name": "1. Submit Interview Request",
          "description": "Creates a new interview run. Required fields: external_id (your ATS reference), position, level, and at least one of candidate_ref | candidate_name | candidate_email.\n\n**IMPORTANT — level enum is UPPERCASE**: ENTRY | MID | SENIOR | LEAD | EXEC. Lowercase values return 400.",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_id\":   \"ats-app-{{$randomUUID}}\",\n  \"candidate_ref\": \"cand-9021\",\n  \"position\":      \"Senior Software Engineer\",\n  \"level\":         \"SENIOR\",\n  \"qualifications\": [\n    \"5+ years production TypeScript experience\",\n    \"Demonstrated ability to design distributed systems at scale\",\n    \"Strong async/concurrency patterns\"\n  ],\n  \"company_name\":     \"Acme Corp\",\n  \"duration_minutes\": 45\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews"]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200 || pm.response.code === 201) {",
                  "  const r = pm.response.json();",
                  "  if (r.run_id)       pm.environment.set('runId', r.run_id);",
                  "  if (r.interview_id) pm.environment.set('interviewId', r.interview_id);",
                  "  console.log('runId:', r.run_id, '| state:', r.state);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "2. Get Interview Status",
          "request": {
            "method": "GET",
            "header": [
              { "key": "X-Tenant-ID", "value": "{{tenantId}}" }
            ],
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}"]
            }
          }
        },
        {
          "name": "3. Supply Missing Info (INFO_NEEDED)",
          "request": {
            "method": "PATCH",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fields\": {\n    \"job_description\": \"We are looking for a senior engineer to lead our platform team...\",\n    \"skills\": \"TypeScript, AWS, PostgreSQL, System Design\"\n  }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/info",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "info"]
            }
          },
          "description": "Only needed when state = INFO_NEEDED. `fields` is a flat Record<string, string> — keys match the `field` values from the interview.info_needed webhook's `missing_fields` array."
        },
        {
          "name": "4. Get Generated Plan",
          "request": {
            "method": "GET",
            "header": [
              { "key": "X-Tenant-ID", "value": "{{tenantId}}" }
            ],
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/plan",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "plan"]
            }
          },
          "description": "Available once state ≥ PENDING. Returns 404 while GENERATING_PLAN."
        },
        {
          "name": "4a. Approve Plan",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"comment\": \"Looks good — solid competency coverage.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/plan/approve",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "plan", "approve"]
            }
          }
        },
        {
          "name": "4b. Reject Plan (terminal)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Role requirements changed — cancelling this run.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/plan/reject",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "plan", "reject"]
            }
          }
        },
        {
          "name": "4c. Revise Plan",
          "request": {
            "method": "PATCH",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"comments\": \"Focus more on system design. Reduce basic syntax questions. Add a distributed caching scenario.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/plan/revise",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "plan", "revise"]
            }
          }
        },
        {
          "name": "5. Issue Candidate Invite",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ttl_seconds\": 259200,\n  \"base_url\":    \"https://app.teamcast.ai\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/invite",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "invite"]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const r = pm.response.json();",
                  "  // joinLink and expiresAt are intentionally camelCase in this response",
                  "  if (r.joinLink) {",
                  "    // Extract the candidate token from the join URL: /interview/join/<token>",
                  "    const parts = r.joinLink.split('/');",
                  "    const token = parts[parts.length - 1];",
                  "    pm.environment.set('candidateToken', token);",
                  "    console.log('candidateToken set:', token);",
                  "  }",
                  "  pm.test('Has joinLink', () => pm.expect(r.joinLink).to.be.a('string'));",
                  "  console.log('joinLink:', r.joinLink, '| expiresAt:', r.expiresAt);",
                  "}"
                ]
              }
            }
          ],
          "description": "Only callable when state = PLAN_APPROVED or INVITED. Returns joinLink (camelCase) — share this URL with the candidate. The candidate clicks it and is authenticated automatically via magic link."
        },
        {
          "name": "6. Get Assessment",
          "request": {
            "method": "GET",
            "header": [
              { "key": "X-Tenant-ID", "value": "{{tenantId}}" }
            ],
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/assessment",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "assessment"]
            }
          },
          "description": "Available once state = ASSESSMENT_READY or COMPLETED. Returns overall_coverage (sufficient/partial/insufficient) + full assessment payload. No numeric scores, no hire labels."
        },
        {
          "name": "7a. Approve Assessment",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"comment\": \"Coverage looks solid. Approving.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/assessment/approve",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "assessment", "approve"]
            }
          }
        },
        {
          "name": "7b. Reject Assessment (terminal)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Assessment quality insufficient — significant gaps remain.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/assessment/reject",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "assessment", "reject"]
            }
          }
        },
        {
          "name": "8. Get Transcript",
          "request": {
            "method": "GET",
            "header": [
              { "key": "X-Tenant-ID", "value": "{{tenantId}}" }
            ],
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/transcript",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "transcript"]
            }
          }
        },
        {
          "name": "9. Get Candidate Rankings",
          "request": {
            "method": "GET",
            "header": [
              { "key": "X-Tenant-ID", "value": "{{tenantId}}" }
            ],
            "url": {
              "raw": "{{baseUrl}}/integration/rankings?position=Senior Software Engineer&level=SENIOR",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "rankings"],
              "query": [
                { "key": "position", "value": "Senior Software Engineer" },
                { "key": "level",    "value": "SENIOR" }
              ]
            }
          },
          "description": "Ranks candidates by coverage tier (sufficient → partial → insufficient), then ascending gap count. `level` filter uses ENTRY|MID|SENIOR|LEAD|EXEC. Note: `level` in each result item is lowercase."
        },
        {
          "name": "Cancel Interview",
          "request": {
            "method": "DELETE",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Position filled before interview completed.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}"]
            }
          }
        },
        {
          "name": "Purge Candidate PII (GDPR)",
          "request": {
            "method": "DELETE",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Retention window elapsed — GDPR Art. 17 erasure request.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/integration/interviews/{{runId}}/candidate-data",
              "host": ["{{baseUrl}}"],
              "path": ["integration", "interviews", "{{runId}}", "candidate-data"]
            }
          },
          "description": "Irreversible. Nulls resume, name, email, transcript, and proctoring events on the interview record. Assessment and audit trail are retained. Returns { purged: true, purged_at: ISO string }."
        }
      ]
    },
    {
      "name": "JSON-RPC 2.0 (A2A)",
      "description": "Single endpoint POST /a2a. Same operations as the REST surface via JSON-RPC 2.0 envelope. Auth: X-API-Key header (same as REST). Note: the endpoint is /a2a, NOT /a2a/task.",
      "item": [
        {
          "name": "interview.create",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"interview.create\",\n  \"params\": {\n    \"external_id\":   \"ats-req-{{$randomUUID}}\",\n    \"candidate_ref\": \"cand-9021\",\n    \"position\":      \"Senior Backend Engineer\",\n    \"level\":         \"SENIOR\",\n    \"skills\":        [\"Node.js\", \"PostgreSQL\", \"distributed systems\"]\n  }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/a2a",
              "host": ["{{baseUrl}}"],
              "path": ["a2a"]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200 || pm.response.code === 201) {",
                  "  const r = pm.response.json();",
                  "  if (r.result?.run_id) pm.environment.set('runId', r.result.run_id);",
                  "  if (r.result?.interview_id) pm.environment.set('interviewId', r.result.interview_id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "interview.status",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 2,\n  \"method\": \"interview.status\",\n  \"params\": { \"run_id\": \"{{runId}}\" }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/a2a",
              "host": ["{{baseUrl}}"],
              "path": ["a2a"]
            }
          }
        },
        {
          "name": "interview.complete-info",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"method\": \"interview.complete-info\",\n  \"params\": {\n    \"run_id\": \"{{runId}}\",\n    \"fields\": { \"job_description\": \"Full role description...\" }\n  }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/a2a",
              "host": ["{{baseUrl}}"],
              "path": ["a2a"]
            }
          }
        },
        {
          "name": "interview.approve (plan)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 4,\n  \"method\": \"interview.approve\",\n  \"params\": { \"run_id\": \"{{runId}}\", \"comment\": \"Looks good\" }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/a2a",
              "host": ["{{baseUrl}}"],
              "path": ["a2a"]
            }
          }
        },
        {
          "name": "interview.modify (plan revision)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 5,\n  \"method\": \"interview.modify\",\n  \"params\": { \"run_id\": \"{{runId}}\", \"comments\": \"Add a system design round.\" }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/a2a",
              "host": ["{{baseUrl}}"],
              "path": ["a2a"]
            }
          }
        },
        {
          "name": "assessment.approve",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 6,\n  \"method\": \"assessment.approve\",\n  \"params\": { \"run_id\": \"{{runId}}\", \"comment\": \"Coverage solid.\" }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/a2a",
              "host": ["{{baseUrl}}"],
              "path": ["a2a"]
            }
          }
        },
        {
          "name": "interview.rankings",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 7,\n  \"method\": \"interview.rankings\",\n  \"params\": { \"position\": \"Senior Software Engineer\", \"level\": \"SENIOR\" }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/a2a",
              "host": ["{{baseUrl}}"],
              "path": ["a2a"]
            }
          }
        },
        {
          "name": "assessment.chat (deferred — not yet implemented)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 8,\n  \"method\": \"assessment.chat\",\n  \"params\": { \"run_id\": \"{{runId}}\", \"message\": \"What were the main gaps?\" }\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/a2a",
              "host": ["{{baseUrl}}"],
              "path": ["a2a"]
            }
          },
          "description": "Reserved — returns 422 (not implemented). Do not depend on it."
        }
      ]
    },
    {
      "name": "Candidate Access",
      "description": "Endpoints used by the candidate's browser. No API key needed — authenticated by the join token (magic link) and a short-lived sessionToken minted after verify.\n\nFlow: Get Invitation Info → Verify Token → Record Consent → Start Interview → LiveKit Session → Complete Interview",
      "auth": { "type": "noauth" },
      "item": [
        {
          "name": "1. Get Invitation Info (pre-auth)",
          "request": {
            "auth": { "type": "noauth" },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/candidate-access/invitation/{{candidateToken}}",
              "host": ["{{baseUrl}}"],
              "path": ["candidate-access", "invitation", "{{candidateToken}}"]
            },
            "description": "Returns role title, duration, and question count so the candidate knows what to expect before authenticating. No auth required. Throws 410 if the link is expired or invalid."
          }
        },
        {
          "name": "2. Verify Token (magic link)",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{candidateToken}}\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/candidate-access/verify",
              "host": ["{{baseUrl}}"],
              "path": ["candidate-access", "verify"]
            },
            "description": "Authenticates the candidate using the join link token. Returns a sessionToken scoped to this interview — used as Authorization: Bearer on all subsequent candidate calls."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const r = pm.response.json();",
                  "  if (r.sessionToken) pm.environment.set('sessionToken', r.sessionToken);",
                  "  if (r.interviewId)  pm.environment.set('interviewId', r.interviewId);",
                  "  pm.test('Has sessionToken', () => pm.expect(r.sessionToken).to.be.a('string'));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "3. Record Consent",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [
              { "key": "Content-Type",  "value": "application/json" },
              { "key": "Authorization", "value": "Bearer {{sessionToken}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"accepted\":        true,\n  \"consent_version\": \"v1\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/candidate-access/interview/{{interviewId}}/consent",
              "host": ["{{baseUrl}}"],
              "path": ["candidate-access", "interview", "{{interviewId}}", "consent"]
            },
            "description": "Records AI-disclosure and recording consent. This is enforced server-side — POST /start will return 403 if consent is not on record."
          }
        },
        {
          "name": "4. Start Interview",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [
              { "key": "Content-Type",  "value": "application/json" },
              { "key": "Authorization", "value": "Bearer {{sessionToken}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/candidate-access/interview/{{interviewId}}/start",
              "host": ["{{baseUrl}}"],
              "path": ["candidate-access", "interview", "{{interviewId}}", "start"]
            },
            "description": "Advances the interview to IN_PROGRESS and starts the countdown clock. Consent must be recorded first (403 otherwise). Idempotent — reconnecting an IN_PROGRESS interview resumes the same clock."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const r = pm.response.json();",
                  "  pm.test('State is IN_PROGRESS', () => pm.expect(r.state).to.eql('IN_PROGRESS'));",
                  "  console.log('remainingSeconds:', r.remainingSeconds);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "5. Get LiveKit Session",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [
              { "key": "Content-Type",  "value": "application/json" },
              { "key": "Authorization", "value": "Bearer {{sessionToken}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/candidate-access/interview/{{interviewId}}/livekit-session",
              "host": ["{{baseUrl}}"],
              "path": ["candidate-access", "interview", "{{interviewId}}", "livekit-session"]
            },
            "description": "Creates the LiveKit WebRTC room and dispatches the Maya agent into it. Returns { url, token, roomName } — the browser uses these to join the live session. 503 if LiveKit is not configured."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const r = pm.response.json();",
                  "  pm.test('Has LiveKit url', () => pm.expect(r.url).to.be.a('string'));",
                  "  pm.test('Has room token',  () => pm.expect(r.token).to.be.a('string'));",
                  "  console.log('roomName:', r.roomName);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "6. Complete Interview",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [
              { "key": "Content-Type",  "value": "application/json" },
              { "key": "Authorization", "value": "Bearer {{sessionToken}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/candidate-access/interview/{{interviewId}}/complete",
              "host": ["{{baseUrl}}"],
              "path": ["candidate-access", "interview", "{{interviewId}}", "complete"]
            },
            "description": "Advances IN_PROGRESS → ASSESSMENT_PENDING and triggers the assessor pipeline. Idempotent — no-op if already past IN_PROGRESS."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const r = pm.response.json();",
                  "  pm.test('State is ASSESSMENT_PENDING', () => pm.expect(r.state).to.eql('ASSESSMENT_PENDING'));",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "OAuth",
      "description": "OAuth 2.1 authorization server endpoints. Discovery routes are at the root (no /api/v2 prefix, per RFC 8414).",
      "item": [
        {
          "name": "Discovery — OAuth Server Metadata",
          "request": {
            "auth": { "type": "noauth" },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "http://localhost:4100/.well-known/oauth-authorization-server",
              "host": ["http://localhost:4100"],
              "path": [".well-known", "oauth-authorization-server"]
            }
          }
        },
        {
          "name": "Discovery — JWKS",
          "request": {
            "auth": { "type": "noauth" },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "http://localhost:4100/.well-known/jwks.json",
              "host": ["http://localhost:4100"],
              "path": [".well-known", "jwks.json"]
            }
          }
        },
        {
          "name": "Token — client_credentials grant",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/x-www-form-urlencoded" }],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                { "key": "grant_type",     "value": "client_credentials" },
                { "key": "client_id",      "value": "{{oauthClientId}}" },
                { "key": "client_secret",  "value": "{{oauthClientSecret}}" },
                { "key": "scope",          "value": "interview:read interview:create" }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/oauth/token",
              "host": ["{{baseUrl}}"],
              "path": ["oauth", "token"]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const r = pm.response.json();",
                  "  if (r.access_token)  pm.environment.set('accessToken', r.access_token);",
                  "  if (r.refresh_token) pm.environment.set('refreshToken', r.refresh_token);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Token — refresh_token grant",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/x-www-form-urlencoded" }],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                { "key": "grant_type",    "value": "refresh_token" },
                { "key": "client_id",     "value": "{{oauthClientId}}" },
                { "key": "client_secret", "value": "{{oauthClientSecret}}" },
                { "key": "refresh_token", "value": "{{refreshToken}}" }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/oauth/token",
              "host": ["{{baseUrl}}"],
              "path": ["oauth", "token"]
            }
          }
        },
        {
          "name": "Introspect Token",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/x-www-form-urlencoded" }],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                { "key": "token",         "value": "{{accessToken}}" },
                { "key": "client_id",     "value": "{{oauthClientId}}" },
                { "key": "client_secret", "value": "{{oauthClientSecret}}" }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/oauth/introspect",
              "host": ["{{baseUrl}}"],
              "path": ["oauth", "introspect"]
            }
          }
        },
        {
          "name": "Revoke Token",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/x-www-form-urlencoded" }],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                { "key": "token",         "value": "{{accessToken}}" },
                { "key": "client_id",     "value": "{{oauthClientId}}" },
                { "key": "client_secret", "value": "{{oauthClientSecret}}" }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/oauth/revoke",
              "host": ["{{baseUrl}}"],
              "path": ["oauth", "revoke"]
            }
          }
        }
      ]
    },
    {
      "name": "Webhook Config",
      "description": "Manage the outbound webhook endpoint for your tenant. All requests use X-API-Key auth.",
      "item": [
        {
          "name": "Get Webhook Config",
          "request": {
            "method": "GET",
            "header": [{ "key": "X-Tenant-ID", "value": "{{tenantId}}" }],
            "url": {
              "raw": "{{baseUrl}}/me/webhook-config",
              "host": ["{{baseUrl}}"],
              "path": ["me", "webhook-config"]
            }
          }
        },
        {
          "name": "Create / Update Webhook Config",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"callback_url\": \"https://your-ats.example.com/teamcast/webhook\",\n  \"events\": [\n    \"interview.plan_generated\",\n    \"interview.assessment_ready\",\n    \"interview.completed\",\n    \"interview.rejected\",\n    \"interview.cancelled\"\n  ],\n  \"is_active\": true\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/me/webhook-config",
              "host": ["{{baseUrl}}"],
              "path": ["me", "webhook-config"]
            }
          },
          "description": "Omit `secret` to have Teamcast auto-generate one. The full secret is only shown once in the response — store it immediately."
        },
        {
          "name": "Delete Webhook Config",
          "request": {
            "method": "DELETE",
            "header": [{ "key": "X-Tenant-ID", "value": "{{tenantId}}" }],
            "url": {
              "raw": "{{baseUrl}}/me/webhook-config",
              "host": ["{{baseUrl}}"],
              "path": ["me", "webhook-config"]
            }
          }
        }
      ]
    },
    {
      "name": "API Keys",
      "description": "Issue and revoke integration API keys for your tenant.",
      "item": [
        {
          "name": "Create API Key",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "X-Tenant-ID",  "value": "{{tenantId}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\":        \"ATS Integration Key\",\n  \"permissions\": [\"interview:create\", \"interview:read\", \"interview:approve\", \"interview:update\", \"interview:delete\"],\n  \"expiresAt\":   null\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/api-keys",
              "host": ["{{baseUrl}}"],
              "path": ["api-keys"]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201 || pm.response.code === 200) {",
                  "  const r = pm.response.json();",
                  "  if (r.id)  pm.environment.set('apiKeyId', r.id);",
                  "  if (r.key) { pm.environment.set('apiKey', r.key); console.log('API key (save now):', r.key); }",
                  "}"
                ]
              }
            }
          ],
          "description": "The raw key is returned ONCE and never stored server-side. Capture it from the response immediately."
        },
        {
          "name": "List API Keys",
          "request": {
            "method": "GET",
            "header": [{ "key": "X-Tenant-ID", "value": "{{tenantId}}" }],
            "url": {
              "raw": "{{baseUrl}}/api-keys",
              "host": ["{{baseUrl}}"],
              "path": ["api-keys"]
            }
          }
        },
        {
          "name": "Revoke API Key",
          "request": {
            "method": "DELETE",
            "header": [{ "key": "X-Tenant-ID", "value": "{{tenantId}}" }],
            "url": {
              "raw": "{{baseUrl}}/api-keys/{{apiKeyId}}",
              "host": ["{{baseUrl}}"],
              "path": ["api-keys", "{{apiKeyId}}"]
            }
          }
        }
      ]
    },
    {
      "name": "Health",
      "description": "Liveness and readiness probes. Not under /api/v2 prefix.",
      "item": [
        {
          "name": "Health Check",
          "request": {
            "auth": { "type": "noauth" },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "http://localhost:4100/health",
              "host": ["http://localhost:4100"],
              "path": ["health"]
            }
          }
        }
      ]
    }
  ]
}
