InterviewRelay

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#

  1. Go to Settings → API Keys
  2. Click Generate New Key
  3. Give the key a name and select permissions
  4. 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#

POST/v1/campaigns

Create a new interview campaign

Request body:

NameTypeDescription
name*stringCampaign name (max 100 characters)
descriptionstringCampaign description (max 500 characters)
scriptId*stringID of the interview script to use
projectIdstringProject to assign the campaign to
settingsobjectCampaign settings (voice, language, duration limits, etc.)
inviteDefaultsobjectDefault settings for invites (expiry, email template, etc.)
metadataobjectCustom 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#

GET/v1/campaigns

List all campaigns in your organization

Query parameters:

NameTypeDescription
statusstringFilter by status: 'active', 'paused', 'archived'
projectIdstringFilter by project ID
searchstringSearch campaigns by name
sortstringSort field: 'name', 'createdAt', 'updatedAt'. Default: 'createdAt'
orderstringSort order: 'asc' or 'desc'. Default: 'desc'
limitnumberResults per page (1-100). Default: 20
offsetnumberPagination 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#

GET/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#

POST/v1/campaigns/{campaignId}/invites

Create and optionally send an invite for a campaign

Request body:

NameTypeDescription
email*stringParticipant email address
namestringParticipant display name
sendEmailbooleanWhether to send the invite email. Default: true
expiresInstringInvite validity period (e.g., '7d', '24h'). Overrides campaign default.
expiresAtdatetimeExact expiry date (ISO 8601). Takes precedence over expiresIn.
metadataobjectCustom metadata for the invite
emailTemplatestringEmail 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#

POST/v1/campaigns/{campaignId}/invites/bulk

Create 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#

GET/v1/invites/{inviteId}

Retrieve a specific invite by ID

List Invites#

GET/v1/campaigns/{campaignId}/invites

List all invites for a campaign

Query parameters:

NameTypeDescription
statusstringFilter by status: 'pending', 'started', 'completed', 'expired', 'cancelled'
emailstringFilter by participant email
limitnumberResults per page (1-100). Default: 20
offsetnumberPagination offset. Default: 0

Cancel Invite#

POST/v1/invites/{inviteId}/cancel

Cancel a pending invite and revoke the token

Resend Invite#

POST/v1/invites/{inviteId}/resend

Resend the invite email to the participant


Sessions API#

List Sessions#

GET/v1/campaigns/{campaignId}/sessions

List all sessions for a campaign

Query parameters:

NameTypeDescription
statusstringFilter by status: 'created', 'in_progress', 'paused', 'completed', 'abandoned'
fromdatetimeStart of date range (ISO 8601)
todatetimeEnd of date range (ISO 8601)
sortstringSort field: 'createdAt', 'completedAt', 'duration'. Default: 'createdAt'
orderstringSort order: 'asc' or 'desc'. Default: 'desc'
limitnumberResults per page (1-100). Default: 20
offsetnumberPagination 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#

GET/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#

GET/v1/sessions/{sessionId}/transcript

Retrieve the full transcript for a completed session

Query parameters:

NameTypeDescription
formatstringResponse format: 'json' (default), 'text', 'srt', 'vtt'
include_sentimentbooleanInclude sentiment per utterance. Default: true
include_keywordsbooleanInclude keywords per utterance. Default: false
redact_piibooleanApply PII redaction. Default: false
speakerstringFilter by speaker: 'ai', 'participant', or 'all' (default)

Export Session as PDF#

GET/v1/sessions/{sessionId}/pdf

Generate and download a formatted PDF transcript

Query parameters:

NameTypeDescription
include_insightsbooleanInclude AI insights in the PDF. Default: true
include_timestampsbooleanInclude timestamps per utterance. Default: true
include_sentimentbooleanInclude sentiment indicators. Default: true
brandingstringBranding style: 'default', 'minimal', 'custom'

Pause Session#

POST/v1/sessions/{sessionId}/pause

Pause an active interview session

Request body:

{
  "reason": "Technical issue reported by participant"
}

Resume Session#

POST/v1/sessions/{sessionId}/resume

Resume a paused interview session

Session Heartbeat#

POST/v1/sessions/{sessionId}/heartbeat

Send a heartbeat to keep the session alive (used by the SDK)

POST/v1/sessions/{sessionId}/consent

Record participant consent for the session

Request body:

NameTypeDescription
granted*booleanWhether consent was granted
type*stringConsent type: 'recording', 'data_processing', 'all'
ipstringParticipant IP address for audit trail

List Session Files#

GET/v1/sessions/{sessionId}/files

List all files (audio, exports) associated with a session

Upload Session File#

POST/v1/sessions/{sessionId}/files

Upload an additional file to a session (e.g., screen recording)


Projects API#

List Projects#

GET/v1/projects

List all projects in your organization

Create Project#

POST/v1/projects

Create a new project

Request body:

NameTypeDescription
name*stringProject name (max 100 characters)
descriptionstringProject description
colorstringProject color (hex code, e.g., '#6366F1')

Get Project#

GET/v1/projects/{projectId}

Retrieve a specific project

Update Project#

PATCH/v1/projects/{projectId}

Update a project's details

Delete Project#

DELETE/v1/projects/{projectId}

Delete a project (campaigns are unassigned, not deleted)


Scripts API#

Create Script#

POST/v1/scripts

Create a new interview script

Request body:

NameTypeDescription
name*stringScript name (max 100 characters)
descriptionstringScript description
projectIdstringProject to assign the script to
systemPrompt*stringSystem prompt defining interviewer behavior and persona
questions*Question[]Array of interview questions
settingsobjectScript settings (follow-ups, language, voice, etc.)

Question object:

NameTypeDescription
id*stringUnique question identifier
text*stringThe question text
type*stringQuestion type: 'open', 'rating', 'multiple_choice', 'yes_no'
requiredbooleanWhether the question must be answered. Default: true
followUpobjectFollow-up question configuration
maxDurationnumberMax response time in seconds for this question
ordernumberDisplay 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#

GET/v1/scripts

List all scripts in your organization

Query parameters:

NameTypeDescription
projectIdstringFilter by project ID
statusstringFilter by status: 'draft', 'published', 'archived'
searchstringSearch scripts by name
limitnumberResults per page (1-100). Default: 20
offsetnumberPagination offset. Default: 0

Get Script#

GET/v1/scripts/{scriptId}

Retrieve a specific script with all questions

Update Script#

PATCH/v1/scripts/{scriptId}

Update a script (creates a new draft version if published)

Delete Script#

DELETE/v1/scripts/{scriptId}

Delete a script (fails if used by active campaigns)

Publish Script#

POST/v1/scripts/{scriptId}/publish

Publish a draft script, making it available for campaigns

Create New Version#

POST/v1/scripts/{scriptId}/versions

Create a new version of an existing script

Request body:

NameTypeDescription
changelogstringDescription of changes in this version
questionsQuestion[]Updated questions array (replaces all questions)
systemPromptstringUpdated system prompt
settingsobjectUpdated settings

Export Script#

GET/v1/scripts/{scriptId}/export

Export a script as JSON for backup or migration

Import Script#

POST/v1/scripts/import

Import a script from a JSON export

Upload Script Media#

POST/v1/scripts/{scriptId}/media

Upload media files (images, audio) for use in script questions


Organizations API#

Get Organization#

GET/v1/organization

Retrieve your organization details

Update Organization#

PATCH/v1/organization

Update organization settings

Request body:

NameTypeDescription
namestringOrganization name
logostringLogo URL for white-label branding
domainstringCustom domain for interview links
settingsobjectOrganization-level settings

List Members#

GET/v1/organization/members

List all organization members and their roles

Invite Member#

POST/v1/organization/members

Invite a new member to the organization

Update Member Role#

PATCH/v1/organization/members/{memberId}

Update a member's role

Remove Member#

DELETE/v1/organization/members/{memberId}

Remove a member from the organization


Billing API#

Get Current Plan#

GET/v1/billing/plan

Retrieve 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#

GET/v1/billing/usage

Retrieve credit usage for a date range

Query parameters:

NameTypeDescription
from*datetimeStart date (ISO 8601)
to*datetimeEnd date (ISO 8601)
group_bystringGroup results: 'day', 'week', 'month', 'campaign'

Purchase Credits#

POST/v1/billing/credits/purchase

Purchase additional credits

Request body:

NameTypeDescription
amount*numberNumber of credits to purchase
paymentMethodIdstringStripe payment method ID (uses default if not specified)

List Invoices#

GET/v1/billing/invoices

List 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#

NameTypeDescription
INVALID_REQUEST400Request body is malformed or missing required fields
INVALID_API_KEY401API key is invalid, expired, or revoked
INSUFFICIENT_PERMISSIONS403API key lacks the required permission scope
RESOURCE_NOT_FOUND404The requested resource does not exist
CAMPAIGN_NOT_ACTIVE409Campaign is paused or archived, cannot create invites
INVITE_EXPIRED409Invite has expired and cannot be modified
SESSION_NOT_ACTIVE409Session is not in a state that allows this action
INSUFFICIENT_CREDITS402Not enough credits to perform this action
VALIDATION_ERROR422One or more fields failed validation
RATE_LIMITED429Too many requests — slow down
INTERNAL_ERROR500Unexpected 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
  }
}
NameTypeDescription
totalnumberTotal number of results matching the query
limitnumberMaximum results per page
offsetnumberCurrent offset
hasMorebooleanWhether 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#

  1. Always check HTTP status codes
  2. Parse the error response body for detailed information
  3. Implement retry logic with exponential backoff for 5xx errors and 429
  4. Don't retry 4xx errors (except 429) — they indicate client-side issues
  5. 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.