Parent: Aligner Tracker Api
API Reference
Purpose
Complete API endpoint documentation for the Aligner Tracker API. Covers all routes, request/response formats, and authentication requirements. Use this as a quick reference when integrating with the API.
Endpoints Summary
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /internal/users | API Key | Create user with Cognito provisioning |
| GET | /auth/users/{cognito_sub} | JWT | Get user profile |
| PATCH | /auth/users/{cognito_sub} | JWT | Update user profile |
| POST | /auth/users/{cognito_sub}/dentist-change-request | JWT | Request dentist change |
| GET | /auth/users/{cognito_sub}/journeys | JWT | List user journeys |
| GET | /auth/users/{cognito_sub}/journeys/active | JWT | Get active journey |
| GET | /auth/users/{cognito_sub}/journeys/{journey_id} | JWT | Get specific journey |
| POST | /auth/users/{cognito_sub}/journeys | JWT | Create new journey |
| PATCH | /auth/users/{cognito_sub}/journeys/{journey_id} | JWT | Update journey |
| GET | /auth/users/{cognito_sub}/progress | JWT | Get current progress (active journey) |
| POST | /auth/users/{cognito_sub}/progress | JWT | Create progress event (active journey) |
| GET | /auth/users/{cognito_sub}/images | JWT | List user images (active journey) |
| POST | /auth/users/{cognito_sub}/images | JWT | Upload progress photo (active journey) |
| GET | /auth/users/{cognito_sub}/images/{id} | JWT | Get image download URL |
| POST | /internal/webhooks/hubspot/dentist-changed | API Key | Webhook: dentist change completed |
| POST | /public/webhooks/hubspot/contact-property-change | HubSpot Sig | Webhook: HubSpot contact property changes |
| GET | /health | None | Health check |
Users
Create User
POST /internal/users
Auth: API Key (internal)
Creates a new user with automatic Cognito identity provisioning.
Example:
curl -X POST http://localhost:5000/internal/users \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"postal_code": "SW1A 1AA",
"jarvis_id": 12345,
"hubspot_id": "abc123",
"assigned_dentist_id": 1,
"terms_accepted": true,
"product": "aligners"
}'
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
first_name | string | Yes | Patient’s first name |
last_name | string | Yes | Patient’s last name |
email | string | Yes | Email (used as Cognito username) |
postal_code | string | No | Patient’s postal code |
jarvis_id | int | No | External Jarvis system ID |
hubspot_id | string | No | HubSpot CRM ID |
assigned_dentist_id | int | No | FK to dentist |
terms_accepted | bool | No | Whether T&Cs accepted |
product | string | No | Product for initial journey: aligners (default) or clear_braces |
Response (201):
{
"id": 1,
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"cognito_sub_identifier": "abc-123-def",
"temporary_password": "generated-temp-password",
...
}
Note: Creating a user automatically creates an initial active Journey with the specified product (defaults to aligners).
Get User
GET /auth/users/{cognito_sub}
Auth: JWT (sub must match path)
Example:
curl http://localhost:5000/auth/users/abc-123-def \
-H "Authorization: Bearer <jwt-token>"
Response (200):
{
"id": 1,
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"has_pending_dentist_change": false,
"terms_accepted": true,
...
}
Note: Progress-related fields (current_progress_stage, total_progress_stages, date_of_starting_treatment, aftercare_stage) are now on the Journey model. Use /journeys/active or /progress endpoints to get these values.
Update User
PATCH /auth/users/{cognito_sub}
Auth: JWT (sub must match path)
Update user profile fields. All fields are optional.
Request Body:
| Field | Type | Description |
|---|---|---|
terms_accepted | bool | Syncs to Jarvis (which syncs to HubSpot) |
Note: total_progress_stages is now updated via the Journey endpoint (PATCH /journeys/{journey_id}).
Example:
curl -X PATCH http://localhost:5000/auth/users/abc-123-def \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{"terms_accepted": true}'
Response (200): Returns updated user object.
Error Responses:
404: User not found502: Jarvis sync failed (only whenterms_acceptedis provided)
Request Dentist Change
POST /auth/users/{cognito_sub}/dentist-change-request
Auth: JWT (sub must match path)
Submit a request to change the user’s assigned dentist. This creates a HubSpot ticket via Jarvis for staff review.
Example:
curl -X POST http://localhost:5000/auth/users/abc-123-def/dentist-change-request \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{"new_dentist_id": 42}'
Response (202):
{
"ticket_id": "12345",
"status": "pending",
"message": "Your request has been submitted and is awaiting review"
}
Error Responses:
404: User not found409: User already has a pending dentist change request502: Failed to submit request to Jarvis
Journeys
Journeys represent a user’s treatment cycle for a specific product (e.g., aligners, clear braces). A user can only have one active journey at a time. Progress events, images, and notifications are associated with journeys.
List Journeys
GET /auth/users/{cognito_sub}/journeys
Auth: JWT
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | Optional. Filter by status: active or completed |
Example:
curl http://localhost:5000/auth/users/abc-123-def/journeys \
-H "Authorization: Bearer <jwt-token>"
Response (200):
{
"user_id": 1,
"journeys": [
{
"id": 1,
"user_id": 1,
"product": "aligners",
"status": "active",
"current_progress_stage": 5,
"total_progress_stages": 20,
"date_of_starting_treatment": "2026-01-01",
"expected_end_date": "2026-07-20",
"aftercare_stage": "aligner_journey_underway",
"created_at": "2026-01-01T10:00:00Z"
}
]
}
Get Active Journey
GET /auth/users/{cognito_sub}/journeys/active
Auth: JWT
Returns the user’s currently active journey.
Response (200):
{
"id": 1,
"user_id": 1,
"product": "aligners",
"status": "active",
"current_progress_stage": 5,
"total_progress_stages": 20,
"date_of_starting_treatment": "2026-01-01",
"expected_end_date": "2026-07-20",
"aftercare_stage": "aligner_journey_underway",
"created_at": "2026-01-01T10:00:00Z"
}
Error Responses:
404: User not found or no active journey
Get Journey by ID
GET /auth/users/{cognito_sub}/journeys/{journey_id}
Auth: JWT
Response (200): Returns the journey object.
Error Responses:
404: Journey not found403: Journey belongs to a different user
Create Journey
POST /auth/users/{cognito_sub}/journeys
Auth: JWT
Creates a new journey for the user. Only one active journey is allowed at a time.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
product | string | No | aligners (default) or clear_braces |
total_progress_stages | int | No | Total stages in treatment plan |
Example:
curl -X POST http://localhost:5000/auth/users/abc-123-def/journeys \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{"product": "clear_braces", "total_progress_stages": 15}'
Response (201): Returns created journey object.
Error Responses:
404: User not found409: User already has an active journey
Update Journey
PATCH /auth/users/{cognito_sub}/journeys/{journey_id}
Auth: JWT
Request Body:
| Field | Type | Description |
|---|---|---|
total_progress_stages | int | Total stages in treatment plan |
status | string | active or completed |
Example:
curl -X PATCH http://localhost:5000/auth/users/abc-123-def/journeys/1 \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{"total_progress_stages": 25}'
Response (200): Returns updated journey object.
Error Responses:
404: Journey not found403: Journey belongs to a different user
Progress
Progress endpoints operate on the user’s active journey. If no active journey exists, requests return 404.
Get Progress
GET /auth/users/{cognito_sub}/progress
Auth: JWT
Example:
curl http://localhost:5000/auth/users/abc-123-def/progress \
-H "Authorization: Bearer <jwt-token>"
Response (200):
{
"user_id": 1,
"journey_id": 1,
"current_progress_stage": 5,
"total_progress_stages": 20,
"start_date": "2026-01-25T10:00:00Z",
"end_date": "2026-02-04T10:00:00Z",
"type": "progress"
}
| Field | Type | Description |
|---|---|---|
user_id | int | User ID |
journey_id | int | Active journey ID |
current_progress_stage | int | Current progress stage number |
total_progress_stages | int | Total stages in treatment plan (null if not set) |
start_date | datetime | When the latest event was created (null if no events) |
end_date | datetime | Scheduled next action (null if no events) |
type | string | Latest event type: progress, pause, delay, continue (null if no events) |
Error Responses:
404: User not found or no active journey
Create Progress Event
POST /auth/users/{cognito_sub}/progress
Auth: JWT
Create a progress event of a specific type. All event types are managed through this single endpoint.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | One of: progress, pause, delay, continue |
end_date | string | Conditional | ISO 8601 datetime. Required for pause/delay, forbidden for progress/continue |
reason | string | Conditional | Explanation for pause/delay. Required for pause/delay, forbidden for progress/continue |
Event Types:
| Type | Purpose | end_date | reason | Behavior |
|---|---|---|---|---|
progress | Record progress stage change | Forbidden | Forbidden | Increments progress stage by 1, schedules next notification |
pause | Pause treatment | Required | Required | Creates pause event with user-provided end_date |
delay | Delay next change | Required | Required | Creates delay event with user-provided end_date |
continue | Resume treatment | Forbidden | Forbidden | Computes end_date based on remaining time from pause |
Validation Rules:
end_datecannot be in the pastpauseanddelayrequire bothend_dateandreasonprogressandcontinuemust not haveend_dateorreason- Extra fields in the request body are rejected
Example (progress - increment aligner):
curl -X POST http://localhost:5000/auth/users/abc-123-def/progress \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{"type": "progress"}'
Example (pause with reason):
curl -X POST http://localhost:5000/auth/users/abc-123-def/progress \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"type": "pause",
"end_date": "2026-03-15T10:00:00Z",
"reason": "My aligners are still hurting my teeth"
}'
Example (delay with reason):
curl -X POST http://localhost:5000/auth/users/abc-123-def/progress \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"type": "delay",
"end_date": "2026-02-20T10:00:00Z",
"reason": "Need more adjustment time"
}'
Example (continue - resume treatment):
curl -X POST http://localhost:5000/auth/users/abc-123-def/progress \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{"type": "continue"}'
Response (201):
{
"user_id": 1,
"journey_id": 1,
"event": {
"id": 10,
"user_id": 1,
"journey_id": 1,
"start_date": "2026-02-04T10:00:00Z",
"progress_stage": 6,
"end_date": "2026-02-14T10:00:00Z",
"next_notification_id": 15,
"type": "progress",
"reason": null
},
"current_progress_stage": 6
}
Error Responses:
404: User not found or no active journey409: Business logic error (e.g., no prior progress event for continue)422: Validation error (e.g., date in past, missing required fields)
Images
Image endpoints operate on the user’s active journey. If no active journey exists, requests return 404.
List Images
GET /auth/users/{cognito_sub}/images
Auth: JWT
Example:
curl http://localhost:5000/auth/users/abc-123-def/images \
-H "Authorization: Bearer <jwt-token>"
Response (200):
{
"user_id": 1,
"journey_id": 1,
"images": [
{
"id": 1,
"date_uploaded": "2026-01-15T10:00:00Z",
"progress_stage": 3,
"download_url": "https://s3...presigned-url"
}
]
}
Error Responses:
404: User not found or no active journey
Upload Image
POST /auth/users/{cognito_sub}/images
Auth: JWT
Content-Type: multipart/form-data
Example:
curl -X POST http://localhost:5000/auth/users/abc-123-def/images \
-H "Authorization: Bearer <jwt-token>" \
-F "file=@/path/to/photo.jpg"
Response (201):
{
"image_id": 5,
"url": "/auth/users/abc-123-def/images/5"
}
Get Image Download URL
GET /auth/users/{cognito_sub}/images/{image_id}
Auth: JWT
Example:
curl http://localhost:5000/auth/users/abc-123-def/images/1 \
-H "Authorization: Bearer <jwt-token>"
Response (200):
{
"download_url": "https://s3...presigned-url"
}
Webhooks
Dentist Changed
POST /internal/webhooks/hubspot/dentist-changed
Auth: API Key (internal)
Webhook called by HubSpot when a dentist change request has been approved and completed. Updates the user’s assigned dentist and clears the pending flag.
Example:
curl -X POST http://localhost:5000/internal/webhooks/hubspot/dentist-changed \
-H "Content-Type: application/json" \
-d '{
"customer_hubspot_id": "12345",
"customer_email": "john@example.com",
"new_dentist_id": 42,
"new_dentist_name": "Dr. Smith"
}'
Response (200):
{
"status": "ok"
}
This endpoint:
-
Updates
assigned_dentist_idon the user -
Sets
has_pending_dentist_changetofalse -
Creates a notification informing the user of the change
HubSpot Contact Property Change
POST /public/webhooks/hubspot/contact-property-change
Auth: HubSpot Signature v3 (validated using HUBSPOT_CLIENT_SECRET)
Required Headers:
| Header | Description |
|---|---|
X-HubSpot-Signature-v3 | HMAC-SHA256 signature |
X-HubSpot-Request-Timestamp | Unix timestamp (ms) |
Payload Schema:
| Field | Type | Description |
|---|---|---|
appId | int | HubSpot app ID |
eventId | int | Unique event identifier |
subscriptionId | int | Webhook subscription ID |
portalId | int | HubSpot portal ID |
occurredAt | int | Unix timestamp (ms) |
subscriptionType | string | Type of subscription |
attemptNumber | int | Delivery attempt number |
objectId | int | HubSpot contact ID (used as hubspot_id lookup) |
changeSource | string | Source of the change |
propertyName | string | Name of changed property |
propertyValue | string | New value |
Handled Properties:
| Property Name | Action |
|---|---|
dentist_assigned | Updates assigned_dentist_id, clears has_pending_dentist_change, creates notification |
waiver_signed_version_jarvis | Sets terms_accepted to true |
aftercare_stage__contact_ | Updates active journey’s aftercare_stage. Special values: “Aligner Journey Underway” sets aftercare_stage to aligner_journey_underway; “Journey Complete” sets aftercare_stage to journey_complete and marks journey as completed |
Response (200):
{"status": "ok"}
Returns {"status": "ignored"} if the property is not in the handled list.
Error Response:
404: User not found with given HubSpot ID