Concepts
Records
The documents Vetnio writes for a consultation (clinical note, client communication, referral letter) and how to generate, read, write, delete and version them.
A record is one of the documents Vetnio writes for a consultation (one visit). After the vet records the visit, Vetnio writes the clinical note for the medical record, and can write a client communication for the animal's owner and a referral letter for another clinic. Most integrations generate the clinical note, read it, and copy it into their practice system.
Record types
recordType | In Vetnio | Written for | Format | Through the API |
|---|---|---|---|---|
clinical note | Clinical note | The medical record | HTML, or a JSON object when generated from a structured template | Generate, read, write, delete, versions |
communication | Client communication | The animal owner | HTML | Generate, read, write, delete, versions |
referral | Referral letter | Another clinician or clinic | HTML | Read and versions only |
Records have no IDs. A consultation holds at most one record of each type, so a record is addressed by its consultation and its recordType. The values contain a space: URL-encode them in query strings (recordType=clinical%20note) and send them as-is in JSON bodies.
Anyone who can open the consultation can read its records; writing needs write access to the consultation. Archived consultations are read-only, and writes return 403 with data.reason set to archived_consultation. All record endpoints need a personal API key (see Authentication).
Reading a record
GET/workspaces/{workspaceId}/consultations/{consultationId}/records returns the content itself, not an envelope: an HTML string, a JSON object for a structured clinical note, or null when the consultation has no record of that type.
curl "https://vetnio.com/api/v2/workspaces/demo-north-clinic/consultations/a7f71ad7-8747-4165-aa79-bafee0044efe/records?recordType=clinical%20note" \
-H "Authorization: Bearer $VETNIO_API_KEY""<strong>History</strong><br />Bella, an 8-year-old Labrador, has had intermittent left hind lameness for 2 weeks.<br /><br /><strong>Plan</strong><br />• Carprofen 2 mg/kg PO q12h for 7 days<br />• Recheck in 14 days"When the consultation's clinical-note template is linked to a structured template, the clinical note is a JSON object with one key per field of that template, and values can contain inline HTML. Read the field definitions with GET/workspaces/{workspaceId}/json-templates/{jsonTemplateId}, using the consultation's clinicalNoteJsonTemplateId. For example:
{
"anamnesis": "Intermittent left hind lameness for 2 weeks; worse after exercise.",
"examination": "T 38.6 °C<br />Mild pain on extension of the left stifle.",
"treatment": "Carprofen 2 mg/kg PO q12h for 7 days."
}Reading has no side effects and is safe to poll. The content has no metadata; use the version history below for timestamps.
Some templates ask Vetnio to highlight text the vet should confirm, such as a value it was unsure of, a finding nobody mentioned or a suggestion of its own. Until the vet settles those highlights in Vetnio, the API leaves the highlighted text out of the clinical notes it returns: GET/workspaces/{workspaceId}/consultations/{consultationId}/records, and the clinicalNote of a generation and of its callback. Text the vet let Vetnio settle is returned as plain text. A version read returns the note exactly as stored, highlights included.
Lifecycle
| State | GET …/records returns | How it gets there |
|---|---|---|
| None | null | A new consultation, or after a delete |
| Generating | The previous content, or null | A generation is running; the consultation's isGeneratingClinicalNote or isGeneratingCommunication is true |
| Current | The content | A generation finished, or you or a user saved content |
| Deleted | null | DELETE/workspaces/{workspaceId}/consultations/{consultationId}/records; earlier versions stay in the history |
Generating a clinical note
POST/workspaces/{workspaceId}/consultations/{consultationId}/clinical-note/generate writes the clinical note with AI and saves it as the record's current version. First it transcribes any recordings that are still waiting and waits for every recording on the consultation to finish, for up to 10 minutes. It then uses the full transcript, the consultation's additional info and uploaded files, the template, the linked patient's details and earlier visits (free-text notes only), and the clinic-wide and personal AI instructions. Document a visit walks through the whole recording flow.
The response depends on the mode:
| Mode | Response | When the note is ready |
|---|---|---|
| Synchronous (default) | { "clinicalNote": "<html>" } | In the response; it is already saved |
| Synchronous, structured template | { "message": "Structured document generation initiated" } | Poll the consultation until isGeneratingClinicalNote is false, then read the record |
callbackUrl set | { "message": "Callback URL has been queued" } | Vetnio POSTs the note or an error to your URL |
Generated notes use <strong> headings, <br /> line breaks and • bullet characters rather than heading, paragraph or list elements:
{
"clinicalNote": "<strong>History</strong><br />Bella, an 8-year-old Labrador, has had intermittent left hind lameness for 2 weeks.<br /><br /><strong>Examination</strong><br />• T 38.6 °C<br />• Mild pain on extension of the left stifle<br /><br /><strong>Plan</strong><br />• Carprofen 2 mg/kg PO q12h for 7 days<br />• Recheck in 14 days"
}Latency. Generation usually takes well under a minute, plus any wait for transcription. The whole request, transcription wait included, is limited to 10 minutes: set your HTTP client timeout to at least 600 seconds, or use a callback. A request cut off by that limit returns 504 in synchronous mode and sends no callback at all in callback mode.
Callbacks. Each callback is a single POST with an X-Vetnio-Callback-Status header of success (body { "clinicalNote": "<html>" }) or error. It is not signed and not retried, and your endpoint must answer within 30 seconds. For a structured-template note the success callback carries {} and is sent when background generation starts, so read the record once isGeneratingClinicalNote is false. Receive the callback shows the exact payloads and a receiver.
Polling. Poll GET/workspaces/{workspaceId}/consultations/{consultationId} every 2–5 seconds. isGeneratingClinicalNote goes back to false when generation fails as well as when it succeeds. Note the record's currentVersionId from the version list before you start: if the flag clears and currentVersionId has not changed, the generation failed.
Template. templateId selects the template and also saves it as the consultation's selected clinical-note template. Without it, the consultation's selected template is used, or none. auto generates without a template and clears the selection. An ID that does not exist in the clinic is not rejected: the note is generated without a template and the selection is cleared, so check IDs against GET/workspaces/{workspaceId}/templates. See Templates.
From scratch or update. shouldGenerateFromScratch defaults to true: the note is rewritten from the full transcript and replaces the current one. Set it to false when more audio was recorded after the first note, for example after lab results: Vetnio keeps the current note, including manual edits, and adds what was said in recordings transcribed since the last generation. It falls back to from-scratch when there is no note yet or when templateId names a different template than the consultation's current one.
If the recording contains nothing clinically relevant, generation does not fail. When the current note holds text someone wrote and the consultation has no uploaded files, Vetnio treats that note as the source and rewrites it from scratch, for example into the selected template. Otherwise it keeps the existing note, or saves a short message in the recording language, such as "There seems to be something wrong with your recording, since no useful information was found in the recording. Please check your microphone and try again." Check for that before you file the note.
Generating with POST /records
POST/workspaces/{workspaceId}/consultations/{consultationId}/records without content also generates, synchronously and from scratch. It is the only way to generate a client communication through the API. Unlike the endpoint above, it does not wait for transcription and never returns 400 for a missing transcript: on an empty consultation it saves the "no useful information" message and returns { "success": true }. Call it once transcriptionStatus is completed, and generate the clinical note first: the client communication is written from the transcript and the current note. When there is no usable recording (none, or nothing clinically relevant) and no uploaded file, a client communication is written from the clinical note alone, so a note you wrote through the API is enough.
Writing your own content
PUT/workspaces/{workspaceId}/consultations/{consultationId}/records replaces a clinical note or client communication with your content and creates the record if it does not exist. POST/workspaces/{workspaceId}/consultations/{consultationId}/records with content does the same. Send the whole body; there are no partial updates, no concurrency check, and the last write wins.
curl -X PUT "https://vetnio.com/api/v2/workspaces/demo-north-clinic/consultations/a7f71ad7-8747-4165-aa79-bafee0044efe/records" \
-H "Authorization: Bearer $VETNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recordType": "clinical note",
"content": "<strong>History</strong><br />Bella, an 8-year-old Labrador, has had intermittent left hind lameness for 2 weeks.<br /><br /><strong>Plan</strong><br />• Carprofen 2 mg/kg PO q12h for 7 days<br />• Recheck in 14 days"
}'{
"success": true
}Content is always stored as HTML, so you cannot write a structured (JSON) clinical note. An empty string saves an empty version, after which a read returns null. Content is limited to 50,000 characters; longer content fails with 500, so check the length before sending.
Allowed HTML
Vetnio sanitizes every write. It keeps common text markup: h1–h6, p, br, strong, b, em, i, u, s, ul, ol, li, blockquote, pre, code, hr, div, span and table elements. The only attributes kept are href, name and target on links (http, https, ftp, mailto and tel URLs), and in clinical notes Vetnio's own highlight attributes data-open-item and data-reason on span. Do not send those: text inside such a highlight is left out when the note is read. Other tags are removed but their text is kept; script and style elements are removed with their content.
Free-text clinical notes are also converted to the format of Vetnio's note editor. Client communications and referral letters are only sanitized. These are real results:
| You send | Stored clinical note | Stored client communication |
|---|---|---|
<h2>History</h2><p>Lame for 2 weeks.</p><p>Worse after exercise.</p> | <h2>History</h2>Lame for 2 weeks.<br />Worse after exercise. | Unchanged |
<table><tr><td>T</td><td>38.6 °C</td></tr></table> | T38.6 °C (cells are joined without a separator) | Unchanged |
<ul><li><p>Carprofen</p></li></ul><p><span class="x">Owner informed.</span></p> | <ul><li>Carprofen</li></ul>Owner informed. | <ul><li><p>Carprofen</p></li></ul><p><span>Owner informed.</span></p> |
<a href="https://example.com/xray" onclick="x()">radiographs</a> | <a target="_blank" href="https://example.com/xray">radiographs</a> | <a href="https://example.com/xray">radiographs</a> |
Line one + newline + Line two (plain text) | Line one<br />Line two | Unchanged |
<p style="color:red">Stable.</p><script>alert(1)</script><img src="x.png"> | Stable. | <p>Stable.</p> |
To round-trip a clinical note unchanged, write it the way Vetnio generates it: <strong> headings, <br /> line breaks and • bullets, with no tables.
Version history
Every save adds a retained version: each generation (changeType: generated), each API write and each edit in Vetnio (edit). GET/workspaces/{workspaceId}/consultations/{consultationId}/records/versions lists them newest first in one unpaginated list, and GET/workspaces/{workspaceId}/consultations/{consultationId}/records/version returns one version's exact content. Use them to compare the AI draft with the clinician's final note.
A delete does not appear as a version. Afterwards currentVersionId is null, every isLatest is false, and the old versions can still be read. This is the history of the note written above, after three writes and a delete:
curl "https://vetnio.com/api/v2/workspaces/demo-north-clinic/consultations/a7f71ad7-8747-4165-aa79-bafee0044efe/records/versions?recordType=clinical%20note" \
-H "Authorization: Bearer $VETNIO_API_KEY"{
"currentVersionId": null,
"versions": [
{ "versionId": "6724a05cf77526035aacd1c4e1aec065", "lastModified": "2026-10-04T16:13:50.000Z", "isLatest": false, "changeType": "edit", "size": 354, "contentType": "text/html" },
{ "versionId": "6724a05e364f052a9350b900d1f11daf", "lastModified": "2026-10-04T16:13:45.000Z", "isLatest": false, "changeType": "edit", "size": 156, "contentType": "text/html" },
{ "versionId": "6724a05ff5ae9cd2b59b4e5ff7514fd6", "lastModified": "2026-10-04T16:13:37.000Z", "isLatest": false, "changeType": "edit", "size": 361, "contentType": "text/html" }
]
}Treat versionId as opaque and URL-encode it: it can contain +, / and =. size of a clinical-note version includes a little internal padding, so it is larger than the content. A version read returns a structured note's JSON as a string, unlike GET/workspaces/{workspaceId}/consultations/{consultationId}/records, which parses it. Versions are kept until the consultation is permanently erased. A clinical-note version can contain Vetnio's highlights for text the vet has not confirmed yet, as <span data-open-item="…" data-reason="…"> around that text; GET/workspaces/{workspaceId}/consultations/{consultationId}/records leaves that text out.
Deleting a record
DELETE/workspaces/{workspaceId}/consultations/{consultationId}/records removes the current clinical note or client communication. It is not an erasure: the history above stays readable. Deleting a record that does not exist returns { "success": true }, so the call is safe to retry. Referral letters cannot be deleted through the API.
Pitfalls
- Referral letters are read-only. The API reads them and their history, but cannot generate, write or delete them.
- A manual edit is not a lock. A later generation for the same record replaces it (from scratch) or builds on it (update mode).
- Retries create versions. Generation is not idempotent. If a generation request times out, read the version list before retrying: the note may have been saved. Nothing stops two generations at once either, so do not start one while
isGeneratingClinicalNoteistrue.
Endpoints used in this guide
- GET
/workspaces/{workspaceId}/consultations/{consultationId}/recordsGet a record - POST
/workspaces/{workspaceId}/consultations/{consultationId}/clinical-note/generateGenerate a clinical note - POST
/workspaces/{workspaceId}/consultations/{consultationId}/recordsCreate a record - PUT
/workspaces/{workspaceId}/consultations/{consultationId}/recordsUpdate a record - DELETE
/workspaces/{workspaceId}/consultations/{consultationId}/recordsDelete a record - GET
/workspaces/{workspaceId}/consultations/{consultationId}/records/versionsList retained record versions - GET
/workspaces/{workspaceId}/consultations/{consultationId}/records/versionRead a retained record version - GET
/workspaces/{workspaceId}/consultations/{consultationId}Get consultation details - GET
/workspaces/{workspaceId}/templatesList templates - GET
/workspaces/{workspaceId}/json-templates/{jsonTemplateId}Get JSON template detail
Last reviewed against the API on 2026-10-30.