API Reference
The SDK includes a comprehensive REST API client for accessing patient data, test results, physician-prescribed treatments, and form templates. This client handles authentication, request serialization, error handling, and automatic retry logic.
API Design Philosophy
The API client follows a read-only design pattern for ANS data integrity:
The API client is designed for reading data only. Critical operations like test uploads, video recording, and initial form submissions are handled internally by the SDK's test flow UI to ensure clinical validity and data integrity. You can update patient demographics and modify existing form submissions, but cannot directly upload ANS test data.
This design ensures:
- Clinical Validity: Tests follow standardized protocols without modification
- Data Integrity: ANS measurements cannot be tampered with or fabricated
- Regulatory Compliance: Audit trail for all clinical data submissions
- Quality Assurance: Every test is validated against quality metrics
Think of it similar to Google Maps SDK - you can read location data but cannot inject false GPS coordinates into their systems.
API Service Initialization
The API service is automatically initialized when you create the SDK instance.
- iOS
- Android
let sdk = AutonomicHealthSDK(
apiKey: "your-api-key",
environment: .production,
deviceManagers: [polarManager]
)
// Access API service
let apiService = sdk.apiService
val sdk = AutonomicHealthSDK.Builder(this)
.apiKey("your-api-key")
.environment(Environment.PRODUCTION)
.deviceManagers(listOf(polarManager))
.build()
// Access API service
val apiService = sdk.apiService
Patients API
The Patients API allows you to retrieve and update patient demographic information and medical history. Patient IDs are typically cached locally after initial registration, but you should never cache medical data or ANS results.
Get Patient Details
Retrieve complete patient information including demographics, medical history, and current medications.
- iOS
- Android
Task {
let patient = try await apiService.patients.getPatientDetails(
patientId: patientId
)
print("Patient: \(patient.firstName) \(patient.lastName)")
print("Date of birth: \(patient.dateOfBirth)")
}
lifecycleScope.launch {
val patient = apiService.patients.getPatientDetails(
patientId = patientId
)
println("Patient: ${patient.firstName} ${patient.lastName}")
println("Date of birth: ${patient.dateOfBirth}")
}
Update Patient Details
Update patient demographic information. This is useful for keeping patient records current when users change their contact information or medical history.
- iOS
- Android
let updates = PatientUpdateIn(
firstName: "John",
lastName: "Doe",
dateOfBirth: "1980-01-15",
email: "john.doe@example.com"
)
let updatedPatient = try await apiService.patients.updatePatientDetails(
patientId: patientId,
updates: updates
)
val updates = PatientUpdateIn(
firstName = "John",
lastName = "Doe",
dateOfBirth = "1980-01-15",
email = "john.doe@example.com"
)
val updatedPatient = apiService.patients.updatePatientDetails(
patientId = patientId,
updates = updates
)
Tests API
The Tests API provides access to historical ANS test results. Each test includes P&S metrics (RFa and LFa values in bpm²), raw RR interval data, and review status. Tests go through a physician review process before treatment recommendations become available.
Get Tests for Patient
Retrieve a paginated list of all tests for a specific patient, ordered by creation date (most recent first).
- iOS
- Android
Task {
let tests = try await apiService.tests.getTestsForPatient(
patientId: patientId,
limit: 20,
offset: 0
)
for test in tests {
print("Test ID: \(test.testId)")
print("Status: \(test.status)")
print("Created: \(test.createdAt)")
}
}
lifecycleScope.launch {
val tests = apiService.tests.getTestsForPatient(
patientId = patientId,
limit = 20,
offset = 0
)
tests.forEach { test ->
println("Test ID: ${test.testId}")
println("Status: ${test.status}")
println("Created: ${test.createdAt}")
}
}
Get Test Details
Fetch complete details for a specific test including all ANS metrics (baseline, deep breathing, Valsalva, standing), RR intervals, and review status.
- iOS
- Android
let test = try await apiService.tests.getTestDetails(testId: testId)
print("Review Status: \(test.reviewStatus)")
print("Baseline RFa: \(test.results.baseline.rfa)")
print("Baseline LFa: \(test.results.baseline.lfa)")
val test = apiService.tests.getTestDetails(testId = testId)
println("Review Status: ${test.reviewStatus}")
println("Baseline RFa: ${test.results.baseline.rfa}")
println("Baseline LFa: ${test.results.baseline.lfa}")
Test Review Status
Tests go through a multi-stage review process before results become available:
| Status | Description | Typical Duration | Next Steps |
|---|---|---|---|
pending_review | Uploaded, awaiting physician review | 0-24 hours | Test is queued for review |
under_review | Physician currently reviewing | 1-48 hours | Provider analyzing data and video |
reviewed | Review complete, treatment available | N/A | Fetch treatment via Treatments API |
requires_retest | Test invalid, retest needed | N/A | Check reviewFeedback for rejection reasons |
Review Feedback Reasons:
When a test status is requires_retest, the reviewFeedback array contains specific reasons:
excessive_movement- Too much fidgeting or motion artifacts during baseline/breathing phasespoor_valsalva_technique- Insufficient pressure during Valsalva maneuverdevice_disconnection- Heart rate monitor lost connection during critical phasesnon_compliance- User did not follow instructions (e.g., talking, using phone, standing early)insufficient_data- RR interval data quality too low for analysiscompanion_not_present- Another adult not visible in video (safety requirement)video_quality_poor- Video too dark or obstructed to verify test validity
Configure webhooks to receive real-time notifications when test review status changes. This provides better UX than polling the API every few minutes. See Webhook Configuration below for setup instructions.
Treatments API
The Treatments API provides access to physician-prescribed treatment plans. Treatments are created by licensed healthcare providers after reviewing ANS test results and include diagnosis, lifestyle recommendations, medications, and supplements.
Get Treatment for Test
Retrieve the treatment plan associated with a reviewed test. Treatment plans include diagnosis codes, clinical recommendations, prescribed medications with dosages, and supplement suggestions.
- iOS
- Android
Task {
let treatment = try await apiService.treatments.getTreatmentForTest(
testId: testId
)
print("Diagnosis: \(treatment.diagnosis)")
print("Recommendations: \(treatment.recommendations)")
for medication in treatment.medications {
print("Medication: \(medication.name) - \(medication.dosage)")
}
}
lifecycleScope.launch {
val treatment = apiService.treatments.getTreatmentForTest(
testId = testId
)
println("Diagnosis: ${treatment.diagnosis}")
println("Recommendations: ${treatment.recommendations}")
treatment.medications.forEach { medication ->
println("Medication: ${medication.name} - ${medication.dosage}")
}
}
Treatments are only available after a test has been reviewed by a physician (reviewStatus: reviewed).
Forms API
The Forms API provides access to questionnaire templates and patient form submissions. Forms are required before conducting ANS tests and include the 28-Symptom Autonomic Questionnaire, medical history, current medications, and lifestyle factors.
List Form Templates
Retrieve all available form templates. This typically includes the required autonomic symptom questionnaire and optional supplementary forms.
- iOS
- Android
let templates = try await apiService.forms.listFormTemplates()
for template in templates {
print("Form: \(template.name)")
print("ID: \(template.formId)")
print("Required: \(template.required)")
}
val templates = apiService.forms.listFormTemplates()
templates.forEach { template ->
println("Form: ${template.name}")
println("ID: ${template.formId}")
println("Required: ${template.required}")
}
Get Form Template
Fetch a specific form template with all questions, input types, and validation rules. Use this to build custom form UIs or pre-populate forms with existing data.
- iOS
- Android
let form = try await apiService.forms.getFormTemplate(
formId: "28-symptom-autonomic"
)
for question in form.questions {
print("Question: \(question.text)")
print("Type: \(question.inputType)")
print("Options: \(question.options)")
}
val form = apiService.forms.getFormTemplate(
formId = "28-symptom-autonomic"
)
form.questions.forEach { question ->
println("Question: ${question.text}")
println("Type: ${question.inputType}")
println("Options: ${question.options}")
}
List My Form Submissions
Retrieve all form submissions for a patient, optionally filtered by form type. Useful for displaying submission history or checking if required forms are completed.
- iOS
- Android
let submissions = try await apiService.forms.listMyFormSubmissions(
patientId: patientId,
formId: "28-symptom-autonomic"
)
for submission in submissions {
print("Submission ID: \(submission.submissionId)")
print("Status: \(submission.status)")
print("Submitted: \(submission.submittedAt)")
}
val submissions = apiService.forms.listMyFormSubmissions(
patientId = patientId,
formId = "28-symptom-autonomic"
)
submissions.forEach { submission ->
println("Submission ID: ${submission.submissionId}")
println("Status: ${submission.status}")
println("Submitted: ${submission.submittedAt}")
}
Get Form Submission
Fetch a specific form submission with all responses. This is useful for displaying completed forms or allowing users to review their previous answers.
- iOS
- Android
let submission = try await apiService.forms.getMyFormSubmission(
submissionId: submissionId
)
print("Form ID: \(submission.formId)")
print("Status: \(submission.status)")
for (questionId, answer) in submission.responses {
print("\(questionId): \(answer)")
}
val submission = apiService.forms.getMyFormSubmission(
submissionId = submissionId
)
println("Form ID: ${submission.formId}")
println("Status: ${submission.status}")
submission.responses.forEach { (questionId, answer) ->
println("$questionId: $answer")
}
Update Form Submission
Update an existing form submission with new or modified responses. This allows users to correct mistakes or update information (e.g., medication changes) before test review.
- iOS
- Android
let updatedResponses: [String: AnyCodable] = [
"q1_dizziness": AnyCodable("4"),
"q2_medications": AnyCodable("Updated medication list")
]
let update = FormSubmissionUpdate(
responses: updatedResponses,
status: .completed
)
let updated = try await apiService.forms.updateMyFormSubmission(
submissionId: submissionId,
update: update
)
val updatedResponses = mapOf(
"q1_dizziness" to "4",
"q2_medications" to "Updated medication list"
)
val update = FormSubmissionUpdate(
responses = updatedResponses,
status = FormStatus.COMPLETED
)
val updated = apiService.forms.updateMyFormSubmission(
submissionId = submissionId,
update = update
)
Error Handling
- iOS
- Android
Task {
do {
let patient = try await apiService.patients.getPatientDetails(
patientId: patientId
)
} catch APIError.unauthorized {
print("Invalid or expired API key")
} catch APIError.notFound {
print("Patient not found")
} catch APIError.rateLimitExceeded {
print("Too many requests, try again later")
} catch {
print("Unexpected error: \(error)")
}
}
lifecycleScope.launch {
try {
val patient = apiService.patients.getPatientDetails(
patientId = patientId
)
} catch (e: APIError.Unauthorized) {
println("Invalid or expired API key")
} catch (e: APIError.NotFound) {
println("Patient not found")
} catch (e: APIError.RateLimitExceeded) {
println("Too many requests, try again later")
} catch (e: Exception) {
println("Unexpected error: ${e.message}")
}
}
Common Error Codes
| Code | Error | Description |
|---|---|---|
| 401 | unauthorized | Invalid or expired API key |
| 403 | forbidden | Insufficient permissions |
| 404 | not_found | Resource not found |
| 429 | rate_limit_exceeded | Too many requests |
| 500 | internal_error | Server error |
Webhook Configuration
Webhooks provide real-time notifications for important events like test reviews completing or treatment plans being updated. Configure webhook endpoints in the Developer Portal to receive these notifications.
Instead of polling the API to check if a test review is complete, configure a webhook to receive instant notifications. This reduces API calls and provides better user experience.
Example: When a physician completes a test review, you'll receive a webhook within seconds, allowing you to immediately notify the user via push notification that their results are ready.
Setting Up Webhooks
1. Create a Webhook Endpoint
Your backend must expose an HTTPS endpoint that can receive POST requests:
POST https://your-api.example.com/webhooks/autonomic-health
Content-Type: application/json
X-AH-Signature: sha256=abc123...
2. Configure in Developer Portal
Navigate to Developer Portal → Webhooks → Add Endpoint:
- Endpoint URL: Your HTTPS endpoint (must use TLS 1.2+)
- Events: Select which events to receive (test reviews, form submissions, etc.)
- Secret Key: Copy your webhook secret for signature verification
3. Verify Signatures
All webhook payloads include an X-AH-Signature header for security:
- iOS (Backend)
- Android (Backend)
import CryptoKit
func verifyWebhookSignature(payload: Data, signature: String, secret: String) -> Bool {
let hmac = HMAC<SHA256>.authenticationCode(for: payload, using: SymmetricKey(data: Data(secret.utf8)))
let computedSignature = "sha256=" + hmac.map { String(format: "%02x", $0) }.joined()
return computedSignature == signature
}
// In your webhook handler
func handleWebhook(request: HTTPRequest) throws {
guard let signature = request.headers["X-AH-Signature"] else {
throw WebhookError.missingSignature
}
let payload = request.body
let isValid = verifyWebhookSignature(
payload: payload,
signature: signature,
secret: webhookSecret
)
guard isValid else {
throw WebhookError.invalidSignature
}
// Process webhook payload
let event = try JSONDecoder().decode(WebhookEvent.self, from: payload)
processWebhookEvent(event)
}
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
fun verifyWebhookSignature(payload: ByteArray, signature: String, secret: String): Boolean {
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(secret.toByteArray(), "HmacSHA256"))
val computedHmac = mac.doFinal(payload)
val computedSignature = "sha256=" + computedHmac.joinToString("") { "%02x".format(it) }
return computedSignature == signature
}
// In your webhook handler
fun handleWebhook(request: HttpRequest) {
val signature = request.headers["X-AH-Signature"]
?: throw WebhookException("Missing signature")
val payload = request.body
val isValid = verifyWebhookSignature(
payload = payload,
signature = signature,
secret = webhookSecret
)
if (!isValid) {
throw WebhookException("Invalid signature")
}
// Process webhook payload
val event = Json.decodeFromString<WebhookEvent>(payload.decodeToString())
processWebhookEvent(event)
}
Never process webhook payloads without verifying the HMAC signature. An attacker could send fake webhook events to your endpoint, potentially causing your app to display incorrect test results or send false notifications to users.
The signature verification ensures the payload came from Autonomic Health and hasn't been tampered with.
Webhook Retry Logic
If your endpoint is unreachable or returns a non-2xx status code, the webhook system automatically retries:
- Retry Schedule: Exponential backoff (1 min, 5 min, 15 min, 1 hour, 6 hours, 24 hours)
- Max Retries: 6 attempts over 24 hours
- Timeout: 30 seconds per request
- Failed Delivery: After max retries, webhook is marked as failed and you'll receive an email alert
Best Practices:
- Respond with
200 OKimmediately after receiving the webhook - Process the payload asynchronously in a background job
- If processing fails, the webhook won't be retried (you'll need to poll the API)
- Log all webhook receipts for debugging
Idempotency: Webhooks may be delivered more than once due to network issues or retries. Your handler should be idempotent:
func processWebhookEvent(_ event: WebhookEvent) async throws {
// Check if this event was already processed
if await database.webhookEventExists(eventId: event.id) {
print("Duplicate webhook, skipping")
return
}
// Mark as processed before handling
await database.saveWebhookEvent(eventId: event.id, timestamp: Date())
// Now process the event
switch event.type {
case .testReviewCompleted:
await notifyUserTestComplete(event.testId)
case .formSubmitted:
await checkIfAllFormsComplete(event.patientId)
}
}
Test Review Events
- iOS
- Android
// Webhook payload structure
struct TestReviewWebhook: Codable {
let event: String // "test.review.completed"
let testId: String
let patientId: String
let reviewStatus: String
let timestamp: String
}
// Handle webhook in your backend
// POST /webhooks/autonomic-health
// {
// "event": "test.review.completed",
// "testId": "test_abc123",
// "patientId": "patient_xyz789",
// "reviewStatus": "reviewed",
// "timestamp": "2024-01-15T10:30:00Z"
// }
// Webhook payload structure
data class TestReviewWebhook(
val event: String, // "test.review.completed"
val testId: String,
val patientId: String,
val reviewStatus: String,
val timestamp: String
)
// Handle webhook in your backend
// POST /webhooks/autonomic-health
// {
// "event": "test.review.completed",
// "testId": "test_abc123",
// "patientId": "patient_xyz789",
// "reviewStatus": "reviewed",
// "timestamp": "2024-01-15T10:30:00Z"
// }
Available Webhook Events
Configure which events you want to receive in the Developer Portal. Each event includes relevant identifiers to fetch fresh data via the API.
| Event | Trigger | Typical Use Case |
|---|---|---|
test.upload.completed | Test data and video uploaded successfully | Update UI to show "Processing..." |
test.review.started | Physician begins reviewing test | Update status in app |
test.review.completed | Review complete, treatment available | Send push notification: "Your results are ready!" |
test.requires_retest | Test invalid, retest needed | Notify user with specific feedback |
form.submitted | Patient completes a form | Check if all required forms are now complete |
form.updated | Patient modifies an existing form submission | Trigger re-analysis if test already reviewed |
treatment.created | New treatment plan created for patient | Notify user: "Your treatment plan is ready" |
treatment.updated | Physician modifies treatment plan | Alert user about dosage changes or new recommendations |
Webhook Payload Examples
Test Review Completed
{
"event": "test.review.completed",
"event_id": "evt_abc123xyz",
"timestamp": "2024-01-15T14:30:00Z",
"data": {
"test_id": "test_550e8400-e29b-41d4-a716-446655440000",
"patient_id": "patient_7c9e6679-7425-40de-944b-e07fc1f90ae7",
"review_status": "reviewed",
"reviewer_id": "provider_123",
"reviewed_at": "2024-01-15T14:29:45Z",
"treatment_available": true
}
}
Response Flow:
- Receive webhook
- Verify HMAC signature
- Fetch test details:
GET /tests/{test_id} - Fetch treatment:
GET /treatments/by-test/{test_id} - Send push notification to user
- Update app UI to show "Results Available"
Test Requires Retest
{
"event": "test.requires_retest",
"event_id": "evt_def456uvw",
"timestamp": "2024-01-15T10:15:00Z",
"data": {
"test_id": "test_660f9511-f39c-52e5-b827-557766551111",
"patient_id": "patient_8d0f7789-8536-51ef-055c-f18gd2g01bf8",
"review_status": "requires_retest",
"reviewer_id": "provider_456",
"reviewed_at": "2024-01-15T10:14:30Z",
"review_feedback": [
"excessive_movement",
"poor_valsalva_technique"
],
"reviewer_notes": "Patient moved frequently during baseline. Valsalva pressure insufficient."
}
}
Response Flow:
- Receive webhook
- Verify signature
- Fetch full test details with feedback
- Send push notification: "Your test needs to be retaken"
- In-app message explaining what went wrong with tips for next attempt
Form Submitted
{
"event": "form.submitted",
"event_id": "evt_ghi789rst",
"timestamp": "2024-01-14T16:45:00Z",
"data": {
"submission_id": "sub_771g0622-g40d-63f6-c938-668877662222",
"form_id": "28-symptom-autonomic",
"patient_id": "patient_8d0f7789-8536-51ef-055c-f18gd2g01bf8",
"status": "completed",
"submitted_at": "2024-01-14T16:44:55Z"
}
}
Response Flow:
- Receive webhook
- Check if all required forms are now complete
- If complete and test is
pending_forms, trigger test review - Update UI to enable "Start Test" button if forms were blocking
Treatment Updated
{
"event": "treatment.updated",
"event_id": "evt_jkl012mno",
"timestamp": "2024-01-16T09:20:00Z",
"data": {
"treatment_id": "tx_882h1733-h51e-74g7-d049-779988773333",
"patient_id": "patient_9e1g8890-9647-62fg-166d-g29he3h12cg9",
"test_id": "test_550e8400-e29b-41d4-a716-446655440000",
"updated_at": "2024-01-16T09:19:45Z",
"updated_by": "provider_789",
"changes": [
"medications_modified",
"dosage_adjusted"
]
}
}
Response Flow:
- Receive webhook
- Fetch updated treatment plan
- Send push notification: "Your treatment plan has been updated"
- In-app badge/indicator on Treatment tab
- Optionally email patient about changes
Testing Webhooks
The Developer Portal provides a webhook testing tool:
- Send test events to your endpoint
- View delivery logs and responses
- Inspect retry attempts
- Validate signature verification
Development Tips:
- Use services like ngrok to expose localhost during development
- Test all event types before going to production
- Verify your idempotency logic with duplicate deliveries
- Test signature verification with invalid signatures
Before enabling webhooks in production:
- ✅ HTTPS endpoint with valid TLS certificate (TLS 1.2+)
- ✅ Signature verification implemented and tested
- ✅ Idempotency handling for duplicate events
- ✅ Asynchronous processing (respond 200 immediately)
- ✅ Error logging and monitoring
- ✅ Graceful degradation if webhook processing fails
- ✅ Tested with all event types you subscribed to