Concepts
Consultations
What a consultation is, how it moves from audio to transcript to clinical note, which status fields to poll, and how externalId, visibility and archiving behave.
A consultation is one visit: a vet examines an animal, talks with the owner, and Vetnio turns the conversation into a clinical note. The consultation holds everything about that visit, belongs to exactly one clinic (a workspace) and can be linked to one patient. Document a visit end to end walks through the calls in order; this page explains the object and its states.
What a consultation holds
| Part | What it is | How you read it |
|---|---|---|
| Recordings | Audio segments you upload, transcribed in order | Not individually; admins can export the joined audio |
| Transcript | Plain text of every transcribed segment, plus any text you add | GET/workspaces/{workspaceId}/consultations/{consultationId}/transcription |
| Additional info | Free-text context, such as history from another clinic, used by every document generation | GET/workspaces/{workspaceId}/consultations/{consultationId}/additional-info |
| Uploaded files | Lab results, referral letters and images; Vetnio extracts their text | Attach files |
| Records | The documents Vetnio writes: clinical note, client communication and referral letter, at most one of each | Records |
| Patient | The animal the visit is about; its owner is the client | Patients and clients |
Identifiers
| Identifier | Example | Notes |
|---|---|---|
id | 4b2730b1-3e78-4d82-9114-030a055e8581 | UUID generated by Vetnio, or the clientConsultationId you sent when creating it. The consultationId in every consultation URL. |
workspaceId | demo-north-clinic | The clinic that owns the consultation. A consultation never moves between clinics. |
externalId | pims-appt-58213 | Your identifier, typically the appointment or visit ID in your practice system. Free text, not unique. |
| Recording ID | 1791130536828-en | <epoch-ms>-<language>, fixed when an upload URL is issued. It sets the segment's position in the transcript and its transcription language. |
Set externalId when you create the consultation or later with PUT/workspaces/{workspaceId}/consultations/{consultationId}/external-id. Integrations use it to find the consultation again and to export notes back to the practice system.
externalId makes creation idempotent
When you send externalId to POST/workspaces/{workspaceId}/consultations, Vetnio first looks for an active consultation in the same clinic with that value. If it finds one, it returns it unchanged with a fresh upload URL; otherwise it creates a new consultation. Repeating a request with the same externalId and a different name returns the original:
{
"consultationId": "4b2730b1-3e78-4d82-9114-030a055e8581",
"recordingUploadUrl": "https://storage.example.com/recordings/demo-north-clinic/4b2730b1-3e78-4d82-9114-030a055e8581/1791130540933-sv?X-Amz-Expires=86400&X-Amz-Signature=…",
"consultation": {
"id": "4b2730b1-3e78-4d82-9114-030a055e8581",
"name": "Bella - lameness left hind",
"createdAt": "2026-10-04T16:15:36.818Z",
"recordingDurationInMilliseconds": 0,
"uploadedRecordingDurationInMilliseconds": 0,
"isGeneratingClinicalNote": false,
"isGeneratingCommunication": false,
"externalId": "pims-appt-58213"
}
}Know the edges of this behavior:
- The match is clinic-wide: it can return a consultation that a colleague, another integration or the Vetnio app created.
- A matched consultation keeps its
nameand language; only the new upload URL uses the requested language. clinicalNoteTemplateIdis ignored wheneverexternalIdis set, even when a consultation is created. Choose the template when you generate the note.- Archived consultations and consultations deleted in the Vetnio app are skipped, so after an archive the same
externalIdcreates a new consultation. - If two active consultations share an
externalId, which one is returned is not defined. - Without
externalId, a retried request can create a duplicate unless you send aclientConsultationId.
clientConsultationId makes any creation retry-safe
Generate a UUID, store it, and send it as clientConsultationId; it becomes the new consultation's id. Repeating the request with the same value returns the consultation the first request created for the same user and clinic, instead of creating another. If the value is the ID of any other consultation, or the consultation it created has since been archived, the call returns 409. Sent together with externalId, a consultation that matches the externalId still wins, and clientConsultationId is only used when a new consultation is created.
Lifecycle
| Stage | Reached by | What you observe |
|---|---|---|
| Created | POST/workspaces/{workspaceId}/consultations | transcriptionStatus: idle, isReadyForGeneration: false |
| Audio uploaded | PUT to an upload URL | transcriptionStatus: processing, even before transcription is started. Durations update shortly after each upload. |
| Transcribing | POST/workspaces/{workspaceId}/consultations/{consultationId}/transcriptions/start, or a request that waits for transcription | processing |
| Transcribed | Every segment finished | completed or failed; isReadyForGeneration: true once there is text |
| Generating | A clinical-note generation request | isGeneratingClinicalNote: true |
| Documented | The note is saved | GET/workspaces/{workspaceId}/consultations/{consultationId}/records returns it |
| Archived | DELETE/workspaces/{workspaceId}/consultations/{consultationId} | Still readable by ID, no longer writable or listed |
Until it is archived, a consultation stays open. You can upload more audio after a note exists: the new segments are transcribed on the next start, and the note can be regenerated or updated from the longer transcript.
Status fields to poll
GET/workspaces/{workspaceId}/consultations/{consultationId} is the status endpoint. It is read-only and fast enough to poll every few seconds.
transcriptionStatus | Meaning | What to do |
|---|---|---|
idle | No audio has been uploaded. Text added as a partial transcription does not change this. | Upload audio, or generate from text you added. |
processing | At least one uploaded segment is not transcribed yet. | Keep polling. If you never called start, nothing happens until you do. |
completed | Every segment is transcribed. Segments shorter than one second count as transcribed with no text. | Read the transcript or generate. |
failed | Nothing is waiting, but at least one segment failed permanently. The other segments' text is in the transcript. | Check transcriptionFailureReason; generation can still proceed. |
transcriptionFailureReason is null unless the status is failed:
| Value | Cause |
|---|---|
unreadable_recording | The uploaded file is empty or is not decodable audio. |
processing_failed | Audio processing kept failing and the retries are used up. |
transcription_failed | The speech-to-text providers rejected the audio. |
The other fields you will use:
isReadyForGenerationistruewhen no segment is waiting for transcription and there is something to write from: transcript text, a text partial transcription or an analyzed uploaded file. Permanently failed segments do not block it, and it is alwaysfalsefor archived consultations.isGeneratingClinicalNoteandisGeneratingCommunicationaretruewhile Vetnio writes that record.recordingDurationInMillisecondsis the total uploaded audio;recordingDurationInMillisecondsExcludingSilenceleaves out stretches quieter than -40 dB. Both update shortly after each upload, not at once, and are0until the first upload is processed. Uploaded audio counts toward the clinic's recorded usage.
Visibility and permissions
Any member of a clinic, and any admin of its organization, can read and change any consultation in it by ID, whoever created it. A consultation that does not exist, belongs to another clinic or is outside your access returns 404 Consultation not found, never 403.
The list is narrower: GET/workspaces/{workspaceId}/consultations returns only consultations created by the key's user, even when the clinic lets members see each other's consultations in the app. To enumerate a whole clinic, a clinic admin uses GET/workspaces/{workspaceId}/data-export/consultations, described in Export clinical data.
Listing and searching
currentPagestarts at 1; a value below 1 causes a500.consultationsPerPagedefaults to 10 with no maximum, but every row checks storage for saved records, so keep pages at 50 or fewer.- Results are newest first, offset-paginated (see Pagination). Follow-up visits linked in a timeline are returned together and count as one entry toward the page size and
count, so a page can hold more items than requested. searchQueryis a case-insensitive substring match on the name, the clinical note text,externalIdand the linked patient's name or identifier. To look up anexternalId, search for it and compare exactly.- Archived consultations and consultations deleted in the app are excluded.
Renaming and additional info
- PATCH
/workspaces/{workspaceId}/consultations/{consultationId}takes{ "name": "…" }and returns{}. Only the name can change. - PUT
/workspaces/{workspaceId}/consultations/{consultationId}/additional-infostores 1 to 10,000 characters of context, replacing any earlier value; it can be overwritten but not cleared. GET/workspaces/{workspaceId}/consultations/{consultationId}/additional-inforeturns404until something is saved. Additional info feeds every record generation but is not part of the transcript.
Archiving
DELETE/workspaces/{workspaceId}/consultations/{consultationId} archives the consultation and returns { "success": true }. Nothing is erased: recordings, transcript, uploaded files and records are kept, and the clinic's audio-retention policy keeps running on its normal schedule.
An archived consultation is read-only. Every write, including a second DELETE, returns 403 with a reason you can branch on:
{
"message": "You cannot modify this consultation",
"code": "FORBIDDEN",
"data": {
"code": "FORBIDDEN",
"httpStatus": 403,
"path": "consultations.delete",
"zodError": null,
"reason": "archived_consultation"
}
}Archived consultations still answer GET/workspaces/{workspaceId}/consultations/{consultationId}, the transcript, additional info and records with 200, but nothing in those responses says the consultation is archived. They disappear from the list and stay in the admin export list. GET/workspaces/{workspaceId}/consultations/{consultationId}/partial-transcriptions returns 404 for them.
Users restore archived consultations in the Vetnio app (Consultations → Archived). The API cannot restore or permanently erase a consultation. A consultation deleted in the Vetnio app behaves like an archived one for the API, with reason: "hidden_consultation".
Common pitfalls
- Uploading audio does not start transcription. Call POST
/workspaces/{workspaceId}/consultations/{consultationId}/transcriptions/startafter your uploads; until then the status staysprocessing. - Segments uploaded late are never transcribed. A segment whose upload URL was issued before an already-transcribed segment is skipped. Fetch each GET
/workspaces/{workspaceId}/consultations/{consultationId}/recording-upload-urlright before uploading that segment, and upload in order. faileddoes not mean there is no transcript. One segment was lost; the rest is usable.- A
403withreason: "archived_consultation"is final. Retrying will not help; create a new consultation if the visit continues.
Endpoints used in this guide
- POST
/workspaces/{workspaceId}/consultationsCreate a new consultation - GET
/workspaces/{workspaceId}/consultations/{consultationId}Get consultation details - GET
/workspaces/{workspaceId}/consultationsGet a page of consultations - PATCH
/workspaces/{workspaceId}/consultations/{consultationId}Rename a consultation - DELETE
/workspaces/{workspaceId}/consultations/{consultationId}Archive a consultation - PUT
/workspaces/{workspaceId}/consultations/{consultationId}/external-idLink consultation to external system - GET
/workspaces/{workspaceId}/consultations/{consultationId}/additional-infoGet additional info - PUT
/workspaces/{workspaceId}/consultations/{consultationId}/additional-infoUpdate additional info - GET
/workspaces/{workspaceId}/consultations/{consultationId}/transcriptionGet consultation transcript - POST
/workspaces/{workspaceId}/consultations/{consultationId}/transcriptions/startStart transcription - GET
/workspaces/{workspaceId}/consultations/{consultationId}/recording-upload-urlGet a signed URL to upload a recording - GET
/workspaces/{workspaceId}/consultations/{consultationId}/partial-transcriptionsList partial transcriptions - GET
/workspaces/{workspaceId}/data-export/consultationsList consultations with export URLs - GET
/workspaces/{workspaceId}/consultations/{consultationId}/recordsGet a record
Last reviewed against the API on 2026-10-26.