API reference · v1
Lorvox AI REST API
A read-only JSON API for pulling transcripts, analytics, QA scores and compliance results into a data warehouse, BI tool or your own application.
https://lorvox.io/api/public/v1New to the platform? Start with the platform guide.
Authentication
Every request requires an API key sent as a bearer token. Keys are scoped to a single company workspace — a key can only ever read that company's calls.
- Go to
Dashboard → Settings → Data warehouse API(company admins only). - Click Create key, name it after the consumer (e.g.
snowflake-sync). - Copy the key immediately — only a hash is stored, so it is shown once. Keep it in your secret manager.
- Revoke a key at any time; requests using it start failing with
401right away.
curl -H "Authorization: Bearer $LORVOX_API_KEY" \
"https://lorvox.io/api/public/v1/calls?limit=100"Rate limits
Each key is limited to 120 requests per minute. Over-budget requests return 429 with a Retry-After header and this body:
{ "error": "Rate limit exceeded", "limit": 120, "retry_after_seconds": 27 }Fetch up to 500 calls per request rather than making many small calls, and back off on 429 before retrying.
GET /calls
Returns calls for the key's company in ascending created_at order, with transcript and analytics inlined by default.
Query parameters
| Field | Type | Description |
|---|---|---|
| limit | integer | 1–500. Default 100. |
| since | ISO 8601 | Only calls created strictly after this timestamp. Use for incremental sync. |
| status | enum | Filter by call status, e.g. analyzed. |
| include_transcript | boolean | Set to false to omit transcript, transcript_formatted and utterances. |
| include_analytics | boolean | Set to false to omit the analytics object. |
Example
curl -H "Authorization: Bearer $LORVOX_API_KEY" \
"https://lorvox.io/api/public/v1/calls?status=analyzed&since=2026-07-01T00:00:00Z&limit=200"{
"data": [
{
"id": "3f0c9f1e-...",
"file_name": "call-10241.mp3",
"status": "analyzed",
"source": "genesys",
"created_at": "2026-07-02T09:14:03.221Z",
"duration_seconds": 384,
"detected_language": "ar-EG",
"sentiment": "negative",
"sentiment_score": -0.42,
"summary": "Customer disputes a duplicate subscription charge...",
"key_topics": ["billing", "duplicate charge", "refund"],
"transcript": "Thank you for calling...",
"transcript_formatted": "[00:00] Agent: Thank you for calling...",
"utterances": [
{ "speaker": "Agent", "start": 0.12, "end": 4.86, "text": "Thank you for calling..." },
{ "speaker": "Customer", "start": 5.02, "end": 9.4, "text": "I was charged twice..." }
],
"analytics": {
"qa_score": 78.5,
"csat_proxy": 3.4,
"sentiment_label": "negative",
"talk_ratio": 0.58,
"silence_ratio": 0.11,
"interruptions": 3,
"handle_time_seconds": 402,
"hold_time_seconds": 46,
"greeting_ok": true,
"verification_ok": true,
"disclosure_ok": false,
"compliance_score": 66.7,
"risk_flags": ["missing_disclosure"],
"flagged": true,
"computed_at": "2026-07-02T09:21:55.004Z"
}
}
],
"count": 1,
"next_since": "2026-07-02T09:14:03.221Z"
}next_since is not null, pass it as the since parameter of your next request. When it is null, you have reached the end of the current data set.GET /calls/{id}
Returns a single call with its full transcript, utterances and analytics. Calls outside the key's company return 404.
curl -H "Authorization: Bearer $LORVOX_API_KEY" \
"https://lorvox.io/api/public/v1/calls/3f0c9f1e-1234-4c8e-9a10-9f0b2f0e77aa"{ "data": { "id": "3f0c9f1e-...", "transcript": "...", "analytics": { "qa_score": 78.5 } } }GET /calls/{id}/analytics
Returns only the analytics row for a call — scores, ratios and compliance results, without transcript payloads. Use this when you only need metrics.
curl -H "Authorization: Bearer $LORVOX_API_KEY" \
"https://lorvox.io/api/public/v1/calls/3f0c9f1e-.../analytics"{
"data": {
"call_id": "3f0c9f1e-...",
"agent_name": "Sara M.",
"qa_score": 78.5,
"csat_proxy": 3.4,
"sentiment_label": "negative",
"sentiment_score": -0.42,
"top_topics": [{ "topic": "billing", "weight": 0.61 }],
"talk_ratio": 0.58,
"compliance_score": 66.7,
"risk_flags": ["missing_disclosure"],
"flagged": true
}
}If the call has not been analyzed yet, data is null.
Field reference
Call object
| Field | Type | Description |
|---|---|---|
| id | uuid | Unique call identifier. |
| file_name | string | Original recording file name. |
| status | enum | uploaded, queued, transcribing, transcribed, processing, analyzed, failed. |
| source | enum | upload, genesys, five9, twilio, nice, talkdesk, aircall, ringcentral, zoom_cc, eight_x_eight, aws_connect, other. |
| created_at | timestamptz | When the call was ingested. Used for cursor pagination. |
| duration_seconds | integer | Audio duration. |
| detected_language | string | BCP-47 tag detected or pinned for transcription (e.g. ar-EG, en-US). |
| sentiment | string | positive, neutral or negative. |
| sentiment_score | number | -1 (very negative) to 1 (very positive). |
| summary | string | AI summary of the conversation. |
| key_topics | string[] | Topics detected in the call. |
| transcript | string | Plain-text transcript. |
| transcript_formatted | string | Transcript with [MM:SS] timestamps and speaker labels. |
| utterances | object[] | Sentence-level segments: { speaker, start, end, text }. |
| analytics | object | null | Analytics object described below. |
Analytics object
| Field | Type | Description |
|---|---|---|
| call_id | uuid | Call this analytics row belongs to. |
| csat_proxy | number | Predicted satisfaction score. |
| sentiment_label | string | positive / neutral / negative. |
| sentiment_score | number | -1 to 1. |
| qa_score | number | Agent quality score, 0–100. |
| agent_name | string | Agent attributed to the call. |
| top_topics | object[] | Ranked topics with weights. |
| talk_ratio | number | Agent share of speech time (0–1). |
| silence_ratio | number | Share of dead air (0–1). |
| interruptions | integer | Overlapping-speech events. |
| duration_seconds | integer | Analyzed audio duration. |
| handle_time_seconds | integer | Total handling time. |
| hold_time_seconds | integer | Time the customer spent on hold. |
| greeting_ok | boolean | Greeting requirement met. |
| verification_ok | boolean | Identity verification performed. |
| disclosure_ok | boolean | Mandatory disclosures delivered. |
| compliance_score | number | Overall compliance score. |
| risk_flags | string[] | Detected risks, e.g. missing_disclosure. |
| flagged | boolean | True when the call needs human review. |
| computed_at | timestamptz | When analytics were computed. |
Syncing to a data warehouse
The API is designed for incremental cursor sync: store the last next_since you received and resume from it on the next run. A scheduled job every 15 minutes is usually enough.
import os, requests
BASE = "https://lorvox.io/api/public/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['LORVOX_API_KEY']}"}
def fetch_calls(since=None):
rows, cursor = [], since
while True:
params = {"limit": 500, "status": "analyzed"}
if cursor:
params["since"] = cursor
r = requests.get(f"{BASE}/calls", headers=HEADERS, params=params, timeout=60)
r.raise_for_status()
body = r.json()
rows.extend(body["data"])
cursor = body.get("next_since")
if not cursor:
return rows, since if not rows else rows[-1]["created_at"]The same endpoints work from any HTTP client. If you expose them through your own service (for example a FastAPI wrapper), keep the Lorvox key server-side and apply your own auth to the wrapper.
import os, httpx
from fastapi import FastAPI, HTTPException
app = FastAPI()
BASE = "https://lorvox.io/api/public/v1"
@app.get("/calls")
async def calls(since: str | None = None, limit: int = 100):
params = {"limit": limit}
if since:
params["since"] = since
async with httpx.AsyncClient(timeout=60) as client:
r = await client.get(
f"{BASE}/calls",
headers={"Authorization": f"Bearer {os.environ['LORVOX_API_KEY']}"},
params=params,
)
if r.status_code != 200:
raise HTTPException(r.status_code, r.text)
return r.json()Recommended warehouse tables
- ·
calls— one row per call, keyed onid, upserted on each sync. - ·
call_analytics— one row per call, keyed oncall_id. - ·
call_utterances— flattenutterancesinto one row per sentence withcall_id,speaker,start,end,text.
Errors
| Field | Type | Description |
|---|---|---|
| 400 | Bad request | Malformed parameter such as an invalid since timestamp. |
| 401 | Unauthorized | Missing bearer token, invalid key, or revoked key. |
| 404 | Not found | The call does not exist or belongs to another company. |
| 429 | Rate limited | Over 120 requests/minute. Honour Retry-After. |
| 500 | Server error | Transient failure — retry with exponential backoff. |
All errors return JSON of the form:
{ "error": "Invalid API key" }CORS is enabled for these endpoints, but because requests require a secret key they should always be made from a server.