{"openapi":"3.1.0","info":{"title":"Vital Logger API","version":"0.1.0","description":"Cloudflare Workers API for Vital Logger authentication, user health profiles, vital measurements, reports, and admin JWT key rotation."},"servers":[{"url":"https://vital-logger.sirjhep.dev","description":"Production"},{"url":"http://localhost:8787","description":"Local Wrangler development"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"adminToken":{"type":"apiKey","in":"header","name":"X-Admin-Token"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"Session":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"},"token":{"type":"string"}},"required":["user","token"]},"User":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string"}},"required":["id","email","name"]},"UserProfile":{"type":"object","properties":{"userId":{"type":"string"},"dateOfBirth":{"type":"string","format":"date"},"referenceSex":{"type":"string","enum":["female","male","not_specified"]},"heightCm":{"type":"number","minimum":30.48,"maximum":274.32},"completedAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["userId","dateOfBirth","referenceSex","heightCm","completedAt","updatedAt"]},"Vital":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["blood_pressure","weight","temperature","heart_rate","spo2","blood_glucose","respiratory_rate","bmi","waist_circumference","head_circumference","custom"]},"source":{"type":"string","enum":["manual","camera_ocr","health_connect","healthkit"],"description":"Provenance of the entry. Defaults to manual for legacy and user-entered records."},"value":{"type":"number","nullable":true},"unit":{"type":"string","nullable":true},"systolic":{"type":"integer","nullable":true},"diastolic":{"type":"integer","nullable":true},"notes":{"type":"string","nullable":true},"imageDataUrl":{"type":"string","nullable":true,"description":"Clients may send a JPEG, PNG, or WebP data URL up to 512 KB. Synced cloud records return an authenticated Vital Logger image URL."},"measuredAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time","nullable":true},"syncVersion":{"type":"integer"}},"required":["id","type","source","measuredAt","updatedAt"]},"Report":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"period":{"type":"string"},"format":{"type":"string","enum":["pdf","image"]},"generatedAt":{"type":"string","format":"date-time"},"summary":{"type":"string"},"insights":{"type":"array","items":{"type":"object"}},"vitals":{"type":"array","items":{"type":"object"}}},"required":["id","title","period","format","generatedAt","summary"]}}},"paths":{"/":{"get":{"summary":"Public Vital Logger landing page with mobile pre-beta download status","responses":{"200":{"description":"HTML landing page"}}}},"/health":{"get":{"summary":"Health check","responses":{"200":{"description":"API is reachable"}}}},"/docs":{"get":{"summary":"Human-readable API documentation","responses":{"200":{"description":"HTML API documentation"}}}},"/docs/openapi.json":{"get":{"summary":"OpenAPI specification","responses":{"200":{"description":"OpenAPI 3.1 JSON document"}}}},"/privacy":{"get":{"summary":"Human-readable privacy policy","responses":{"200":{"description":"HTML privacy policy"}}}},"/delete-account":{"get":{"summary":"Human-readable account deletion instructions","responses":{"200":{"description":"HTML account deletion page"}}}},"/assets/vital-logger-logo.png":{"get":{"summary":"Public Vital Logger PNG logo for transactional emails","responses":{"200":{"description":"PNG logo"}}}},"/verify-email":{"get":{"summary":"Browser email verification landing page","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Email verified; HTML confirmation page"},"400":{"description":"Invalid, expired, or missing token"}}}},"/reset-password":{"get":{"summary":"Browser password reset form","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"HTML password reset form"},"400":{"description":"Missing token"}}}},"/auth/register":{"post":{"summary":"Create an account and send verification email","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","password","confirmPassword"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8},"confirmPassword":{"type":"string","minLength":8}}}}}},"responses":{"201":{"description":"Account created; email verification required"},"409":{"description":"Email already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/login":{"post":{"summary":"Login after email verification","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"Session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"401":{"description":"Invalid credentials"},"403":{"description":"Email is not verified"}}}},"/auth/verify-email":{"post":{"summary":"Consume an email verification token","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"}}}}}},"responses":{"200":{"description":"Email verified and session returned"},"400":{"description":"Invalid or expired token"}}}},"/auth/resend-verification":{"post":{"summary":"Request another verification email","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Generic success response"}}}},"/auth/forgot-password":{"post":{"summary":"Send password reset email when the account exists","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Generic success response"}}}},"/auth/reset-password":{"post":{"summary":"Consume a password reset token","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","password","confirmPassword"],"properties":{"token":{"type":"string"},"password":{"type":"string","minLength":8},"confirmPassword":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"Password changed"},"400":{"description":"Invalid or expired token"},"422":{"description":"Validation error"}}}},"/profile":{"get":{"summary":"Fetch the authenticated user's health reference profile","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Current profile or null when incomplete","content":{"application/json":{"schema":{"type":"object","properties":{"profile":{"anyOf":[{"$ref":"#/components/schemas/UserProfile"},{"type":"null"}]}}}}}},"401":{"description":"Missing or invalid token"}}},"put":{"summary":"Create or update the authenticated user's health reference profile","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["dateOfBirth","heightCm"],"properties":{"dateOfBirth":{"type":"string","format":"date"},"referenceSex":{"type":"string","enum":["female","male","not_specified"]},"heightCm":{"type":"number","minimum":30.48,"maximum":274.32}}}}}},"responses":{"200":{"description":"Profile saved","content":{"application/json":{"schema":{"type":"object","properties":{"profile":{"$ref":"#/components/schemas/UserProfile"}}}}}},"401":{"description":"Missing or invalid token"},"422":{"description":"Validation error"}}}},"/vitals":{"get":{"summary":"List vital measurements","security":[{"bearerAuth":[]}],"parameters":[{"name":"since","in":"query","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"includeDeleted","in":"query","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Vital list"},"401":{"description":"Missing or invalid token"}}},"post":{"summary":"Create a vital measurement","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vital"}}}},"responses":{"200":{"description":"Existing vital returned for idempotent client id retry"},"201":{"description":"Vital created"},"409":{"description":"ID belongs to another record"}}}},"/vitals/{id}":{"put":{"summary":"Update a vital measurement","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Vital updated"},"404":{"description":"Vital not found"}}},"delete":{"summary":"Soft-delete a vital measurement","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Vital deleted"},"404":{"description":"Vital not found"}}}},"/vitals/{id}/image/{file}":{"get":{"summary":"Fetch an authenticated vital image attachment","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"file","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Image bytes"},"401":{"description":"Missing or invalid token"},"404":{"description":"Image not found"}}}},"/reports/generate":{"post":{"summary":"Generate a report from stored vitals","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Report generated"}}}},"/reports":{"get":{"summary":"List generated reports","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Report list"}}}},"/account/cloud-data":{"delete":{"summary":"Delete backed-up health data while preserving the user account","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Cloud vitals, reports, and profile health details deleted"},"401":{"description":"Missing or invalid token"}}}},"/account":{"delete":{"summary":"Delete the authenticated account and associated cloud data","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Account and associated cloud data deleted"},"401":{"description":"Missing or invalid token"}}}},"/admin/jwt/rotate":{"post":{"summary":"Rotate JWT signing key","security":[{"adminToken":[]}],"responses":{"200":{"description":"Key rotation summary"},"403":{"description":"Missing admin token"}}}}}}