API reference · v1

VetFlash API

The VetFlash API gives your software the same veterinary AI that powers the VetFlash app: clinical chat, scribe notes, transcription and shareable Ask sessions. Requests are JSON over HTTPS and every call spends the credits on your VetFlash account at the in-app rate.

https://vetflash.io/api/v1
Get an API key
curl https://vetflash.io/api/v1/credits \
  -H "Authorization: Bearer $VETFLASH_API_KEY"

Authentication

Send a bearer token in the Authorization header. There are two kinds:

API keys

For your own servers. vf_live_… keys return real answers and spend credits; vf_test_… keys return sample responses and are always free. Create, scope, roll and revoke them in the API dashboard. Never ship a key in a browser or mobile app.

OAuth access tokens

For apps other vets connect to. vfat_… tokens act on behalf of the vet who approved your app, with only the scopes they granted, and spend their credits.

{
  "error": {
    "message": "Invalid or missing credentials.",
    "type": "authentication_error",
    "code": "invalid_api_key"
  }
}

OAuth 2.0 (authorization code + PKCE)

Register an app in OAuth apps to get a client ID (and a secret for confidential apps). PKCE with S256 is required for every client. Server metadata lives at https://vetflash.io/.well-known/oauth-authorization-server.

  1. 1. Send the vet to the consent screen

    Generate a random code_verifier (43-128 characters) and a state. The vet signs in, reviews the scopes and approves. We redirect back to your exact registered redirect_uri with code and state.

    https://vetflash.io/oauth/authorize
      ?response_type=code
      &client_id=vfc_...
      &redirect_uri=https://yourapp.com/callback
      &scope=profile:read chat:write
      &state=RANDOM_STATE
      &code_challenge=BASE64URL(SHA256(code_verifier))
      &code_challenge_method=S256
  2. 2. Exchange the code

    Codes expire after 10 minutes and work once. Confidential clients authenticate with HTTP Basic (or client_secret in the body); public clients send client_id only.

    curl -X POST https://vetflash.io/api/oauth/token \
      -u "$CLIENT_ID:$CLIENT_SECRET" \
      -d grant_type=authorization_code \
      -d code=vfac_... \
      -d redirect_uri=https://yourapp.com/callback \
      -d code_verifier=$CODE_VERIFIER
    {
      "access_token": "vfat_...",
      "token_type": "Bearer",
      "expires_in": 3600,
      "refresh_token": "vfrt_...",
      "scope": "profile:read chat:write"
    }
  3. 3. Refresh and revoke

    Access tokens last one hour. Refresh tokens last 30 days and rotate on every use; presenting an already-used refresh token revokes the whole connection.

    curl -X POST https://vetflash.io/api/oauth/token \
      -u "$CLIENT_ID:$CLIENT_SECRET" \
      -d grant_type=refresh_token \
      -d refresh_token=vfrt_...

Scopes

profile:readYour name, email address and plan.
credits:readMessage, scribe, voice and FlashForm credit balances.
chat:writeAsk clinical questions. Spends message credits.
scribe:writeTurn consultations into clinical notes and transcripts. Spends scribe credits.
ask:readList Ask sessions and read their conversations.
ask:writeCreate and revoke shareable patient chats. Messages spend message credits.

API keys get all 6 scopes unless you narrow them. A call without the required scope returns 403 with code: "insufficient_scope".

Credits & billing

Calls spend the credits on the account behind the key or token, at exactly the in-app rate. Every response reports what it cost (X-VetFlash-Credits-Charged header or a vetflash / credits_charged field). Failed generations are refunded automatically, and max_credits rejects a chat request that would cost more than you allow.

Chat modelWhat it isCredits
vetflashOur fine-tuned veterinary model with medicines-database lookups and a senior clinical review pass.10
vetflowThe practice-operations consultant: rotas, HR, pricing, client comms.30

Legacy model names still work: vetflash-ask → vetflash, vetflash-vetflow → vetflow.

Errors

Errors share one shape. Every response carries an X-Request-Id; include it when you contact us.

400invalid_request_errorThe request is malformed or missing a field.
401authentication_errorMissing, invalid, expired or revoked key or token.
402insufficient_creditsNot enough credits, or the request exceeds `max_credits`.
403permission_errorThe token lacks the scope, or your plan doesn't include the feature.
404not_found_errorThe resource doesn't exist or isn't yours.
429rate_limit_errorMore than 60 requests a minute. Retry after `Retry-After` seconds.
500server_errorSomething failed on our side. Credits are refunded automatically.

Rate limits

60 requests per 60 seconds per API key or OAuth connection. Over the limit you get 429 with a Retry-After header. Ask session links are separately limited to 20 visitor messages a minute.

Streaming

Set "stream": true on chat completions to receive server-sent events in the OpenAI format. VetFlash M2 streams its reasoning as delta.reasoning_content while it researches, then the clinically reviewed answer as delta.content. The final chunk includes the credits charged.

data: {"object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant","content":""}}]}

data: {"object":"chat.completion.chunk","choices":[{"delta":{"reasoning_content":"Considering GI vs systemic causes..."}}]}

data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"Top differentials: "}}]}

data: {"object":"chat.completion.chunk","choices":[{"delta":{},"finish_reason":"stop"}],"vetflash":{"credits_charged":10}}

data: [DONE]

Account

Who am I

GET/api/v1/me

The account behind the key or token, its plan and the scopes this request carries.

Scope profile:readFree
curl https://vetflash.io/api/v1/me \
  -H "Authorization: Bearer $VETFLASH_API_KEY"

Response

{
  "id": "5b0c…",
  "email": "vet@practice.co.uk",
  "name": "Dr Sam Reid",
  "plan": "VetFlash Pro",
  "auth": {
    "method": "api_key",
    "sandbox": false,
    "scopes": [
      "profile:read",
      "credits:read"
    ]
  }
}

Credit balance

GET/api/v1/credits

Live balances for every credit type, plus your plan's monthly allowance.

Scope credits:readFree
curl https://vetflash.io/api/v1/credits \
  -H "Authorization: Bearer $VETFLASH_API_KEY"

Response

{
  "credits": {
    "text": 1240,
    "scribe": 96.5,
    "voice": 24,
    "flashform": 15000
  },
  "allowance": {
    "text": 1500,
    "scribe": 100,
    "voice": 30,
    "flashform": 0
  },
  "last_reset_at": "2026-09-01T00:00:00Z"
}

AI

Chat completions

POST/api/v1/chat/completions

OpenAI-compatible chat with VetFlash models. Point any OpenAI SDK at the base URL and it just works. Set stream: true for server-sent events.

Scope chat:write10 (vetflash) · 30 (vetflow)
modelstringrequired
vetflash or vetflow.
messagesarrayrequired
Conversation so far: { role, content } with roles system, user, assistant.
streamboolean
Stream chat.completion.chunk events. Reasoning arrives as delta.reasoning_content.
patient_idinteger
One of your patients: adds species, breed and age to the context.
max_creditsnumber
Reject with 402 instead of spending more than this.
curl -X POST https://vetflash.io/api/v1/chat/completions \
  -H "Authorization: Bearer $VETFLASH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "vetflash",
    "messages": [
      {
        "role": "user",
        "content": "Maintenance fluid rate for a 12 kg dog?"
      }
    ],
    "max_credits": 20
  }'

Response

{
  "id": "chatcmpl-vf-…",
  "object": "chat.completion",
  "model": "vetflash",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "For a 12 kg dog…"
      },
      "finish_reason": "stop"
    }
  ],
  "vetflash": {
    "credits_charged": 10,
    "sandbox": false
  }
}

Scribe notes

POST/api/v1/scribe/notes

Turn a consultation into a structured clinical note. Send a transcript as JSON, or upload the audio as multipart file and we transcribe it too.

Scope scribe:write1 scribe credit per started 20 min (max 45 min)
transcriptstring
Consultation transcript. Required unless you upload file.
duration_secondsinteger
Length of the consultation. Billing never goes below what the transcript length implies.
fileaudio (multipart)
Audio recording instead of a transcript.
formatstring
markdown (default) or text.
languagestring
Audio language hint for uploads, e.g. en-GB.
curl -X POST https://vetflash.io/api/v1/scribe/notes \
  -H "Authorization: Bearer $VETFLASH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transcript": "Owner reports Bella, a 6 year old Labrador, has been limping on the left fore for three days…",
    "duration_seconds": 540,
    "format": "markdown"
  }'

Response

{
  "id": "note_…",
  "note": "## Presenting complaint\n- Left forelimb lameness, 3 days…",
  "duration_seconds": 540,
  "credits_charged": 1
}

Transcribe audio

POST/api/v1/audio/transcriptions

OpenAI-compatible speech-to-text for consultation recordings. Upload multipart file.

Scope scribe:write1 scribe credit per started 20 min
fileaudio (multipart)required
The recording (mp3, m4a, wav, webm…), up to 45 minutes.
languagestring
Language hint, e.g. en-GB.
curl -X POST https://vetflash.io/api/v1/audio/transcriptions \
  -H "Authorization: Bearer $VETFLASH_API_KEY" \
  -F "file=@consultation.m4a" \
  -F "language=en-GB"

Response

{
  "text": "Owner reports Bella has been limping…",
  "duration_seconds": 540,
  "vetflash": {
    "credits_charged": 1,
    "sandbox": false
  }
}

Ask sessions

Create an Ask session

POST/api/v1/ask/sessions

A shareable chat link seeded with a patient, for a colleague or pet owner. You set the expiry and a credit cap; messages bill to you.

Scope ask:writeFree to create · 10 credits per message
patient_idinteger
One of your patients. Or send patient / context instead.
patientobject
{ pet_name, species, breed, age, notes }.
contextstring
Free-text clinical context.
titlestring
Shown at the top of the chat.
welcome_messagestring
First message the visitor sees.
languagestring
Answer language, e.g. Spanish.
expires_ininteger
Seconds until the link expires (default 7 days, max 90).
credit_limitnumber
Most credits the session may spend (default 100).
max_messagesinteger
Optional cap on visitor messages.
curl -X POST https://vetflash.io/api/v1/ask/sessions \
  -H "Authorization: Bearer $VETFLASH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Bella - post-op questions",
    "patient": {
      "pet_name": "Bella",
      "species": "Dog",
      "breed": "Labrador",
      "age": "6 years"
    },
    "welcome_message": "Hi! Ask me anything about Bella'\''s recovery.",
    "expires_in": 604800,
    "credit_limit": 60
  }'

Response

{
  "token": "0f7c…",
  "url": "https://vetflash.io/ask/0f7c…",
  "state": "active",
  "credit_limit": 60,
  "credits_used": 0,
  "expires_at": "2026-09-22T09:00:00Z"
}

List Ask sessions

GET/api/v1/ask/sessions

Newest first. Page with limit (max 100) and the next_cursor from the previous page.

Scope ask:readFree
limitinteger
Page size, 1-100 (default 20).
cursorstring
next_cursor from the previous page.
curl https://vetflash.io/api/v1/ask/sessions \
  -H "Authorization: Bearer $VETFLASH_API_KEY"

Response

{
  "data": [
    {
      "token": "0f7c…",
      "title": "Bella - post-op questions",
      "state": "active"
    }
  ],
  "next_cursor": null
}

Retrieve an Ask session

GET/api/v1/ask/sessions/{token}

The session with its full conversation.

Scope ask:readFree
curl https://vetflash.io/api/v1/ask/sessions/SESSION_TOKEN \
  -H "Authorization: Bearer $VETFLASH_API_KEY"

Response

{
  "token": "0f7c…",
  "state": "active",
  "messages": [
    {
      "role": "user",
      "content": "Can she climb stairs yet?",
      "created_at": "2026-09-15T10:02:00Z"
    }
  ]
}

Revoke an Ask session

DELETE/api/v1/ask/sessions/{token}

The link stops working immediately. The conversation stays readable to you.

Scope ask:writeFree
curl -X DELETE https://vetflash.io/api/v1/ask/sessions/SESSION_TOKEN \
  -H "Authorization: Bearer $VETFLASH_API_KEY"

Response

{
  "token": "0f7c…",
  "state": "revoked"
}

Ready to build?

Create a free test key and try every endpoint from the playground.

Open the playground