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

MethodPathAuthDescription
POST/internal/usersAPI KeyCreate user with Cognito provisioning
GET/auth/users/{cognito_sub}JWTGet user profile
PATCH/auth/users/{cognito_sub}JWTUpdate user profile
POST/auth/users/{cognito_sub}/dentist-change-requestJWTRequest dentist change
GET/auth/users/{cognito_sub}/journeysJWTList user journeys
GET/auth/users/{cognito_sub}/journeys/activeJWTGet active journey
GET/auth/users/{cognito_sub}/journeys/{journey_id}JWTGet specific journey
POST/auth/users/{cognito_sub}/journeysJWTCreate new journey
PATCH/auth/users/{cognito_sub}/journeys/{journey_id}JWTUpdate journey
GET/auth/users/{cognito_sub}/progressJWTGet current progress (active journey)
POST/auth/users/{cognito_sub}/progressJWTCreate progress event (active journey)
GET/auth/users/{cognito_sub}/imagesJWTList user images (active journey)
POST/auth/users/{cognito_sub}/imagesJWTUpload progress photo (active journey)
GET/auth/users/{cognito_sub}/images/{id}JWTGet image download URL
POST/internal/webhooks/hubspot/dentist-changedAPI KeyWebhook: dentist change completed
POST/public/webhooks/hubspot/contact-property-changeHubSpot SigWebhook: HubSpot contact property changes
GET/healthNoneHealth 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:

FieldTypeRequiredDescription
first_namestringYesPatient’s first name
last_namestringYesPatient’s last name
emailstringYesEmail (used as Cognito username)
postal_codestringNoPatient’s postal code
jarvis_idintNoExternal Jarvis system ID
hubspot_idstringNoHubSpot CRM ID
assigned_dentist_idintNoFK to dentist
terms_acceptedboolNoWhether T&Cs accepted
productstringNoProduct 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:

FieldTypeDescription
terms_acceptedboolSyncs 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 found
  • 502: Jarvis sync failed (only when terms_accepted is 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 found
  • 409: User already has a pending dentist change request
  • 502: 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:

ParameterTypeDescription
statusstringOptional. 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 found
  • 403: 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:

FieldTypeRequiredDescription
productstringNoaligners (default) or clear_braces
total_progress_stagesintNoTotal 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 found
  • 409: User already has an active journey

Update Journey


PATCH /auth/users/{cognito_sub}/journeys/{journey_id}

Auth: JWT

Request Body:

FieldTypeDescription
total_progress_stagesintTotal stages in treatment plan
statusstringactive 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 found
  • 403: 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"
}
 
FieldTypeDescription
user_idintUser ID
journey_idintActive journey ID
current_progress_stageintCurrent progress stage number
total_progress_stagesintTotal stages in treatment plan (null if not set)
start_datedatetimeWhen the latest event was created (null if no events)
end_datedatetimeScheduled next action (null if no events)
typestringLatest 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:

FieldTypeRequiredDescription
typestringYesOne of: progress, pause, delay, continue
end_datestringConditionalISO 8601 datetime. Required for pause/delay, forbidden for progress/continue
reasonstringConditionalExplanation for pause/delay. Required for pause/delay, forbidden for progress/continue

Event Types:

TypePurposeend_datereasonBehavior
progressRecord progress stage changeForbiddenForbiddenIncrements progress stage by 1, schedules next notification
pausePause treatmentRequiredRequiredCreates pause event with user-provided end_date
delayDelay next changeRequiredRequiredCreates delay event with user-provided end_date
continueResume treatmentForbiddenForbiddenComputes end_date based on remaining time from pause

Validation Rules:

  • end_date cannot be in the past
  • pause and delay require both end_date and reason
  • progress and continue must not have end_date or reason
  • 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 journey
  • 409: 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_id on the user

  • Sets has_pending_dentist_change to false

  • 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:

HeaderDescription
X-HubSpot-Signature-v3HMAC-SHA256 signature
X-HubSpot-Request-TimestampUnix timestamp (ms)

Payload Schema:

FieldTypeDescription
appIdintHubSpot app ID
eventIdintUnique event identifier
subscriptionIdintWebhook subscription ID
portalIdintHubSpot portal ID
occurredAtintUnix timestamp (ms)
subscriptionTypestringType of subscription
attemptNumberintDelivery attempt number
objectIdintHubSpot contact ID (used as hubspot_id lookup)
changeSourcestringSource of the change
propertyNamestringName of changed property
propertyValuestringNew value

Handled Properties:

Property NameAction
dentist_assignedUpdates assigned_dentist_id, clears has_pending_dentist_change, creates notification
waiver_signed_version_jarvisSets 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