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.
| Method | Header(s) | When to Use |
|---|---|---|
| Platform API key | X-API-Key: <key> + X-Tenant-ID: <tenant-uuid> | Your backend calls the API on behalf of multiple tenants. One key, many tenants. |
| Tenant API key | X-API-Key: <key> (no X-Tenant-ID needed) | A single tenant's own automation. The key is already scoped to one tenant. |
| JWT bearer token | Authorization: Bearer <token> | Tenant admin users interacting from the Admin UI or short-lived scripts. |
# 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 '{ ... }'/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.
| Field | Type | Required | Description |
|---|---|---|---|
| position | string | Yes | Job title (e.g. "Senior Software Engineer") |
| level | JUNIOR|MID|SENIOR|LEAD | Yes | Experience level |
| qualifications | string[] | Recommended | Natural-language qualification statements. 2+ items gives best plan quality. |
| jobDescription | string | Alternative to qualifications | Raw job description text (min 50 chars if provided) |
| candidateRef | string | Recommended | Your ATS application/candidate ID — no PII needed |
| candidateName | string | No | Optional PII — used in plan and InMail draft |
| candidateEmail | string (email) | No | Optional PII — used for invite email |
| candidateProfile | string | No | Free-text candidate summary for context |
| companyName | string | No | Company name shown in the interview plan |
| callbackUrl | HTTPS URL | No | Per-interview webhook override. Falls back to tenant config, then platform config. |
| externalId | string | Recommended | Idempotency key — your ATS job/application ID. Prevents duplicates on retries. |
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"
}'{
"runId": "run_1749123456_a1b2c3d4",
"interviewId": "interview-uuid",
"state": "VALIDATING_SKILLS",
"message": "Interview request accepted. Skill validation in progress.",
"dataQuality": "EXCELLENT"
}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.
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"
}'{
"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.
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" }'{
"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.
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"{
"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.
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" }'{
"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.
{
"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"
}
}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),
);
}| Header | Description |
|---|---|
| X-Webhook-Signature | HMAC-SHA256 hex digest of the raw request body |
| X-Webhook-Timestamp | ISO 8601 timestamp of when the webhook was sent |
| X-Tenant-ID | The tenant this event belongs to |
| Content-Type | application/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.
| Method | Endpoint | Description |
|---|---|---|
| GET | /platform-admin/tenants/:tenantId/webhook-config | Fetch tenant webhook config (secret masked) |
| PUT | /platform-admin/tenants/:tenantId/webhook-config | Create or update tenant webhook |
| DELETE | /platform-admin/tenants/:tenantId/webhook-config | Remove tenant webhook |
| POST | /platform-admin/tenants/:tenantId/webhook-test | Send a synthetic test webhook for debugging |
| GET / PUT / DELETE | /tenants/:id/webhook-config | Shared per-tenant route (also usable by Tenant Admin / Super Admin with caller-aware scoping) |
| GET / PUT / DELETE | /platform-admin/webhook-config | Platform-level fallback webhook used when no tenant-specific config exists |
Need more detail? See the Webhooks reference for the full payload shape, signature scheme, and event catalogue.
Additional Integration Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /integration/rankings | Candidates ranked by assessment score. Filter by position and level. |
| DELETE | /integration/interviews/:runId/candidate-data | Purge candidate PII (GDPR). Assessment scores retained for audit. |
| POST | /integration/interviews/:runId/assessment/chat | Ask free-form questions about an assessment report. |
| DELETE | /integration/interviews/:runId | Cancel an interview (soft delete). Cannot cancel terminal states. |
Error Reference
| HTTP Status | Cause | Fix |
|---|---|---|
| 401 Unauthorized | Missing or expired API key / JWT | Check X-API-Key header or refresh JWT |
| 403 Forbidden | Key lacks required permission | Re-issue key with the correct permissions (see Platform Setup) |
| 404 Not Found | runId does not exist or belongs to a different tenant | Verify runId and X-Tenant-ID match |
| 400 Bad Request | Wrong state for the action (e.g. approving a non-PENDING interview) | Check current state via GET /integration/interviews/:runId |
| 409 Conflict | externalId already used for another interview | Same externalId = idempotent — the existing interview is returned |
| 429 Too Many Requests | Rate limit exceeded (100 req/min per IP) | Back off and retry after the Retry-After header value |
Complete Workflow Reference
| State | Triggered By | Next Action |
|---|---|---|
| RECEIVED | POST /integration/interviews | Automatic — validates data |
| INFO_NEEDED | Missing critical fields | PATCH .../info to supply missing data |
| VALIDATING_SKILLS | Data complete or info supplied | Automatic — plan generation starts |
| GENERATING_PLAN | Plan revision requested | Wait for interview.plan_generated webhook |
| PENDING | Plan generated | POST .../plan/approve or reject (or auto-approved) |
| APPROVED | Plan approved | interviewLink returned — send to candidate |
| IN_PROGRESS | Candidate joins interview | Wait for interview to complete |
| COMPLETED | Interview session ends | Automatic — assessment generation starts |
| ASSESSMENT_PENDING | Assessment ready | POST .../assessment/approve or reject |
| ASSESSMENT_APPROVED | Assessment approved | Full report sent via webhook — terminal |
| CANCELLED | DELETE .../runId or assessment rejected | Terminal |