VetFlash API
A realtime veterinary scribe. One WebSocket: audio in β live transcript out β clinical note out.
Overview
The API is a single WebSocket. A client streams microphone audio and receives live transcription from a real-time speech-to-text engine. The client supplies a typed prompt (the note template as free text); when the session stops, the transcript + prompt are sent to a language model and a structured clinical note is streamed back. A thin HTTP surface handles login and readiness.
Base URL
https://api.vetflash.io Β· WebSocket: wss://api.vetflash.io
Authentication
Exchange an email + password for a short-lived sessionToken. The realtime socket only ever consumes that token β never raw credentials.
POST /v3/auth/session
// Request { "email": "vet@clinic.com", "password": "β’β’β’β’β’β’β’β’" } // 200 Response { "sessionToken": "v3.β¦", "expiresAt": "2026-β¦Z", "scribeCredits": 100 } // 401 β wrong password, unknown email, or disabled account (indistinguishable) { "error": "invalid email or password" }
Connecting to the scribe
WS /v3/scribe/stream β pass the token one of two ways:
- Query param (browsers):
wss://api.vetflash.io/v3/scribe/stream?sessionToken=<token> - Header (server clients):
Authorization: Bearer <token>
A bad or expired token is rejected with HTTP 401 before the socket opens. A disallowed browser Origin β 403. An optional ?language= sets the transcription language (the start frame's language still wins).
Client β Server frames
| Frame | Encoding | Shape & notes |
|---|---|---|
| start | JSON text | { "type":"start", "prompt":"β¦", "language?":"en-GB", "mimetype?":"audio/webm" }Must arrive before audio. prompt required, β€ 8000 chars. |
| audio | binary | Raw Opus/WebM (from MediaRecorder) or linear16 PCM β forwarded to the speech engine as-is. |
| stop | JSON text | { "type":"stop" } β ends transcription, charges credits, triggers the note. |
| cancel | JSON text | { "type":"cancel" } β abort note generation / tear down. |
Server β Client frames
| Frame | Shape |
|---|---|
| ready | { "type":"ready", "sessionId":"β¦", "model":"vetflash-scribe", "scribeCredits":100 } |
| transcript | { "type":"transcript", "text":"β¦", "isFinal":true|false } |
| note (delta) | { "type":"note", "delta":"β¦", "done":false } |
| note (final) | { "type":"note", "text":"β¦", "transcript":"β¦", "done":true, "creditsCharged":1, "scribeCreditsRemaining":99 } |
| error | { "type":"error", "code":"β¦", "message":"β¦" } |
isFinal:false are interim (they update in place for live display); isFinal:true segments are committed and are what the note is built from.Message sequence
Client Server
β POST /v3/auth/session ββββββΆ { sessionToken }
β WS connect ?sessionToken βββββΆ
β {type:start, prompt} βββββΆ pre-check credits β open transcription
β βββββ {type:ready, scribeCredits}
β Β«binary audioΒ» βββββΆ forward to speech engine
β βββββ {type:transcript, isFinal:false} interim
β βββββ {type:transcript, isFinal:true} committed
β {type:stop} βββββΆ flush β charge credits β LLM
β βββββ {type:note, delta} Γ N
β βββββ {type:note, done:true, text, transcript, creditsCharged}Full browser example
const API = "https://api.vetflash.io"; const { sessionToken } = await fetch(`${API}/v3/auth/session`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), }).then(r => r.json()); const ws = new WebSocket(`${API.replace(/^http/,"ws")}/v3/scribe/stream?sessionToken=${sessionToken}`); ws.onopen = async () => { ws.send(JSON.stringify({ type: "start", prompt: "Write a SOAP note.", language: "en-GB" })); const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const rec = new MediaRecorder(stream, { mimeType: "audio/webm" }); rec.ondataavailable = e => { if (ws.readyState === 1) ws.send(e.data); }; rec.start(250); // emit a chunk every 250ms stopBtn.onclick = () => { rec.stop(); ws.send(JSON.stringify({ type: "stop" })); }; }; ws.onmessage = ev => { const m = JSON.parse(ev.data); if (m.type === "ready") console.log("credits:", m.scribeCredits); if (m.type === "transcript") renderTranscript(m.text, m.isFinal); if (m.type === "note" && !m.done) appendNote(m.delta); if (m.type === "note" && m.done) finishNote(m.text, m.creditsCharged, m.scribeCreditsRemaining); if (m.type === "error") console.error(m.code, m.message); };
Languages
Set language per session in the start frame (or ?language= on the URL). Default en-GB.
- Transcription β supports English variants,
es,fr,pt,it,de,nl,hi,ru,jaand more, plusmultifor real-time code-switching (mixed languages in one consult). - Note β written in the same language as the transcript (a Spanish consult β a Spanish note), unless your prompt requests another language.
Credits
scribe_creditsis an integer; 1 credit = 20 minutes of audio.- Session cost:
ceil(seconds / 60 / 20), minimum 1. - Pre-checked at
start(0 credits βinsufficient_creditsbefore transcription opens). - Charged atomically at
stop; a usage row is written in the same transaction, so balances never go negative. - Hard cap 45 minutes per session; cut off cleanly at your credit boundary.
Error codes
| code | Meaning |
|---|---|
invalid_frame | Control frame wasn't valid JSON / failed validation. Session stays open. |
protocol_error | Frame sent in the wrong state (e.g. stop with no active session). |
insufficient_credits | No credits at start, or the session hit your credit boundary. |
inactive_account | The account was disabled after the token was issued. |
session_in_progress | The account already has an active session. Connection stays open β retry after it ends. |
audio_limit / session_limit | Session exceeded the byte cap / the 45-minute cap. |
no_speech | stop produced an empty transcript; no note generated. |
stt_error / note_failed | The speech engine signalled an error / the note model call failed. |
HTTP endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /v3/auth/session | Exchange credentials for a sessionToken. |
| GET | /v3 | Self-describing API metadata (JSON). |
| GET | /health | Readiness (also reports the active storage driver). |
| WS | /v3/scribe/stream | The realtime scribe pipeline. |