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 recordingsClinicConversation— Legacy clinic conversationsDictationAudio— Dictation recordingsAudioButtonRecording— Manual button recordingsNxMIC— 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
Log in to app.nx.vet
Navigate to Organization → Developer in the sidebar
Click Create API Key
Enter a name (e.g., "Production", "Scribble Vet Integration")
Select the scopes your integration needs (see below)
Choose an expiration period (30 days to 2 years)
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 |
| Verify organization access and features |
| List and view device details |
| Create, update, and delete devices |
| List and view patient records |
| Create, update, and delete patient records |
| View organization details |
| Check and download firmware updates |
| Upload audio recordings via NxHUB |
| 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 |
| string[] | Yes | Record types to filter (e.g., CombinedClinicConv) |
| int | No | Max records per page (default 20, max 100) |
| int | No | Pagination offset (default 0) |
| string | No | Sort field (default FromTime) |
| bool | No | Sort order (default true) |
| 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 |
| string | Yes | Always |
| 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 |
| Invalid, expired, or revoked API key |
| Valid key but missing required scope for this endpoint |
| 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
Provide NerveX with your HTTPS webhook endpoint URL
You will receive a
partner_idandhmac_secret(shared once, store securely)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 |
| NxHUB | Fired when a conversation recording completes. Includes transcript, SOAP note, and audio URL. |
| 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
Verify timestamp freshness (plus or minus 5 minutes)
Verify HMAC signature (constant-time comparison)
Deduplicate by
event_idEnqueue for async processing
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
limitandoffset— 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
