sealverity.ai
Developer docs

Self-hosted detector API

Run an open-source or in-house model (for example Resemble DETECT on your own GPU servers) behind a small HTTPS service, register it in Settings → Detection Providers → Custom / self-hosted detectors, and Verify treats it like any other detector: it joins the combined verdict (with its own weight), benchmarks, image regions, video/audio timelines and signed reports.

REST API reference (OpenAPI 3) — covers /v1/check (media link or file, used by the browser extension), /v1/me, /v1/validate, /v1/voice/check, /v1/kyc/check and the outbound webhook event format. Download openapi.json. Import it into Postman, Swagger UI or your SOAR platform.
curl -X POST https://sealverity.ai/api/public/v1/check \
  -H "x-api-key: lvk_..." -H "content-type: application/json" \
  -d '{"url":"https://example.com/photo.jpg"}'

# Verify an outbound webhook (json format)
expected = "sha256=" + hex(HMAC_SHA256(secret, X-Lavisho-Timestamp + "." + raw_body))

1. Endpoint

POST https://your-host/detect — public HTTPS only (private network addresses are refused). Optional GET /health returning 200 powers the "Test connection" button.

2. Authentication

Verify sends one header you choose (e.g. Authorization: Bearer … or X-API-Key). The value is stored as a server secret whose name starts with CUSTOM_DETECTOR_; it never reaches the browser.

3. Request

Multipart file (default) — multipart/form-data fields:

file          the original evidence file (or a single video frame / 4-second audio chunk)
media_type    image | video | audio | text | document
sha256        hex SHA-256 of the original
duration_sec  (audio/video, when known)

Signed URL — application/json; download the file within 10 minutes:

{
  "media_type": "audio",
  "sha256": "9f2c…",
  "file_url": "https://…signed…",
  "file_name": "call.wav",
  "duration_sec": 42.5
}

Text and documents are sent as JSON with a text field. Sampled video frames and audio chunks are always sent as multipart files.

4. Response

200 OK
{
  "score": 0.93,                 // probability the media is synthetic or manipulated (0–1 or 0–100)
  "label": "synthetic",          // optional
  "model_version": "detect-2b@2026-09",
  "regions": [                   // optional, images: fractions of width/height (0–1)
    { "x": 0.31, "y": 0.12, "w": 0.22, "h": 0.30, "score": 0.97, "label": "face" }
  ],
  "segments": [                  // optional, audio/video: seconds
    { "start": 12.0, "end": 16.0, "score": 0.88 }
  ]
}

Field names can differ: set the score path (e.g. result.fake_prob or $.data[0].score), scale, and optional label, version, regions and segments paths when registering. Region and segment scores use the same scale as the main score.

5. Errors and timeouts

Return a non-2xx status with a short message on failure. Each call has the timeout you set (1–120 s). A failed or slow detector is recorded but never blocks the verdict; the other detectors still decide.

6. Minimal reference server (Python)

from fastapi import FastAPI, UploadFile, Form, Header, HTTPException
app = FastAPI()
API_KEY = "…"  # same value as your CUSTOM_DETECTOR_… secret

@app.get("/health")
def health(): return {"ok": True}

@app.post("/detect")
async def detect(file: UploadFile, media_type: str = Form(...), sha256: str = Form(...),
                 authorization: str = Header("")):
    if authorization != f"Bearer {API_KEY}": raise HTTPException(401)
    data = await file.read()
    score, segments = run_model(data, media_type)   # your model, e.g. Resemble DETECT
    return {"score": score, "label": "synthetic" if score >= 0.5 else "authentic",
            "model_version": "detect-2b", "segments": segments}

7. Good practice

8. Call-centre voice check API

POST https://sealverity.ai/api/public/v1/voice/check with header x-api-key: lvk_… (create keys in Settings → API). Send a short chunk (2–10 s works best). The result usually returns in a few seconds; detectors slower than 8 s are skipped for that chunk. Minutes analysed are counted for billing.

Accepted bodies:

# 1) Multipart file (wav / mp3 / m4a)
curl -X POST .../api/public/v1/voice/check -H "x-api-key: lvk_..." -F audio=@chunk.wav

# 2) JSON with raw audio (Twilio sends 8 kHz mu-law)
{"audio_base64": "...", "encoding": "mulaw" | "pcm16" | "wav" | "mp3", "sample_rate": 8000, "reference": "call-123"}

# 3) JSON with a public HTTPS recording URL (SIP / telephony recording, max 10 MB)
{"url": "https://recordings.example.com/call-123.wav"}

Response:

{ "synthetic_voice_score": 72.4, "threshold": 25, "status": "synthetic_suspected" | "uncertain" | "likely_human",
  "agreement": "high", "duration_s": 5, "detectors": [{"provider":"resemble","score":80.1,"error":null}, ...],
  "processing_ms": 3100, "note": "Indicator only, not proof." }

In sovereign mode only self-hosted audio detectors run and recording URLs are not fetched.

Twilio Media Streams example (Node). Add <Start><Stream url="wss://your-relay/twilio"/></Start> to your TwiML; the relay buffers 5 s of caller audio and checks it:

import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 8080, path: "/twilio" });
wss.on("connection", (ws) => {
  let buf = [];   // mu-law bytes, 8000 per second
  ws.on("message", async (raw) => {
    const m = JSON.parse(raw);
    if (m.event !== "media" || m.media.track !== "inbound") return;
    buf.push(Buffer.from(m.media.payload, "base64"));
    if (buf.reduce((a, b) => a + b.length, 0) < 8000 * 5) return;
    const chunk = Buffer.concat(buf); buf = [];
    const r = await fetch("https://sealverity.ai/api/public/v1/voice/check", {
      method: "POST",
      headers: { "content-type": "application/json", "x-api-key": process.env.VERIFY_KEY },
      body: JSON.stringify({ audio_base64: chunk.toString("base64"), encoding: "mulaw", sample_rate: 8000, reference: m.streamSid }),
    }).then((x) => x.json());
    if (r.status === "synthetic_suspected") console.warn("Possible synthetic voice", m.streamSid, r.synthetic_voice_score);
  });
});

9. Live meeting bot media stream

Meeting bots (Live calls page) use Recall.ai. Recall streams each participant's audio (16 kHz PCM) and camera frames to wss://…/api/public/recall/ws with a per-session token; Verify scores 10-second chunks with the same detectors as uploads.

10. Identity check (KYC) API

POST https://sealverity.ai/api/public/v1/kyc/check with x-api-key: lvk_…, as multipart/form-data:

document_front   required  JPEG/PNG, max 8 MB
document_back    optional  JPEG/PNG
selfie           required  photo (JPEG/PNG) or short video (MP4/WebM, max 25 MB)
selfie_frame     optional  a JPEG still from the selfie video (needed for face match when selfie is a video)
reference        optional  your customer / case ID
callback_url     optional  public HTTPS URL that receives the result as well

curl -X POST .../api/public/v1/kyc/check -H "x-api-key: lvk_..." \
  -F document_front=@id-front.jpg -F selfie=@selfie.jpg -F reference=cust-981 \
  -F callback_url=https://example.com/hooks/kyc

Response (also POSTed to callback_url with event: "kyc.check.completed"; header X-Verify-Signature: sha256=HMAC-SHA256(body, your API key)):

{ "id": "…", "decision": "pass" | "review" | "reject",
  "reasons": [{"code":"face_weak","severity":"review","message":"Selfie vs ID photo similarity 84% is below the pass level 90%."}],
  "doc_score": 12.5, "selfie_score": 8.1, "face_similarity": 84.0, "mrz_valid": true,
  "masked_fields": {"surname":"E***","document_number":"******02C","date_of_birth":"1974-**-**", …},
  "reference": "cust-981" }

Thresholds come from your organisation's Identity check settings. "review" results appear in the Review Queue for a human decision. Typical response time is 10–40 seconds. Identity images are deleted after your retention period (default 30 days) unless attached to a case.