CSV Upload API
Upload a CSV for automated patient intake or case enrichment. The endpoint accepts a CSV, stores it, and processes each row through the pipeline for its type.
Upload CSV
Submit a CSV for processing.
POST /api/v1/csv
Purpose
Accepts a multipart CSV upload, validates the file, and processes each row through the pipeline selected by type. Processing is synchronous (typically 5-15s for 1-500 rows).
Request Body
Content-Type: multipart/form-data
Your HTTP client sets Content-Type automatically when you attach a file — no need to set it manually.
| Field | Type | Required | Values |
|---|---|---|---|
type | string | Yes | appointments, medications, consult-notes |
file | file | Yes | UTF-8 .csv; max 10MB; header row must contain at least one comma |
Response
{
"success": true,
"data": {
"file_name": "appointments_20260413.csv",
"type": "appointments",
"size": 3939,
"uploaded_at": "2026-04-13T17:52:51.825Z"
},
"message": "CSV uploaded and processed successfully."
}
Notes
- Uploads are deduplicated by
(organization_id, file_name, type)— re-uploading the same filename is a no-op - Use filenames with a unique timestamp component (e.g.
appointments_20260413_093000.csv) - Row-level validation errors are recorded internally but not returned in the response
- The
NewConsultfield determines which voice agent handles each row's intake call
Error Responses
400 Bad Request: File missing, empty, not.csv, or invalid CSV format400 Bad Request:typemissing or not one ofappointments,medications,consult-notes413 Payload Too Large: File exceeds 10MB422 Unprocessable Entity: No agent routing rules configured for your organization (appointmentsonly)
Example
curl -X POST "https://orchestrator.helloblair.com/api/v1/csv" \
-H "X-API-Key: YOUR_API_KEY" \
-F "type=appointments" \
-F "file=@appointments_20260413.csv"
CSV Schemas
Header rows are case-sensitive. Column order does not matter. Extra columns are silently ignored.
Appointments (type=appointments)
Required and optional columns
Required Columns
| Column | Type | Notes |
|---|---|---|
AppointmentId | string | Unique appointment identifier, used as the case key |
PatientRecordId | string | Stable patient identifier across uploads |
AppointmentStatus | enum | Common values: Scheduled, Cancelled, Waitlist, Triage. Cancelled, Waitlist, Triage auto-cancel the appointment |
AppointmentDoctor | string | Doctor's last name, must match an active Blair clinician |
Virtual | boolean | Must be true to proceed; any other value auto-cancels |
AppointmentDate | date | YYYY-MM-DD |
EarliestAppointmentTime | time | HH:MM or HH:MM:SS (24-hour, Eastern Time) |
PatientFirstName | string | |
PatientLastName | string | |
DOB | date | YYYY-MM-DD |
Phone | string | Any North American format, normalized to E.164 |
NewConsult | boolean | Determines per-row voice agent routing |
TestStatus | enum | Pending, Removed; Removed auto-cancels the appointment |
IntakeCharted | boolean | true auto-cancels (intake already completed externally) |
Re-uploading an existing AppointmentId triggers reprocessing only when one of these fields changes: AppointmentDate, AppointmentStatus, AppointmentDoctor, Virtual, IntakeCharted, TestStatus.
Optional Columns
AppointmentTestId, OfficeId, Office, AppointmentStatusId, TestStatusId, TestId, TestCode, TestName, TestStartTime, email, PhoneType, HealthCardStatus, PharmacyName, PharmacyAddress, PharmacyCity, PharmacyPostalCode, PharmacyFax, LastModifiedDate
Medications (type=medications)
Enriches existing cases with medication data. Rows are grouped by PatientRecordId. Max 5000 rows per upload.
Required and optional columns
Required Columns
| Column | Type | Notes |
|---|---|---|
PatientRecordId | string | Patient identifier; rows are grouped by this |
MedicationName | string | Rows with an empty MedicationName are silently dropped |
Dose | string | Feeds dosage; Strength used as fallback if Dose is empty |
Strength | string | Fallback for dosage when Dose is empty |
SIG | string | Feeds frequency |
DateStarted | string | Feeds duration |
Missing fields fall back to "unknown" in the enriched record.
Optional Columns
PatientMedicationId, MedicationClass, Form, Route, Ingredients, Instructions, HasDIN, DIN, DateCreated
Consult Notes (type=consult-notes)
Extracts medications and allergies from consult-note text via LLM. Rows are grouped by AppointmentId. Max 500 rows per upload.
Required and optional columns
Required Columns
| Column | Type | Notes |
|---|---|---|
AppointmentId | string | Rows are grouped by this appointment |
VPText | string | Consult-note text; rows with no VPText contribute no context |
CategoryNameReport | string | Category label; CategoryNameTemplate used as fallback if empty |
CategoryNameTemplate | string | Fallback for category label when CategoryNameReport is empty |
Missing category falls back to "Unknown" in the extraction context.
Optional Columns
VPTextId, AppointmentTestId, PatientRecordId, wRootCategoryId, ReportingDoctorId, DoctorRootCategoryId, CapturedDate
Consumer Checklist
- Register your doctors with Blair. Every
AppointmentDoctorvalue must match an active clinician in Blair's system. - Lock in your filename convention. Timestamp-based names are safest. Never reuse a filename for different content.
- Include all required columns in every CSV header row.