openapi: 3.0.0
info:
  title: Vetnio API
  version: "1.0.0"
  description: <b> Before using this API, please authorize using your personal API key.</b> <br><br>A typical workflow would be to first create a consultation, then upload your recordings using the GET /recording-upload-url endpoint, then generate a document using the POST /generate endpoint with your chosen JSON template. Available JSON templates can be found in the GET /json-templates endpoint.
servers:
  - url: https://vetnio.com/api/v1
    description: Production server
  - url: http://localhost:3000/api/v1
    description: Local development server

paths:
  /workspaces:
    get:
      summary: List workspaces
      description: List all workspaces accessible to the user.
      tags:
        - Workspaces
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: A list of workspaces.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListWorkspacesResponse"
              examples:
                successfulResponse:
                  value:
                    workspaces:
                      - id: "workspace1"
                        name: "Veterinary Clinic"
                        role: "admin"
                      - id: "workspace2"
                        name: "Animal Shelter"
                        role: "member"
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
    post:
      summary: Create a new workspace
      description: Create a new workspace with the specified name.
      tags:
        - Workspaces
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWorkspaceBody"
            examples:
              createWorkspace:
                value:
                  name: "New Veterinary Workspace"
      responses:
        "201":
          description: Workspace created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateWorkspaceResponse"
              examples:
                successfulCreation:
                  value:
                    id: "workspace123"
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/consultations:
    get:
      summary: List consultations
      description: List consultations in a workspace.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: currentPage
          in: query
          required: false
          schema:
            type: integer
            default: 1
          description: Current page number (optional).
        - name: itemsPerPage
          in: query
          required: false
          schema:
            type: integer
            default: 10
            maximum: 100
          description: Number of items per page (optional, max 100).
        - name: search
          in: query
          required: false
          schema:
            type: string
          description: Search term to filter consultations by name.
      responses:
        "200":
          description: A list of consultations.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListConsultationsResponse"
              examples:
                successfulResponse:
                  value:
                    count: 2
                    currentPage: 1
                    itemsPerPage: 10
                    totalPages: 1
                    consultations:
                      - id: "consultation1"
                        name: "Annual Check-up"
                        recordingDurationInMilliseconds: 150000
                        recordingDurationInMillisecondsExcludingSilence: 120000
                        isGeneratingClinicalNote: false
                        isGeneratingCommunication: false
                      - id: "consultation2"
                        name: "Vaccination"
                        recordingDurationInMilliseconds: 200000
                        recordingDurationInMillisecondsExcludingSilence: 180000
                        isGeneratingClinicalNote: true
                        isGeneratingCommunication: false
                searchExample:
                  summary: Example with search query
                  value:
                    count: 1
                    currentPage: 1
                    itemsPerPage: 10
                    totalPages: 1
                    consultations:
                      - id: "consultation1"
                        name: "Annual Check-up"
                        recordingDurationInMilliseconds: 150000
                        recordingDurationInMillisecondsExcludingSilence: 120000
                        isGeneratingClinicalNote: false
                        isGeneratingCommunication: false
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
    post:
      summary: Create or upsert a consultation
      description: Create a new consultation in a workspace. If an externalId is provided and a consultation with that externalId already exists, the existing consultation will be returned instead of creating a new one.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateConsultationBody"
      responses:
        "201":
          description: Consultation created or retrieved.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateConsultationResponse"
              examples:
                successfulCreation:
                  value:
                    consultationId: "consultation123"
                    recordingUploadUrl: "https://s3.amazonaws.com/yourbucket/recordings/workspaceId/consultationId/1631234567890-en"
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/consultations/{consultationId}:
    get:
      summary: Get consultation details
      description: Retrieve details of a specific consultation.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      responses:
        "200":
          description: Consultation details retrieved.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetConsultationResponse"
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
    patch:
      summary: Rename a consultation
      description: Update the name of a consultation.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RenameConsultationBody"
            examples:
              renameConsultation:
                value:
                  name: "Updated Consultation Name"
      responses:
        "204":
          description: Consultation renamed successfully.
        "400":
          description: Bad Request - Invalid input.
        "404":
          description: Consultation not found.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
    delete:
      summary: Delete a consultation
      description: Permanently delete a consultation and all associated recordings.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      responses:
        "204":
          description: Consultation deleted.
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/consultations/{consultationId}/recording-upload-url:
    get:
      summary: Get a recording upload URL
      description: Retrieve a new recording upload URL for adding more audio clips to a consultation. The URL received from this endpoint is a presigned S3 PUT URL. This URL can be used to upload a single object/file. The body of the PUT request should contain the binary audio file. More info here - https://docs.aws.amazon.com/AmazonS3/latest/userguide/presigned-urls.html
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
        - name: recordingLanguageCode
          in: query
          required: true
          schema:
            $ref: "#/components/schemas/RecordingLanguageCode"
          description: The recording language code.
      responses:
        "200":
          description: Recording upload URL retrieved.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetRecordingUploadUrlResponse"
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/consultations/{consultationId}/transcribe:
    post:
      summary: Transcribe recordings of a consultation
      description: Start the transcription process for any untranscribed recordings in a consultation. This endpoint initiates an asynchronous process and returns immediately.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      responses:
        "202":
          description: Transcription process started successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: Status message
                  recordingsProcessed:
                    type: integer
                    description: Number of recordings being processed
              examples:
                successfulTranscription:
                  value:
                    message: "Transcription process started"
                    recordingsProcessed: 2
        "200":
          description: No untranscribed recordings found.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  recordingsProcessed:
                    type: integer
              examples:
                noRecordings:
                  value:
                    message: "No untranscribed recordings found"
                    recordingsProcessed: 0
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/consultations/{consultationId}/documents:
    post:
      summary: Create a document based on the consultation
      description: Generate a document based on the consultation recording using a predefined structured schema.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateBodyStructuredSchema"
            examples:
              generateSoapNote:
                value:
                  jsonTemplateId: "2a859133-0e04-4f63-88d2-aa0ee318cd8d"
                  callbackUrl: "https://example.com/callback"
                  languageCode: "sv"
      responses:
        "201":
          description: Document generated.
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/consultations/{consultationId}/clinical-notes:
    post:
      summary: Create a clinical note based on the consultation
      description: Generate a clinical note based on the consultation transcription. Supports asynchronous callback delivery or immediate response.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateClinicalNoteBody"
            examples:
              generateClinicalNote:
                value:
                  templateId: "clinical-note"
                  callbackUrl: "https://example.com/callback"
      responses:
        "201":
          description: Clinical note generation started or completed.
          content:
            application/json:
              examples:
                queued:
                  value:
                    message: "Callback URL has been queued"
                immediate:
                  value:
                    recordType: "clinical_note"
                    record: "<p>...</p>"
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/consultations/{consultationId}/partial-transcriptions:
    post:
      summary: Create a text-based partial transcription
      description: Add custom text content (e.g., SMS messages, notes, or other information) as a partial transcription to a consultation. This text will be included in the transcription when generating documents. Partial transcriptions are ordered chronologically based on when they were created, maintaining the correct sequence with any existing audio transcriptions.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePartialTranscriptionBody"
            examples:
              createPartialTranscription:
                value:
                  text: "SMS from client: The dog has been eating well and seems more energetic today."
                  languageCode: "en"
      responses:
        "201":
          description: Partial transcription created successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreatePartialTranscriptionResponse"
              examples:
                successfulCreation:
                  value:
                    success: true
                    recordingId: "1742065166113-text-en"
                    message: "Partial transcription created successfully. It will be included in document generation."
        "400":
          description: Bad Request - Invalid input or text exceeds character limit.
        "404":
          description: Consultation not found.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
    get:
      summary: List partial transcriptions
      description: Retrieve a list of all partial transcriptions for a consultation. Returns metadata for each transcription including whether it's text-based or from audio.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      responses:
        "200":
          description: Partial transcriptions listed successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListPartialTranscriptionsResponse"
              examples:
                successfulResponse:
                  value:
                    count: 3
                    partialTranscriptions:
                      - recordingId: "1742065166113-en"
                        key: "transcriptions/workspace123/consultation456/1742065166113-en"
                        isTextBased: false
                      - recordingId: "1742065166200-text-en"
                        key: "transcriptions/workspace123/consultation456/1742065166200-text-en"
                        isTextBased: true
                      - recordingId: "1742065166300-sv"
                        key: "transcriptions/workspace123/consultation456/1742065166300-sv"
                        isTextBased: false
        "404":
          description: Consultation not found.
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/consultations/{consultationId}/additional-info:
    get:
      summary: Get additional information for a consultation
      description: Retrieve additional information text that has been stored for a specific consultation. This information is used as context when generating documents from the consultation.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      responses:
        "200":
          description: Additional information retrieved successfully.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetAdditionalInfoResponse"
              examples:
                successfulResponse:
                  value:
                    additionalInfo: "Patient showed improvement after treatment. Follow-up recommended in 2 weeks."
        "404":
          description: No additional information found for this consultation or consultation not found.
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
    put:
      summary: Create or update additional information for a consultation
      description: Store or update additional information text for a specific consultation. This additional information will be used as context when generating documents from the consultation. This endpoint handles both creation and updates of additional information.
      tags:
        - Consultations
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PutAdditionalInfoBody"
            examples:
              createAdditionalInfo:
                value:
                  additionalInfo: "Patient showed improvement after treatment. Follow-up recommended in 2 weeks."
      responses:
        "201":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PutAdditionalInfoResponse"
              examples:
                successfulCreation:
                  value:
                    success: true
        "400":
          description: Bad Request - Invalid input or additional info exceeds character limit.
        "404":
          description: Consultation not found.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/json-templates:
    get:
      summary: List JSON templates
      description: Retrieve all JSON templates for a workspace.
      tags:
        - Templates
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
      responses:
        "200":
          description: A list of templates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListTemplatesResponse"
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/templates:
    get:
      summary: List templates
      description: Retrieve all HTML templates for a workspace.
      tags:
        - Templates
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
      responses:
        "200":
          description: A list of templates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListTemplatesResponse"
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
    post:
      summary: Create a template
      description: Create a new HTML template in a workspace. Supports only simple HTML tags such as <strong> and <br />
      tags:
        - Templates
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTemplateBody"
            examples:
              createTemplate:
                value:
                  name: "SOAP Note"
                  template: "<strong>Subjective</strong>..."
      responses:
        "201":
          description: Template created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateTemplateResponse"
              examples:
                created:
                  value:
                    templateId: "f7b8b9a2-2c4f-4b3d-b0e5-1b2a3c4d5e6f"
        "400":
          description: Bad Request - Invalid input or template limit reached.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/templates/{templateId}:
    put:
      summary: Update a template
      description: Update the HTML content of an existing template. Supports only simple HTML tags such as <strong> and <br />
      tags:
        - Templates
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: templateId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the template.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTemplateBody"
            examples:
              updateTemplate:
                value:
                  template: "<strong>Updated</strong> content..."
      responses:
        "200":
          description: Template updated successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                example:
                  success: true
        "404":
          description: Template not found.
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
    delete:
      summary: Delete a template
      description: Permanently delete a template from a workspace.
      tags:
        - Templates
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: templateId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the template.
      responses:
        "204":
          description: Template deleted.
        "404":
          description: Template not found.
        "400":
          description: Bad Request - Invalid input.
        "500":
          description: Internal Server Error.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.

  /workspaces/{workspaceId}/patients:
    get:
      summary: List patients
      description: List patients in a workspace with optional search and filters.
      tags:
        - Patients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: page
          in: query
          required: false
          schema:
            type: integer
            default: 1
            minimum: 1
          description: Page number.
        - name: pageSize
          in: query
          required: false
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
          description: Number of patients per page.
        - name: searchQuery
          in: query
          required: false
          schema:
            type: string
            maxLength: 200
          description: Search by patient name, identifier, or owner name.
        - name: speciesFilter
          in: query
          required: false
          schema:
            type: string
          description: Filter by species.
        - name: insuranceFilter
          in: query
          required: false
          schema:
            type: string
          description: Filter by insurance provider.
        - name: genderFilter
          in: query
          required: false
          schema:
            type: string
          description: Filter by gender.
        - name: hasWeightFilter
          in: query
          required: false
          schema:
            type: string
            enum:
              - "yes"
              - "no"
          description: Filter by whether the patient has a weight recorded.
      responses:
        "200":
          description: A paginated list of patients.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListPatientsResponse"
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.
    post:
      summary: Create a patient
      description: Create a new patient, optionally with owner (client) information.
      tags:
        - Patients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePatientBody"
            examples:
              createPatient:
                value:
                  name: "Buddy"
                  species: "Dog"
                  gender: "Male"
                  weight: "25kg"
                  identifier: "PAT-001"
                  insurance: "Agria"
                  client:
                    name: "Jane Doe"
                    email: "jane@example.com"
                    phoneNumbers: ["+46701234567"]
      responses:
        "201":
          description: Patient created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PatientWithClientResponse"
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.

  /workspaces/{workspaceId}/patients/{patientId}:
    get:
      summary: Get patient details
      description: Retrieve details of a specific patient including their owner information.
      tags:
        - Patients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: patientId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the patient.
      responses:
        "200":
          description: Patient details retrieved.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PatientWithClientResponse"
        "404":
          description: Patient not found.
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.
    patch:
      summary: Update a patient
      description: Update fields of an existing patient.
      tags:
        - Patients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: patientId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the patient.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePatientBody"
            examples:
              updateWeight:
                value:
                  weight: "27kg"
      responses:
        "200":
          description: Patient updated successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                example:
                  success: true
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.
    delete:
      summary: Delete a patient
      description: Permanently delete a patient and all associated files. Requires admin role.
      tags:
        - Patients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: patientId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the patient.
      responses:
        "204":
          description: Patient deleted.
        "404":
          description: Patient not found.
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.

  /workspaces/{workspaceId}/clients:
    get:
      summary: List clients (owners)
      description: List all patient owners in a workspace.
      tags:
        - Clients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
      responses:
        "200":
          description: A list of clients.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListClientsResponse"
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.
    post:
      summary: Create a client (owner)
      description: Create a new client (patient owner) in a workspace.
      tags:
        - Clients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateClientBody"
            examples:
              createClient:
                value:
                  name: "Jane Doe"
                  email: "jane@example.com"
                  address: "123 Main St"
                  zipCode: "12345"
                  phoneNumbers: ["+46701234567"]
      responses:
        "201":
          description: Client created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientResponse"
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.

  /workspaces/{workspaceId}/clients/{clientId}:
    get:
      summary: Get client (owner) details
      description: Retrieve details of a specific client (patient owner).
      tags:
        - Clients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: clientId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the client.
      responses:
        "200":
          description: Client details retrieved.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientResponse"
        "404":
          description: Client not found.
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.
    patch:
      summary: Update a client (owner)
      description: Update fields of an existing client (patient owner).
      tags:
        - Clients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: clientId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the client.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateClientBody"
            examples:
              updateEmail:
                value:
                  email: "newemail@example.com"
      responses:
        "200":
          description: Client updated successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                example:
                  success: true
        "404":
          description: Client not found.
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.
    delete:
      summary: Delete a client (owner)
      description: Permanently delete a client and all their patients (cascade). Requires admin role.
      tags:
        - Clients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: clientId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the client.
      responses:
        "204":
          description: Client deleted.
        "404":
          description: Client not found.
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.

  /workspaces/{workspaceId}/consultations/{consultationId}/patient:
    post:
      summary: Link a patient to a consultation
      description: Attach an existing patient to a consultation. The patient must belong to the same workspace.
      tags:
        - Consultations
        - Patients
      security:
        - ApiKeyAuth: []
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the workspace.
        - name: consultationId
          in: path
          required: true
          schema:
            type: string
          description: The ID of the consultation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LinkPatientBody"
            examples:
              linkPatient:
                value:
                  patientId: "patient-uuid-here"
      responses:
        "200":
          description: Patient linked to consultation successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
              examples:
                success:
                  value:
                    success: true
        "404":
          description: Patient or consultation not found.
        "400":
          description: Bad Request - Invalid input.
        "403":
          description: Forbidden - insufficient permissions or invalid API key.
        "500":
          description: Internal Server Error.

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'Enter the token with the `Bearer: ` prefix, e.g. "Bearer myApiKey".'
  schemas:
    ListWorkspacesResponse:
      type: object
      properties:
        workspaces:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              role:
                type: string
                enum:
                  - member
                  - admin
                  - superadmin
    CreateWorkspaceBody:
      type: object
      required:
        - name
      properties:
        name:
          type: string
      example:
        name: "New Veterinary Workspace"
    CreateWorkspaceResponse:
      type: object
      properties:
        id:
          type: string
      example:
        id: "workspace123"
    ListConsultationsResponse:
      type: object
      properties:
        count:
          type: integer
          description: Total number of consultations.
        currentPage:
          type: integer
          description: Current page number.
        itemsPerPage:
          type: integer
          description: Number of items per page.
        totalPages:
          type: integer
          description: Total number of pages.
        consultations:
          type: array
          items:
            $ref: "#/components/schemas/GetConsultationResponse"
      example:
        count: 2
        currentPage: 1
        itemsPerPage: 10
        totalPages: 1
        consultations:
          - id: "consultation1"
            name: "Annual Check-up"
            recordingDurationInMilliseconds: 150000
            recordingDurationInMillisecondsExcludingSilence: 120000
            isGeneratingClinicalNote: false
            isGeneratingCommunication: false
          - id: "consultation2"
            name: "Vaccination"
            recordingDurationInMilliseconds: 200000
            recordingDurationInMillisecondsExcludingSilence: 180000
            isGeneratingClinicalNote: true
            isGeneratingCommunication: false
    CreateConsultationResponse:
      type: object
      properties:
        consultationId:
          type: string
          description: The ID of the newly created consultation.
        recordingUploadUrl:
          type: string
          description: A pre-signed S3 URL valid for 24 hours.
      example:
        consultationId: "consultation123"
        recordingUploadUrl: "https://s3.amazonaws.com/yourbucket/recordings/workspaceId/consultationId/1631234567890-en"
    GetConsultationResponse:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        recordingDurationInMilliseconds:
          type: integer
          description: Total duration of uploaded recordings in milliseconds
        recordingDurationInMillisecondsExcludingSilence:
          type: integer
          description: Duration of uploaded recordings excluding silence in milliseconds
        isGeneratingClinicalNote:
          type: boolean
        isGeneratingCommunication:
          type: boolean
        externalId:
          type: string
          description: External identifier used for deduplication
    DeleteConsultationResponse:
      type: object
      properties:
        success:
          type: boolean
    GetRecordingUploadUrlResponse:
      type: object
      properties:
        recordingUploadUrl:
          type: string
          description: A pre-signed S3 URL valid for 24 hours.
    SubmitConsultationBody:
      type: object
      required:
        - recordingLanguageCode
      properties:
        recordingLanguageCode:
          type: string
          description: ISO-639-1 language code.
        additionalInfo:
          type: string
          description: Optional additional information.
        callbackUrl:
          type: string
          description: Optional callback URL.
    SubmitConsultationResponse:
      type: object
      properties:
        consultationId:
          type: string
    DeleteSegmentResponse:
      type: object
      properties:
        success:
          type: boolean
    GenerateBodyStructuredSchema:
      type: object
      required:
        - jsonTemplateId
      properties:
        jsonTemplateId:
          type: string
          description: ID of a predefined structured schema.
        callbackUrl:
          type: string
          description: Optional callback URL to receive the generated document.
        languageCode:
          $ref: "#/components/schemas/RecordingLanguageCode"
          description: The language of the generated document. If not specified, the language of the recording will be used.
      example:
        jsonTemplateId: "2a859133-0e04-4f63-88d2-aa0ee318cd8d"
        callbackUrl: "https://example.com/callback"
        languageCode: "sv"
    GenerateClinicalNoteBody:
      type: object
      properties:
        templateId:
          type: string
          description: Optional template identifier to influence formatting.
        callbackUrl:
          type: string
          description: Optional callback URL to receive the generated note asynchronously.
    ListTemplatesResponse:
      type: array
      items:
        type: object
        properties:
          id:
            type: string
          name:
            type: string
    CreateTemplateBody:
      type: object
      required:
        - name
        - template
      properties:
        name:
          type: string
          description: Name of the template.
        template:
          type: string
          description: HTML content of the template.
      example:
        name: "SOAP Note"
        template: "<strong>Subjective</strong>..."
    CreateTemplateResponse:
      type: object
      properties:
        templateId:
          type: string
      example:
        templateId: "f7b8b9a2-2c4f-4b3d-b0e5-1b2a3c4d5e6f"
    UpdateTemplateBody:
      type: object
      required:
        - template
      properties:
        template:
          type: string
          description: The updated HTML content of the template.
      example:
        template: "<strong>Updated</strong> content..."
    RecordingLanguageCode:
      type: string
      enum:
        - sv
        - en
        - af
        - ar
        - hy
        - az
        - be
        - bs
        - bg
        - ca
        - zh
        - hr
        - cs
        - da
        - nl
        - et
        - fi
        - fr
        - gl
        - de
        - el
        - he
        - hi
        - hu
        - is
        - id
        - it
        - ja
        - kn
        - kk
        - ko
        - lv
        - lt
        - mk
        - ms
        - mi
        - mr
        - ne
        - no
        - fa
        - pl
        - pt
        - ro
        - ru
        - sr
        - sk
        - sl
        - es
        - sw
        - tl
        - ta
        - th
        - tr
        - uk
        - ur
        - vi
        - cy
      description: The recording language code.
    CreateConsultationBody:
      type: object
      required:
        - name
        - recordingLanguageCode
      properties:
        name:
          type: string
          description: Name of the consultation.
          example: "Consultation 1"
        recordingLanguageCode:
          $ref: "#/components/schemas/RecordingLanguageCode"
        externalId:
          type: string
          description: External identifier for the consultation, used for deduplication.
      example:
        name: "Dental Cleaning"
        recordingLanguageCode: "en"
        externalId: "5f4dcc3b5aa765d61d8327deb882cf99"
    RenameConsultationBody:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: New name for the consultation.
          example: "Updated Consultation Name"
    GetAdditionalInfoResponse:
      type: object
      properties:
        additionalInfo:
          type: string
          description: The additional information text stored for the consultation. This information is used as context when generating documents from the consultation.
      example:
        additionalInfo: "Patient showed improvement after treatment. Follow-up recommended in 2 weeks."
    PutAdditionalInfoBody:
      type: object
      required:
        - additionalInfo
      properties:
        additionalInfo:
          type: string
          minLength: 1
          maxLength: 10000
          description: Additional information text to store for the consultation (1-10000 characters). This information will be used as context when generating documents from the consultation.
      example:
        additionalInfo: "Patient showed improvement after treatment. Follow-up recommended in 2 weeks."
    PutAdditionalInfoResponse:
      type: object
      properties:
        success:
          type: boolean
          description: Indicates if the operation was successful.
        s3Key:
          type: string
          description: The S3 key where the additional information is stored.
      example:
        success: true
        s3Key: "additionalInfos/workspace123/consultation456"
    CreatePartialTranscriptionBody:
      type: object
      required:
        - text
      properties:
        text:
          type: string
          minLength: 1
          maxLength: 50000
          description: The text content to add as a partial transcription. This can be any text, such as an SMS message, notes, or other information.
        languageCode:
          $ref: "#/components/schemas/RecordingLanguageCode"
          description: The language code of the text content. If not specified, defaults to 'en' (English).
      example:
        text: "SMS from client: The dog has been eating well and seems more energetic today."
        languageCode: "en"
    CreatePartialTranscriptionResponse:
      type: object
      properties:
        success:
          type: boolean
          description: Indicates if the partial transcription was created successfully.
        recordingId:
          type: string
          description: The unique identifier for the partial transcription. Format is {timestamp}-text-{languageCode}.
        message:
          type: string
          description: A status message about the operation.
      example:
        success: true
        recordingId: "1742065166113-text-en"
        message: "Partial transcription created successfully. It will be included in document generation."
    ListPartialTranscriptionsResponse:
      type: object
      properties:
        count:
          type: integer
          description: Total number of partial transcriptions.
        partialTranscriptions:
          type: array
          items:
            type: object
            properties:
              recordingId:
                type: string
                description: The unique identifier for the partial transcription.
              key:
                type: string
                description: The S3 key where the partial transcription is stored.
              isTextBased:
                type: boolean
                description: Whether this partial transcription is text-based (true) or from audio (false).
      example:
        count: 2
        partialTranscriptions:
          - recordingId: "1742065166113-en"
            key: "transcriptions/workspace123/consultation456/1742065166113-en"
            isTextBased: false
          - recordingId: "1742065166200-text-en"
            key: "transcriptions/workspace123/consultation456/1742065166200-text-en"
            isTextBased: true
    ClientResponse:
      type: object
      properties:
        id:
          type: string
          description: The client (owner) ID.
        name:
          type: string
          nullable: true
          description: Client name.
        address:
          type: string
          nullable: true
          description: Client address.
        zipCode:
          type: string
          nullable: true
          description: Client zip code.
        phoneNumbers:
          type: array
          items:
            type: string
          description: Client phone numbers.
        email:
          type: string
          nullable: true
          description: Client email.
      example:
        id: "client-uuid"
        name: "Jane Doe"
        address: "123 Main St"
        zipCode: "12345"
        phoneNumbers: ["+46701234567"]
        email: "jane@example.com"
    PatientResponse:
      type: object
      properties:
        id:
          type: string
          description: The patient ID.
        name:
          type: string
          nullable: true
          description: Patient name.
        species:
          type: string
          nullable: true
          description: Patient species.
        gender:
          type: string
          nullable: true
          description: Patient gender.
        weight:
          type: string
          nullable: true
          description: Patient weight.
        identifier:
          type: string
          nullable: true
          description: External identifier (e.g., from a practice management system).
        insurance:
          type: string
          nullable: true
          description: Insurance provider.
        clientId:
          type: string
          nullable: true
          description: ID of the owning client.
        createdAt:
          type: string
          format: date-time
          nullable: true
          description: When the patient was created.
        updatedAt:
          type: string
          format: date-time
          nullable: true
          description: When the patient was last updated.
    PatientWithClientResponse:
      allOf:
        - $ref: "#/components/schemas/PatientResponse"
        - type: object
          properties:
            client:
              $ref: "#/components/schemas/ClientResponse"
      example:
        id: "patient-uuid"
        name: "Buddy"
        species: "Dog"
        gender: "Male"
        weight: "25kg"
        identifier: "PAT-001"
        insurance: "Agria"
        clientId: "client-uuid"
        createdAt: "2025-01-01T00:00:00.000Z"
        updatedAt: "2025-01-01T00:00:00.000Z"
        client:
          id: "client-uuid"
          name: "Jane Doe"
          address: "123 Main St"
          zipCode: "12345"
          phoneNumbers: ["+46701234567"]
          email: "jane@example.com"
    PatientListItem:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
          nullable: true
        species:
          type: string
          nullable: true
        gender:
          type: string
          nullable: true
        weight:
          type: string
          nullable: true
        identifier:
          type: string
          nullable: true
        insurance:
          type: string
          nullable: true
        clientId:
          type: string
          nullable: true
        clientName:
          type: string
          nullable: true
          description: Name of the patient's owner.
        createdAt:
          type: string
          format: date-time
          nullable: true
        updatedAt:
          type: string
          format: date-time
          nullable: true
    ListPatientsResponse:
      type: object
      properties:
        patients:
          type: array
          items:
            $ref: "#/components/schemas/PatientListItem"
        totalCount:
          type: integer
          description: Total number of patients matching the filters.
      example:
        patients:
          - id: "patient-uuid"
            name: "Buddy"
            species: "Dog"
            gender: "Male"
            weight: "25kg"
            identifier: "PAT-001"
            insurance: "Agria"
            clientId: "client-uuid"
            clientName: "Jane Doe"
            createdAt: "2025-01-01T00:00:00.000Z"
            updatedAt: "2025-01-01T00:00:00.000Z"
        totalCount: 1
    ListClientsResponse:
      type: object
      properties:
        clients:
          type: array
          items:
            $ref: "#/components/schemas/ClientResponse"
      example:
        clients:
          - id: "client-uuid"
            name: "Jane Doe"
            address: "123 Main St"
            zipCode: "12345"
            phoneNumbers: ["+46701234567"]
            email: "jane@example.com"
    CreatePatientBody:
      type: object
      properties:
        name:
          type: string
          description: Patient name.
        species:
          type: string
          description: Patient species.
        gender:
          type: string
          description: Patient gender.
        weight:
          type: string
          description: Patient weight.
        identifier:
          type: string
          description: External identifier.
        insurance:
          type: string
          description: Insurance provider.
        client:
          type: object
          description: Optional owner information. A client record is always created (empty if omitted).
          properties:
            name:
              type: string
            email:
              type: string
            address:
              type: string
            zipCode:
              type: string
            phoneNumbers:
              type: array
              items:
                type: string
      example:
        name: "Buddy"
        species: "Dog"
        gender: "Male"
        weight: "25kg"
        client:
          name: "Jane Doe"
          email: "jane@example.com"
    UpdatePatientBody:
      type: object
      properties:
        name:
          type: string
        species:
          type: string
        gender:
          type: string
        weight:
          type: string
        identifier:
          type: string
        insurance:
          type: string
      example:
        weight: "27kg"
    CreateClientBody:
      type: object
      properties:
        name:
          type: string
          description: Client name.
        email:
          type: string
          description: Client email.
        address:
          type: string
          description: Client address.
        zipCode:
          type: string
          description: Client zip code.
        phoneNumbers:
          type: array
          items:
            type: string
          description: Client phone numbers.
      example:
        name: "Jane Doe"
        email: "jane@example.com"
        phoneNumbers: ["+46701234567"]
    UpdateClientBody:
      type: object
      properties:
        name:
          type: string
        email:
          type: string
        address:
          type: string
        zipCode:
          type: string
        phoneNumbers:
          type: array
          items:
            type: string
      example:
        email: "newemail@example.com"
    LinkPatientBody:
      type: object
      required:
        - patientId
      properties:
        patientId:
          type: string
          description: The ID of the patient to link to the consultation.
      example:
        patientId: "patient-uuid-here"
