Skip to main content

NxVET Developer Guide — API Keys, Pull API, Webhooks & Integration Patterns

Dr. Mark de Wolde avatar
Written by Dr. Mark de Wolde
Updated this week

NxHUB Developer Quickstart

Get up and running with the NxVET API in 5 minutes. This guide walks you through authenticating, listing devices, fetching consultation records, and retrieving transcripts.

Prerequisites

  • An NxVET account at app.nx.vet

  • An API key — create one from Organization → Developer in your NxVET dashboard

  • Your Organization ID (visible in Settings or returned by the introspect endpoint)

Step 1: Verify Your API Key

Confirm your key works and retrieve your organization details:

curl

curl -s -H "Authorization: Bearer YOUR_API_KEY" \
  "https://app.nx.vet/api/auth/introspect-org"

Python

import requestsAPI_KEY = "YOUR_API_KEY"
headers = {"Authorization": f"Bearer {API_KEY}"}resp = requests.get("https://app.nx.vet/api/auth/introspect-org", headers=headers)
org = resp.json()
print(f"Organization: {org['organizationName']} ({org['organizationId']}")

JavaScript (Node.js)

const API_KEY = "YOUR_API_KEY";const resp = await fetch("https://app.nx.vet/api/auth/introspect-org", {
  headers: { Authorization: `Bearer ${API_KEY}` },
});
const org = await resp.json();
console.log(`Organization: ${org.organizationName} (${org.organizationId})`);

Response

{
  "userId": "...",
  "email": "...",
  "organizationId": "your-org-uuid",
  "organizationName": "Your Clinic",
  "features": { "orgHasNxMicAccess": true },
  "standaloneFeatures": [...]
}

Step 2: List Consultation Records

Fetch recent NxHUB consultations for your organization. The types parameter filters by record type.

curl -s -H "Authorization: Bearer YOUR_API_KEY" \
  "https://app.nx.vet/api/organizations/YOUR_ORG_ID/labels?types=CombinedClinicConv&limit=10&isSortingDescending=true"

Python

ORG_ID = org["organizationId"]resp = requests.get(
    f"https://app.nx.vet/api/organizations/{ORG_ID}/labels",
    headers=headers,
    params={
        "types": "CombinedClinicConv",
        "limit": 10,
        "isSortingDescending": True,
    },
)
records = resp.json()
total = resp.headers.get("X-Total-Count", "?")
print(f"Found {total} records, showing {len(records)}")for r in records:
    print(f"  {r['id']} - {r['name']} ({r['fromTime']} to {r['toTime']})")

Available record types:

  • CombinedClinicConv — NxHUB consultation recordings

  • ClinicConversation — Legacy clinic conversations

  • DictationAudio — Dictation recordings

  • AudioButtonRecording — Manual button recordings

  • NxMIC — NxMIC recordings

Response (array, with X-Total-Count header for pagination):

[
  {
    "id": "record-uuid",
    "name": "Consultation - Room 1",
    "type": "CombinedClinicConv",
    "fromTime": "2026-02-20T10:00:00Z",
    "toTime": "2026-02-20T10:25:00Z",
    "patientId": "patient-uuid",
    "patient": {
      "id": "patient-uuid",
      "name": "Bella",
      "breed": "Golden Retriever",
      "sex": "Female"
    },
    "createdBy": { "id": "...", "firstName": "Dr.", "lastName": "Smith" },
    "deviceSerial": "NXH-A1B2C3",
    "friendlyName": "Room 1 NxHUB"
  }
]

Step 3: Get Transcript and SOAP Notes

Fetch the full record detail including transcripts and AI-generated SOAP notes:

curl -s -H "Authorization: Bearer YOUR_API_KEY" \
  "https://app.nx.vet/api/labels/RECORD_ID"

Python

record_id = records[0]["id"]resp = requests.get(
    f"https://app.nx.vet/api/labels/{record_id}",
    headers=headers,
)
detail = resp.json()for note in detail.get("ownedPatientNotes", []):
    if note["type"] == "Transcript":
        print("=== Transcript ===")
        print(note["content"][:500])
    elif note["type"] == "SOAP":
        print("=== SOAP Note ===")
        print(note["content"][:500])

Response

{
  "id": "record-uuid",
  "name": "Consultation - Room 1",
  "ownedPatientNotes": [
    {
      "id": "note-uuid",
      "type": "Transcript",
      "content": "Dr. Smith: So Bella has been limping for about three days...",
      "createdAt": "2026-02-20T10:30:00Z"
    },
    {
      "id": "note-uuid-2",
      "type": "SOAP",
      "content": "S: 4yo FS Golden Retriever, presenting for 3-day lameness LF...",
      "createdAt": "2026-02-20T10:30:05Z"
    }
  ]
}

Step 4: Download Audio (Optional)

Download the audio recording for a consultation:

curl -s -H "Authorization: Bearer YOUR_API_KEY" \
  "https://app.nx.vet/api/s3-file/download?bucketName=nxvet-audio-recording-uploads&key=uploads/YOUR_ORG_ID/RECORD_ID.wav" \
  --output recording.wav

For the complete endpoint reference, see the Pull API documentation.


Authentication and API Keys

Creating an API Key

  1. Log in to app.nx.vet

  2. Navigate to Organization → Developer in the sidebar

  3. Click Create API Key

  4. Enter a name (e.g., "Production", "Scribble Vet Integration")

  5. Select the scopes your integration needs (see below)

  6. Choose an expiration period (30 days to 2 years)

  7. Click Create Key

Important: The full API key is shown only once at creation. Copy it immediately and store it securely. If lost, revoke the key and create a new one.

Using Your API Key

Include the key as a Bearer token in the Authorization header of every request:

Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Available Scopes

Each API key is granted a subset of scopes that control which endpoints it can access:

Scope

Access

auth:introspect-org

Verify organization access and features

devices:read

List and view device details

devices:write

Create, update, and delete devices

patients:read

List and view patient records

patients:write

Create, update, and delete patient records

organizations:read

View organization details

nxhub-firmware:read

Check and download firmware updates

nxhub-audio:write

Upload audio recordings via NxHUB

labels:write

Submit L1 measurement data

For most integrations, you will need: auth:introspect-org, organizations:read, devices:read, and patients:read.

Key Expiration and Revocation

  • Expiration: Keys expire after the period you chose at creation (default 1 year, max 2 years). Expired keys are automatically rejected.

  • Revocation: You can revoke any key immediately from the Developer Settings page. Revoked keys stop working instantly.

  • Rotation: Create a new key before revoking the old one to avoid downtime. Update your integration config, verify it works, then revoke the old key.


Pull API — Consultations and Transcripts

The Pull API lets you query consultation records, transcripts, SOAP notes, and audio on demand. All endpoints are REST-based and require JWT Bearer authentication.

Base URL: https://app.nx.vet

Endpoints

GET /api/organizations/{organizationId}/labels

List consultation records for an organization.

Parameter

Type

Required

Description

types

string[]

Yes

Record types to filter (e.g., CombinedClinicConv)

limit

int

No

Max records per page (default 20, max 100)

offset

int

No

Pagination offset (default 0)

sortingProperty

string

No

Sort field (default FromTime)

isSortingDescending

bool

No

Sort order (default true)

createdBy

UUID

No

Filter by user ID

Returns an array of RecordSummary objects. Total count is in the X-Total-Count response header.

GET /api/labels/{labelId}

Get full record detail including transcript and SOAP notes. Returns a RecordDetail object with an ownedPatientNotes array containing Transcript and SOAP entries.

GET /api/s3-file/download

Download the audio recording (WAV format).

Parameter

Type

Required

Description

bucketName

string

Yes

Always nxvet-audio-recording-uploads

key

string

Yes

S3 path: uploads/{orgId}/{recordId}.wav

Pagination Example

import requestsAPI_KEY = "YOUR_API_KEY"
ORG_ID = "your-org-uuid"
headers = {"Authorization": f"Bearer {API_KEY}"}all_records = []
offset = 0
limit = 50while True:
    resp = requests.get(
        f"https://app.nx.vet/api/organizations/{ORG_ID}/labels",
        headers=headers,
        params={"types": "CombinedClinicConv", "limit": limit, "offset": offset},
    )
    batch = resp.json()
    all_records.extend(batch)
    total = int(resp.headers.get("X-Total-Count", 0))
    offset += limit
    if offset >= total:
        breakprint(f"Fetched {len(all_records)} of {total} records")

Error Handling

Status

Meaning

401

Invalid, expired, or revoked API key

403

Valid key but missing required scope for this endpoint

404

Record or organization not found

See the full Pull API reference for complete schema details.


Webhooks — Real-Time Events

Receive real-time notifications when consultations complete or measurements are captured, without polling the API.

Setup

  1. Provide NerveX with your HTTPS webhook endpoint URL

  2. You will receive a partner_id and hmac_secret (shared once, store securely)

  3. Each clinic user links their NxVET account using a partner_user_key — a unique, opaque identifier (UUID recommended) that maps to their account in your system

Event Types

Event

Source

Description

record.created

NxHUB

Fired when a conversation recording completes. Includes transcript, SOAP note, and audio URL.

measurement.created

NxSCOPE

Fired when a TPR measurement is captured. Includes temperature, pulse, respiration, and audio.

Webhook Payload

Headers:

X-Nxvet-Partner-Id: your_partner_id
X-Nxvet-Timestamp: 1708444800
X-Nxvet-Signature: a1b2c3d4e5f6...

Body (JSON):

{
  "event_id": "evt_abc123",
  "event_type": "record.created",
  "occurred_at": "2026-02-20T15:00:00Z",
  "partner_id": "your_partner_id",
  "partner_user_key": "user-uuid-in-your-system",
  "device_id": "NXH-A1B2C3",
  "device_friendly_name": "Room 1 NxHUB",
  "transcript": "Dr. Smith: So Bella has been limping...",
  "soap": "S: 4yo FS Golden Retriever presenting for lameness...",
  "signed_urls": [
    {
      "type": "hub_audio_wav",
      "url": "https://signed-url.example.com/recording.wav",
      "content_type": "audio/wav",
      "expires_in_seconds": 3600
    }
  ],
  "data": {}
}

HMAC Signature Verification

Always verify the signature before processing. The signature is computed over timestamp.raw_body using HMAC-SHA256.

Python

import hmac, hashlib, timedef verify_webhook(request_headers, raw_body, hmac_secret):
    timestamp = request_headers["X-Nxvet-Timestamp"]
    signature = request_headers["X-Nxvet-Signature"]    # Reject stale requests (replay protection)
    if abs(time.time() - int(timestamp)) > 300:
        return False    # Compute expected signature
    message = f"{timestamp}.{raw_body}".encode()
    expected = hmac.new(
        hmac_secret.encode(), message, hashlib.sha256
    ).hexdigest()    # Constant-time comparison
    return hmac.compare_digest(expected, signature)

JavaScript (Node.js)

const crypto = require("crypto");function verifyWebhook(headers, rawBody, hmacSecret) {
  const timestamp = headers["x-nxvet-timestamp"];
  const signature = headers["x-nxvet-signature"];  // Replay protection
  if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false;  const message = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac("sha256", hmacSecret)
    .update(message)
    .digest("hex");  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex")
  );
}

Delivery and Retry Policy

  • At-least-once delivery: Webhooks may be delivered more than once. Deduplicate by event_id.

  • Retries: NxVET retries on network errors, HTTP 5xx, and 429 responses.

  • Success: Any 2xx response signals successful delivery.

  • Signed URLs expire after 1 hour — download audio promptly.

Recommended Processing Flow

  1. Verify timestamp freshness (plus or minus 5 minutes)

  2. Verify HMAC signature (constant-time comparison)

  3. Deduplicate by event_id

  4. Enqueue for async processing

  5. Return 200 OK immediately

For the complete webhook specification, see the Webhooks documentation.


Integration Patterns

Choose the right approach for your integration based on your needs.

Pull API vs. Webhooks

Pull API (REST)

Webhooks (Push)

Latency

Depends on poll interval

Near real-time (seconds)

Setup complexity

Low — just API key

Medium — HTTPS endpoint + HMAC

Best for

Batch sync, backfill, on-demand queries

Live dashboards, instant PIMS sync

Infrastructure

Any client (server, CLI, script)

Publicly accessible HTTPS server

Auth

Self-service API key

Partner onboarding with NerveX

Common Architectures

1. Poll and Sync (Simplest)

Best for: PIMS integrations that sync consultation data periodically.

# Cron job: every 15 minutes, sync new records
import requestsAPI_KEY = "YOUR_API_KEY"
ORG_ID = "your-org-uuid"
headers = {"Authorization": f"Bearer {API_KEY}"}resp = requests.get(
    f"https://app.nx.vet/api/organizations/{ORG_ID}/labels",
    headers=headers,
    params={"types": "CombinedClinicConv", "limit": 20},
)
records = resp.json()for record in records:
    if not already_synced(record["id"]):
        detail = requests.get(
            f"https://app.nx.vet/api/labels/{record['id']}",
            headers=headers,
        ).json()
        save_to_pims(detail)

2. Event-Driven (Recommended)

Best for: Real-time integrations where data should appear in your system immediately.

# Flask webhook receiver
from flask import Flask, request, jsonifyapp = Flask(__name__)@app.route("/webhooks/nxvet", methods=["POST"])
def handle_nxvet_webhook():
    if not verify_webhook(request.headers, request.get_data(as_text=True), HMAC_SECRET):
        return jsonify({"error": "Invalid signature"}), 401    event = request.json    if event["event_type"] == "record.created":
        save_consultation(
            partner_user_key=event["partner_user_key"],
            transcript=event.get("transcript"),
            soap=event.get("soap"),
            audio_urls=event.get("signed_urls", []),
        )    return jsonify({"status": "ok"}), 200

3. Hybrid (Pull + Push)

Best for: Mission-critical integrations that need both real-time delivery and guaranteed completeness.

  • Webhooks for real-time — process events as they arrive

  • Pull API for daily reconciliation — sweep for any records the webhook missed

  • This is what we recommend for production PIMS integrations

Rate Limits

  • Pull API requests are subject to standard rate limiting. If you receive a 429 Too Many Requests response, back off and retry with exponential delay.

  • Paginate with limit and offset — do not fetch all records in a single request.

  • For bulk historical data, contact NerveX support rather than scraping the API.

Need Help?

For integration support, partner onboarding, or webhook configuration, contact us at support@nervex.tech.

Full API reference: api.nx.vet

Did this answer your question?