Ultrasound API

Developer documentation

Ultrasound API

Programmatic access to patient pre-admission for facilities running on the Ultrasound platform. Authenticate with a machine credential, prefill the admission from the booking link, resolve dropdowns through facility lookups and save as the patient progresses.

Base URL https://api.ultrasound.dev.build.scriptex.co.za
Postman collection

Overview

The API is organised into areas, each versioned independently under its own path: /security/v1 for authentication and /preadmit/v1 for pre-admission. All requests and responses are JSON (UTF-8) unless stated otherwise; errors use RFC 7807 problem details.

A pre-admission journey is anchored on the booking GUID issued to the patient in their pre-admission link. It is the only external identifier; facility, patient and booking context resolve from it server-side.

Authentication

The API uses machine credentials: a client id and client secret issued per integrating party. Exchange the credential for a bearer token, then send it on every call in the Authorization header. Tokens expire after 15 minutes; request a new one when a call returns 401. Access is granted per facility: your credential carries the facilities your organisation may work with, and a call against any other facility returns 403. The Postman collection has every endpoint preconfigured: set the clientId and clientSecret variables, run the auth request and the token is applied to the rest automatically.

POST /security/v1/auth

Request a token

Exchanges a machine credential for a bearer token.

Requestjson
POST /security/v1/auth
Content-Type: application/json

{
  "clientId": "your-client-id",
  "clientSecret": "your-client-secret"
}
Response 200json
{
  "token": "eyJhbGciOi...",
  "expiresIn": 900
}
Using the tokenhttp
GET /preadmit/v1/admissions/{bookingGuid}
Authorization: Bearer eyJhbGciOi...

Pre-admission flow

  1. 1

    Authenticate. Exchange the machine credential for a bearer token.

  2. 2

    Fetch the admission. GET the booking GUID from the patient's link to prefill previously captured data and booking context.

  3. 3

    Load lookups. Resolve dropdowns from the facility lookup endpoints; filter schemes by the selected medical aid.

  4. 4

    Save as the patient goes. POST partial submissions freely; each response reports the outstanding fields.

  5. 5

    Upload documents. One call per document type; mandatory types follow Ultrasound's rules for the admission.

  6. 6

    Complete. When a save returns isComplete: true the pre-admission is done and visible to facility staff.

The form journey

The Ultrasound patient form walks these steps; your form can follow any order, but the capture below is what the facility expects. The patient type chosen in step 2 decides which sections apply.

Step Captures
1. Patient information Name, surname, date of birth, ID or passport number, contact numbers, email, residential and postal address, gender, title, nationality, language, religion, allergies
2. Payment information Patient type; for Medical Aid: aid, scheme, membership number, authorisation number, gap cover, main member; otherwise: person responsible for the account, occupation, and for Injury on Duty the employer and incident details; co-payment where the facility requires one
3. Next of kin Next of kin name, contact and relationship, plus the person fetching the patient (or a flag that it is the next of kin)
4. Meals and beverages Meal time, meal type, meal and beverage choices from the facility's menu (shown when the facility runs menu planning)
5. Documents Typed uploads: ID or passport, medical aid card, medical aid authorisation, main member's ID
6. Review and submit The patient confirms and submits; completion requires every mandatory field below

What is required when

The save response's missingFields always tells you exactly what is outstanding, and POST .../validate runs the same check without saving. The rules it applies:

When Required
Every admission Patient name, surname, date of birth, ID number, mobile number, residential address, gender, title, nationality, language, patient type, next of kin name and contact
Patient type is Medical Aid Medical aid, scheme, membership number, and whether the patient is the main member
Any other patient type Person responsible for the account: full name and contact number
Patient is not the main member Main member full name and ID or passport number
Patient type is Injury on Duty Employer name, employer contact details and the type of incident
Has allergies is yes The allergies description
Co-payment required is yes The co-payment amount

Patient types are facility-configured, so the rules key on the type's name from the patient-types lookup, not its id. Documents follow the same scenarios: ID or passport for everyone, the medical aid card and authorisation for Medical Aid admissions, and the main member's ID when the patient is a dependant.

Admissions

GET /preadmit/v1/admissions/{bookingGuid}

Fetch the admission

Returns previously captured pre-admission data for prefill plus booking context. Documents are listed separately on the documents endpoint. An expired link returns 410 Gone rather than data.

Response 200 (abridged)json
{
  "bNumber": "B0012345",
  "isComplete": false,
  "patientName": "Thandi",
  "patientSurname": "Mokoena",
  "dateOfBirth": "1986-03-12T00:00:00",
  "mobileNumber": "0821234567",
  "emailAddress": "thandi@example.com",
  "patientTypeID": 2,
  "medicalAidID": 1,
  "medicalAidSchemeID": 4,
  "medicalAidNumber": "123456789",
  "mainMember": true
}
POST /preadmit/v1/admissions/{bookingGuid}

Save the admission

Saves the submission. Partial saves are supported: send what has been captured so far and the response lists what is still missing. The record is only marked complete once every field passes the same validation as the Ultrasound pages.

Response 200json
{
  "bookingGuid": "11111111-1111-1111-1111-111111111111",
  "saved": true,
  "isComplete": false,
  "missingFields": [
    "NextOfKinFullName",
    "NextOfKinContactDetails",
    "MedicalAidNumber"
  ]
}
POST /preadmit/v1/admissions/{bookingGuid}/validate

Validate without saving

Dry-run of the save validation: returns isComplete and missingFields for the payload exactly as a save would, but persists nothing. Useful for a live checklist while the patient works through the form.

GET /preadmit/v1/admissions/{bookingGuid}/documents

List uploaded documents

Metadata of the documents uploaded so far, one entry per document type. Use it to show the patient what has already been provided.

Response 200json
[
  {
    "documentType": "IDOrPassport",
    "fileName": "id.pdf",
    "sizeBytes": 182400,
    "contentType": "application/pdf",
    "downloadLink": "https://api.ultrasound.africa/preadmit/v1/admissions/{bookingGuid}/documents/IDOrPassport",
    "uploadedAt": "2026-11-10T08:12:00+00:00"
  },
  {
    "documentType": "MedicalAidCard",
    "fileName": "card.jpg",
    "sizeBytes": 91230,
    "contentType": "image/jpeg",
    "downloadLink": "https://api.ultrasound.africa/preadmit/v1/admissions/{bookingGuid}/documents/MedicalAidCard",
    "uploadedAt": "2026-11-10T08:15:00+00:00"
  }
]
GET /preadmit/v1/admissions/{bookingGuid}/documents/{documentType}

Download a document

The stored file itself, streamed through the API with the same bearer token. Storage is never exposed directly; the downloadLink in the metadata points here.

POST /preadmit/v1/admissions/{bookingGuid}/documents

Upload a document

Multipart upload, one call per document type. Re-uploading a type supersedes the previous file.

Form fields: file (the binary) and documentType, one of:

IDOrPassportMainMemberIDOrPassportMedicalAidCardMedicalAidAuthorisation
Response 201json
{
  "documentType": "MedicalAidCard",
  "fileName": "card.jpg",
  "sizeBytes": 91230,
  "contentType": "image/jpeg",
  "downloadLink": "https://api.ultrasound.africa/preadmit/v1/admissions/{bookingGuid}/documents/MedicalAidCard",
  "uploadedAt": "2026-11-10T08:15:00+00:00"
}

Facility lookups

Dropdown values are facility-scoped and served as-is from the facility's configuration; no mapping layer sits in between. Every lookup lives under /preadmit/v1/facilities/{facilityId}/... and returns the same shape:

Lookup responsejson
[
  { "id": 1, "name": "Discovery Health" },
  { "id": 2, "name": "Bonitas" }
]
Endpoint
GET titles
GET genders
GET nationalities
GET languages
GET religions
GET patient-types
GET medical-aids
GET medical-aids/{medicalAidId}/schemes
GET meal-times
GET meal-types
GET meals
GET beverages

Model reference

The admission body is one flat object. Mandatory fields depend on the patient type: medical aid fields apply to Medical Aid admissions, main member fields apply when the patient is a dependant, and employer fields apply to Injury on Duty. The save response lists exactly what is still outstanding.

Full payload example ⌄

A Medical Aid admission where the patient is a dependant, so the main member section is populated and the employer and incident section (Injury on Duty only) is null. bNumber and isComplete are returned on GET and ignored on POST. Documents live on their own endpoint and are not part of this payload.

{
  "bNumber": "B0012345",
  "isComplete": false,

  "patientName": "Thandi",
  "patientSurname": "Mokoena",
  "dateOfBirth": "1986-03-12T00:00:00",
  "age": "40",
  "gapCover": true,

  "mobileNumber": "0821234567",
  "homeNumber": "0115551234",
  "emailAddress": "thandi@example.com",

  "identificationNumber": "8603120000089",
  "identificationTypeID": 1,
  "residentialAddress": "12 Protea Street",
  "residentialAddressLine2": "Morningside",
  "residentialAddressLine3": "Sandton",
  "residentialAddressLine4": "Gauteng",
  "residentialAddressPostalCode": "2196",
  "postalAddress": "PO Box 123",
  "postalAddressLine2": "Sandton",
  "postalAddressLine3": "Johannesburg",
  "postalAddressLine4": "Gauteng",
  "postalAddressPostalCode": "2146",
  "occupation": "Teacher",
  "patientTypeID": 2,
  "genderID": 2,
  "nationalityID": 1,
  "titleID": 2,
  "languageID": 1,
  "religionID": 1,
  "hasAllergies": true,
  "allergies": "Penicillin",

  "personResponsibleForAccountFullName": "Sipho Mokoena",
  "personResponsibleForAccountContactNumber": "0837654321",
  "personResponsibleForAccountEmailAddress": "sipho@example.com",
  "personResponsibleForAccountBusinessName": "Mokoena Holdings",
  "personResponsibleForAccountBusinessAddress": "4 Main Road",
  "personResponsibleForAccountBusinessAddressLine2": "Rivonia",
  "personResponsibleForAccountBusinessAddressLine3": "Sandton",
  "personResponsibleForAccountBusinessAddressLine4": "Gauteng",
  "personResponsibleForAccountBusinessAddressPostalCode": "2128",
  "personResponsibleForAccountOccupation": "Engineer",
  "coPaymentAmountRequired": true,
  "coPaymentAmount": 1500.00,

  "medicalAidID": 1,
  "medicalAidSchemeID": 4,
  "medicalAidNumber": "123456789",
  "medicalAidAuthNumber": "AUTH-884422",

  "nextOfKinFullName": "Sipho Mokoena",
  "nextOfKinContactDetails": "0837654321",
  "nextOfKinRelationship": "Spouse",
  "nextOfKinAddress": "12 Protea Street",
  "nextOfKinAddressLine2": "Morningside",
  "nextOfKinAddressLine3": "Sandton",
  "nextOfKinAddressLine4": "Gauteng",
  "nextOfKinAddressPostalCode": "2196",

  "personFetchingIsNextOfKin": false,
  "personFetchingPatientFullName": "Lerato Dlamini",
  "personFetchingPatientPhoneNumber": "0829876543",
  "personFetchingPatientRelationship": "Sister",
  "personFetchingPatientAddress": "8 Acacia Avenue",
  "personFetchingPatientAddressLine2": "Bryanston",
  "personFetchingPatientAddressLine3": "Sandton",
  "personFetchingPatientAddressLine4": "Gauteng",
  "personFetchingPatientAddressPostalCode": "2191",

  "mealTimeID": 1,
  "mealTypeID": 2,
  "mealID": 3,
  "beverageID": 2,

  "typeOfIncident": null,
  "employerName": null,
  "employerContactDetails": null,
  "employerStreetAddress": null,
  "employerStreetAddress2": null,
  "employerStreetAddress3": null,
  "employerStreetAddress4": null,
  "employerStreetAddressPostalCode": null,

  "mainMember": false,
  "mainMemberInitials": "S",
  "mainMemberSurname": "Mokoena",
  "mainMemberTitleID": 1,
  "mainMemberContactDetails": "0837654321",
  "mainMemberEmailAddress": "sipho@example.com",
  "mainMemberFullName": "Sipho Mokoena",
  "mainMemberOccupation": "Engineer",
  "mainMemberIDOrPassportNumber": "8201010000083",
  "mainMemberBusinessOrEmployerName": "Mokoena Holdings",
  "mainMemberBusinessOrEmployerContactNumber": "0114441234",
  "mainMemberBusinessOrEmployerAddress": "4 Main Road",
  "mainMemberBusinessOrEmployerAddressLine2": "Rivonia",
  "mainMemberBusinessOrEmployerAddressLine3": "Sandton",
  "mainMemberBusinessOrEmployerAddressLine4": "Gauteng",
  "mainMemberBusinessOrEmployerAddressPostalCode": "2128",
  "dependantCode": "02"
}
Booking context ⌄

Read-only context returned on GET; ignored on POST.

bNumber string Facility booking reference, display only. The booking GUID in the route is the external identifier.
isComplete boolean True once every mandatory field has passed validation.
Patient ⌄
patientName string First name.
patientSurname string Surname.
dateOfBirth date-time ISO 8601.
age string Derived display value.
gapCover boolean Whether the patient holds gap cover.
Contact details ⌄
mobileNumber string Mandatory.
homeNumber string
emailAddress string Mandatory.
Demographics ⌄

Lookup IDs resolve through the facility lookup endpoints.

identificationNumber string SA ID or passport number.
identificationTypeID integer
residentialAddress .. residentialAddressPostalCode string Residential address lines 1 to 4 plus postal code.
postalAddress .. postalAddressPostalCode string Postal address lines 1 to 4 plus postal code.
occupation string
patientTypeID integer From patient-types. Drives conditional validation.
genderID / nationalityID / titleID / languageID / religionID integer From the matching lookups.
hasAllergies boolean
allergies string Required when hasAllergies is true.
Person responsible for the account ⌄
personResponsibleForAccountFullName string Mandatory.
personResponsibleForAccountContactNumber string Mandatory.
personResponsibleForAccountEmailAddress string
personResponsibleForAccountBusinessName string
personResponsibleForAccountBusinessAddress .. PostalCode string Business address lines 1 to 4 plus postal code.
personResponsibleForAccountOccupation string
coPaymentAmountRequired boolean
coPaymentAmount decimal
Medical aid ⌄

Mandatory when the patient type is Medical Aid.

medicalAidID integer From medical-aids.
medicalAidSchemeID integer From the schemes of the selected medical aid.
medicalAidNumber string Membership number.
medicalAidAuthNumber string Authorisation number when already issued.
Main member ⌄

Mandatory when the patient is not the main member (mainMember = false).

mainMember boolean True when the patient is the main member.
mainMemberFullName / mainMemberInitials / mainMemberSurname string
mainMemberTitleID integer From titles.
mainMemberContactDetails / mainMemberEmailAddress string
mainMemberIDOrPassportNumber string
mainMemberOccupation string
mainMemberBusinessOrEmployerName / ContactNumber / Address .. PostalCode string Employer details with address lines 1 to 4 plus postal code.
dependantCode string
Next of kin ⌄
nextOfKinFullName string Mandatory.
nextOfKinContactDetails string Mandatory.
nextOfKinRelationship string
nextOfKinAddress .. nextOfKinAddressPostalCode string Address lines 1 to 4 plus postal code.
Person fetching the patient ⌄
personFetchingIsNextOfKin boolean When true the next of kin details apply.
personFetchingPatientFullName / PhoneNumber / Relationship string
personFetchingPatientAddress .. PostalCode string Address lines 1 to 4 plus postal code.
Meal preferences ⌄
mealTimeID / mealTypeID / mealID / beverageID integer From the matching meal and beverage lookups.
Employer and incident ⌄

Relevant for Injury on Duty admissions.

typeOfIncident string
employerName / employerContactDetails string
employerStreetAddress .. employerStreetAddressPostalCode string Address lines 1 to 4 plus postal code.

Test bookings

Reserved booking ids let you exercise every state without touching a real booking. They work in every environment and still require a valid credential: authenticate as usual, and the editable bookings resolve to a facility your credential is granted on, so authorisation behaves exactly as in production. Saves and uploads against them are held in memory and reset periodically.

Booking id Behaviour
11111111-1111-1111-1111-111111111111 Editable booking with partial prefill: saves and document uploads are accepted and read back, validation runs for real
22222222-2222-2222-2222-222222222222 Admission already complete (isComplete: true); further saves are accepted
33333333-3333-3333-3333-333333333333 Booking whose state has progressed: reads work, writes return 409
44444444-4444-4444-4444-444444444444 Booking on a facility your credential is not granted: always 403
00000000-0000-0000-0000-000000000000 Expired pre-admission link: always 410

Errors

Errors are RFC 7807 problem details with application/problem+json.

Status Meaning
400 Bad Request The body failed validation, or an unknown documentType was supplied.
401 Unauthorized Missing, invalid or expired bearer token. Authenticate again.
403 Forbidden The token is valid but has no access to the facility the request addresses.
409 Conflict The booking state no longer allows changes: cancelled, already processed by the facility, or the procedure date has passed. Reads still work.
404 Not Found The route does not exist, or the identifier is malformed.
410 Gone The pre-admission link has expired. The patient must request a new link from the facility.
500 Internal Server Error Unexpected failure; retry with backoff and contact support if it persists.
Example: expired linkjson
{
  "status": 410,
  "title": "Link expired",
  "detail": "This pre-admission link has expired. The patient must request a new link from the facility."
}