{"openapi":"3.1.0","info":{"title":"GoldNovaAI Voice QA API","version":"1.0.0","description":"Send call transcripts from any voice platform and read back the QA result. Results are QA signals for human review, not legal or compliance determinations. Audio recordings are uploaded in the app (or arrive through a Twilio connection); this API accepts transcripts."},"servers":[{"url":"https://goldnovaai.com"}],"security":[{"apiKey":[]}],"paths":{"/api/v1/calls":{"post":{"operationId":"ingestCall","summary":"Ingest a call transcript","description":"Idempotent on externalCallId: replaying the identical call returns 200 with duplicate=true and stores nothing twice. Evaluation runs asynchronously after the response.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestRequest"}}}},"responses":{"200":{"description":"The identical call was already stored (duplicate=true).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResponse"}}}},"201":{"description":"Call accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResponse"},"example":{"id":"6f1c0a52-8d1e-4c43-9a7b-2f6a3d9e1b10","externalCallId":"call-1042","status":"ready","duplicate":false}}}},"400":{"description":"Validation failed; details lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, unknown or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The 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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the calls:write scope, or it is an MCP credential (those only work on /api/mcp).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The same externalCallId is already stored with different content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The JSON body is larger than the limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key; retry after the Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait before retrying (always 30).","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The key store is briefly unavailable; retry after the Retry-After seconds. Not an invalid key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/calls/{id}":{"get":{"operationId":"getCall","summary":"Read status, QA result and findings","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The id returned by ingestCall."}],"responses":{"200":{"description":"The call with its latest QA result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallResult"}}}},"400":{"description":"Validation failed; details lists each problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, unknown or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the calls:write scope, or it is an MCP credential (those only work on /api/mcp).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such call in the key’s organization (also returned for another organization’s call id).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests for this key; retry after the Retry-After seconds.","headers":{"Retry-After":{"description":"Seconds to wait before retrying (always 30).","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The key store is briefly unavailable; retry after the Retry-After seconds. Not an invalid key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An organization API key (Organization & API → API keys). \"x-api-key: <key>\" is accepted as well."}},"schemas":{"Segment":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"speaker":{"type":"string","maxLength":40,"description":"Case-insensitive. agent, assistant, bot, ai, model, voice_agent, voiceagent → agent; customer, user, caller, client, human, contact, lead → customer; anything else (or missing) → unknown."},"text":{"type":"string","minLength":1,"maxLength":4000},"startTime":{"type":"number","minimum":0,"maximum":86400,"description":"Seconds. Provide together with endTime."},"endTime":{"type":"number","minimum":0,"maximum":86400,"description":"Seconds, not before startTime."}}},"IngestRequest":{"type":"object","additionalProperties":false,"required":["externalCallId"],"oneOf":[{"required":["transcript"],"not":{"required":["transcriptText"]}},{"required":["transcriptText"],"not":{"required":["transcript"]}}],"description":"Provide exactly one of transcript or transcriptText. Unknown fields are rejected.","properties":{"externalCallId":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[\\w.:@/+=~-]+$"},"agent":{"type":"object","additionalProperties":false,"required":["id"],"properties":{"id":{"type":"string","minLength":1,"maxLength":200,"pattern":"^[\\w.:@/+=~-]+$"},"name":{"type":"string","minLength":1,"maxLength":200}}},"startedAt":{"type":"string","format":"date-time","description":"ISO-8601 with offset."},"endedAt":{"type":"string","format":"date-time","description":"Not before startedAt."},"durationSeconds":{"type":"number","minimum":0,"maximum":86400},"recordingUrl":{"type":"string","format":"uri","maxLength":2000,"pattern":"^https://","description":"Reference only: the server never fetches it."},"transcript":{"type":"array","minItems":1,"maxItems":5000,"items":{"$ref":"#/components/schemas/Segment"}},"transcriptText":{"type":"string","minLength":1,"maxLength":400000,"description":"Lines like \"Agent: …\" / \"Customer: …\", optional [mm:ss] prefixes."},"metadata":{"type":"object","maxProperties":50,"description":"Scalar values, short arrays or flat objects; at most 16384 bytes.","additionalProperties":true}}},"IngestResponse":{"type":"object","required":["id","externalCallId","status","duplicate"],"properties":{"id":{"type":"string","format":"uuid"},"externalCallId":{"type":"string"},"status":{"type":"string","enum":["received","ready","evaluating","evaluated","failed"]},"duplicate":{"type":"boolean"}}},"Evidence":{"type":"object","required":["quote","startTime","endTime"],"properties":{"quote":{"type":"string","description":"Appears verbatim in the transcript."},"startTime":{"type":["number","null"]},"endTime":{"type":["number","null"]}}},"Finding":{"type":"object","required":["rule","ruleType","severity","status","explanation","evidence"],"properties":{"rule":{"type":"string"},"ruleType":{"type":"string"},"severity":{"type":"string","enum":["low","medium","high","critical"]},"status":{"type":"string","enum":["review","violation"]},"explanation":{"type":"string"},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/Evidence"}}}},"CallResult":{"type":"object","required":["id","externalCallId","status","transcriptSource","transcriptQuality","failureReason","evaluation","findings","notice"],"properties":{"id":{"type":"string","format":"uuid"},"externalCallId":{"type":"string"},"status":{"type":"string","enum":["received","ready","evaluating","evaluated","failed"]},"transcriptSource":{"type":["string","null"],"enum":["provided","asr",null]},"transcriptQuality":{"type":["string","null"],"enum":["ok","low",null],"description":"'low' when speech-to-text output looks unreliable; null for provided transcripts."},"failureReason":{"type":["string","null"]},"evaluation":{"type":["object","null"],"required":["result","resultLabel","qaScore","policyVersion","evaluatedAt"],"properties":{"result":{"type":"string","enum":["pass","review","violation"],"description":"\"No issues detected\", \"Review recommended\" or \"Potential violation\"."},"resultLabel":{"type":"string"},"qaScore":{"type":"number","minimum":0,"maximum":100},"policyVersion":{"type":"integer"},"evaluatedAt":{"type":"string","format":"date-time"}}},"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"}},"notice":{"type":"string"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"},"details":{"type":"array","items":{"type":"string"}}}}}}}