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.
/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))POST https://your-host/detect — public HTTPS only (private network addresses are refused). Optional GET /health returning 200 powers the "Test connection" button.
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.
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.
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.
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.
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}model_version so reports and benchmarks stay reproducible.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);
});
});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.
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.