REST API v1

ORAVYS API

Voice authenticity analysis over HTTP. Send an audio file with your API key and a consent attestation, and receive a result. /api/v1/verdict-only returns the code authentic, synthetic or inconclusive. authentic means no strong synthetic signal was found; it is not a certification of authenticity. Results are advisory.

API key in a header

Every call carries your API key in the X-API-Key header, or as Authorization: Bearer <key>. A key sent in the query string or in a form field is refused. Create and revoke keys on the API keys page.

Key formats

# Live key
oravys_live_...

# Test key: on POST /api/v1/analyze it returns a
# sample response without running an analysis
oravys_test_...

Sending the key

X-API-Key: oravys_live_...

# or
Authorization: Bearer oravys_live_...
Required on every analysis call

A voice is biometric data. Every call that sends audio must carry consent=true, or the pair consent_data_processing=true and consent_voice_processing=true. By sending it you attest that the person whose voice is analyzed gave explicit consent (GDPR Art. 9.2.a, BIPA). Without it the API answers 400 consent_required, and the call is not counted against your quota.

The analysis routes require the detect:audio scope on the key. A key without it receives 403 insufficient_permissions. Keys issued before scopes existed keep their access.

Endpoints

All requests go to https://app.oravys.com. Audio is sent as multipart/form-data in the field audio (wav, mp3, flac, m4a, ogg, webm). The full contract is in the OpenAPI specification.

RouteUseAnswer
POST /api/v1/verdict-only Fast verdict, no report. The call waits for the detector, up to about 90 seconds. Audio up to 25 MB. 10 calls per hour per key. verdict, confidence (0 to 100), verdict_label
POST /api/v1/analyze/async Full analysis as a background job. Audio up to 15 MB. 202 with job_id and poll_url
GET /api/v1/job/{job_id} Status of a job: pending, running, completed, failed. Only the key that submitted it can read it. report_html once completed
DELETE /api/v1/job/{job_id} Cancel a job. status: cancelled
POST /api/v1/analyze Full analysis in one call. The response is streamed: a space every 15 seconds, then the JSON result on the last line. The server stops after about 270 seconds; prefer the async route for long recordings. verdict, confidence_score (0 to 1), report_url
GET /api/v1/health Service status. No key required. status

Retries: send an Idempotency-Key header (8 to 128 characters) on POST requests. The same key with the same body within 24 hours returns the first answer; the same key with a different body answers 422 idempotency_conflict. The streamed answer of /api/v1/analyze is not replayed.

Code examples

Submit a recording as a job, then poll it. Replace the key and the file name.

# Fast verdict
curl -X POST https://app.oravys.com/api/v1/verdict-only \
  -H "X-API-Key: $ORAVYS_API_KEY" \
  -F "audio=@recording.wav" \
  -F "consent=true"

# Full analysis as a job
curl -X POST https://app.oravys.com/api/v1/analyze/async \
  -H "X-API-Key: $ORAVYS_API_KEY" \
  -H "Idempotency-Key: 5f0c2d6e-recording-01" \
  -F "audio=@recording.wav" \
  -F "consent=true"

# Poll until status is completed or failed
curl https://app.oravys.com/api/v1/job/JOB_ID \
  -H "X-API-Key: $ORAVYS_API_KEY"
import os, time, requests

BASE = "https://app.oravys.com"
HEADERS = {"X-API-Key": os.environ["ORAVYS_API_KEY"]}

with open("recording.wav", "rb") as f:
    r = requests.post(
        f"{BASE}/api/v1/analyze/async",
        headers=HEADERS,
        files={"audio": f},
        data={"consent": "true"},
    )
if r.status_code >= 400:
    err = r.json()["error"]
    raise SystemExit(f"{err['code']}: {err['message']}")
job_id = r.json()["job_id"]

while True:
    job = requests.get(f"{BASE}/api/v1/job/{job_id}", headers=HEADERS).json()
    if job["status"] in ("completed", "failed"):
        break
    time.sleep(5)
API calls per month

Each accepted call counts as one API call. Polling or cancelling a job is not counted, and neither is a call refused for missing consent. Beyond the quota the API answers 429 monthly_quota_exceeded until the first day of the next month (UTC).

Plan API calls per month
API plan 1,000
Enterprise Set by contract

Short-term rate limits also apply per key (per minute and per day) and answer 429 rate_limit_exceeded with a retry delay. The fast verdict route is limited to 10 calls per hour per key.

One error format

Every error of the /api/v1 routes has the same shape. code is stable and meant for programs; message is for people; doc_url points here. The top-level code and message are copies kept for clients written against the former format, where error was a string.

{
  "error": {
    "code": "consent_required",
    "message": "...",
    "doc_url": "https://oravys.com/developers#errors"
  },
  "code": "consent_required",
  "message": "..."
}

On POST /api/v1/analyze, an error that happens after the stream has started arrives in the final JSON line with HTTP 200, "success": false and the original status.

CodeHTTPMeaning
missing_api_key401No key in the headers.
invalid_api_key401Unknown, revoked or expired key.
insufficient_permissions403The key lacks the required scope.
consent_required400No consent attestation in the request.
missing_audio, empty_audio400No audio file, or an empty one.
invalid_audio, audio_decode_error400The file could not be read as audio.
audio_too_large413The file exceeds the size limit of the route.
job_not_found404Unknown job, or a job submitted with another key.
result_not_retrievable503The job finished but its result is not reachable yet. Retry shortly.
idempotency_conflict422Idempotency-Key reused with a different body.
monthly_quota_exceeded429Monthly API call quota reached.
rate_limit_exceeded429Too many calls in a short window. Retry after the indicated delay.
analysis_timeout504The synchronous analysis did not finish in time. Use the async route.
internal_error500Unexpected server error.

API access

API calls are included in the API plan. For higher volumes, contact us about Enterprise.