Example Flows

Common API usage patterns and integration workflows

Last updated: February 20, 2026

Overview

This page demonstrates common integration flows using the NxVET API. Each example shows the sequence of API calls, request parameters, and how to work with the response data.

Prerequisites: All examples assume you have an API key. See Authentication for setup instructions.

All examples use the following base configuration:

# Set your credentials
API_KEY="nxvet_sk_YOUR_API_KEY"
ORG_ID="YOUR_ORGANIZATION_ID"
BASE_URL="https://app.nx.vet/api"
const API_KEY = process.env.NXVET_API_KEY;
const ORG_ID = process.env.NXVET_ORG_ID;
const BASE_URL = 'https://app.nx.vet/api';

const headers = {
  'Authorization': `Bearer ${API_KEY}`,
  'Content-Type': 'application/json'
};
import os
import requests

API_KEY = os.environ['NXVET_API_KEY']
ORG_ID = os.environ['NXVET_ORG_ID']
BASE_URL = 'https://app.nx.vet/api'

headers = {
    'Authorization': f'Bearer {API_KEY}',
    'Content-Type': 'application/json'
}

List & Filter Labels

Fetch consultation records (labels) for your organization with pagination and filtering.

Step 1 — List devices (for filtering)

Optionally fetch the list of devices to enable device-based filtering:

curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/devices?organizationId=$ORG_ID&limit=200"
const res = await fetch(
  `${BASE_URL}/devices?organizationId=${ORG_ID}&limit=200`,
  { headers }
);
const devices = await res.json();
// devices = [{ value: "device-id", displayName: "Room 1", serial: "NX-1234" }, ...]
res = requests.get(
    f'{BASE_URL}/devices',
    params={'organizationId': ORG_ID, 'limit': 200},
    headers=headers
)
devices = res.json()
# devices = [{"value": "device-id", "displayName": "Room 1", "serial": "NX-1234"}, ...]

Step 2 — Fetch labels with pagination

List labels sorted by date, with optional filters for record type, patient search, and user.

# Paginated list with filters
curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/organizations/$ORG_ID/labels?\
limit=10&\
offset=0&\
sortingProperty=FromTime&\
isSortingDescending=true&\
patientSearch=Luna&\
types=ClinicConversation,NxHubBatch"
const params = new URLSearchParams({
  limit: 10,
  offset: 0,
  sortingProperty: 'FromTime',
  isSortingDescending: true,
  patientSearch: 'Luna',
  types: 'ClinicConversation,NxHubBatch'
});

const res = await fetch(
  `${BASE_URL}/organizations/${ORG_ID}/labels?${params}`,
  { headers }
);

// Total count is in the response header
const totalCount = res.headers.get('X-Total-Count');
const labels = await res.json();

console.log(`Showing ${labels.length} of ${totalCount} labels`);
res = requests.get(
    f'{BASE_URL}/organizations/{ORG_ID}/labels',
    params={
        'limit': 10,
        'offset': 0,
        'sortingProperty': 'FromTime',
        'isSortingDescending': True,
        'patientSearch': 'Luna',
        'types': 'ClinicConversation,NxHubBatch'
    },
    headers=headers
)

total_count = res.headers.get('X-Total-Count')
labels = res.json()

print(f'Showing {len(labels)} of {total_count} labels')

Available sorting properties: FromTime, Type, Patient

Available record types:

  • ClinicConversation — Live clinic recording
  • NxHubBatch — NxHub ambient recording
  • DictationAudio — Dictation
  • DocumentSoap — Document upload
  • PhoneCallAudio — Phone call recording
  • ButtonRecording, AudioButtonRecording — Button-triggered recordings
  • AggregateLabel — Combined medical record

Get Label Details

Retrieve full details for a specific label, including transcript, notes, and patient data.

LABEL_ID="019e1234-5678-7000-abcd-123456789abc"

curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/labels/$LABEL_ID"
const labelId = '019e1234-5678-7000-abcd-123456789abc';
const res = await fetch(`${BASE_URL}/labels/${labelId}`, { headers });
const label = await res.json();

// Access patient info
console.log(`Patient: ${label.patient?.name}`);
console.log(`Type: ${label.type}`);
console.log(`Duration: ${label.fromTime} — ${label.toTime}`);

// Access transcript and notes
for (const note of label.ownedPatientNotes) {
  const content = JSON.parse(note.content);

  // Transcript text
  if (content.english_soap) {
    console.log('Transcript:', content.english_soap);
  }

  // Clinical note
  if (content.clinical_note) {
    console.log('Clinical Note:', content.clinical_note);
  }
}
import json

label_id = '019e1234-5678-7000-abcd-123456789abc'
res = requests.get(f'{BASE_URL}/labels/{label_id}', headers=headers)
label = res.json()

# Access patient info
print(f"Patient: {label.get('patient', {}).get('name')}")
print(f"Type: {label['type']}")

# Access transcript and notes
for note in label.get('ownedPatientNotes', []):
    content = json.loads(note['content'])

    if 'english_soap' in content:
        print(f"Transcript: {content['english_soap']}")

    if 'clinical_note' in content:
        print(f"Clinical Note: {content['clinical_note']}")

Response structure

The label detail response includes:

  • id, type, fromTime, toTime — Basic label metadata
  • patient — Assigned patient object (id, name)
  • ownedPatientNotes[] — Array of notes. Each note has a content field (JSON string) containing transcript, clinical notes, and metadata
  • friendlyName, deviceSerial — Device info

Assign Patient to Label

Associate a patient with a consultation label.

LABEL_ID="019e1234-5678-7000-abcd-123456789abc"
PATIENT_ID="019e5678-abcd-7000-1234-abcdef012345"

curl -X POST -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"patientId\": \"$PATIENT_ID\"}" \
  "$BASE_URL/labels/$LABEL_ID/assign"
const labelId = '019e1234-5678-7000-abcd-123456789abc';
const patientId = '019e5678-abcd-7000-1234-abcdef012345';

const res = await fetch(`${BASE_URL}/labels/${labelId}/assign`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ patientId })
});

if (res.ok) {
  console.log('Patient assigned successfully');
}
label_id = '019e1234-5678-7000-abcd-123456789abc'
patient_id = '019e5678-abcd-7000-1234-abcdef012345'

res = requests.post(
    f'{BASE_URL}/labels/{label_id}/assign',
    json={'patientId': patient_id},
    headers=headers
)

if res.ok:
    print('Patient assigned successfully')

Generate Medical Record

Combine multiple labels into a single medical record using a template. This is useful for creating comprehensive records from several consultation sessions.

Step 1 — Fetch available templates

curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/organizations/$ORG_ID/medical-templates"
const res = await fetch(
  `${BASE_URL}/organizations/${ORG_ID}/medical-templates`,
  { headers }
);
const templates = await res.json();
// templates = [{ id: "...", name: "Standard SOAP", content: "..." }, ...]

Step 2 — Aggregate labels

Combine up to 5 labels into a medical record:

curl -X POST -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "LabelIds": [
      "019e1111-1111-7000-aaaa-111111111111",
      "019e2222-2222-7000-bbbb-222222222222"
    ],
    "PatientId": "019e5678-abcd-7000-1234-abcdef012345",
    "MedicalTemplateId": "template-id-here"
  }' \
  "$BASE_URL/labels/aggregate"
const res = await fetch(`${BASE_URL}/labels/aggregate`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    LabelIds: [
      '019e1111-1111-7000-aaaa-111111111111',
      '019e2222-2222-7000-bbbb-222222222222'
    ],
    PatientId: '019e5678-abcd-7000-1234-abcdef012345',
    MedicalTemplateId: 'template-id-here'
  })
});

const newLabel = await res.json();
console.log(`Created aggregate label: ${newLabel.id}`);

Step 3 — Trigger report regeneration (optional)

Reprocess a label with a different template. This is a fire-and-forget operation — both 200 and 504 responses indicate the job was triggered successfully.

LABEL_ID="019e1234-5678-7000-abcd-123456789abc"
TEMPLATE_ID="template-id-here"

curl -X POST -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/labels/$LABEL_ID/lambda?medicalTemplateId=$TEMPLATE_ID"

# Note: 504 Gateway Timeout is expected — the job runs asynchronously
const labelId = '019e1234-5678-7000-abcd-123456789abc';
const templateId = 'template-id-here';

try {
  await fetch(
    `${BASE_URL}/labels/${labelId}/lambda?medicalTemplateId=${templateId}`,
    { method: 'POST', headers }
  );
} catch (e) {
  // 504 is expected — the job is processing asynchronously
}

// Use webhooks to know when the label is updated (label_updated event)
Tip: Subscribe to label_updated webhook events to get notified when the regenerated report is ready.

Manage Conversations

List and inspect NxHub ambient recording conversations with token-based pagination.

Step 1 — List conversations

# List active conversations
curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/nxhub/conversations?\
organizationId=$ORG_ID&\
status=ACTIVE&\
pageSize=20"

# Fetch next page using the token from the previous response
curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/nxhub/conversations?\
organizationId=$ORG_ID&\
pageSize=20&\
pageToken=NEXT_PAGE_TOKEN"
async function listConversations(status = null, pageToken = null) {
  const params = new URLSearchParams({
    organizationId: ORG_ID,
    pageSize: 20
  });
  if (status) params.set('status', status);
  if (pageToken) params.set('pageToken', pageToken);

  const res = await fetch(
    `${BASE_URL}/nxhub/conversations?${params}`,
    { headers }
  );
  return res.json();
}

// List active conversations
const result = await listConversations('ACTIVE');
console.log(`Found ${result.items.length} conversations`);

// Paginate if more results exist
if (result.hasMore) {
  const nextPage = await listConversations('ACTIVE', result.nextPageToken);
}
def list_conversations(status=None, page_token=None):
    params = {'organizationId': ORG_ID, 'pageSize': 20}
    if status:
        params['status'] = status
    if page_token:
        params['pageToken'] = page_token

    res = requests.get(
        f'{BASE_URL}/nxhub/conversations',
        params=params,
        headers=headers
    )
    return res.json()

# List active conversations
result = list_conversations(status='ACTIVE')
print(f"Found {len(result['items'])} conversations")

# Paginate
if result.get('hasMore'):
    next_page = list_conversations('ACTIVE', result['nextPageToken'])

Conversation statuses: ACTIVE, COMPLETED, PROCESSING, UPLOADED, FAILED, FILTERED

Step 2 — Get conversation details

DEVICE_ID="device-id-here"
CONVERSATION_ID="conversation-id-here"

curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/nxhub/conversations/$DEVICE_ID/$CONVERSATION_ID"
const deviceId = 'device-id-here';
const conversationId = 'conversation-id-here';

const res = await fetch(
  `${BASE_URL}/nxhub/conversations/${deviceId}/${conversationId}`,
  { headers }
);
const conversation = await res.json();

console.log(`Status: ${conversation.status}`);
console.log(`Speech: ${conversation.speechMinuteCount} min`);
console.log(`Transcript: ${conversation.transcriptText || '(processing)'}`);

Create a Conversation

Create a new NxHub conversation from a time range on a device. This flow involves previewing available audio, creating the conversation, and completing it.

Step 1 — View the device timeline

Fetch the timeline to see when speech was detected (max 6-hour window):

DEVICE_ID="device-id-here"
# 3-hour window: now minus 3 hours to now (in milliseconds)
END_MS=$(date +%s000)
START_MS=$((END_MS - 10800000))

curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/nxhub/conversations/timeline/$DEVICE_ID?\
startMs=$START_MS&\
endMs=$END_MS"
const deviceId = 'device-id-here';
const endMs = Date.now();
const startMs = endMs - (3 * 60 * 60 * 1000); // 3 hours ago

const res = await fetch(
  `${BASE_URL}/nxhub/conversations/timeline/${deviceId}?startMs=${startMs}&endMs=${endMs}`,
  { headers }
);
const timeline = await res.json();

// timeline.minutes[] contains per-minute VAD (voice activity detection) data
for (const minute of timeline.minutes) {
  if (!minute.vadIsSilent) {
    console.log(`Speech at ${new Date(minute.minuteStartMs).toLocaleTimeString()}`);
    console.log(`  Duration: ${minute.vadSpeechDurationMs}ms`);
    if (minute.transcriptText) {
      console.log(`  Text: ${minute.transcriptText}`);
    }
  }
}

Step 2 — Preview the time range

Check the audio content before creating (max 90-minute range):

# Select a 30-minute window with speech activity
curl -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/nxhub/conversations/create/preview?\
deviceId=$DEVICE_ID&\
startMs=$START_MS&\
endMs=$END_MS"
const previewRes = await fetch(
  `${BASE_URL}/nxhub/conversations/create/preview?deviceId=${deviceId}&startMs=${startMs}&endMs=${endMs}`,
  { headers }
);
const preview = await previewRes.json();

console.log(`Total: ${preview.totalMinutes} min`);
console.log(`Speech: ${preview.speechMinutes} min`);
console.log(`Silent: ${preview.silentMinutes} min`);

Step 3 — Create the conversation

curl -X POST -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"deviceId\": \"$DEVICE_ID\",
    \"startMs\": $START_MS,
    \"endMs\": $END_MS
  }" \
  "$BASE_URL/nxhub/conversations/create"
const res = await fetch(`${BASE_URL}/nxhub/conversations/create`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ deviceId, startMs, endMs })
});
const result = await res.json();

if (result.success) {
  console.log(`Created conversation: ${result.conversation.conversationId}`);
} else {
  console.log(`Error: ${result.message}`);
}

Step 4 — Complete the conversation

When the conversation is ready, mark it as complete to trigger processing:

curl -X POST -H "Authorization: Bearer $API_KEY" \
  "$BASE_URL/nxhub/conversations/$DEVICE_ID/$CONVERSATION_ID/complete"
const res = await fetch(
  `${BASE_URL}/nxhub/conversations/${deviceId}/${conversationId}/complete`,
  { method: 'POST', headers }
);
const result = await res.json();

if (result.success) {
  console.log('Conversation completed — processing will begin');
  // Subscribe to conversation_completed webhook to know when it's done
}
Tip: Subscribe to conversation_completed webhook events to get notified when processing finishes and the label is created.

Webhook-Driven Sync

Use webhooks to keep your system in sync with NxVET in real-time. When an event occurs, fetch the relevant data from the API.

Example: Sync new labels to your system

const express = require('express');
const crypto = require('crypto');

const app = express();
const WEBHOOK_SECRET = process.env.NXVET_WEBHOOK_SECRET; // whsec_...

// Parse raw body for signature verification
app.post('/webhooks/nxvet', express.raw({ type: '*/*' }), async (req, res) => {
  // 1. Verify signature
  const signature = req.headers['x-nervex-signature'];
  const expected = 'sha256=' + crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (signature !== expected) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Process the event
  const event = JSON.parse(req.body);
  const deliveryId = req.headers['x-nervex-delivery'];

  switch (event.type) {
    case 'new_label': {
      // Fetch the full label details
      const label = await fetch(
        `${BASE_URL}/labels/${event.data.labelId}`,
        { headers }
      ).then(r => r.json());

      console.log(`New label: ${label.id}, patient: ${label.patient?.name}`);
      // Save to your database...
      break;
    }

    case 'label_updated': {
      // Re-fetch the label to get updated content
      const label = await fetch(
        `${BASE_URL}/labels/${event.data.labelId}`,
        { headers }
      ).then(r => r.json());

      console.log(`Label updated: ${label.id}`);
      // Update in your database...
      break;
    }

    case 'conversation_completed': {
      // Fetch conversation details
      const conv = await fetch(
        `${BASE_URL}/nxhub/conversations/${event.data.deviceId}/${event.data.conversationId}`,
        { headers }
      ).then(r => r.json());

      console.log(`Conversation completed: ${conv.conversationId}`);
      break;
    }
  }

  // 3. Return 200 to acknowledge receipt
  res.status(200).send('OK');
});

app.listen(3000);
import hmac
import hashlib
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = os.environ['NXVET_WEBHOOK_SECRET']  # whsec_...

@app.route('/webhooks/nxvet', methods=['POST'])
def handle_webhook():
    # 1. Verify signature
    signature = request.headers.get('X-NerveX-Signature', '')
    expected = 'sha256=' + hmac.new(
        WEBHOOK_SECRET.encode(),
        request.data,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return 'Invalid signature', 401

    # 2. Process the event
    event = request.get_json()
    delivery_id = request.headers.get('X-NerveX-Delivery')

    if event['type'] == 'new_label':
        label = requests.get(
            f"{BASE_URL}/labels/{event['data']['labelId']}",
            headers=headers
        ).json()
        print(f"New label: {label['id']}, patient: {label.get('patient', {}).get('name')}")
        # Save to your database...

    elif event['type'] == 'conversation_completed':
        conv = requests.get(
            f"{BASE_URL}/nxhub/conversations/{event['data']['deviceId']}/{event['data']['conversationId']}",
            headers=headers
        ).json()
        print(f"Conversation completed: {conv['conversationId']}")

    # 3. Return 200 to acknowledge receipt
    return 'OK', 200

Webhook + API pattern summary

Webhook Event Follow-up API Call Use Case
new_label GET /api/labels/{labelId} Import new consultation into your system
label_updated GET /api/labels/{labelId} Sync updated transcript or notes
label_deleted Remove consultation from your system
conversation_created GET /api/nxhub/conversations/{deviceId}/{conversationId} Track new ambient recording
conversation_completed GET /api/nxhub/conversations/{deviceId}/{conversationId} Retrieve completed conversation with transcript