WebSocket Integration Guide

Comprehensive guide for integrating CareScribe WebSocket audio streaming with your application

API Gateway URL

Base URL for all API requests:

https://carescribe-app-api-8to2squd.wl.gateway.dev
Important:
  • All API endpoints should be prefixed with this gateway URL
  • Example: https://carescribe-app-api-8to2squd.wl.gateway.dev/software/speciality/list
  • The gateway URL is required for all API calls.

WebSocket Integration

WebSocket Integration allows you to stream real-time audio from your client application to CareScribe's server for AI-powered medical documentation. This integration enables live transcription and processing of doctor-patient conversations.

The integration follows a step-by-step process to set up your hospital, create doctors, and establish WebSocket connections for audio streaming.

Step 1: Create a Hospital

Call the CareScribe team to book an appointment: https://carescribe.health/

Note: Hospital setup is done through the CareScribe team. Contact them to get your hospital registered in the system.

Required Hospital Information

When contacting the CareScribe team, please provide the following information for hospital setup:

Hospital Details
Hospital Name: The official name of your hospital or organization
Hospital ID: Must be in format "orgname-somenumber" (e.g., carescribe-001, carescribe-002). If you have multiple hospitals with the same organization, use sequential numbers.
Time Zone: Hospital time zone (e.g., Asia/Kolkata, America/Los_Angeles, Europe/London)
Client Endpoint URL: The URL endpoint in your HMS system where CareScribe will send POST requests with OPD data. This should be a publicly accessible HTTPS endpoint (e.g., https://api.yourhospital.com/carescribe/opd)
Location: Address details including:
  • Street Address
  • City
  • State/Province
  • Country
  • Postal/PIN Code

Additional Information (Optional)

You may also provide the following optional details:

  • Phone Number: Hospital contact number
  • Email: Hospital contact email
  • Website: Hospital website URL
  • Fax Number: Hospital fax number
  • Tax ID Number: Hospital tax identification number

Step 2: Get the Specialty List

GET /software/speciality/list

Summary: Get all specialities

Description: Retrieves a list of all specialities available in the system.

Sample Response (200 OK):

[
    {
        "speciality_id": 5,
        "specialty_name": "Cardiology",
        "description": "Deals with heart and cardiovascular system.",
        "short_prompt_id": "6",
        "long_prompt_id": "6"
    },
    {
        "speciality_id": 6,
        "specialty_name": "Dermatology",
        "description": "Handles skin health and disorders.",
        "short_prompt_id": "7",
        "long_prompt_id": "7"
    },
    {
        "speciality_id": 7,
        "specialty_name": "Gastroenterology",
        "description": "Focuses on digestive system health.",
        "short_prompt_id": "8",
        "long_prompt_id": "8"
    },
    {
        "speciality_id": 8,
        "specialty_name": "Gynaecology",
        "description": "Cares for women's reproductive health.",
        "short_prompt_id": "9",
        "long_prompt_id": "9"
    }
]

Responses:

  • 200: Successful operation - Returns array of specialty objects
  • 500: Internal server error

Step 3: Create a Doctor

POST /software/doctor/create

Summary: Create a doctor

Description: Creates a new doctor record in the system.

Body

{
    "first_name": "Alex",
    "last_name": "Patel",
    "speciality_id": 1,
    "salutation": "Dr",
    "hospital_id": "carescribe-001",
    "license_no": "TN12345",
    "phone_number": "+918883761709",
    "email": "alex.patel@example.com",
    "practitioner_id": "carescribe-001-dr001"
}

Note:

  • hospital_id: Required — Must be in format "orgname-somenumber" (e.g., carescribe-001, carescribe-002). If you have multiple hospitals with the same organization, use sequential numbers like carescribe-001, carescribe-002, etc.
  • practitioner_id: Required and must be unique — Must be in format "hospital_id-drsomenumber" (e.g., carescribe-001-dr001, carescribe-001-dr002). Format: your hospital_id followed by "-dr" and a number.
  • first_name: Required
  • last_name: Required
  • speciality_id: Required — fetch from /software/speciality/list endpoint
  • license_no: Required — if not available, pass as an empty string (" ")

Responses:

  • 201: Doctor created successfully
  • 400: Invalid input
  • 409: Doctor with practitioner_id or email already exists
  • 500: Internal server error

Step 4: Connect WebSocket

Summary: Stream real-time audio from client to server via WebSocket

Description: Establishes a WebSocket connection for audio streaming. Sends an initial JSON payload with patient and interaction details. Streams audio chunks (WebM/Opus format) at regular intervals. Receives processed results from the server (diagnosis, notes, medications). Finalizes the stream by sending a stop command and last audio blob.

⚠️ Two Types of WebSocket Connections Available

You can choose between two different WebSocket endpoints based on your audio transmission format:

BINARY Binary Audio Chunks

Endpoint:

wss://app.carescribe.health/wsaudio

Format:

  • Uses socket.binaryType = "arraybuffer"
  • Sends raw binary data (ArrayBuffer)
  • More efficient (smaller payload)
  • Direct binary transmission

BASE64 Base64 Audio Chunks

Endpoint:

wss://app.carescribe.health/wsaudiobase64

Format:

  • No special binaryType config needed
  • Sends JSON with audio field containing base64 string
  • Text-based (easier to debug)
  • ~33% larger payload size

⚠️ Important: Replace const token = "Api_Key" in the code with your actual API key for security purposes.

Init Payload

The initial payload is sent immediately after the WebSocket connection is established:

{
  "hospital_patient_id": "9004",
  "hospital_id": "9",
  "practitioner_id": "100",
  "first_name": "ragul",
  "last_name": "P",
  "date_of_birth": null,
  "gender": "M",
  "age": 10,
  "interaction_detail_type": "Audio url",
  "attenderName": null,
  "attenderRelationship": null
}

Note:

  • interaction_detail_type can be either "Audio url" or "Counselling".
  • If interaction_detail_type = "Counselling", provide the following data:
    • attenderName: e.g., "Ragul"
    • attenderRelationship: e.g., "Father"
  • gender: "M" or "F" or "O"

Audio Stream

Audio chunks are sent every 1 second (1000ms) using MediaRecorder with format audio/webm;codecs=opus at 32 kbps.

Stop Payload

When stopping the stream, send the following JSON payload:

{
  "type": "stop"
}

Response

The server responds with processed data when the audio stream is complete:

{
  "status": "processed",
  "allData": {
    "interaction_id": "336190",
    "patient_id": "GEN9-202603-00090",
    "user_id": null,
    "doctor_id": 853,
    "organization_id": 9,
    "interaction_type": "get-opd",
    "attachment_url": "gs://medscribe-prod/webm_files/get-opd/record-notes-9_Gen-9090_47c44be4-4377-4fc1-a0b1-6c1e59d1cfb8.txt",
    "virtual_consult": false,
    "textingestIntraction": null,
    "processedText": "{\"chief_complaints\": \"Fever and cough.\", \"history_of_presenting_illness\": \"\", \"past_medical_history\": \"\", \"personal_history\": \"\", \"examination_findings\": \"\", \"procedure\": \"**Procedure:** \", \"investigations\": \"\", \"diagnosis\": \"Fever, Cough.\", \"recommendations\": \"Do yoga.\", \"diet\": \"**Diet:** Avoid oily foods and junk foods.\", \"follow_up\": \"Follow-up:\", \"plan\": \"\", \"past_medications\": \"**Past Medications:** \", \"assessment\": [], \"medication_templates\": [{\"medication_name\": \"Cetirizine\", \"medication_type\": \"tablet\", \"dosage\": null, \"route\": \"Oral\", \"frequency_morning\": \"0\", \"frequency_afternoon\": \"0\", \"frequency_evening\": \"0\", \"frequency_night\": \"0\", \"duration\": \"2 days\", \"instructions\": null, \"vaccine\": false, \"is_stat\": false, \"is_once_only\": false, \"is_sos\": false, \"medication_start_date\": \"24/03/2026\", \"medication_end_date\": \"25/03/2026\", \"auto_correct\": false}, {\"medication_name\": \"Paracetamol\", \"medication_type\": \"tablet\", \"dosage\": null, \"route\": \"Oral\", \"frequency_morning\": \"0\", \"frequency_afternoon\": \"0\", \"frequency_evening\": \"0\", \"frequency_night\": \"0\", \"duration\": \"2 days\", \"instructions\": null, \"vaccine\": false, \"is_stat\": false, \"is_once_only\": false, \"is_sos\": false, \"medication_start_date\": \"26/03/2026\", \"medication_end_date\": \"27/03/2026\", \"auto_correct\": false}], \"past_medications_templates\": [], \"vitals\": [], \"additional_response\": \"\", \"lab_data\": {\"lab_abnormal_data\": []}, \"cross_dr_id\": []}"
  }
}

Note: Use wss:// in production for secure communication.

Method 1: Binary Audio Chunks

WSS wss://app.carescribe.health/wsaudio

Summary: WebSocket connection for binary audio streaming

Description: Establishes a WebSocket connection using binary format. Uses socket.binaryType = "arraybuffer" and sends raw binary data (ArrayBuffer). More efficient with smaller payload size.

Example Code:

Complete HTML example for streaming audio using binary format:

<!DOCTYPE html>
          <html lang="en">
          <head>
            <meta charset="UTF-8">
            <title>Audio Streaming with WebSocket</title>
            <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
            <style>
              body {
                font-family: Arial, sans-serif;
                margin: 20px;
                background: #f9f9f9;
              }
              h1 {
                text-align: center;
              }
              #controls {
                text-align: center;
                margin-bottom: 20px;
              }
              button {
                padding: 10px 20px;
                margin: 0 10px;
                border: none;
                border-radius: 6px;
                cursor: pointer;
                font-size: 16px;
              }
              #startBtn { background-color: #4CAF50; color: white; }
              #stopBtn { background-color: #f44336; color: white; }
              #output {
                background: #fff;
                padding: 15px;
                border-radius: 8px;
                box-shadow: 0 2px 6px rgba(0,0,0,0.1);
                margin-top: 20px;
              }
              #loading {
                text-align: center;
                font-size: 16px;
                font-weight: bold;
                color: #555;
                display: none;
                margin-top: 10px;
              }
              table {
                border-collapse: collapse;
                width: 100%;
                margin-top: 15px;
              }
              th, td {
                border: 1px solid #ccc;
                padding: 8px;
                text-align: left;
              }
              th {
                background: #f2f2f2;
              }
              h3 {
                margin-top: 15px;
                color: #333;
              }
              pre {
                background: #f7f7f7;
                padding: 10px;
                border-radius: 6px;
                overflow-x: auto;
              }
            </style>
          </head>
          <body>
            <h1>Audio Streaming</h1>
            <div id="controls">
              <button id="startBtn">Start Streaming</button>
              <button id="stopBtn" disabled>Stop Streaming</button>
            </div>
          
            <div id="loading">⏳ Processing...</div>
          
            <div id="output">
              <h2>Processed Data</h2>
              <div id="result"></div>
            </div>
          
            <script>
              let socket;
              let mediaRecorder;
              let recordedChunks = [];
          
              const initPayload = {
                hospital_patient_id: "9004",
                hospital_id: "carescribe001",
                practitioner_id: "carescribe001-dr001",
                first_name: "Ragul",
                last_name: "P",
                date_of_birth: "2015-04-12",
                gender: "M",
                age: 10,
                interaction_detail_type: "Audio url",
                attenderName: null,
                attenderRelationship: null
              };
          
              const sampleResponse = {allData:{
                "interaction_id": "76571",
                "attachment_url": "gs://medscribe-dev/webm_files/get-opd/record-notes-9_GEN9-202509-00438_58bab159-071d-4cf6-beea-b113c0b18e3c.txt",
                "processedText": "{\"chief_complaints\": \"Persistent pain in the chest area.\", \"history_of_presenting_illness\": \"Patient reports a persistent pain issue. The pain is continuous and sometimes becomes severe, feeling like a wound. Patient mentions that taking the tablet ultra p tablets provides some relief, reducing the pain by about 80%. Patient takes this medication intermittently, once every four, five, or seven days, not daily. Patient avoids heavy lifting. The pain is localized to a specific point and is described as nerve pain or fascial pain, not bone pain. The pain has been continuous, with periods of increased severity.\", \"past_medical_history\": \"Hypercholesterolemia Fatty liver disease\", \"personal_history\": \"Patient does not work night shifts.\", \"examination_findings\": \" On examination, the pain is localized to a specific point.\", \"investigations\": \"Blood tests reviewed: LFT and other enzyme levels are within normal limits, except for elevated triglycerides.\", \"diagnosis\": \"Costochondritis\", \"recommendations\": \"Patient is advised to continue walking and stretching exercises as they provide relief from stiffness. Patient is advised to use ultraplus h 162.5 mg tablets for 5 days.\", \"follow_up\": \"Patient is advised to follow up after 3 days to assess response to treatment and decide on further management, such as a nerve block injection\", \"plan\": \" A FibroScan is planned to further evaluate the liver. The procedure will be scheduled for the upcoming Saturday.\", \"assessment\": [{\"assessment_template\": \"General Health Assessment\", \"template_tests\": \"Blood Test, X-Ray\", \"template_id\": 22}], \"medication_templates\": [{\"medication_name\": \"Ibuprofen\", \"medication_type\": \"tablet\", \"dosage\": null, \"route\": \"Oral\", \"frequency_morning\": \"1\", \"frequency_afternoon\": \"0\", \"frequency_evening\": \"1\", \"frequency_night\": null, \"duration\": \"3 days\", \"instructions\": null, \"auto_correct\": false}, {\"medication_name\": \"Calpol\", \"medication_type\": \"tablet\", \"dosage\": null, \"route\": \"Oral\", \"frequency_morning\": \"1\", \"frequency_afternoon\": \"0\", \"frequency_evening\": \"1\", \"frequency_night\": null, \"duration\": \"3 days\", \"instructions\": null, \"auto_correct\": false}, {\"medication_name\": \"Cetirizine\", \"medication_type\": \"tablet\", \"dosage\": null, \"route\": \"Oral\", \"frequency_morning\": \"1\", \"frequency_afternoon\": \"0\", \"frequency_evening\": \"1\", \"frequency_night\": null, \"duration\": \"3 days\", \"instructions\": null, \"auto_correct\": false}, {\"medication_name\": \"Nexpro DSR\", \"medication_type\": \"tablet\", \"dosage\": null, \"route\": \"Oral\", \"frequency_morning\": \"1\", \"frequency_afternoon\": \"0\", \"frequency_evening\": \"1\", \"frequency_night\": null, \"duration\": \"3 days\", \"instructions\": null, \"auto_correct\": false}], \"additional_response\": \"\", \"vitals\": [], \"lab_data\": {\"lab_abnormal_data\": \"\"}}"
              }};
          
              const resultDiv = document.getElementById("result");
              const startBtn = document.getElementById("startBtn");
              const stopBtn = document.getElementById("stopBtn");
              const loadingDiv = document.getElementById("loading");
          
              startBtn.addEventListener("click", startStreaming);
              stopBtn.addEventListener("click", stopStreaming);
          
              async function startStreaming() {
                try {
                  const token = "Api_Key";
                  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                  socket = new WebSocket("wss://app.carescribe.health/wsaudio",token);
                  socket.binaryType = "arraybuffer";
          
                  startBtn.disabled = true;
                  stopBtn.disabled = false;
                  resultDiv.innerHTML = "";
                  loadingDiv.style.display = "none";
          
                  socket.onopen = () => {
                    console.log("✅ WebSocket connected");
                    socket.send(JSON.stringify(initPayload));
          
                    mediaRecorder = new MediaRecorder(stream, {
                      mimeType: "audio/webm;codecs=opus",
                      audioBitsPerSecond: 32000
                    });
          
                    mediaRecorder.ondataavailable = async (event) => {
                      if (event.data.size > 0 && socket.readyState === WebSocket.OPEN) {
                        const buffer = await event.data.arrayBuffer();
                        socket.send(buffer);
                        recordedChunks.push(event.data);
                        console.log("📤 Sent audio chunk:", buffer.byteLength);
                      }
                    };
          
                    mediaRecorder.start(1000);
                  };
          
                  socket.onmessage = (event) => {
                    try {
                      const parsedData = JSON.parse(event.data);
                      if (parsedData?.status === "processed") {
                        loadProcessedData(parsedData);
                      }
                    } catch (err) {
                      console.error("❌ Invalid server data:", event.data, err);
                    }
                  };
          
                  socket.onerror = (err) => console.error("❌ WebSocket error:", err);
                  socket.onclose = () => {
                    console.log("🔒 WebSocket closed");
                    if (mediaRecorder && mediaRecorder.state !== "inactive") mediaRecorder.stop();
                  };
                } catch (error) {
                  console.error("❌ Error accessing mic:", error);
                }
              }
          
              async function stopStreaming() {
                stopBtn.disabled = true; 
                loadingDiv.style.display = "block";
          
                if (mediaRecorder && mediaRecorder.state !== "inactive") {
                  await new Promise(resolve => {
                    mediaRecorder.onstop = async () => {
                      const blob = new Blob(recordedChunks, { type: "audio/webm;codecs=opus" });
                      if (socket?.readyState === WebSocket.OPEN) {
                        const buffer = await blob.arrayBuffer();
                        socket.send(buffer);
                        socket.send(JSON.stringify({ type: "stop" }));
                        console.log("📤 Sent final audio blob and stop command");
                      }
                      recordedChunks = [];
                      resolve();
                    };
                    mediaRecorder.stop();
                  });
                }
              }
          
              // 🔹 Function to render processedText dynamically
              function loadProcessedData(parsedData) {
                loadingDiv.style.display = "none";
                startBtn.disabled = false;
                stopBtn.disabled = true;
          
                let processedObj;
                try {
                  processedObj = JSON.parse(parsedData.allData.processedText);
                } catch {
                  processedObj = { response: parsedData.allData.processedText };
                }
          
                let htmlOutput = "";
                for (const [key, value] of Object.entries(processedObj)) {
                  if (key === "medication_templates") continue;
          
                  let displayValue;
                  if (Array.isArray(value) || (typeof value === "object" && value !== null)) {
                    displayValue = `<pre>${JSON.stringify(value, null, 2)}</pre>`;
                  } else {
                    displayValue = value || "-";
                  }
          
                  htmlOutput += `
                    <h3>📌 ${key.replace(/_/g, " ")}</h3>
                    <div>${displayValue}</div>
                    <hr/>
                  `;
                }
          
                let medsTable = "";
                if (processedObj?.medication_templates?.length) {
                  medsTable = `
                  <h3>💊 Prescribed Medications</h3>
                  <table>
                    <thead>
                      <tr>
                        <th>Name</th>
                        <th>Type</th>
                        <th>Dosage</th>
                        <th>Route</th>
                        <th>Frequency</th>
                        <th>Duration</th>
                        <th>Instructions</th>
                      </tr>
                    </thead>
                    <tbody>
                      ${processedObj.medication_templates.map(med => `
                        <tr>
                          <td>${med.medication_name || "-"}</td>
                          <td>${med.medication_type || "-"}</td>
                          <td>${med.dosage || "-"}</td>
                          <td>${med.route || "-"}</td>
                          <td>
                            M:${med.frequency_morning || "-"}, 
                            A:${med.frequency_afternoon || "-"}, 
                            E:${med.frequency_evening || "-"}, 
                            N:${med.frequency_night || "-"}
                          </td>
                          <td>${med.duration || "-"}</td>
                          <td>${med.instructions || "-"}</td>
                        </tr>
                      `).join("")}
                    </tbody>
                  </table>`;
                }
          
                resultDiv.innerHTML = htmlOutput + medsTable;
              }
            </script>
          </body>
          </html>

Method 2: Base64 Audio Chunks

WSS wss://app.carescribe.health/wsaudiobase64

Summary: WebSocket connection for base64 audio streaming

Description: Establishes a WebSocket connection using base64 format. No special binaryType config needed. Sends JSON with audio field containing base64 string. Text-based format that's easier to debug, but ~33% larger payload size.

Example Code:


          <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Audio Streaming with WebSocket (base64)</title>
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <style>
    body { font-family: Arial, sans-serif; margin: 20px; background: #f9f9f9; }
    h1 { text-align: center; }
    #controls { text-align: center; margin-bottom: 20px; }
    button { padding: 10px 20px; margin: 0 10px; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; }
    #startBtn { background-color: #4CAF50; color: white; }
    #stopBtn { background-color: #f44336; color: white; }
    #output { background: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); margin-top: 20px; }
    #loading { text-align: center; font-size: 16px; font-weight: bold; color: #555; display: none; margin-top: 10px; }
    table { border-collapse: collapse; width: 100%; margin-top: 15px; }
    th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
    th { background: #f2f2f2; }
    h3 { margin-top: 15px; color: #333; }
    pre { background: #f7f7f7; padding: 10px; border-radius: 6px; overflow-x: auto; }
  </style>
</head>
<body>
  <h1>Audio Streaming</h1>
  <div id="controls">
    <button id="startBtn">Start Streaming</button>
    <button id="stopBtn" disabled>Stop Streaming</button>
  </div>

  <div id="loading">⏳ Processing...</div>

  <div id="output">
    <h2>Processed Data</h2>
    <div id="result"></div>
  </div>

  <script>
    let socket;
    let mediaRecorder;
    let recordedChunks = [];

    const initPayload = {
      hospital_patient_id: "Gen-9090",
      hospital_id: "9",
      practitioner_id: "100",
      first_name: "Ragul",
      last_name: "P",
      date_of_birth: "2015-04-12",
      gender: "M",
      age: 10,
      interaction_detail_type: "Audio url",
      attenderName: null,
      attenderRelationship: null
    };

    const resultDiv = document.getElementById("result");
    const startBtn = document.getElementById("startBtn");
    const stopBtn = document.getElementById("stopBtn");
    const loadingDiv = document.getElementById("loading");

    startBtn.addEventListener("click", startStreaming);
    stopBtn.addEventListener("click", stopStreaming);

    // Convert a Blob to base64 using FileReader (safe for Opus/WebM chunks)
    function blobToBase64(blob) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onloadend = () => {
          try {
            const dataUrl = reader.result; // "data:audio/webm;codecs=opus;base64,AAAA..."
            const base64 = (dataUrl || "").toString().split(",")[1] || "";
            resolve(base64);
          } catch (e) { reject(e); }
        };
        reader.onerror = reject;
        reader.readAsDataURL(blob);
      });
    }

    // Light back-pressure: wait while bufferedAmount is high
    async function waitForDrain(ws, threshold = 512 * 1024) {
      while (ws.readyState === WebSocket.OPEN && ws.bufferedAmount > threshold) {
        await new Promise(r => setTimeout(r, 20));
      }
    }

    async function startStreaming() {
      try {
        const token = "Api_Key"; // optional subprotocol
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });

        // Pass token as subprotocol so server can read it from 'sec-websocket-protocol'
        socket = new WebSocket("wss://app.carescribe.health/wsaudiobase64", [token]);

        // We are sending ONLY JSON text (no binary)
        startBtn.disabled = true;
        stopBtn.disabled = false;
        resultDiv.innerHTML = "";
        loadingDiv.style.display = "none";

        socket.onopen = () => {
          console.log("✅ WebSocket connected");
          // 1) Send init JSON first
          socket.send(JSON.stringify(initPayload));

          // 2) Start MediaRecorder and send base64 JSON per chunk
          mediaRecorder = new MediaRecorder(stream, {
            mimeType: "audio/webm;codecs=opus",
            audioBitsPerSecond: 32000
          });

          mediaRecorder.ondataavailable = async (event) => {
            if (event.data && event.data.size > 0 && socket.readyState === WebSocket.OPEN) {
              try {
                const base64 = await blobToBase64(event.data);
                await waitForDrain(socket);
                socket.send(JSON.stringify({ audio: base64 }));
                recordedChunks.push(event.data);
                console.log("📤 Sent base64 chunk:", (base64.length / 1024).toFixed(1), "KB");
              } catch (err) {
                console.error("❌ Failed to convert/send chunk:", err);
              }
            }
          };

          // Send a chunk roughly every second
          mediaRecorder.start(1000);
        };

        socket.onmessage = (event) => {
          // Expecting JSON messages from server (e.g., processed result or ack)
          try {
            const parsed = JSON.parse(event.data);
            if (parsed?.status === "processed") {
              loadProcessedData(parsed);
            } else {
              // You can log/handle other statuses here
              // console.log("Server message:", parsed);
            }
          } catch (err) {
            console.error("❌ Invalid server data:", event.data, err);
          }
        };

        socket.onerror = (err) => console.error("❌ WebSocket error:", err);
        socket.onclose = () => {
          console.log("🔒 WebSocket closed");
          if (mediaRecorder && mediaRecorder.state !== "inactive") {
            mediaRecorder.stop();
          }
        };
      } catch (error) {
        console.error("❌ Error accessing mic:", error);
        startBtn.disabled = false;
        stopBtn.disabled = true;
      }
    }

    async function stopStreaming() {
      stopBtn.disabled = true;
      loadingDiv.style.display = "block";

      // Gracefully stop recorder, flush last chunks, then tell server to stop
      if (mediaRecorder && mediaRecorder.state !== "inactive") {
        await new Promise(resolve => {
          mediaRecorder.onstop = async () => {
            try {
              if (socket?.readyState === WebSocket.OPEN) {
                await waitForDrain(socket);
                socket.send(JSON.stringify({ type: "stop" }));
                console.log("📤 Sent stop command");
              }
            } finally {
              recordedChunks = [];
              resolve();
            }
          };
          mediaRecorder.stop();
        });
      }
    }

    // Render processed data helper
    function loadProcessedData(parsedData) {
      loadingDiv.style.display = "none";
      startBtn.disabled = false;
      stopBtn.disabled = true;

      let processedObj;
      try {
        processedObj = JSON.parse(parsedData.allData?.processedText || "{}");
      } catch {
        processedObj = { response: parsedData.allData?.processedText || "" };
      }

      let htmlOutput = "";
      for (const [key, value] of Object.entries(processedObj)) {
        if (key === "medication_templates") continue;

        let displayValue;
        if (Array.isArray(value) || (typeof value === "object" && value !== null)) {
          displayValue = `<pre>${JSON.stringify(value, null, 2)}</pre>`;
        } else {
          displayValue = value || "-";
        }

        htmlOutput += `
          <h3>📌 ${key.replace(/_/g, " ")}</h3>
          <div>${displayValue}</div>
          <hr/>
        `;
      }

      let medsTable = "";
      if (processedObj?.medication_templates?.length) {
        medsTable = `
        <h3>💊 Prescribed Medications</h3>
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Type</th>
              <th>Dosage</th>
              <th>Route</th>
              <th>Frequency</th>
              <th>Duration</th>
              <th>Instructions</th>
            </tr>
          </thead>
          <tbody>
            ${processedObj.medication_templates.map(med => `
              <tr>
                <td>${med.medication_name || "-"}</td>
                <td>${med.medication_type || "-"}</td>
                <td>${med.dosage || "-"}</td>
                <td>${med.route || "-"}</td>
                <td>
                  M:${med.frequency_morning || "-"},
                  A:${med.frequency_afternoon || "-"},
                  E:${med.frequency_evening || "-"},
                  N:${med.frequency_night || "-"}
                </td>
                <td>${med.duration || "-"}</td>
                <td>${med.instructions || "-"}</td>
              </tr>
            `).join("")}
          </tbody>
        </table>`;
      }

      resultDiv.innerHTML = htmlOutput + medsTable;
    }
  </script>
</body>
</html>
        

Multi-source Audio WebSocket

WSS wss://app.carescribe.health/wsaudio/multisource/v1

Summary: Stream two or more audio sources for one consultation.

Each participant opens a separate WebSocket using the same session_id, encounter_id, patient, hospital, and expected_sources. Every socket has a unique source_id. The server stores the sources independently and creates one interaction after the doctor completes the session.

Protocol requirements
  • Send init within 10 seconds of connecting.
  • Authenticate with X-API-Key during the upgrade, or put api_key in init when the client cannot set custom headers.
  • The API key must belong to the organization identified by hospital_id.
  • Declare 2 to 8 unique source IDs (the configured server maximum may differ).
  • Send audio as binary frames. The default maximum frame size is 1 MiB.
  • Only a doctor source may disable sources or complete the session.

1. Initialize every source

Every source sends the same source list and consultation metadata. IDs may contain letters, numbers, dots, underscores, colons, and hyphens, up to 128 characters.

Complete doctor init payload

{
  "type": "init",
  "api_key": "YOUR_API_KEY",
  "protocol_version": 1,
  "session_id": "SESSION-MTCLYMQA1",
  "encounter_id": "ENCOUNTER-MTCLYMQA1",
  "source_id": "doctor-mic1",
  "source_role": "doctor",
  "expected_sources": ["doctor-mic1", "patient-mic1"],
  "hospital_id": "9",
  "hospital_patient_id": "rag123",
  "practitioner_id": "1001",
  "primary_practitioner_id": "1001",
  "patient_category": "Outpatient",
  "first_name": "ragul",
  "last_name": "P",
  "date_of_birth": null,
  "gender": "M",
  "age": 10,
  "interaction_detail_type": "opd",
  "attenderName": null,
  "attenderRelationship": null,
  "language_code": "mr-IN",
  "preferred_language": "English",
  "upload_file_mime_type": "audio/webm;codecs=opus"
}

Complete patient init payload

{
  "type": "init",
  "api_key": "YOUR_API_KEY",
  "protocol_version": 1,
  "session_id": "SESSION-MTCLYMQA1",
  "encounter_id": "ENCOUNTER-MTCLYMQA1",
  "source_id": "patient-mic1",
  "source_role": "patient",
  "expected_sources": ["doctor-mic1", "patient-mic1"],
  "hospital_id": "9",
  "hospital_patient_id": "rag123",
  "patient_category": "Outpatient",
  "language_code": "mr-IN",
  "upload_file_mime_type": "audio/webm;codecs=opus"
}

Init fields

FieldRequiredDescription
typeYesMust be init.
protocol_versionYesInteger 1; identifies this WebSocket contract version.
session_idYesShared consultation session ID.
encounter_idYesShared encounter ID.
source_idYesUnique ID for this audio source.
source_roleYesdoctor or patient.
expected_sourcesYesComplete list of 2–8 sources, including this source.
hospital_idYesHospital authorized by the API key.
hospital_patient_idYesPatient identifier in the hospital system.
api_keyConditionalRequired here only if the upgrade request has no X-API-Key.
primary_practitioner_idNoDoctor only; when supplied, it must match practitioner_id.
language_codeNoLanguage metadata stored for the source.
patient_categoryNoOutpatient, Inpatient, or Master Health Checkup, as supported by the interaction flow.
upload_file_mime_typeBrowser suppliedActual MediaRecorder format, such as audio/webm;codecs=opus.

Doctor-only and patient-demographic fields

FieldRequiredDescription
practitioner_idDoctor sourceExternal practitioner identifier for this doctor microphone.
primary_practitioner_idPrimary doctorSelects the authoritative primary doctor and must match practitioner_id on that source.
first_nameNoPatient first name used when creating or updating the patient record.
last_nameNoPatient last name.
date_of_birthNoDate in YYYY-MM-DD format, or null.
genderNoPatient gender expected by the existing interaction flow.
ageNoNumeric patient age.
interaction_detail_typeYesCurrent client processing mode: opd. The server always stores the created audio detail as Audio url.
attenderNameNoName of the accompanying person, or null.
attenderRelationshipNoRelationship of the attender to the patient, or null.
preferred_language No Structured-output language. Allowed values: English, Tamil, Kannada, Malayalam, Hindi, Japanese, and Patient Language. If omitted or empty, the server uses the doctor’s configured preferred language, then defaults to English. This differs from language_code, which describes the spoken audio.
Values that must match: session_id, encounter_id, hospital_id, hospital_patient_id, and the full expected_sources array must be identical on every socket.

OPD input and audio-detail storage

For the current multi-source consultation flow, send:

{
  "interaction_detail_type": "opd"
}

Success returns source.accepted, followed by a session-wide session.state broadcast:

{
  "type": "source.accepted",
  "session_id": "consultation-20260831-001",
  "encounter_id": "encounter-9004-001",
  "source_id": "doctor-device",
  "source_role": "doctor",
  "primary_practitioner_id": "carescribe-001-dr001",
  "status": "streaming"
}

2. Stream binary audio

After source.accepted, send raw binary audio frames. The server acknowledges each frame with chunk.ack:

{
  "type": "chunk.ack",
  "session_id": "consultation-20260831-001",
  "source_id": "doctor-device",
  "source_role": "doctor",
  "chunk_number": 12,
  "chunk_bytes": 16384,
  "chunks_received": 12,
  "bytes_received": 196608,
  "received_at_ms": 1788172200000
}

3. Stop an individual source

Flush the recorder's last chunk before sending:

{
  "type": "source.stop",
  "session_id": "SESSION-MTCLYMQA1",
  "source_id": "doctor-mic1",
  "reason": "doctor_mic_off",
  "stopped_at_ms": 1787892000000
}

reason may be doctor_mic_off, patient_audio_off, or session_complete. The server responds with source.stopped and broadcasts the new session.state.

4. Disable a source (doctor only)

{
  "type": "source.disable",
  "session_id": "SESSION-MTCLYMQA1",
  "source_id": "patient-mic1",
  "reason": "patient_audio_off"
}

A connected source receives source.stop_requested and a grace period. An expected source that never connected can also be disabled.

5. Complete the consultation (doctor only)

{
  "type": "session.complete",
  "session_id": "SESSION-MTCLYMQA1",
  "requested_by": "doctor",
  "completed_at_ms": 1787892000000
}

The server finalizes active sources. Every expected source must be uploaded or explicitly disabled. On success, all connected sources receive:

{
  "type": "interaction.created",
  "session_id": "consultation-20260831-001",
  "encounter_id": "encounter-9004-001",
  "interaction_id": "336190",
  "source_count": 2,
  "status": "processing"
}

Runnable doctor and patient examples

Open both pages over HTTPS or localhost, enter matching consultation values, then start the doctor and patient sources:

JavaScript connection example

const shared = {
  protocol_version: 1,
  session_id: "SESSION-MTCLYMQA1",
  encounter_id: "ENCOUNTER-MTCLYMQA1",
  expected_sources: ["doctor-mic1", "patient-mic1"],
  hospital_id: "9",
  hospital_patient_id: "rag123",
  patient_category: "Outpatient",
  language_code: "mr-IN",
  upload_file_mime_type: "audio/webm;codecs=opus"
};

function connectSource({ apiKey, sourceId, sourceRole, practitionerId }) {
  const socket = new WebSocket(
    "wss://app.carescribe.health/wsaudio/multisource/v1"
  );
  socket.binaryType = "arraybuffer";

  socket.addEventListener("open", () => socket.send(JSON.stringify({
    type: "init",
    api_key: apiKey,
    ...shared,
    source_id: sourceId,
    source_role: sourceRole,
    practitioner_id: practitionerId,
    ...(sourceRole === "doctor"
      ? {
          primary_practitioner_id: practitionerId,
          first_name: "ragul",
          last_name: "P",
          date_of_birth: null,
          gender: "M",
          age: 10,
          interaction_detail_type: "Audio url",
          attenderName: null,
          attenderRelationship: null,
          preferred_language: "English"
        }
      : {})
  })));

  socket.addEventListener("message", ({ data }) => {
    console.log(sourceId, JSON.parse(data));
  });
  return socket;
}

// After source.accepted:
// socket.send(arrayBuffer);
// socket.send(JSON.stringify({ type: "source.stop" }));
// doctorSocket.send(JSON.stringify({ type: "session.complete" }));

Server messages

MessageMeaning
source.acceptedThe source may begin streaming.
chunk.ackA binary frame was accepted.
session.stateCurrent state of all expected sources.
source.stop_requestedThe doctor disabled this connected source.
source.stoppedThe source upload was finalized.
session.completion_requestedThe doctor requested completion.
session.completion_blockedA source is missing or failed.
session.completion_rejectedNo source has processable audio.
interaction.createdThe combined interaction was created.
session.processedFinal response containing allData. Parse allData.processedText as a second JSON document.
session.failedInteraction creation failed.
errorInspect the code and message fields.

Final processed response

After interaction.created, keep the doctor WebSocket open and show a processing state until this message arrives:

{
  "type": "session.processed",
  "session_id": "SESSION-MTCLYMQA1",
  "interaction_id": "133096",
  "status": "processed",
  "allData": {
    "interaction_id": "133096",
    "doctor_id": 143,
    "organization_id": 9,
    "attachment_url": "gs://bucket/path/result.txt",
    "processedText": "{\"chief_complaints\":\"Dizziness for 2 days\",\"diagnosis\":\"Vertigo\"}",
    "interaction_type": "get-opd"
  }
}
processedText is a JSON-encoded string inside the WebSocket response. Call JSON.parse(message.allData.processedText) before rendering clinical sections.

Validation and operational rules

ID lifecycle: Generate a new session_id and encounter_id for every consultation. Doctor and patient must send the same values for those IDs, hospital_id, hospital_patient_id, and expected_sources. Use the Source ID input values for source_id (for example doctor and patient) and keep them unchanged for that consultation. Do not open a second socket with an already-connected source_id; the server returns duplicate_source.
  • Never place the API key in a WebSocket URL. Browser clients use api_key in init; native clients may use X-API-Key.
  • Each source uses a separate GCS object. Audio sources are not concatenated into one upload file.
  • source_id remains bound to the same socket and role for the connection lifetime.
  • Request one MediaRecorder binary chunk every 5,000 ms and flush the final chunk before sending source.stop.
  • Do not disconnect after completion. Wait for session.processed, then disconnect after the result is loaded.
  • Session coordination is process-local. Multi-pod deployments require session affinity until cross-pod coordination is implemented.

Errors and close behavior

  • Authentication, session metadata, primary-doctor, and duplicate-source errors close the socket with policy-violation code 1008.
  • Common recoverable error codes include init_required, source_not_streaming, frame_too_large, forbidden, unknown_source, unsupported_message, and invalid_message.
  • Closing before source.stop marks the source disconnected and aborts its upload. Reconnect with a new source_id, or have the doctor disable the expected source.

Step 5: Upload Medication List

Summary: Upload medication data for your organization

Description: You can upload medication lists in two ways: via JSON payload or via CSV file upload. Both methods allow you to add brand names and generic names for medications that will be used in your hospital setup.

⚠️ Important: Use post medication api call only for the initial upload. If you use upload again for appending data, it will delete the existing list and replace it completely with the new data. For any modifications or additions, always use the update (put) medication api call.

Two Methods Available

Choose the method that best fits your workflow:

JSON JSON Payload Method

Endpoint:

POST /software/post-medication

CSV CSV File Upload Method

Endpoint:

POST /software/uploadMedicationCsv

Method 1: JSON Payload

POST /software/post-medication

Summary: Upload medication master data (JSON) and store as CSV

Description: Uploads medication master data for a hospital in JSON format and stores it as a CSV file.

Requirements:

  • hospital_id and a non-empty medications array are required.
  • Each medication must contain BRAND NAME and GENERIC NAME.
  • BRAND ID is optional, but if provided in any item, it must be present in all items.

Optional Fields:

  • UOM CODE
  • UOM DESCRIPTION (if provided, UOM CODE is required).
  • ACTIVE FROM (optional date in DD/MM/YYYY format)
  • ACTIVE TO (optional date in DD/MM/YYYY format)
  • MEDICATION TYPE

Validation:

  • Duplicate BRAND ID values in the request are marked invalid.
  • Duplicate BRAND NAME and GENERIC NAME combinations are marked invalid.

The API stores the valid data as a CSV file and returns the processed file URL along with any invalid rows.

Body

Example 1: Basic payload (BRAND NAME and GENERIC NAME only)

{
  "hospital_id": "9",
  "medications": [
    {
      "BRAND NAME": "Tylenol",
      "GENERIC NAME": "Acetaminophen"
    },
    {
      "BRAND NAME": "Advil",
      "GENERIC NAME": "Ibuprofen"
    }
  ]
}

Example 2: Complete payload (all possible fields)

{
  "hospital_id": "9",
  "medications": [
    {
      "BRAND ID": "13752",
      "BRAND NAME": "OLVANCE 40MG TAB",
      "GENERIC NAME": "OLMESARTAN MEDOXOMIL 40MG",
      "UOM CODE": "mg",
      "UOM DESCRIPTION": "Milligram",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026",
      "MEDICATION TYPE": "Tablet"
    },
    {
      "BRAND ID": "13753",
      "BRAND NAME": "CROXIN 500MG TAB",
      "GENERIC NAME": "PARACETAMOL 500MG",
      "UOM CODE": "mg",
      "UOM DESCRIPTION": "Milligram",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026",
      "MEDICATION TYPE": "Tablet"
    }
  ]
}

Supported fields (case-sensitive as shown in examples):

  • BRAND NAME (required)
  • GENERIC NAME (required)
  • BRAND ID (conditionally required if present in any item)
  • UOM CODE (optional)
  • UOM DESCRIPTION (optional; if provided, UOM CODE is required)
  • ACTIVE FROM (optional; DD/MM/YYYY)
  • ACTIVE TO (optional; DD/MM/YYYY)
  • MEDICATION TYPE (optional)

Response (200 OK):

{
  "message": "Medication data uploaded successfully",
  "data": "https://storage.googleapis.com/bucket/processed/ORG123_20260305123456.json",
  "invalid": [
    {
      "BRAND ID": "13752",
      "BRAND NAME": "MERSILK 5062",
      "GENERIC NAME": "MERSILK",
      "UOM CODE": "",
      "UOM DESCRIPTION": "Milligram",
      "error": "BRAND NAME and GENERIC NAME combination already exists"
    }
  ]
}

Responses:

  • 200: Medication data uploaded successfully — Returns success message, processed output URL, and list of invalid rows rejected during validation
  • 400: Bad Request (validation failure) — Examples: {"message": "Invalid request body: hospital_id and non-empty medications array are required"}, {"message": "Inconsistent data: All medications must include BRAND ID if any include it"}, {"message": "No valid medications provided"}
  • 404: Organization not found — {"message": "Organization not found"}
  • 500: Internal server error — {"message": "Internal server error", "error": "Unexpected error"}

Method 2: CSV File Upload

POST /software/uploadMedicationCsv

Summary: Upload medication CSV

Description: Uploads a medication master CSV for a hospital and replaces the existing stored file (if any). The API validates the CSV, stores only valid rows, and returns the processed file URL.

Query Parameters:

  • hospital_id (required): Hospital/organization identifier

Form Data:

  • csvFile (required, binary): Medication CSV file to upload

Supported CSV header formats (case-sensitive):

  • BRAND NAME, GENERIC NAME
  • BRAND ID, BRAND NAME, GENERIC NAME
  • BRAND NAME, GENERIC NAME, UOM CODE, UOM DESCRIPTION
  • BRAND ID, BRAND NAME, GENERIC NAME, UOM CODE, UOM DESCRIPTION
  • Any format above may continue with optional ACTIVE FROM, ACTIVE TO, and MEDICATION TYPE columns in that order.

Validation rules:

  • BRAND NAME and GENERIC NAME are required.
  • BRAND ID is required only if the header contains BRAND ID.
  • UOM CODE and UOM DESCRIPTION are optional.
  • If UOM DESCRIPTION is provided, UOM CODE must also be provided.
  • ACTIVE FROM and ACTIVE TO, when present, must use DD/MM/YYYY.
  • Duplicate BRAND ID values are rejected.
  • Duplicate BRAND NAME + GENERIC NAME combinations are rejected.
  • Empty values or values containing only , or . are rejected.

Invalid rows are returned in the invalid field of the response.

Response (200 OK):

{
  "message": "CSV uploaded and saved",
  "data": "https://storage.googleapis.com/bucket/processed/ORG123_20260305123456.json",
  "invalid": []
}

Responses:

  • 200: CSV uploaded successfully — Returns success message, processed URL generated from the uploaded CSV, and rows rejected due to validation errors
  • 400: Bad request — Examples: {"message": "No valid rows in CSV"}, {"error": "Invalid hospital_id provided.", "invalid": []}
  • 404: Organization not found — {"message": "Organization not found"}
  • 500: Internal server error — {"message": "Failed to upload", "error": "Error processing CSV file"}

Update Medication List

Description: Update existing medication lists by merging new medication entries with existing data. You can update medication lists in two ways: via JSON payload or via CSV file upload.

Two Methods Available

Choose the method that best fits your workflow:

JSON JSON Payload Method

Endpoint:

PUT /software/updateMedicationCsv

CSV CSV File Upload Method

Endpoint:

PUT /software/updateMedicationCsvList

Method 1: JSON Payload

PUT /software/updateMedicationCsv

Summary: Update medication data (merge into existing CSV)

Description: Updates the medication master data for a hospital by merging the given JSON medications with the existing stored CSV.

  • hospital_id and non-empty medications are required.
  • Each item must contain BRAND NAME and GENERIC NAME.
  • BRAND ID is optional, but if used in any item, it must be present in all items.
  • UOM CODE and UOM DESCRIPTION are optional (if UOM DESCRIPTION is provided, UOM CODE is required).
  • ACTIVE FROM, ACTIVE TO, and MEDICATION TYPE are optional. Dates must use DD/MM/YYYY.
  • If an existing CSV is present, both datasets must agree on whether BRAND ID is present.
  • Final merged data is stored back as a CSV file.

Body

Example 1: Basic payload (BRAND NAME and GENERIC NAME only)

{
  "hospital_id": "9",
  "medications": [
    {
      "BRAND NAME": "Amlodipine",
      "GENERIC NAME": "Amlodipine Besylate"
    },
    {
      "BRAND NAME": "Metformin",
      "GENERIC NAME": "Metformin Hydrochloride"
    }
  ]
}

Example 2: Complete payload (all possible fields)

{
  "hospital_id": "9",
  "medications": [
    {
      "BRAND ID": "13752",
      "BRAND NAME": "OLVANCE 40MG TAB",
      "GENERIC NAME": "OLMESARTAN MEDOXOMIL 40MG",
      "UOM CODE": "mg",
      "UOM DESCRIPTION": "Milligram",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026",
      "MEDICATION TYPE": "Tablet"
    },
    {
      "BRAND ID": "13753",
      "BRAND NAME": "CROXIN 500MG TAB",
      "GENERIC NAME": "PARACETAMOL 500MG",
      "UOM CODE": "mg",
      "UOM DESCRIPTION": "Milligram",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026",
      "MEDICATION TYPE": "Tablet"
    }
  ]
}

Response (200 OK):

{
  "message": "Medication data updated successfully (delta applied)",
  "data": [
    {
      "BRAND ID": "13752",
      "BRAND NAME": "OLVANCE 40MG TAB",
      "GENERIC NAME": "OLMESARTAN MEDOXOMIL 40MG",
      "UOM CODE": "mg",
      "UOM DESCRIPTION": "Milligram"
    }
  ],
  "invalid": []
}

Responses:

  • 200: Medication data updated successfully — Returns success message, final merged medication rows (stored in CSV), and list of invalid entries (will be empty when update succeeds)
  • 400: Bad Request (validation/header mismatch) — Examples: {"message": "Invalid request body: hospital_id and non-empty medications array are required"}, {"message": "Inconsistent data: All medications must include BRAND ID if any include it"}, {"message": "Validation errors in medication data"}, {"message": "Header mismatch between existing and new medication data", "invalid": [...]}
  • 404: Organization not found — {"message": "Organization not found"}
  • 500: Internal server error — {"message": "Internal server error", "error": "Unexpected error"}

Method 2: CSV File Upload

PUT /software/updateMedicationCsvList

Summary: Update medication CSV data

Description: Uploads a medication CSV for the given hospital_id and merges it with the existing medication CSV stored in Google Cloud Storage (GCS).

Query Parameters:

  • hospital_id (required): Hospital identifier for the organization

Form Data:

  • csvFile (required, binary): Medication CSV file to upload (must end with .csv)

CSV header formats (case-sensitive, must match exactly one):

  • 1) BRAND NAME, GENERIC NAME
  • 2) BRAND ID, BRAND NAME, GENERIC NAME
  • 3) BRAND NAME, GENERIC NAME, UOM CODE, UOM DESCRIPTION
  • 4) BRAND ID, BRAND NAME, GENERIC NAME, UOM CODE, UOM DESCRIPTION
  • Each format may continue with optional ACTIVE FROM, ACTIVE TO, and MEDICATION TYPE columns in that order.

Validation rules (applies to both existing CSV and uploaded CSV):

  • BRAND NAME and GENERIC NAME are required in every row.
  • Empty values or values containing only , or . are rejected.
  • Duplicate BRAND ID values (when BRAND ID exists) are rejected.
  • Duplicate BRAND NAME + GENERIC NAME combinations are rejected.
  • If UOM DESCRIPTION is provided, UOM CODE is required.
  • ACTIVE FROM and ACTIVE TO, when present, must use DD/MM/YYYY.
  • If an existing medication CSV is present, both datasets must agree on whether BRAND ID is present.

Merge behavior:

  • If the uploaded header includes BRAND ID, the merge key is BRAND ID.
  • Otherwise, the merge key is BRAND NAME + GENERIC NAME.
  • Uploaded rows overwrite matching existing rows.

Response (200 OK):

{
  "message": "Medication data updated successfully (merged, duplicates removed)",
  "url": "https://storage.googleapis.com/bucket/csv_uploads/123_20260305_120102.csv",
  "invalid": []
}

Responses:

  • 200: Medication data updated successfully (merged and uploaded) — Returns success message, GCS URL of the merged CSV, and rows rejected due to validation errors (may be empty)
  • 400: Invalid request (missing hospital_id, invalid CSV, invalid columns, header mismatch, or no valid rows) — Examples: {"message": "Header mismatch between existing and new CSV", "invalid": []}
  • 404: Organization not found — {"message": "Organization not found"}
  • 500: Internal server error — {"message": "Internal server error", "error": "Unexpected error"}

Step 6: Upload Investigation List

Summary: Upload investigation data for your organization

Description: You can upload investigation lists in two ways: via JSON payload or via CSV file upload. Service ID and Standard Lab Test Name are required. Alias Name, Service Type, Hospital ID, ACTIVE FROM, and ACTIVE TO are optional. A blank Alias Name defaults to Standard Lab Test Name. Active dates, when provided, must use DD/MM/YYYY.

⚠️ Important: Use upload only for the initial upload. If you use upload again, it will delete the existing list and replace it completely with the new data. For any modifications or additions, always use the update call.

Two Methods Available

Choose the method that best fits your workflow:

JSON JSON Payload Method

Endpoint:

POST /software/uploadInvestigationJson

CSV CSV File Upload Method

Endpoint:

POST /software/uploadInvestigationCsv

Method 1: JSON Payload

POST /software/uploadInvestigationJson

Summary: Upload investigation data as JSON

Description: Upload investigation data for an organization as a JSON array. The data is converted to CSV format and stored in Google Cloud Storage, replacing any existing investigation data.

Query Parameters:

  • hospital_id (required): The ID of the hospital associated with the organization

Body

{
  "data": [
    {
      "Service ID": "INV001",
      "Standard Lab Test Name": "Complete Blood Count",
      "Alias Name": "CBC",
      "Service Type": "Laboratory",
      "Hospital ID": "HOSP123",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026"
    },
    {
      "Service ID": "INV002",
      "Standard Lab Test Name": "Blood Glucose",
      "Alias Name": "BG",
      "Service Type": "Laboratory"
    }
  ]
}

Note:

  • hospital_id: Required — The ID of the hospital/organization (query parameter)
  • data: Required — Array of investigation objects
  • Service ID: Required — Identifier that forms the row key together with Service Type
  • Standard Lab Test Name: Required — Standard name of the lab test
  • Alias Name: Optional — Alias or short name for the test; a blank value defaults to Standard Lab Test Name
  • Service Type: Optional — Type of service (e.g., "Laboratory")
  • Hospital ID: Optional — Hospital identifier
  • ACTIVE FROM: Optional — Start date in DD/MM/YYYY format
  • ACTIVE TO: Optional — End date in DD/MM/YYYY format

Response (200 OK):

{
  "message": "Investigation data uploaded successfully",
  "data": "https://storage.googleapis.com/bucket/investigation_csv/org123_2025_09_02_13_30_45_filtered.csv",
  "invalid": [
    {
      "Service ID": "INV001",
      "error": "Service ID is required"
    }
  ]
}

Responses:

  • 200: Investigation data uploaded successfully — Returns success message, data URL, and list of invalid entries if any
  • 400: Invalid request — {"message": "JSON data is required in request body", "error": "Invalid hospital_id provided."}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Failed to upload investigation data", "error": "Error message details"}

Method 2: CSV File Upload

POST /software/uploadInvestigationCsv

Summary: Upload an investigation CSV file

Description: Upload a CSV file containing investigation data for an organization, identified by hospital_id. The file is stored in Google Cloud Storage, and any existing file is deleted.

Query Parameters:

  • hospital_id (required): The ID of the hospital associated with the organization

Form Data:

  • csvFile (required, binary): CSV with required columns Service ID and Standard Lab Test Name; optional columns are Alias Name, Service Type, Hospital ID, ACTIVE FROM, and ACTIVE TO.

Note: CSV data should contain the following columns:

SERVICE ID STANDARD LAB TEST NAME ALIAS NAME
INV001 Complete Blood Count CBC
INV002 Blood Glucose BG

Optional columns: Alias Name, Service Type, Hospital ID, ACTIVE FROM, ACTIVE TO. Active dates must use DD/MM/YYYY.

Response (200 OK):

{
  "message": "CSV updated and saved",
  "data": "https://storage.googleapis.com/bucket/investigation_csv/org123_2025_09_02_13_30_45_filtered.csv",
  "invalid": [
    {
      "Service ID": "INV001",
      "Standard Lab Test Name": "Complete Blood Count",
      "Alias Name": "CBC",
      "error": "Service ID is required"
    }
  ]
}

Responses:

  • 200: CSV updated and saved successfully — Returns success message, data URL, and list of invalid entries with error details
  • 400: Invalid request — {"message": "A valid CSV file is required", "error": "Invalid hospital_id provided."}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Failed to upload", "error": "Error message details"}

Update Investigation List

Description: You can update existing investigation lists in two ways: via JSON payload or via CSV file upload. Both methods allow you to add new investigations to your existing investigation list.

Two Methods Available

Choose the method that best fits your workflow:

JSON JSON Payload Method

Endpoint:

POST /software/updateInvestigationJson

Note: Merges new investigation data with existing data and stores the updated file.

CSV CSV File Upload Method

Endpoint:

POST /software/updateInvestigationCsvs

Note: New file will be merged with existing file.

Method 1: JSON Payload

POST /software/updateInvestigationJson

Summary: Update investigation data as JSON

Description: Update investigation data for an organization by merging new JSON data with existing data. The merged data is converted to CSV format and stored in Google Cloud Storage.

Query Parameters:

  • hospital_id (required): The ID of the hospital associated with the organization

Body

{
  "data": [
    {
      "Service ID": "INV001",
      "Standard Lab Test Name": "Complete Blood Count",
      "Alias Name": "CBC",
      "Service Type": "Laboratory",
      "Hospital ID": "HOSP123",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026"
    },
    {
      "Service ID": "INV002",
      "Standard Lab Test Name": "Blood Glucose",
      "Alias Name": "BG",
      "Service Type": "Laboratory"
    }
  ]
}

Note:

  • hospital_id: Required — The ID of the hospital/organization (query parameter)
  • data: Required — Array of investigation objects to merge with existing data
  • Service ID: Required — Identifier that forms the row key together with Service Type
  • Standard Lab Test Name: Required — Standard name of the lab test
  • Alias Name: Optional — Alias or short name for the test
  • Service Type: Optional — Type of service (e.g., "Laboratory")
  • Hospital ID: Optional — Hospital identifier
  • ACTIVE FROM: Optional — Start date in DD/MM/YYYY format
  • ACTIVE TO: Optional — End date in DD/MM/YYYY format
  • The system will merge new data with existing data

Response (200 OK):

{
  "message": "Investigation data updated successfully",
  "data": "https://storage.googleapis.com/bucket/investigation_csv/org123_2025_09_02_13_30_45_filtered.csv",
  "invalid": [
    {
      "Service ID": "INV001",
      "error": "Service ID is required"
    }
  ]
}

Responses:

  • 200: Investigation data updated successfully — Returns success message, data URL, and list of invalid entries if any
  • 400: Invalid request — {"message": "JSON must be an array of objects", "error": "Invalid hospital_id provided."}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Failed to update investigation data", "error": "Error message details"}

Method 2: CSV File Upload

POST /software/updateInvestigationCsvs

Summary: Update an investigation CSV file

Description: Update a CSV file containing investigation data for an organization, identified by hospital_id. The file is stored in Google Cloud Storage, and new file will be merged with existing file.

Query Parameters:

  • hospital_id (required): The ID of the hospital associated with the organization

Form Data:

  • csvFile (required, binary): CSV with required columns Service ID and Standard Lab Test Name; optional columns are Alias Name, Service Type, Hospital ID, ACTIVE FROM, and ACTIVE TO.

Note: CSV data should contain the following columns (same as upload):

SERVICE ID STANDARD LAB TEST NAME ALIAS NAME
INV001 Complete Blood Count CBC
INV002 Blood Glucose BG

Optional columns: Alias Name, Service Type, Hospital ID, ACTIVE FROM, ACTIVE TO. Active dates must use DD/MM/YYYY. The case-insensitive Service ID + Service Type pair is the merge key.

Important: The new CSV file will be merged with the existing file, not replaced.

Response (200 OK):

{
  "message": "CSV uploaded and saved",
  "data": "https://storage.googleapis.com/bucket/investigation_csv/org123_2025_09_02_13_30_45_filtered.csv",
  "invalid": [
    {
      "Service ID": "INV001",
      "Standard Lab Test Name": "Complete Blood Count",
      "Alias Name": "CBC",
      "error": "Service ID is required"
    }
  ]
}

Responses:

  • 200: CSV uploaded and saved successfully — Returns success message, data URL, and list of invalid entries with error details
  • 400: Invalid request — {"message": "A valid CSV file is required", "error": "Invalid hospital_id provided."}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Failed to update", "error": "Error message details"}

Step 7: Upload Route List

Summary: Upload route data for your organization

Description: You can upload route lists using JSON or CSV. ROUTE CODE and ROUTE DESCRIPTION are required. ACTIVE FROM and ACTIVE TO are optional and, when provided, must use DD/MM/YYYY.

⚠️ Important: Use upload route api call only for the initial upload. If you use upload again for appending data, it will delete the existing list and replace it completely with the new data. For any modifications or additions, always use the update (post) route api call.

Two Methods Available

Choose the method that best fits your workflow:

JSON JSON Payload Method

Endpoint:

POST /software/uploadRouteJson

CSV CSV File Upload Method

Endpoint:

POST /software/uploadRouteCsv

Method 1: JSON Payload

POST /software/uploadRouteJson

Summary: Upload route data

Description: Uploads Route master data for an organization in JSON format and stores it as a CSV file. This API should be used only for the initial upload of Route data. If this API is called again, the existing Route list will be completely deleted and replaced with the newly uploaded data. For any modifications or additions to the existing Route list, please use the updateRouteJson API.

Query Parameters:

  • hospital_id (required): The ID of the hospital/organization

Body

{
  "data": [
    {
      "ROUTE CODE": "24",
      "ROUTE DESCRIPTION": "Oral",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026"
    },
    {
      "ROUTE CODE": "25",
      "ROUTE DESCRIPTION": "Intravenous"
    }
  ]
}

Note:

  • data: Required — Array of route objects
  • ROUTE CODE: Required — Code representing the route of administration
  • ROUTE DESCRIPTION: Required — Description of the route of administration

Response (200 OK):

{
  "message": "JSON uploaded and saved as CSV",
  "data": "https://storage.googleapis.com/bucket/route_csv/ORG123_20250901123000_filtered.csv",
  "invalid": [
    {
      "index": 1,
      "error": "ROUTE DESCRIPTION is empty"
    }
  ],
  "totalItems": 2,
  "invalidItemsCount": 0
}

Responses:

  • 200: Route JSON uploaded and converted to CSV successfully — Returns success message, data URL, and list of invalid entries if any
  • 400: Invalid request — {"message": "Request body must be an array or { data: array }"}
  • 404: Organization not found — {"message": "Organization not found"}
  • 500: Internal server error — {"message": "Failed to upload", "error": "Unexpected server error"}

Method 2: CSV File Upload

POST /software/uploadRouteCsv

Summary: Upload Route CSV

Description: Uploads a Route master CSV for the specified hospital_id. ROUTE CODE and ROUTE DESCRIPTION are required; ACTIVE FROM and ACTIVE TO are optional. Active dates must use DD/MM/YYYY. Use this API only for the initial upload; use updateRouteCsvs for later changes.

Query Parameters:

  • hospital_id (required): Hospital ID used to resolve organization

Form Data:

  • csvFile (required, binary): CSV with ROUTE CODE and ROUTE DESCRIPTION; optional ACTIVE FROM and ACTIVE TO columns are supported.

CSV Format: ROUTE CODE and ROUTE DESCRIPTION are required. ACTIVE FROM and ACTIVE TO are optional and must use DD/MM/YYYY.

ROUTE CODE ROUTE DESCRIPTION ACTIVE FROM ACTIVE TO
24 Oral 01/01/2026 31/12/2026
25 Intravenous

Response (200 OK):

{
  "message": "CSV uploaded and saved",
  "data": "https://storage.googleapis.com/bucket/route_csv/ORG_20260304_123000_filtered.csv",
  "invalid": [
    {
      "ROUTE CODE": "",
      "ROUTE DESCRIPTION": "Oral",
      "error": "ROUTE CODE is empty"
    }
  ]
}

Responses:

  • 200: CSV uploaded and saved successfully — Returns success message, data URL, and list of invalid entries with error details
  • 400: Bad request — Examples: {"message": "Hospital ID is required", "error": "Invalid hospital_id provided."}
  • 404: Organization not found — {"message": "Organization not found"}
  • 500: Internal server error — {"message": "Failed to upload", "error": "Invalid column names. Required: [\"ROUTE CODE\",\"ROUTE DESCRIPTION\"]."}

Update Route List

Description: You can update existing route lists in two ways: via JSON payload or via CSV file upload. Both methods allow you to merge new route entries with existing data.

Two Methods Available

Choose the method that best fits your workflow:

JSON JSON Payload Method

Endpoint:

POST /software/updateRouteJson

CSV CSV File Upload Method

Endpoint:

POST /software/updateRouteCsvs

Method 1: JSON Payload

POST /software/updateRouteJson

Summary: Update Route Data using JSON

Description: This API updates route data for a hospital. The uploaded Route json will be merged with the existing route data (if available) stored for the organization and saved as a CSV file.

Query Parameters:

  • hospital_id (required): Hospital ID used to identify the organization

Body

{
  "data": [
    {
      "ROUTE CODE": "24",
      "ROUTE DESCRIPTION": "Oral",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026"
    },
    {
      "ROUTE CODE": "26",
      "ROUTE DESCRIPTION": "Intramuscular"
    }
  ]
}

Requirements:

  • ROUTE CODE and ROUTE DESCRIPTION are required; ACTIVE FROM and ACTIVE TO are optional and must use DD/MM/YYYY
  • Each ROUTE CODE must be unique within the uploaded file

Validation:

  • If duplicate ROUTE CODE values are found in the uploaded CSV, those rows will be reported in the `invalid` field of the response

Merge Behavior:

  • ROUTE CODE is used as the unique key
  • If a ROUTE CODE already exists in the stored Route data, the uploaded value overwrites the existing one
  • New ROUTE CODE entries are added to the dataset

The final merged dataset is stored as a CSV file and linked to the organization

Response (200 OK):

{
  "status": "success",
  "message": "Route data updated successfully (merged, stored as CSV)",
  "data": "https://storage.googleapis.com/bucket/route_csv/file.csv",
  "invalid": [
    {
      "ROUTE CODE": "24",
      "ROUTE DESCRIPTION": "Oral",
      "error": "ROUTE CODE already exists"
    }
  ]
}

Responses:

  • 200: Route data updated successfully — Returns success message, data URL, and list of invalid entries if any
  • 400: Validation error — {"status": "error", "message": "Validation failed", "invalid": [...]}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Internal server error", "error": "Failed to process route data"}

Method 2: CSV File Upload

POST /software/updateRouteCsvs

Summary: Update (merge) Route CSV

Description: Uploads a Route CSV file for the specified hospital_id and merges it with the existing Route data (if available) stored for the organization.

Requirements:

  • ROUTE CODE and ROUTE DESCRIPTION are required; ACTIVE FROM and ACTIVE TO are optional and must use DD/MM/YYYY
  • Each ROUTE CODE must be unique within the uploaded file

Validation:

  • If duplicate ROUTE CODE values are found in the uploaded CSV, those rows will be reported in the `invalid` field of the response

Merge Behavior:

  • ROUTE CODE is used as the unique key
  • If a ROUTE CODE already exists in the stored Route data, the uploaded value overwrites the existing one
  • New ROUTE CODE entries are added to the dataset

The final merged dataset is stored as a CSV file and linked to the organization

Query Parameters:

  • hospital_id (required): Hospital ID used to resolve organization

Form Data:

  • csvFile (required, binary): CSV with ROUTE CODE and ROUTE DESCRIPTION; optional ACTIVE FROM and ACTIVE TO columns are supported.

Response (200 OK):

{
  "message": "Route data updated successfully (merged, duplicates removed)",
  "data": "https://storage.googleapis.com/bucket/csv_uploads/route_ORG_20260304_123000.csv",
  "invalid": [
    {
      "ROUTE CODE": "24",
      "ROUTE DESCRIPTION": "Oral",
      "error": "ROUTE CODE already exists"
    }
  ]
}

Responses:

  • 200: Route data updated successfully (merged, duplicates removed) — Returns success message, data URL, and list of invalid entries
  • 400: Bad request — {"message": "Bad request: invalid headers or missing/invalid file", "invalid": [...]}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Internal server error", "error": "Failed to process CSV file"}

Step 8: Upload Frequency List

Summary: Upload frequency data for your organization

Description: You can upload frequency lists using JSON or CSV. FREQUENCY CODE and FREQUENCY DESCRIPTION are required. ACTIVE FROM and ACTIVE TO are optional and, when provided, must use DD/MM/YYYY.

⚠️ Important: Use upload frequency api call only for the initial upload. If you use upload again for appending data, it will delete the existing list and replace it completely with the new data. For any modifications or additions, always use the update (post) frequency api call.

Two Methods Available

Choose the method that best fits your workflow:

JSON JSON Payload Method

Endpoint:

POST /software/uploadFrequencyJson

CSV CSV File Upload Method

Endpoint:

POST /software/uploadFrequencyCsv

Method 1: JSON Payload

POST /software/uploadFrequencyJson

Summary: Upload frequency data

Description: This API uploads or updates Frequency master data for a hospital using JSON input and stores it as a CSV file. For the initial setup, this API can be used to upload the complete Frequency master list for the organization. If this API is called again, the existing Frequency list will be completely deleted and replaced with the newly uploaded data. For any modifications or incremental updates to the existing Frequency list, please use the updateFrequencyJson API.

Requirements:

  • The request body must be an array of objects or an object in the format { data: array }
  • Each object must contain the fields "FREQUENCY CODE" and "FREQUENCY DESCRIPTION"
  • Each FREQUENCY CODE must be unique within the request payload

Validation:

  • If FREQUENCY CODE or FREQUENCY DESCRIPTION is missing or empty, the item will be reported in the `invalid` field and the API returns a 400 error
  • If duplicate FREQUENCY CODE values are found within the request payload, those items will be reported in the `invalid` field and the API returns a 400 error

Behavior:

  • FREQUENCY CODE is treated as the unique identifier for frequency records
  • During updates, if a FREQUENCY CODE already exists in the stored frequency data, the uploaded value overwrites the existing one
  • New FREQUENCY CODE entries will be added to the dataset

The final dataset is stored as a CSV file and linked to the organization

Query Parameters:

  • hospital_id (required): The ID of the hospital/organization

Body

{
  "data": [
    {
      "FREQUENCY CODE": "OD",
      "FREQUENCY DESCRIPTION": "Once daily",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026"
    },
    {
      "FREQUENCY CODE": "BD",
      "FREQUENCY DESCRIPTION": "Twice daily"
    }
  ]
}

Note:

  • data: Required — Array of frequency objects
  • FREQUENCY CODE: Required — Code representing the frequency of administration
  • FREQUENCY DESCRIPTION: Required — Description of the frequency of administration

Response (200 OK):

{
  "message": "JSON uploaded and saved as CSV",
  "data": "https://storage.googleapis.com/bucket/frequency_csv/ORG123_20250901123000_filtered.csv",
  "invalid": [
    {
      "index": 1,
      "error": "FREQUENCY DESCRIPTION is empty"
    }
  ],
  "totalItems": 2,
  "invalidItemsCount": 0
}

Responses:

  • 200: Frequency JSON uploaded and converted to CSV successfully — Returns success message, data URL, and list of invalid entries if any
  • 400: Invalid request — {"message": "Request body must be an array or { data: array }"}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Failed to upload", "error": "Unexpected server error"}

Method 2: CSV File Upload

POST /software/uploadFrequencyCsv

Summary: Upload Frequency CSV

Description: Uploads a Frequency master CSV for the given hospital_id. FREQUENCY CODE and FREQUENCY DESCRIPTION are required; ACTIVE FROM and ACTIVE TO are optional. Active dates must use DD/MM/YYYY. Use this API only for the initial upload; use updateFrequencyCsvs for later changes.

Validation:

  • If FREQUENCY CODE is empty, the row is reported in `invalid`
  • If FREQUENCY DESCRIPTION is empty, the row is reported in `invalid`

Query Parameters:

  • hospital_id (required): Hospital ID used to resolve organization

Form Data:

  • csvFile (required, binary): CSV with FREQUENCY CODE and FREQUENCY DESCRIPTION; optional ACTIVE FROM and ACTIVE TO columns are supported.

CSV Format: FREQUENCY CODE and FREQUENCY DESCRIPTION are required. ACTIVE FROM and ACTIVE TO are optional and must use DD/MM/YYYY.

FREQUENCY CODE FREQUENCY DESCRIPTION ACTIVE FROM ACTIVE TO
OD Once daily 01/01/2026 31/12/2026
BD Twice daily

Response (200 OK):

{
  "message": "CSV uploaded and saved",
  "data": "https://storage.googleapis.com/bucket/frequency_csv/ORG_20260304_123000_filtered.csv",
  "invalid": [
    {
      "FREQUENCY CODE": "",
      "FREQUENCY DESCRIPTION": "Once daily",
      "error": "FREQUENCY CODE is empty"
    }
  ]
}

Responses:

  • 200: Frequency CSV uploaded and saved successfully — Returns success message, data URL, and list of invalid entries with error details
  • 400: Bad request — Examples: {"message": "Hospital ID is required", "error": "Invalid hospital_id provided."}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Failed to upload", "error": "Invalid column names. Required: [\"FREQUENCY CODE\",\"FREQUENCY DESCRIPTION\"]."}

Update Frequency List

Description: You can update existing frequency lists in two ways: via JSON payload or via CSV file upload. Both methods allow you to merge new frequency entries with existing data.

Two Methods Available

Choose the method that best fits your workflow:

JSON JSON Payload Method

Endpoint:

POST /software/updateFrequencyJson

CSV CSV File Upload Method

Endpoint:

POST /software/updateFrequencyCsvs

Method 1: JSON Payload

POST /software/updateFrequencyJson

Summary: Update (merge) Frequency data using JSON

Description: This API updates frequency master data for a hospital using JSON input. The provided Frequency list will be merged with the existing frequency data (if available) stored for the organization and saved as a CSV file.

Requirements:

  • The request body must be an array of objects (or { data: array })
  • Each object must contain: "FREQUENCY CODE" and "FREQUENCY DESCRIPTION"
  • Each FREQUENCY CODE must be unique within the request payload

Validation:

  • If FREQUENCY CODE or FREQUENCY DESCRIPTION is missing/empty, the item will be reported in `invalid` and the API returns 400
  • If duplicate FREQUENCY CODE values are found within the request payload, the duplicates will be reported in `invalid` and the API returns 400

Merge Behavior:

  • FREQUENCY CODE is used as the unique key
  • If a FREQUENCY CODE already exists in the stored frequency data, the uploaded value overwrites it
  • New FREQUENCY CODE entries are added to the dataset

The final merged dataset is stored as a CSV file and linked to the organization

Query Parameters:

  • hospital_id (required): Hospital ID used to resolve organization

Body

{
  "data": [
    {
      "FREQUENCY CODE": "OD",
      "FREQUENCY DESCRIPTION": "Once daily",
      "ACTIVE FROM": "01/01/2026",
      "ACTIVE TO": "31/12/2026"
    },
    {
      "FREQUENCY CODE": "TID",
      "FREQUENCY DESCRIPTION": "Three times daily"
    }
  ]
}

Response (200 OK):

{
  "status": "success",
  "message": "Frequency data updated successfully (merged, stored as CSV)",
  "data": "https://storage.googleapis.com/bucket/frequency_csv/ORG_20260304_123000.csv",
  "invalid": [
    {
      "FREQUENCY CODE": "OD",
      "FREQUENCY DESCRIPTION": "Once daily",
      "error": "FREQUENCY CODE already exists"
    }
  ]
}

Responses:

  • 200: Frequency data updated successfully (merged, stored as CSV) — Returns success message, GCS URL of the merged CSV file, and list of invalid entries
  • 400: Validation failed / bad request — {"status": "error", "message": "Validation failed", "invalid": [...]}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Internal server error", "error": "Failed to process frequency data"}

Method 2: CSV File Upload

POST /software/updateFrequencyCsvs

Summary: Update (merge) Frequency CSV

Description: This API updates frequency data for a hospital. The uploaded Frequency CSV will be merged with the existing frequency data (if available) stored for the organization and saved as a CSV file.

Requirements:

  • FREQUENCY CODE and FREQUENCY DESCRIPTION are required; ACTIVE FROM and ACTIVE TO are optional and must use DD/MM/YYYY
  • Each FREQUENCY CODE must be unique within the uploaded file

Validation:

  • If duplicate FREQUENCY CODE values are found in the uploaded CSV, those rows will be reported in the `invalid` field of the response
  • If FREQUENCY CODE is empty, the row will be reported in `invalid`
  • If FREQUENCY DESCRIPTION is empty, the row will be reported in `invalid`

Merge Behavior:

  • FREQUENCY CODE is used as the unique key
  • If a FREQUENCY CODE already exists in the stored frequency data, the uploaded value overwrites the existing one
  • New FREQUENCY CODE entries will be added to the dataset

The final merged dataset will be stored as a CSV file and linked to the organization

Query Parameters:

  • hospital_id (required): Hospital ID used to resolve organization

Form Data:

  • csvFile (required, binary): CSV with FREQUENCY CODE and FREQUENCY DESCRIPTION; optional ACTIVE FROM and ACTIVE TO columns are supported.

Response (200 OK):

{
  "message": "Frequency data updated successfully (merged, duplicates removed)",
  "data": "https://storage.googleapis.com/bucket/csv_uploads/frequency_ORG_20260304_123000.csv",
  "invalid": [
    {
      "FREQUENCY CODE": "OD",
      "FREQUENCY DESCRIPTION": "Once daily",
      "error": "FREQUENCY CODE already exists"
    }
  ]
}

Responses:

  • 200: Frequency data updated successfully (merged, duplicates removed) — Returns success message, data URL, and list of invalid entries
  • 400: Bad request — {"message": "Bad request: invalid headers or missing/invalid file", "invalid": [...]}
  • 404: Organization not found
  • 500: Internal server error — {"message": "Internal server error", "error": "Failed to process CSV file"}