Skip to main content

Overview

Import complete Apple Health data exports via XML files using one of these methods:
  1. S3-compatible multipart upload (Recommended) - For large files, uploads parts directly to an S3-compatible bucket (AWS S3 or self-hosted), reports progress, and explicitly queues processing.
  2. S3 presigned POST with SNS - AWS-only workflow for deployments configured with APPLE_XML_UPLOAD_COMPLETION_MODE=sns.
  3. Direct Upload - For smaller files or testing, uploads directly to the API

Authentication

All endpoints require authentication via Bearer token (user login) or API key.
Then use the token in subsequent requests:

Endpoints

Method 1: S3 Presigned POST (SNS mode only)

Uploads directly to AWS S3. This workflow starts processing only when APPLE_XML_UPLOAD_COMPLETION_MODE=sns and the bucket publishes object-created events through SNS. In the default client mode, use the multipart workflow below.

Step 1: Request Presigned URL

string
required
The ID of the user to import data for
string
default:""
Custom filename (max 200 characters)
integer
default:"300"
URL expiration time in seconds (60 - 3600)
integer
default:"52428800"
Maximum file size in bytes (1KB - 5GiB). Default is 50MB.

Step 2: Upload File to S3

Use the upload_url and form_fields from the previous response to upload your XML file:
Important: When uploading to S3 with presigned POST, you must include all form_fields as form data, and the file field must be last.

Method 1b: Multipart Upload (S3-compatible)

The portal uploads large exports using multipart upload, which splits the file into parts uploaded in parallel via presigned URLs. It works identically against any S3-compatible bucket (AWS S3 or a self-hosted server), so a local deployment with no AWS account can still accept multi-gigabyte Apple Health exports through the UI. The flow is four calls (all authenticated, all under /import/apple/xml/s3/multipart):
1

create

POST .../multipart/create with { "filename", "content_type", "file_size" } → returns { "upload_id", "key", "bucket", "part_size" }. Split the file into parts of part_size bytes (the last part may be smaller).
2

sign

POST .../multipart/sign with { "key", "upload_id", "part_numbers": [1, 2, ...] } → returns a presigned PUT URL for each part.
3

upload parts

PUT each part’s bytes to its presigned URL. Capture the ETag response header of every part.
4

complete

POST .../multipart/complete with { "key", "upload_id", "parts": [{ "part_number", "etag" }] }. In the default client completion mode this also dispatches the import task and returns a task_id. Use POST .../multipart/abort to discard an incomplete upload.
Multipart XML uploads accept files from 5 MiB through 5 GiB. The backend returns its configured recommendation (APPLE_XML_MULTIPART_PART_SIZE_BYTES, 100 MiB by default) from the create endpoint. Always use that returned part_size instead of hard-coding it in a client.
Browser part uploads read the ETag from the PUT response, so the bucket’s CORS policy must allow PUT and expose the ETag header (ExposeHeaders: ["ETag"]).
Completion mode - the deployment’s APPLE_XML_UPLOAD_COMPLETION_MODE controls how a finished upload starts processing, and thus the /complete response you get: client (default) dispatches the import from the /complete call and returns 202 Accepted with a task_id; sns instead waits for an S3 bucket event and /complete only finalizes the object (200 OK). Only the selected mode dispatches, so a file is never processed twice.
See the AWS S3 setup guide for the storage environment variables - custom endpoints (any S3-compatible bucket), completion mode, and size limits - with their defaults.

Complete multipart example

This script authenticates with an API key, creates and signs an upload, sends every part, captures its ETag, and completes the upload. If any step fails after creation, it makes a best-effort abort request so uploaded parts do not linger.
upload_xml_multipart.py
Expected completion response in the default client mode:
202 Accepted
The portal listens to GET /api/v1/users/{user_id}/sync/stream. The XML import’s terminal sync.status event uses the returned task ID as its run_id; on success or failure the portal refreshes the user’s health data. Recent results are also available from GET /api/v1/users/{user_id}/sync/runs for 24 hours.

Multipart responses and errors

There is no endpoint-specific application rate limit. Your API gateway and object storage may impose their own request limits; treat 429 and transient 503 responses as retryable with exponential backoff.
Configure the bucket to abort incomplete multipart uploads after an appropriate number of days. This covers the case where the create response is lost before the client learns the upload_id. On AWS S3, merge an AbortIncompleteMultipartUpload rule into the bucket’s existing lifecycle configuration; applying a new lifecycle file replaces the existing configuration. See AWS’s incomplete multipart upload guidance. Use the equivalent lifecycle control offered by your S3-compatible provider.
lifecycle-rule.json

Complete Example Workflow

1

Login to Get Access Token

2

Request Presigned URL

3

Upload File to S3

4

Processing Happens Automatically

With APPLE_XML_UPLOAD_COMPLETION_MODE=sns (AWS), the system automatically:
  • Detects the S3 upload via S3 event notification → SNS
  • SNS sends an HTTPS notification to the backend
  • process_aws_upload Celery task downloads and processes the XML file
  • Imports workouts and time series data to database
In the default client mode, the presigned-POST method above has no completion trigger - use Method 1b (multipart) and its /complete call instead, which is what the portal uses and which works for any S3-compatible bucket.
upload_xml_s3.py

Data Imported

Workouts

  • Activity type (running, cycling, swimming, etc.)
  • Duration and timestamps
  • Distance, calories, elevation
  • Heart rate statistics (min/max/avg)

Time Series Samples

  • Heart rate
  • Steps
  • Active energy
  • Distance
  • Blood oxygen
  • And 100+ other metrics
See Data Types Guide for complete list.

Best Practices

Use S3 for Large Files

Files over 10MB should use multipart upload to avoid API request limits

Handle Async Processing

Import processing is asynchronous. Don’t expect immediate data availability

Monitor Task Status

Use Celery Flower or logs to monitor processing status

Dedupe Handled Automatically

Records with the same external_id won’t be duplicated