API Reference#
The InterviewRelay REST API gives you programmatic access to all platform features. Create campaigns, manage invites, retrieve transcripts, and automate your interview workflows.
Base URL#
All API requests are made to:
https://api.interviewrelay.com/v1
All requests must use HTTPS. HTTP requests will be redirected.
Authentication#
Authenticate using a Bearer token in the Authorization header:
curl https://api.interviewrelay.com/v1/campaigns \
-H "Authorization: Bearer YOUR_API_KEY"
Getting Your API Key#
- Go to Settings → API Keys
- Click Generate New Key
- Give the key a name and select permissions
- Copy the key — it's only shown once
Keep Keys Secret
API keys grant full access to your account. Never expose them in client-side code, public repositories, or logs. Use environment variables to store keys.
Key Permissions#
| Permission | Description |
|-----------|-------------|
| campaigns:read | List and view campaigns |
| campaigns:write | Create and update campaigns |
| invites:read | List and view invites |
| invites:write | Create and manage invites |
| sessions:read | View sessions and transcripts |
| sessions:write | Manage session lifecycle |
| billing:read | View billing and usage |
| billing:write | Manage subscriptions and credits |
| organization:read | View organization settings |
| organization:write | Manage organization settings |
Campaigns API#
Create Campaign#
/v1/campaignsCreate a new interview campaign
Request body:
| Name | Type | Description |
|---|---|---|
name* | string | Campaign name (max 100 characters) |
description | string | Campaign description (max 500 characters) |
scriptId* | string | ID of the interview script to use |
projectId | string | Project to assign the campaign to |
settings | object | Campaign settings (voice, language, duration limits, etc.) |
inviteDefaults | object | Default settings for invites (expiry, email template, etc.) |
metadata | object | Custom metadata key-value pairs |
Example request:
curl -X POST https://api.interviewrelay.com/v1/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q1 Customer Feedback",
"description": "Quarterly feedback interviews with enterprise customers",
"scriptId": "scr_abc123",
"projectId": "prj_xyz789",
"settings": {
"voice": "alloy",
"language": "en",
"maxDuration": 1800,
"requireConsent": true
},
"inviteDefaults": {
"expiresIn": "7d",
"emailTemplate": "default"
}
}'
Example response:
{
"id": "cam_a1b2c3d4",
"name": "Q1 Customer Feedback",
"description": "Quarterly feedback interviews with enterprise customers",
"scriptId": "scr_abc123",
"projectId": "prj_xyz789",
"status": "active",
"settings": {
"voice": "alloy",
"language": "en",
"maxDuration": 1800,
"requireConsent": true
},
"inviteDefaults": {
"expiresIn": "7d",
"emailTemplate": "default"
},
"stats": {
"totalInvites": 0,
"completedSessions": 0,
"averageDuration": 0
},
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
List Campaigns#
/v1/campaignsList all campaigns in your organization
Query parameters:
| Name | Type | Description |
|---|---|---|
status | string | Filter by status: 'active', 'paused', 'archived' |
projectId | string | Filter by project ID |
search | string | Search campaigns by name |
sort | string | Sort field: 'name', 'createdAt', 'updatedAt'. Default: 'createdAt' |
order | string | Sort order: 'asc' or 'desc'. Default: 'desc' |
limit | number | Results per page (1-100). Default: 20 |
offset | number | Pagination offset. Default: 0 |
Example request:
curl https://api.interviewrelay.com/v1/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-G \
-d "status=active" \
-d "limit=10"
Get Campaign#
/v1/campaigns/{campaignId}Retrieve a specific campaign by ID
Example request:
curl https://api.interviewrelay.com/v1/campaigns/cam_a1b2c3d4 \
-H "Authorization: Bearer YOUR_API_KEY"
Invites API#
Create Invite#
/v1/campaigns/{campaignId}/invitesCreate and optionally send an invite for a campaign
Request body:
| Name | Type | Description |
|---|---|---|
email* | string | Participant email address |
name | string | Participant display name |
sendEmail | boolean | Whether to send the invite email. Default: true |
expiresIn | string | Invite validity period (e.g., '7d', '24h'). Overrides campaign default. |
expiresAt | datetime | Exact expiry date (ISO 8601). Takes precedence over expiresIn. |
metadata | object | Custom metadata for the invite |
emailTemplate | string | Email template ID to use. Default: campaign default. |
Example request:
curl -X POST https://api.interviewrelay.com/v1/campaigns/cam_a1b2c3d4/invites \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "participant@example.com",
"name": "Jane Doe",
"sendEmail": true,
"expiresIn": "7d",
"metadata": {
"userId": "12345",
"segment": "enterprise"
}
}'
Example response:
{
"id": "inv_x1y2z3",
"campaignId": "cam_a1b2c3d4",
"token": "inv_8f7d9e2c1a0b3h4k5j6m",
"email": "participant@example.com",
"name": "Jane Doe",
"status": "pending",
"inviteUrl": "https://interviewrelay.com/interview/inv_8f7d9e2c1a0b3h4k5j6m",
"emailStatus": "sent",
"expiresAt": "2024-01-22T10:30:00Z",
"metadata": {
"userId": "12345",
"segment": "enterprise"
},
"createdAt": "2024-01-15T10:30:00Z"
}
Bulk Create Invites#
/v1/campaigns/{campaignId}/invites/bulkCreate multiple invites at once (max 500 per request)
Request body:
{
"invites": [
{ "email": "alice@example.com", "name": "Alice", "metadata": { "segment": "trial" } },
{ "email": "bob@example.com", "name": "Bob", "metadata": { "segment": "enterprise" } }
],
"sendEmail": true,
"expiresIn": "7d"
}
Get Invite#
/v1/invites/{inviteId}Retrieve a specific invite by ID
List Invites#
/v1/campaigns/{campaignId}/invitesList all invites for a campaign
Query parameters:
| Name | Type | Description |
|---|---|---|
status | string | Filter by status: 'pending', 'started', 'completed', 'expired', 'cancelled' |
email | string | Filter by participant email |
limit | number | Results per page (1-100). Default: 20 |
offset | number | Pagination offset. Default: 0 |
Cancel Invite#
/v1/invites/{inviteId}/cancelCancel a pending invite and revoke the token
Resend Invite#
/v1/invites/{inviteId}/resendResend the invite email to the participant
Sessions API#
List Sessions#
/v1/campaigns/{campaignId}/sessionsList all sessions for a campaign
Query parameters:
| Name | Type | Description |
|---|---|---|
status | string | Filter by status: 'created', 'in_progress', 'paused', 'completed', 'abandoned' |
from | datetime | Start of date range (ISO 8601) |
to | datetime | End of date range (ISO 8601) |
sort | string | Sort field: 'createdAt', 'completedAt', 'duration'. Default: 'createdAt' |
order | string | Sort order: 'asc' or 'desc'. Default: 'desc' |
limit | number | Results per page (1-100). Default: 20 |
offset | number | Pagination offset. Default: 0 |
Example request:
curl https://api.interviewrelay.com/v1/campaigns/cam_a1b2c3d4/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-G \
-d "status=completed" \
-d "limit=50"
Get Session#
/v1/sessions/{sessionId}Retrieve a specific session with full details
Example response:
{
"id": "ses_m1n2o3p4",
"campaignId": "cam_a1b2c3d4",
"inviteId": "inv_x1y2z3",
"status": "completed",
"participant": {
"email": "participant@example.com",
"name": "Jane Doe"
},
"startedAt": "2024-01-15T14:00:00Z",
"completedAt": "2024-01-15T14:12:30Z",
"duration": 750,
"questionsAnswered": 8,
"totalQuestions": 10,
"language": "en",
"sentiment": {
"overall": "positive",
"score": 0.72
},
"device": {
"browser": "Chrome 120",
"os": "macOS 14.2",
"type": "desktop"
},
"metadata": {
"userId": "12345",
"segment": "enterprise"
},
"createdAt": "2024-01-15T13:59:45Z"
}
Get Session Transcript#
/v1/sessions/{sessionId}/transcriptRetrieve the full transcript for a completed session
Query parameters:
| Name | Type | Description |
|---|---|---|
format | string | Response format: 'json' (default), 'text', 'srt', 'vtt' |
include_sentiment | boolean | Include sentiment per utterance. Default: true |
include_keywords | boolean | Include keywords per utterance. Default: false |
redact_pii | boolean | Apply PII redaction. Default: false |
speaker | string | Filter by speaker: 'ai', 'participant', or 'all' (default) |
Export Session as PDF#
/v1/sessions/{sessionId}/pdfGenerate and download a formatted PDF transcript
Query parameters:
| Name | Type | Description |
|---|---|---|
include_insights | boolean | Include AI insights in the PDF. Default: true |
include_timestamps | boolean | Include timestamps per utterance. Default: true |
include_sentiment | boolean | Include sentiment indicators. Default: true |
branding | string | Branding style: 'default', 'minimal', 'custom' |
Pause Session#
/v1/sessions/{sessionId}/pausePause an active interview session
Request body:
{
"reason": "Technical issue reported by participant"
}
Resume Session#
/v1/sessions/{sessionId}/resumeResume a paused interview session
Session Heartbeat#
/v1/sessions/{sessionId}/heartbeatSend a heartbeat to keep the session alive (used by the SDK)
Record Consent#
/v1/sessions/{sessionId}/consentRecord participant consent for the session
Request body:
| Name | Type | Description |
|---|---|---|
granted* | boolean | Whether consent was granted |
type* | string | Consent type: 'recording', 'data_processing', 'all' |
ip | string | Participant IP address for audit trail |
List Session Files#
/v1/sessions/{sessionId}/filesList all files (audio, exports) associated with a session
Upload Session File#
/v1/sessions/{sessionId}/filesUpload an additional file to a session (e.g., screen recording)
Projects API#
List Projects#
/v1/projectsList all projects in your organization
Create Project#
/v1/projectsCreate a new project
Request body:
| Name | Type | Description |
|---|---|---|
name* | string | Project name (max 100 characters) |
description | string | Project description |
color | string | Project color (hex code, e.g., '#6366F1') |
Get Project#
/v1/projects/{projectId}Retrieve a specific project
Update Project#
/v1/projects/{projectId}Update a project's details
Delete Project#
/v1/projects/{projectId}Delete a project (campaigns are unassigned, not deleted)
Scripts API#
Create Script#
/v1/scriptsCreate a new interview script
Request body:
| Name | Type | Description |
|---|---|---|
name* | string | Script name (max 100 characters) |
description | string | Script description |
projectId | string | Project to assign the script to |
systemPrompt* | string | System prompt defining interviewer behavior and persona |
questions* | Question[] | Array of interview questions |
settings | object | Script settings (follow-ups, language, voice, etc.) |
Question object:
| Name | Type | Description |
|---|---|---|
id* | string | Unique question identifier |
text* | string | The question text |
type* | string | Question type: 'open', 'rating', 'multiple_choice', 'yes_no' |
required | boolean | Whether the question must be answered. Default: true |
followUp | object | Follow-up question configuration |
maxDuration | number | Max response time in seconds for this question |
order | number | Display order (auto-assigned if not provided) |
Example request:
curl -X POST https://api.interviewrelay.com/v1/scripts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Customer Satisfaction Script",
"systemPrompt": "You are a friendly interviewer conducting a customer satisfaction survey. Be conversational and follow up on interesting responses.",
"questions": [
{
"id": "q1",
"text": "Tell me about your experience using our product.",
"type": "open",
"followUp": {
"enabled": true,
"maxFollowUps": 2,
"prompt": "Ask a follow-up that digs deeper into their specific experience"
}
},
{
"id": "q2",
"text": "On a scale of 1-10, how likely are you to recommend us?",
"type": "rating"
}
],
"settings": {
"maxFollowUps": 2,
"language": "en",
"voice": "alloy"
}
}'
List Scripts#
/v1/scriptsList all scripts in your organization
Query parameters:
| Name | Type | Description |
|---|---|---|
projectId | string | Filter by project ID |
status | string | Filter by status: 'draft', 'published', 'archived' |
search | string | Search scripts by name |
limit | number | Results per page (1-100). Default: 20 |
offset | number | Pagination offset. Default: 0 |
Get Script#
/v1/scripts/{scriptId}Retrieve a specific script with all questions
Update Script#
/v1/scripts/{scriptId}Update a script (creates a new draft version if published)
Delete Script#
/v1/scripts/{scriptId}Delete a script (fails if used by active campaigns)
Publish Script#
/v1/scripts/{scriptId}/publishPublish a draft script, making it available for campaigns
Create New Version#
/v1/scripts/{scriptId}/versionsCreate a new version of an existing script
Request body:
| Name | Type | Description |
|---|---|---|
changelog | string | Description of changes in this version |
questions | Question[] | Updated questions array (replaces all questions) |
systemPrompt | string | Updated system prompt |
settings | object | Updated settings |
Export Script#
/v1/scripts/{scriptId}/exportExport a script as JSON for backup or migration
Import Script#
/v1/scripts/importImport a script from a JSON export
Upload Script Media#
/v1/scripts/{scriptId}/mediaUpload media files (images, audio) for use in script questions
Organizations API#
Get Organization#
/v1/organizationRetrieve your organization details
Update Organization#
/v1/organizationUpdate organization settings
Request body:
| Name | Type | Description |
|---|---|---|
name | string | Organization name |
logo | string | Logo URL for white-label branding |
domain | string | Custom domain for interview links |
settings | object | Organization-level settings |
List Members#
/v1/organization/membersList all organization members and their roles
Invite Member#
/v1/organization/membersInvite a new member to the organization
Update Member Role#
/v1/organization/members/{memberId}Update a member's role
Remove Member#
/v1/organization/members/{memberId}Remove a member from the organization
Billing API#
Get Current Plan#
/v1/billing/planRetrieve your current subscription plan and credit balance
Example response:
{
"plan": "professional",
"status": "active",
"credits": {
"balance": 342,
"included": 500,
"purchased": 100,
"used": 258
},
"billingPeriod": {
"start": "2024-01-01T00:00:00Z",
"end": "2024-01-31T23:59:59Z"
},
"nextInvoice": {
"date": "2024-02-01T00:00:00Z",
"amount": 19900,
"currency": "usd"
}
}
Get Usage#
/v1/billing/usageRetrieve credit usage for a date range
Query parameters:
| Name | Type | Description |
|---|---|---|
from* | datetime | Start date (ISO 8601) |
to* | datetime | End date (ISO 8601) |
group_by | string | Group results: 'day', 'week', 'month', 'campaign' |
Purchase Credits#
/v1/billing/credits/purchasePurchase additional credits
Request body:
| Name | Type | Description |
|---|---|---|
amount* | number | Number of credits to purchase |
paymentMethodId | string | Stripe payment method ID (uses default if not specified) |
List Invoices#
/v1/billing/invoicesList all invoices for your organization
Rate Limiting#
API requests are rate-limited per API key:
| Plan | Rate Limit | |------|-----------| | Free | 60 requests/minute | | Starter | 300 requests/minute | | Professional | 1,000 requests/minute | | Enterprise | Custom (up to 10,000 requests/minute) |
Rate Limit Headers#
Every response includes rate limit headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1705312800
Handling Rate Limits#
When rate limited, the API returns 429 Too Many Requests:
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Try again in 45 seconds.",
"retryAfter": 45
}
}
Implement exponential backoff in your client:
async function apiRequest(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
Error Codes#
All error responses follow a consistent format:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": {}
}
}
HTTP Status Codes#
| Status | Meaning |
|--------|---------|
| 200 | Success |
| 201 | Created |
| 204 | No Content (successful deletion) |
| 400 | Bad Request — invalid parameters |
| 401 | Unauthorized — invalid or missing API key |
| 403 | Forbidden — insufficient permissions |
| 404 | Not Found — resource doesn't exist |
| 409 | Conflict — resource state conflict |
| 422 | Unprocessable Entity — validation error |
| 429 | Too Many Requests — rate limit exceeded |
| 500 | Internal Server Error |
| 503 | Service Unavailable — temporary outage |
Common Error Codes#
| Name | Type | Description |
|---|---|---|
INVALID_REQUEST | 400 | Request body is malformed or missing required fields |
INVALID_API_KEY | 401 | API key is invalid, expired, or revoked |
INSUFFICIENT_PERMISSIONS | 403 | API key lacks the required permission scope |
RESOURCE_NOT_FOUND | 404 | The requested resource does not exist |
CAMPAIGN_NOT_ACTIVE | 409 | Campaign is paused or archived, cannot create invites |
INVITE_EXPIRED | 409 | Invite has expired and cannot be modified |
SESSION_NOT_ACTIVE | 409 | Session is not in a state that allows this action |
INSUFFICIENT_CREDITS | 402 | Not enough credits to perform this action |
VALIDATION_ERROR | 422 | One or more fields failed validation |
RATE_LIMITED | 429 | Too many requests — slow down |
INTERNAL_ERROR | 500 | Unexpected server error — contact support if persistent |
Pagination#
List endpoints return paginated results. Use limit and offset parameters:
# First page
curl https://api.interviewrelay.com/v1/campaigns?limit=20&offset=0
# Second page
curl https://api.interviewrelay.com/v1/campaigns?limit=20&offset=20
Pagination Response#
All list endpoints include pagination metadata:
{
"data": [...],
"pagination": {
"total": 156,
"limit": 20,
"offset": 0,
"hasMore": true
}
}
| Name | Type | Description |
|---|---|---|
total | number | Total number of results matching the query |
limit | number | Maximum results per page |
offset | number | Current offset |
hasMore | boolean | Whether more results exist beyond the current page |
Best Practices#
Idempotency#
For POST requests, include an Idempotency-Key header to prevent duplicate operations:
curl -X POST https://api.interviewrelay.com/v1/campaigns/cam_abc/invites \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: unique-request-id-12345" \
-H "Content-Type: application/json" \
-d '{"email": "participant@example.com"}'
Idempotency keys are valid for 24 hours. Repeat requests with the same key return the original response.
Webhooks vs. Polling#
Use webhooks for real-time event notifications instead of polling the API. Webhooks are more efficient and provide instant updates for:
- Session started / completed / abandoned
- Invite delivered / bounced / opened
- Transcript ready
- Credit balance alerts
Error Handling#
- Always check HTTP status codes
- Parse the error response body for detailed information
- Implement retry logic with exponential backoff for 5xx errors and 429
- Don't retry 4xx errors (except 429) — they indicate client-side issues
- Log error details for debugging
Caching#
- Campaign and script data changes infrequently — cache for 5-10 minutes
- Session data may change rapidly during active interviews — don't cache
- Transcripts are immutable after generation — cache indefinitely
SDKs & Libraries#
Official client libraries are available for popular languages:
JavaScript / TypeScript#
npm install @interviewrelay/api-client
import { InterviewRelayClient } from '@interviewrelay/api-client';
const client = new InterviewRelayClient({ apiKey: process.env.IR_API_KEY });
const campaign = await client.campaigns.create({
name: 'My Campaign',
scriptId: 'scr_abc123'
});
const invite = await client.invites.create(campaign.id, {
email: 'participant@example.com'
});
Python#
pip install interviewrelay
from interviewrelay import Client
client = Client(api_key="YOUR_API_KEY")
campaign = client.campaigns.create(
name="My Campaign",
script_id="scr_abc123"
)
invite = client.invites.create(
campaign_id=campaign.id,
email="participant@example.com"
)
Ruby#
gem install interviewrelay
require 'interviewrelay'
client = InterviewRelay::Client.new(api_key: ENV['IR_API_KEY'])
campaign = client.campaigns.create(
name: 'My Campaign',
script_id: 'scr_abc123'
)
cURL#
All examples in this documentation use cURL. No additional installation needed.
For more integration examples, see the Examples section. For embedding interviews in your website, use the JavaScript SDK instead of the REST API.