InterviewRelay

API Reference#

This page documents the REST endpoints currently exposed by the application. Everything described here is implemented and live; if a feature is not on this page, it does not exist yet.

The API lives at the same origin as the dashboard. There is no separate api.* host.

Authentication#

Two bearer-token mechanisms are supported. Use whichever fits your client:

| Token type | When to use | How to get one | |---|---|---| | Supabase JWT | Browser sessions, mobile apps, anything with a logged-in user | Issued automatically on login; available in the sb-access-token cookie | | API key (ir_live_…) | Headless scripts, cron jobs, server-to-server integrations | Generate one in Settings → API Keys |

Send the token in the Authorization header:

Authorization: Bearer <token>

API keys are scoped to a single user and inherit that user's permissions on the underlying projects and campaigns. Keys can be revoked at any time from the dashboard; revocation takes effect on the next request.

Plaintext API keys are shown exactly once on creation and never stored. If you lose a key, revoke it and create a new one.

Rate limits#

There is no per-key rate limit at the moment. Sustained abuse may trigger temporary IP-level blocks. The bulk export endpoints have a hard cap of 500 sessions per ZIP request to keep response times bounded.

Endpoints#

Export campaign transcripts#

GET /api/campaigns/{campaignId}/transcripts

Returns every transcript for one campaign.

| Query param | Type | Default | Description | |---|---|---|---| | format | json | csv | json | Output format. Also decides the inner file extension when include_audio=true. | | include_audio | true | false | false | When true, returns a streaming ZIP that bundles transcripts and per-session audio files. |

Text response (default): application/json or text/csv body. The JSON envelope includes campaign metadata, the generated-at timestamp, and one entry per session with both messages (raw conversation log) and turns (structured Q&A).

ZIP response (include_audio=true): application/zip containing:

transcripts.json          (or .csv if format=csv)
audio/<sessionId>.<ext>   one file per session that has audio
manifest.json             metadata + list of any sessions whose audio was missing

Examples:

# JSON, text only
curl -H "Authorization: Bearer ir_live_..." \
  "https://your-host/api/campaigns/<campaignId>/transcripts" \
  -o campaign.json

# CSV, text only
curl -H "Authorization: Bearer ir_live_..." \
  "https://your-host/api/campaigns/<campaignId>/transcripts?format=csv" \
  -o campaign.csv

# ZIP, transcripts + audio
curl -H "Authorization: Bearer ir_live_..." \
  "https://your-host/api/campaigns/<campaignId>/transcripts?include_audio=true" \
  -o campaign.zip

Returns 404 if the campaign does not exist or the caller does not own the parent project. Returns 413 if the ZIP request exceeds the session cap.

Export project transcripts#

GET /api/projects/{projectId}/transcripts

Same shape as the campaign endpoint, but walks every campaign in the project.

| Query param | Type | Default | Description | |---|---|---|---| | format | json | csv | json | Output format. | | include_audio | true | false | false | When true, returns a streaming ZIP. |

Text response:

  • JSON: an envelope with campaigns: [{ campaign, transcripts }] so each campaign's data is grouped.
  • CSV: a flat table with Campaign ID and Campaign Name columns up front so the rows can be split by campaign downstream.

ZIP response:

manifest.json
<campaign-slug>/transcripts.json   (or .csv)
<campaign-slug>/audio/<sessionId>.<ext>

The 500-session cap applies across the whole project.

Example:

curl -H "Authorization: Bearer ir_live_..." \
  "https://your-host/api/projects/<projectId>/transcripts?format=csv" \
  -o project.csv

Per-session downloads#

| Method | Path | Returns | |---|---|---| | GET | /api/session/{sessionId}/transcript | Transcript JSON (messages + timestamps) | | GET | /api/session/{sessionId}/pdf | Generated PDF transcript | | GET | /api/session/{sessionId}/files | List of artifacts (transcript JSON, audio, PDF) with 1-hour signed download URLs |

These existed before the bulk export endpoints and remain the right choice when you only need data for a single session.

Script export#

GET /api/scripts/{scriptId}/export

Exports the script definition (steps, locales, policy) as JSON. Useful for backing up or moving a script between environments.

API key management#

These endpoints accept only Supabase JWT tokens (browser sessions). An API key cannot be used to create or revoke another API key — that would defeat the point of being able to revoke a leaked key from the dashboard.

GET    /api/me/api-keys           list non-revoked keys (no plaintext)
POST   /api/me/api-keys           body: { "name": "..." }; returns plaintext ONCE
DELETE /api/me/api-keys/{id}      revoke

The POST response includes key.plaintext. This is the only time it is ever returned — store it immediately or generate a new key.

Errors#

All endpoints return JSON errors of the form { "error": "<message>" } with conventional HTTP status codes:

| Status | Meaning | |---|---| | 400 | Invalid input (bad format, malformed body, …) | | 401 | Missing, malformed, expired, or revoked token | | 404 | Resource not found, or caller does not own it | | 413 | ZIP request exceeded the session cap | | 5xx | Server error — check your logs and retry |

Beyond the cap#

If your project genuinely needs more than 500 sessions in a single archive, the recommended pattern today is to call the campaign endpoint per-campaign in a loop and assemble the result client-side. A real async job pipeline (queue + email-when-ready) is on the roadmap; in the meantime, please get in touch if you hit this limit regularly.