Voloridge Health Report
An enpoint to generate a PDF health report from timeseries scoring events
The Voloridge Health Report endpoint accepts the same time series input as the scoring endpoint and returns a generated PDF report tailored to the individual user. Rather than returning raw scoring data, this endpoint produces a ready-to-deliver document designed to be shared directly with the report recipient.
The report is intended to encourage a general state of health and promote a healthy lifestyle by presenting the user's Volo Scores, Volo Ages, and risk ratios across health domains in a clear, accessible format. By visualizing how scores have changed over time, the report helps recipients understand the trajectory of their health and motivates them to make healthy choices that are well-understood to reduce the risk or impact of, or play a role in the outcome of, a chronic disease or condition.
PDF generation is handled asynchronously. Once complete, the report is delivered either to a pre-signed AWS S3 bucket URL or to a callback URL as raw bytes in the response body — specified at the time of the request. The report is not intended to constitute medical advice or provide a clinical diagnosis, and recipients should be encouraged to consult a qualified healthcare professional when making health-related decisions.
Full details of the endpoint and its definition, along with tools for testing it can be found under the API Reference page.
Request
The request schema is the same as the one for the Timeseries scoring endpoint, apart from some additional configuration information that needs to be provided about how to manage the output. Below are the changes:
- report_metadata: The metadata for the report generation request. This is the same as the health scoring request metadata object, apart from the following additional configuration object.
- configuration: Object containing configuration information about how to manage the output PDF
- s3_presigned_url: A pre-signed S3 URL for uploading the PDF. This must be a
PUTpre-signed URL (notGET). See the section below on how to generate a pre-signed URL. - callback_url: The URL for a webhook callback when report processing completes. When provided, the service will send a
POSTrequest to the specified URL upon completion (success or failure). - auth_type: Authentication method for the webhook request. Options:
bearer: Bearer token authentication (Authorization: Bearer {token})api_key: API key in custom header (default:X-API-Key)basic: HTTP Basic authentication (Authorization: Basic {encoded})none: No authentication (default)
- auth_token: The authentication token/credential (required when
auth_typeisbearer,api_key, orbasic) - auth_key_name: Custom header name for API key authentication (optional, defaults to
X-API-Key)
- s3_presigned_url: A pre-signed S3 URL for uploading the PDF. This must be a
- configuration: Object containing configuration information about how to manage the output PDF
Note: you will need to provide either a pre-signed URL or a callback URL. An error will be thrown if neither of these are provided in the request.
Generating pre-signed URLs
Important: Do NOT use aws s3 presign CLI command - it generates GET pre-signed URLs which will fail with 403 errors. The service requires PUT pre-signed URLs.
Using Python (boto3)
import boto3
s3 = boto3.client('s3')
presigned_url = s3.generate_presigned_url(
'put_object',
Params={
'Bucket': 'vhr-dev-model-files',
'Key': 'reports/test.pdf',
'ContentType': 'application/pdf'
},
ExpiresIn=3600 # 1 hour
)
Example request
Below is sample request to the report generation endpoint.
{
"report_metadata": {
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"event_asof_dtutc": "2024-02-05T14:30:00Z",
"mode": "report",
"configuration": {
"callback_url": "https://myapp-endpoint.com/report-callback",
"auth_type": "none"
}
},
"uid_ext": "SPOT1WJOQH",
"health_events": [
{
"health_event_id": "evt_001",
"event_age": 45.3,
"predictors": [
{
"id": "6b0ca208-86a7-440d-b0c1-0818ad53002e",
"observation_code": "30525-0",
"value": "28",
"unit": "a"
},
{
"id": "aa653593-7071-4266-b010-953cefff9eb3",
"observation_code": "46098-0",
"value": "male",
"unit": ""
},
{
"id": "c40aa00e-2e3d-4e7e-9718-95a25c2df8e9",
"observation_code": "8302-2",
"value": "78",
"unit": "inches"
},
{
"id": "1f796753-f375-494a-8539-b0f140ff4f56",
"observation_code": "29463-7",
"value": "210",
"unit": "lbs"
},
{
"id": "8835a752-bc03-473c-a4ba-37387c045f14",
"observation_code": "64219-9",
"value": "0",
"unit": "pack/years"
},
{
"id": "0dd946b0-d10a-477a-8a43-dc313e0ef0a9",
"observation_code": "4548-4",
"value": "5.4",
"unit": "%"
},
{
"id": "a624e601-d519-40d9-b972-b45e15d2a43e",
"observation_code": "2885-2",
"value": "8.1",
"unit": "g/dL"
},
{
"id": "3197ae19-9438-40d5-aded-67228b498a9e",
"observation_code": "2571-8",
"value": "40",
"unit": "mg/dL"
},
{
"id": "3d9b1dcd-3c70-4c36-9f7b-fc8e2e57bd49",
"observation_code": "10835-7",
"value": "6.2",
"unit": "mg/dL"
},
{
"id": "29ebacd2-baf0-4b86-b66d-92ece13264e3",
"observation_code": "1751-7",
"value": "4.8",
"unit": "g/dL"
},
{
"id": "f644ac87-420c-4656-8f07-daa6350bca0c",
"observation_code": "17861-6",
"value": "9.6",
"unit": "mg/dL"
},
{
"id": "0dfe36d5-a8d3-4a36-9d6d-7dcec20e647d",
"observation_code": "2160-0",
"value": "0.83",
"unit": "mg/dL"
},
{
"id": "c6f4d776-d8e8-486a-9ed0-2d6260c0a583",
"observation_code": "2498-4",
"value": "106",
"unit": "ug/dL"
},
{
"id": "bd9d03b9-1be7-4e51-a3c9-5fd7dee92ea8",
"observation_code": "2086-7",
"value": "76",
"unit": "mg/dL"
},
{
"id": "852a0141-90a0-4397-9480-081786fedc54",
"observation_code": "1975-2",
"value": "0.7",
"unit": "mg/dL"
},
{
"id": "120c6aff-1613-4a14-9a29-88803c8eef71",
"observation_code": "77147-7",
"value": "117.25",
"unit": "mL/min/1.73m^2"
},
{
"id": "aed5cd82-513a-47d8-b8ae-7109dc8a8065",
"observation_code": "13457-7",
"value": "80",
"unit": "mg/dL"
},
{
"id": "199bd4d3-07ac-494f-93e4-d96979f86824",
"observation_code": "2276-4",
"value": "89.6",
"unit": "ng/mL"
}
]
},
{
"health_event_id": "evt_002",
"event_age": 47.8,
"predictors": [
{
"id": "6b0ca208-86a7-440d-b0c1-0818ad53002f",
"observation_code": "30525-0",
"value": "28",
"unit": "a"
},
{
"id": "aa653593-7071-4266-b010-953cefff9eb4",
"observation_code": "46098-0",
"value": "male",
"unit": ""
},
{
"id": "c40aa00e-2e3d-4e7e-9718-95a25c2df8e0",
"observation_code": "8302-2",
"value": "78",
"unit": "inches"
},
{
"id": "1f796753-f375-494a-8539-b0f140ff4f57",
"observation_code": "29463-7",
"value": "210",
"unit": "lbs"
},
{
"id": "8835a752-bc03-473c-a4ba-37387c045f15",
"observation_code": "64219-9",
"value": "0",
"unit": "pack/years"
},
{
"id": "0dd946b0-d10a-477a-8a43-dc313e0ef0a0",
"observation_code": "4548-4",
"value": "5.4",
"unit": "%"
},
{
"id": "a624e601-d519-40d9-b972-b45e15d2a43f",
"observation_code": "2885-2",
"value": "8.1",
"unit": "g/dL"
},
{
"id": "3197ae19-9438-40d5-aded-67228b498a9f",
"observation_code": "2571-8",
"value": "40",
"unit": "mg/dL"
},
{
"id": "3d9b1dcd-3c70-4c36-9f7b-fc8e2e57bd40",
"observation_code": "10835-7",
"value": "6.2",
"unit": "mg/dL"
},
{
"id": "29ebacd2-baf0-4b86-b66d-92ece13264e4",
"observation_code": "1751-7",
"value": "4.8",
"unit": "g/dL"
},
{
"id": "f644ac87-420c-4656-8f07-daa6350bca0d",
"observation_code": "17861-6",
"value": "9.6",
"unit": "mg/dL"
},
{
"id": "0dfe36d5-a8d3-4a36-9d6d-7dcec20e647e",
"observation_code": "2160-0",
"value": "0.83",
"unit": "mg/dL"
},
{
"id": "c6f4d776-d8e8-486a-9ed0-2d6260c0a584",
"observation_code": "2498-4",
"value": "106",
"unit": "ug/dL"
},
{
"id": "bd9d03b9-1be7-4e51-a3c9-5fd7dee92ea9",
"observation_code": "2086-7",
"value": "76",
"unit": "mg/dL"
},
{
"id": "852a0141-90a0-4397-9480-081786fedc55",
"observation_code": "1975-2",
"value": "0.7",
"unit": "mg/dL"
},
{
"id": "120c6aff-1613-4a14-9a29-88803c8eef72",
"observation_code": "77147-7",
"value": "117.25",
"unit": "mL/min/1.73m^2"
},
{
"id": "aed5cd82-513a-47d8-b8ae-7109dc8a8066",
"observation_code": "13457-7",
"value": "80",
"unit": "mg/dL"
},
{
"id": "199bd4d3-07ac-494f-93e4-d96979f86825",
"observation_code": "2276-4",
"value": "89.6",
"unit": "ng/mL"
}
]
}
]
}Response
Immediate response
After validating correctly formatted input, the API will immediately return a HTTP 202 Accepted response with a JSON object with the following data.
- job_id: A unique server-generated UUID for tracking the job
- status: A status for the report generation job. Values can be
acceptedorfailed - message: A message representing the status of the job, for example, "Health report generation initiated"
- estimated_completion_seconds: Estimated time for completion
{
"job_id": "a7f3c2e4-1234-5678-9abc-def012345678",
"status": "completed",
"message": "Health report generation initiated",
"estimated_completion_seconds": 40
}Webhook response - Job status
When processing completes, the service sends a POST request to the callback URL with a JSON object containing the following fields:
- job_id: The unique job identifier
- status: “success” or “failed”
- processing_time: Time taken to process
- timestamp: UTC timestamp when processing completed
- s3_location: S3 URL of the generated report (on success only)
- error_type: Exception type name (on failure only)
- error_message: Error message (on failure only)
- error_details: Additional error information (on failure only)
Webhook response - PDF file
When processing completes and there is not S3 pre-signed URL provided in the request, the service sends a PUT request to the callback URL with raw bytes of the file in the body. Note: this is the same callback URL that receives the POST request with the job status. You will need to implement both a POST and PUT implementation for the callback endpoint. Below is an example of how you might access the generated report.
@router.put("/report-callback")
async def receive_health_score_report(job_id: str, request: Request):
body = await request.body() # raw bytes
"""Endpoint to receive health score report callbacks from the scoring service."""
logger.info(f"Received health score report callback for job_id: {job_id}")
logger.info(f"File size: {len(body)} bytes")Job status endpoint
The jobs status endpoint retrieves the current status of a health report generation job. This endpoint is useful for:
- Polling job progress when no webhook callback is configured
- Checking status if webhook delivery failed
- Monitoring long-running report generation tasks
Request
Full details of the endpoint and its definition, along with tools for testing it can be found under the API Reference page. This is GET endpoint that takes the job_id as a path parameter, as below:
GET "https://api.voloridgehealth.com/health-score/report/status/{job_id}"Response
The response returns a JSON object with the following fields:
- job_id: The unique job identifier
- status: Current job status. Possible values:
pending: Job created, waiting to start processingprocessing: report generation in progresscompleted: Report successfully generated and uploadedfailed: Error occurred during processing
- created_at: UTC timestamp when the job was created
- updated_at: UTC timestamp when the status was last updated
- error_type: Exception type name (only present when the status is “failed”)
- error_message: Human-readable error message (only present when the status is “failed”)
{
"job_id": "a7f3c2e4-1234-5678-9abc-def012345678",
"status": "completed",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:31:30Z",
"error_type": null,
"error_message": null
}{
"job_id": "a7f3c2e4-1234-5678-9abc-def012345678",
"status": "failed",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:15Z",
"error_type": "ValueError",
"error_message": "Invalid predictor data"
}Updated 11 days ago
