Platform Admin

Tenant API Integration

Connect your ATS or HR system to the Integration API — authentication, webhooks, interview lifecycle, and error handling.

This guide is for developers integrating an ATS, HR portal, or hiring platform with the AI Interview System. It covers authentication, sending your first interview request, handling webhooks, and progressing through the full interview lifecycle.

Authentication

The Integration API accepts two auth methods. Both work on all /api/v1/integration/* endpoints.

MethodHeader(s)When to Use
Platform API keyX-API-Key: <key> + X-Tenant-ID: <tenant-uuid>Your backend calls the API on behalf of multiple tenants. One key, many tenants.
Tenant API keyX-API-Key: <key> (no X-Tenant-ID needed)A single tenant's own automation. The key is already scoped to one tenant.
JWT bearer tokenAuthorization: Bearer <token>Tenant admin users interacting from the Admin UI or short-lived scripts.
bash
# Platform key — include X-Tenant-ID to identify which customer
curl -X POST https://mayaapi.teamcast.ai/api/v1/integration/interviews \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxx" \
  -H "X-Tenant-ID: tenant-uuid" \
  -H "Content-Type: application/json" \
  -d '{ ... }'
To create a tenant-scoped API key, log in to the Admin UI at /admin/api-keys or call POST /api/v1/api-keys with a JWT that has api-key:create permission.

Step 1 — Submit an Interview Request

The minimum required fields are position and either qualifications[] or jobDescription. No candidate PII is required to create an interview — use candidateRef as an opaque identifier from your ATS.

FieldTypeRequiredDescription
positionstringYesJob title (e.g. "Senior Software Engineer")
levelJUNIOR|MID|SENIOR|LEADYesExperience level
qualificationsstring[]RecommendedNatural-language qualification statements. 2+ items gives best plan quality.
jobDescriptionstringAlternative to qualificationsRaw job description text (min 50 chars if provided)
candidateRefstringRecommendedYour ATS application/candidate ID — no PII needed
candidateNamestringNoOptional PII — used in plan and InMail draft
candidateEmailstring (email)NoOptional PII — used for invite email
candidateProfilestringNoFree-text candidate summary for context
companyNamestringNoCompany name shown in the interview plan
callbackUrlHTTPS URLNoPer-interview webhook override. Falls back to tenant config, then platform config.
externalIdstringRecommendedIdempotency key — your ATS job/application ID. Prevents duplicates on retries.
bash
curl -X POST https://mayaapi.teamcast.ai/api/v1/integration/interviews \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxx" \
  -H "X-Tenant-ID: tenant-uuid" \
  -H "Content-Type: application/json" \
  -d '{
    "candidateRef": "ats_app_8821",
    "candidateProfile": "8 years backend engineering, strong TypeScript background.",
    "position": "Senior Software Engineer",
    "level": "SENIOR",
    "qualifications": [
      "5+ years of production TypeScript experience",
      "Demonstrated distributed systems design at scale",
      "Strong async/concurrency patterns knowledge"
    ],
    "companyName": "Acme Corp",
    "externalId": "ats_job_app_8821"
  }'
json
{
  "runId": "run_1749123456_a1b2c3d4",
  "interviewId": "interview-uuid",
  "state": "VALIDATING_SKILLS",
  "message": "Interview request accepted. Skill validation in progress.",
  "dataQuality": "EXCELLENT"
}
Save the runId — it is the stable identifier for all subsequent calls. The interviewId is the internal DB UUID; prefer runId for API calls.

Step 2 — Handle INFO_NEEDED (if applicable)

If the response state is INFO_NEEDED, a webhook is sent to your registered callbackUrl listing the missing fields. Supply them via PATCH and the system re-validates automatically.

bash
curl -X PATCH https://mayaapi.teamcast.ai/api/v1/integration/interviews/run_1749123456_a1b2c3d4/info \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxx" \
  -H "X-Tenant-ID: tenant-uuid" \
  -H "Content-Type: application/json" \
  -d '{
    "jobDescription": "We are looking for a Senior Software Engineer with 5+ years of TypeScript experience...",
    "userId": "recruiter-id"
  }'
Response 200 — all clear
{
  "message": "Interview information updated. Transitioning to skill validation.",
  "state": "VALIDATING_SKILLS"
}

Step 3 — Receive Webhook and Approve Plan

When the Planner Agent finishes, a interview.plan_generated webhook fires. The interview enters PENDING state. If autoApprovePlans is enabled on the tenant, this step is skipped automatically.

bash
curl -X POST https://mayaapi.teamcast.ai/api/v1/integration/interviews/run_1749123456_a1b2c3d4/plan/approve \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxx" \
  -H "X-Tenant-ID: tenant-uuid" \
  -H "Content-Type: application/json" \
  -d '{ "actorId": "recruiter-id" }'
Response 200 — approved
{
  "message": "Interview plan approved. Candidate interview link generated.",
  "workflowState": "APPROVED",
  "interviewLink": "{YOUR_APP_URL}/interview/join/eyJhbGc...",
  "inmailDraft": {
    "subject": "Senior Software Engineer Opportunity at Acme Corp — Interview Invitation",
    "body": "Hi there,

We would like to invite you to complete an AI-powered interview...

{YOUR_APP_URL}/interview/join/eyJhbGc..."
  }
}

Step 4 — Poll Status (Alternative to Webhooks)

If you cannot receive webhooks, poll the status endpoint until the state you need is reached. Recommended interval: 5 seconds.

bash
curl https://mayaapi.teamcast.ai/api/v1/integration/interviews/run_1749123456_a1b2c3d4 \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxx" \
  -H "X-Tenant-ID: tenant-uuid"
Response 200
{
  "runId": "run_1749123456_a1b2c3d4",
  "interviewId": "interview-uuid",
  "state": "PENDING",
  "candidateRef": "ats_app_8821",
  "position": "Senior Software Engineer",
  "level": "SENIOR",
  "plan": {
    "id": "plan-uuid",
    "title": "Senior Software Engineer Technical Interview",
    "duration": 45,
    "sections": [...]
  },
  "assessment": null,
  "recordingUrls": [],
  "createdAt": "2026-03-20T10:00:00.000Z",
  "updatedAt": "2026-03-20T10:12:00.000Z"
}

Step 5 — Approve the Assessment

After the candidate completes the interview, the Assessor Agent generates a structured report. An interview.assessment_pending webhook fires. Approve to receive the full report.

bash
curl -X POST https://mayaapi.teamcast.ai/api/v1/integration/interviews/run_1749123456_a1b2c3d4/assessment/approve \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxx" \
  -H "X-Tenant-ID: tenant-uuid" \
  -H "Content-Type: application/json" \
  -d '{ "actorId": "recruiter-id" }'
Response 200
{
  "message": "Assessment approved. The full report has been sent via webhook.",
  "workflowState": "ASSESSMENT_APPROVED"
}

Webhook Payload Format

All webhooks are signed with HMAC-SHA256 using your platform or tenant signing secret. Verify the signature before processing. See Webhooks for the full 3-tier resolution order and event subscription details.

Example webhook payload
{
  "event": "interview.plan_generated",
  "runId": "run_1749123456_a1b2c3d4",
  "interviewId": "interview-uuid",
  "candidateRef": "ats_app_8821",
  "state": "PENDING",
  "timestamp": "2026-03-20T10:12:00.000Z",
  "tenantId": "tenant-uuid",
  "data": {
    "planId": "plan-uuid"
  }
}
Signature verification (Node.js)
import crypto from 'crypto';

function verifyWebhook(
  body: string,           // raw request body string
  signature: string,      // X-Webhook-Signature header value
  secret: string,         // your platform or tenant webhook signing secret
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}
HeaderDescription
X-Webhook-SignatureHMAC-SHA256 hex digest of the raw request body
X-Webhook-TimestampISO 8601 timestamp of when the webhook was sent
X-Tenant-IDThe tenant this event belongs to
Content-Typeapplication/json

Platform Admin Webhook Management

Platform Admins can provision and manage webhook configurations for any tenant under their platform without needing tenant-admin credentials. Useful for onboarding, centralised support, and debugging delivery failures. All endpoints enforce strict isolation — the tenant must belong to the calling platform or the API returns 403.

MethodEndpointDescription
GET/platform-admin/tenants/:tenantId/webhook-configFetch tenant webhook config (secret masked)
PUT/platform-admin/tenants/:tenantId/webhook-configCreate or update tenant webhook
DELETE/platform-admin/tenants/:tenantId/webhook-configRemove tenant webhook
POST/platform-admin/tenants/:tenantId/webhook-testSend a synthetic test webhook for debugging
GET / PUT / DELETE/tenants/:id/webhook-configShared per-tenant route (also usable by Tenant Admin / Super Admin with caller-aware scoping)
GET / PUT / DELETE/platform-admin/webhook-configPlatform-level fallback webhook used when no tenant-specific config exists
The test endpoint resolves the effective callback URL using the same priority order as live events (tenant config first, platform fallback) and returns which layer was used in the response. If a tenant reports “webhooks not triggered”, run this first.

Need more detail? See the Webhooks reference for the full payload shape, signature scheme, and event catalogue.

Additional Integration Endpoints

MethodEndpointDescription
GET/integration/rankingsCandidates ranked by assessment score. Filter by position and level.
DELETE/integration/interviews/:runId/candidate-dataPurge candidate PII (GDPR). Assessment scores retained for audit.
POST/integration/interviews/:runId/assessment/chatAsk free-form questions about an assessment report.
DELETE/integration/interviews/:runIdCancel an interview (soft delete). Cannot cancel terminal states.

Error Reference

HTTP StatusCauseFix
401 UnauthorizedMissing or expired API key / JWTCheck X-API-Key header or refresh JWT
403 ForbiddenKey lacks required permissionRe-issue key with the correct permissions (see Platform Setup)
404 Not FoundrunId does not exist or belongs to a different tenantVerify runId and X-Tenant-ID match
400 Bad RequestWrong state for the action (e.g. approving a non-PENDING interview)Check current state via GET /integration/interviews/:runId
409 ConflictexternalId already used for another interviewSame externalId = idempotent — the existing interview is returned
429 Too Many RequestsRate limit exceeded (100 req/min per IP)Back off and retry after the Retry-After header value

Complete Workflow Reference

StateTriggered ByNext Action
RECEIVEDPOST /integration/interviewsAutomatic — validates data
INFO_NEEDEDMissing critical fieldsPATCH .../info to supply missing data
VALIDATING_SKILLSData complete or info suppliedAutomatic — plan generation starts
GENERATING_PLANPlan revision requestedWait for interview.plan_generated webhook
PENDINGPlan generatedPOST .../plan/approve or reject (or auto-approved)
APPROVEDPlan approvedinterviewLink returned — send to candidate
IN_PROGRESSCandidate joins interviewWait for interview to complete
COMPLETEDInterview session endsAutomatic — assessment generation starts
ASSESSMENT_PENDINGAssessment readyPOST .../assessment/approve or reject
ASSESSMENT_APPROVEDAssessment approvedFull report sent via webhook — terminal
CANCELLEDDELETE .../runId or assessment rejectedTerminal
Was this page helpful?