Docs

API reference

Two endpoints: send a call, read its result. Machine-readable description: OpenAPI 3.1 (JSON). Results are QA signals for human review.

Authentication

Create a key in Settings & API (organization owners and admins). Send it on every request as Authorization: Bearer gnk_… or x-api-key: gnk_…. A key belongs to one organization and can only read and write that organization’s calls. Only a one-way hash is stored, so a lost key cannot be recovered: revoke it and create another. Keep keys on your server, never in a browser or mobile app. MCP connection credentials are a different kind and are refused here (and API keys are refused on the MCP endpoint).

POST /api/v1/calls

Ingest a call transcript. Unknown fields are rejected. Provide exactly one of transcript or transcriptText. Audio recordings are uploaded in the app or arrive through a Twilio connection; this endpoint accepts transcripts.

FieldTypeNotes
externalCallIdstring, required1–200 characters: letters, digits and . _ : @ / + = ~ -. Your identifier; the idempotency key.
agent{ id, name? }The voice agent that handled the call.
startedAt, endedAtISO-8601 with offsetendedAt must not precede startedAt.
durationSecondsnumber0 to 24 hours.
recordingUrlhttps:// URLA reference only. The server never fetches it.
transcriptarray of segments{ speaker, text, startTime?, endTime? }, 1–5,000 segments; startTime and endTime together, in seconds. speaker (case-insensitive): agent / assistant / bot / ai / model / voice_agent / voiceagent → agent; customer / user / caller / client / human / contact / lead → customer; anything else or missing → unknown.
transcriptTextstringAlternative to transcript: lines such as Agent: … and Customer: …, optional [mm:ss] prefixes. Up to 400,000 characters.
metadataobjectUp to 50 keys and 16 KB; scalar values, short arrays or flat objects.
Example
curl -X POST https://goldnovaai.com/api/v1/calls \
  -H "Authorization: Bearer $GOLDNOVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalCallId": "call-1042",
    "agent": { "id": "support-bot-1", "name": "Support bot" },
    "transcript": [
      { "speaker": "agent", "text": "Hello, this call is recorded for quality purposes.", "startTime": 0, "endTime": 4 },
      { "speaker": "customer", "text": "Okay.", "startTime": 4, "endTime": 5 },
      { "speaker": "agent", "text": "We guarantee a full refund.", "startTime": 41.2, "endTime": 44.0 }
    ]
  }'
Response: 201, or 200 with duplicate: true
{ "id": "6f1c0a52-8d1e-4c43-9a7b-2f6a3d9e1b10", "externalCallId": "call-1042", "status": "ready", "duplicate": false }

Idempotency

externalCallId is unique per organization. Replaying the identical call returns 200 with "duplicate": true and stores nothing twice (it also does not count against your allowance twice, and is still accepted when the allowance is used up). Sending the same id with different content returns 409 call_conflict; the stored call and its evidence are kept.

GET /api/v1/calls/{id}

The status, the QA result and the findings of a call in the key’s organization. evaluation is null until the call has been evaluated.

Example
curl https://goldnovaai.com/api/v1/calls/6f1c0a52-8d1e-4c43-9a7b-2f6a3d9e1b10 \
  -H "Authorization: Bearer $GOLDNOVA_API_KEY"
Response
{
  "id": "6f1c0a52-8d1e-4c43-9a7b-2f6a3d9e1b10",
  "externalCallId": "call-1042",
  "status": "evaluated",
  "transcriptSource": "provided",
  "transcriptQuality": null,
  "failureReason": null,
  "evaluation": {
    "result": "violation",
    "resultLabel": "Potential violation",
    "qaScore": 62.5,
    "policyVersion": 3,
    "evaluatedAt": "2026-10-01T09:31:02.114Z"
  },
  "findings": [
    {
      "rule": "Prohibited claims",
      "ruleType": "prohibited_claim",
      "severity": "high",
      "status": "violation",
      "explanation": "The agent used a prohibited phrase.",
      "evidence": [{ "quote": "We guarantee a full refund.", "startTime": 41.2, "endTime": 44.0 }]
    }
  ],
  "notice": "QA signal for human review. Findings are automated indications, not legal or compliance determinations."
}

Call lifecycle

received (accepted) → ready (transcript attached) → evaluating → evaluated. A call that cannot be processed ends as failed with a failureReason; a failed call is never shown as evaluated and never carries a QA result. Re-sending a call that already has a transcript changes nothing. Evaluation runs after the response, so read the call again until status is evaluated.

Limits and allowances

  • Request body: 1.5 MB (413 above that).
  • Rate limit: 120 requests per minute per key. 429 rate_limited carries Retry-After: 30 (seconds). If the limiter itself is unavailable the request is also refused with 429 rather than let through, so a 429 is always safe to retry after the delay.
  • Allowances are per organization and plan, and are enforced on the server for every route that creates work. This API consumes the QA call allowance only (transcripts need no transcription); audio transcription minutes and AI assessments are separate allowances. When the call allowance is used up, new calls are refused with 402 quota_exceeded (body: limit, used, resetsAt); stored calls and reports stay available. See pricing.

Allowances per organization and billing period (the numbers the server enforces):

Allowances per organization and period
PlanPriceQA callsAudio transcriptionAI assessments
Free€0200300 min50
Starter€99 / month2,0003,000 min500
Team€299 / month10,00015,000 min3,000
EnterpriseIndividual terms: contact us

Audio recordings

There is no REST endpoint for audio. Recordings are uploaded in the app (up to 50 MiB and 2 hours; they are converted and, if needed, split before speech-to-text) or arrive from a Twilio connection (developer preview). Calls created from audio have the transcript source asr, carry no speaker labels, and use the audio-minutes allowance. If transcription is temporarily unavailable on our side the call is marked failed with a retryable message; the recording is kept and can be retried in the app.

Retry semantics

  • Safe to repeat: POST /api/v1/calls with the same externalCallId and content (returns the stored call), and every GET.
  • Retry after Retry-After: 429, and 5xx responses (use exponential back-off).
  • Do not retry unchanged: 400, 401, 402 (allowance used up until resetsAt or a plan change), 409, 413, 422.
  • API keys carry a single scope, calls:write; there is no read-only REST key, and the same key is needed to read a call.

Deletion and retention

There is no public API to delete a call. An organization admin can delete a call in the app: this removes its recording, transcript, evaluations and evidence, but does not give back the allowance it used. Raw recordings are scheduled for automatic deletion 90 days after upload by default; transcripts, reports and evidence are kept until you delete them. See security.

Versioning

The version is in the path (/api/v1) and in the OpenAPI document (info.version). Within v1 we only add optional request fields, response fields and error codes; a breaking change ships under a new path version with notice in these docs. Clients should ignore response fields they do not know.

Errors

Error bodies are JSON: { "error": "…", "code": "…" }; validation errors add details. Some 400 responses (a malformed call id or unreadable JSON) carry no code. There is no outbound provider-error code on this API: transcripts need no provider call.

StatuscodeMeaning
400invalid_payloadValidation failed; details lists each problem.
401invalid_api_keyMissing, malformed, unknown or revoked key.
402quota_exceededThe organization used its call allowance for the current period. The body carries limit, used and resetsAt. Re-sending a call that is already stored is still accepted.
403insufficient_scopeThe key lacks the calls:write scope, or it is an MCP credential (those only work on /api/mcp).
404—No such call in the key’s organization (also returned for another organization’s call id).
409call_conflictThe same externalCallId is already stored with different content.
413—The JSON body is larger than the limit.
503temporarily_unavailableThe key store is briefly unavailable; retry after the Retry-After seconds. Not an invalid key.
429rate_limitedToo many requests for this key; retry after the Retry-After seconds.