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.

Base URL
https://lorvox.io/api/public/v1

New 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.

  1. Go to Dashboard → Settings → Data warehouse API (company admins only).
  2. Click Create key, name it after the consumer (e.g. snowflake-sync).
  3. Copy the key immediately — only a hash is stored, so it is shown once. Keep it in your secret manager.
  4. Revoke a key at any time; requests using it start failing with 401 right away.
Request
curl -H "Authorization: Bearer $LORVOX_API_KEY" \
  "https://lorvox.io/api/public/v1/calls?limit=100"
API keys are secrets equivalent to read access over all of your call transcripts. Never embed one in a browser, mobile app or public repository.

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

FieldTypeDescription
limitinteger1–500. Default 100.
sinceISO 8601Only calls created strictly after this timestamp. Use for incremental sync.
statusenumFilter by call status, e.g. analyzed.
include_transcriptbooleanSet to false to omit transcript, transcript_formatted and utterances.
include_analyticsbooleanSet to false to omit the analytics object.

Example

Request
curl -H "Authorization: Bearer $LORVOX_API_KEY" \
  "https://lorvox.io/api/public/v1/calls?status=analyzed&since=2026-07-01T00:00:00Z&limit=200"
Response 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"
}
Pagination: when 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.

Request
curl -H "Authorization: Bearer $LORVOX_API_KEY" \
  "https://lorvox.io/api/public/v1/calls/3f0c9f1e-1234-4c8e-9a10-9f0b2f0e77aa"
Response 200
{ "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.

Request
curl -H "Authorization: Bearer $LORVOX_API_KEY" \
  "https://lorvox.io/api/public/v1/calls/3f0c9f1e-.../analytics"
Response 200
{
  "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

FieldTypeDescription
iduuidUnique call identifier.
file_namestringOriginal recording file name.
statusenumuploaded, queued, transcribing, transcribed, processing, analyzed, failed.
sourceenumupload, genesys, five9, twilio, nice, talkdesk, aircall, ringcentral, zoom_cc, eight_x_eight, aws_connect, other.
created_attimestamptzWhen the call was ingested. Used for cursor pagination.
duration_secondsintegerAudio duration.
detected_languagestringBCP-47 tag detected or pinned for transcription (e.g. ar-EG, en-US).
sentimentstringpositive, neutral or negative.
sentiment_scorenumber-1 (very negative) to 1 (very positive).
summarystringAI summary of the conversation.
key_topicsstring[]Topics detected in the call.
transcriptstringPlain-text transcript.
transcript_formattedstringTranscript with [MM:SS] timestamps and speaker labels.
utterancesobject[]Sentence-level segments: { speaker, start, end, text }.
analyticsobject | nullAnalytics object described below.

Analytics object

FieldTypeDescription
call_iduuidCall this analytics row belongs to.
csat_proxynumberPredicted satisfaction score.
sentiment_labelstringpositive / neutral / negative.
sentiment_scorenumber-1 to 1.
qa_scorenumberAgent quality score, 0–100.
agent_namestringAgent attributed to the call.
top_topicsobject[]Ranked topics with weights.
talk_rationumberAgent share of speech time (0–1).
silence_rationumberShare of dead air (0–1).
interruptionsintegerOverlapping-speech events.
duration_secondsintegerAnalyzed audio duration.
handle_time_secondsintegerTotal handling time.
hold_time_secondsintegerTime the customer spent on hold.
greeting_okbooleanGreeting requirement met.
verification_okbooleanIdentity verification performed.
disclosure_okbooleanMandatory disclosures delivered.
compliance_scorenumberOverall compliance score.
risk_flagsstring[]Detected risks, e.g. missing_disclosure.
flaggedbooleanTrue when the call needs human review.
computed_attimestamptzWhen 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.

Python — incremental pull
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.

FastAPI — proxy endpoint
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 on id, upserted on each sync.
  • · call_analytics — one row per call, keyed on call_id.
  • · call_utterances — flatten utterances into one row per sentence with call_id, speaker, start, end, text.

Errors

FieldTypeDescription
400Bad requestMalformed parameter such as an invalid since timestamp.
401UnauthorizedMissing bearer token, invalid key, or revoked key.
404Not foundThe call does not exist or belongs to another company.
429Rate limitedOver 120 requests/minute. Honour Retry-After.
500Server errorTransient 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.