Lifecycle flows

First, one overall story per app — patient, provider, admin. Then the AWS sections show how those APIs are hosted. New resources are named DBP- plus Title-Case words. The API is Python 3.14 on Amazon Linux 2023.

India · IST product, offset on the wire 50,000 bookings / day APIs only — not the apps Phase 1 EC2 · Phase 2 EKS Python 3.14 on Amazon Linux 2023 Names: DBP-Api, DBP-Holds Money = integer paise

00 — Three apps, one product

Read this as a clinic day. Provider sets up care. Patient books a token (not a clock time). Admin approves people and watches money. Your APIs sit in the middle.

flowchart LR Provider[Provider_app] Patient[Patient_app] Admin[Admin_portal] API[Your_APIs] Provider -->|clinic_sets_doctors_and_queue| API Patient -->|search_pay_watch_token| API Admin -->|approve_and_monitor| API API -->|push_and_refunds| Patient API -->|alerts| Provider
Patient appBase /api/patient. Phone OTP. Live immediately. Books for a family member. Pays, gets a token number, watches the queue.
Provider appBase /api/provider. One app, three hats: clinic front desk, doctor, diagnostic centre. Same phone can hold more than one hat.
Admin portalBase /api/admin. Humans approve clinics/doctors/centres, set fees, retry refunds. Not used by patients.

01 — Patient app (overall flow)

What a patient does from first open to leaving the clinic — and later, paying for lab tests.

1. Sign inPhone + OTP. Add yourself, family, place, allergies.
2. SearchPick a speciality or name. See doctors at nearby clinics and their slots.
3. HoldChoose who is visiting. Seat is reserved 8 minutes. No token yet.
4. PayOn success they get a token number. Failed pay can retry in the same 8 minutes.
5. Clinic dayWhen the desk starts the doctor, watch “now serving”. Walk in on your number.
6. AfterCancel before 7:00 am that day (full refund), or approve lab tests the doctor ordered.
flowchart TD OTP[Open_app_phone_OTP] Profile[Add_family_and_area] Search[Search_doctor_or_clinic] Hold[Hold_seat_8_minutes] Pay[Pay] Token[Get_token_number] Queue[Watch_now_serving] Seen[See_the_doctor] Cancel[Cancel_before_7am_refund] Offer[Doctor_unavailable_accept_or_decline] Labs[Pay_for_lab_tests] OTP --> Profile --> Search --> Hold --> Pay --> Token Token --> Queue --> Seen Token --> Cancel Token --> Offer Seen --> Labs
Simple ruleNo payment = no token. The number is given only after money is captured.
If the doctor drops outClinic may offer another doctor. Patient has 4 hours. Decline or silence = full refund.
Missed turnIf they were called and not there, or the desk closed the queue, there is no refund.

02 — Provider app (overall flow)

One mobile app. After OTP, the person picks a role. Clinic, doctor and diagnostic centre each wait for admin approval before they can work.

flowchart TD OTP[Phone_OTP] Role[Choose_role] Apply[Fill_details_and_documents] Wait[Wait_for_admin] Clinic[Clinic_desk] Doctor[Doctor] Centre[Diagnostic_centre] OTP --> Role --> Apply --> Wait Wait -->|approved| Clinic Wait -->|approved| Doctor Wait -->|approved| Centre Wait -->|rejected| Apply

Clinic desk

1. SetupPublish specialities. Add a doctor with their DOC code.
2. TimingsSet which days and hours each doctor sits, and how many tokens.
3. TodayCheck the doctor in. Queue goes live for patients.
4. ConsoleCall next, skip if absent, recall later, then close.
5. If doctor cancels6 hours to offer another doctor, or everyone is refunded (clinic pays).
6. Remove doctorTakes effect after 7 days. App shows the cost first.
flowchart LR Spec[Add_specialities] Attach[Attach_doctor] Hours[Set_hours_and_token_limit] Checkin[Check_in_today] Call[Call_next] Close[Close_queue] Spec --> Attach --> Hours --> Checkin --> Call --> Close

Doctor

1. CodeAfter approval they get a DOC id to give clinics.
2. Join clinicsAccept invites. One doctor can sit at many clinics.
3. CalendarSee appointments per clinic and date.
4. Leave / awayBlock new bookings, or mark a session unavailable.
5. TestsPick tests first, then one or more centres that cover them.
flowchart LR Code[Get_DOC_code] Join[Join_clinics] See[See_appointments] Away[Leave_or_unavailable] Tests[Order_lab_tests] Code --> Join --> See See --> Away See --> Tests

Diagnostic centre

1. ServicesTurn on tests from the admin catalogue and set a price.
2. IncomingSee an order only after the patient has paid that centre.
3. WorkCollect sample → process → mark reported.
4. HolidaysBlock dates so new bookings cannot land.
flowchart LR Offer[List_tests_and_prices] Paid[Paid_order_appears] Work[Collect_process_report] Offer --> Paid --> Work
Same phone, two hatsA doctor who owns a clinic switches role in the app. The server issues a new login token for that hat.
Desk wifi dropsCall-next is saved on the phone and sent again. The API must accept the same action twice safely.
Centre privacyThey see name and tests. They must not see allergies or medical history.

03 — Admin portal (overall flow)

A browser app for the platform team. They do not treat patients. They unlock providers and keep money honest.

1. TodayHome shows what needs a human: new applications, stuck refunds, open reassignments.
2. VerifyOpen a clinic/doctor/centre file. Approve, or reject one document with a reason.
3. CatalogueSpecialities, fee bands, lab tests and price ceilings.
4. WatchSessions, bookings, lab orders — read-only monitors.
5. MoneyRetry a failed refund. Mark a clinic settlement paid with a UTR.
6. PolicyHold minutes, 6-hour / 4-hour windows, 7-day removal notice.
flowchart TD Home[Open_admin_home] Verify[Approve_or_reject_application] Code[Provider_gets_CLN_or_DOC_or_DGC_code] Watch[Watch_bookings_and_queues] Money[Refunds_and_settlements] Policy[Change_platform_numbers] Audit[Everything_is_logged] Home --> Verify --> Code Home --> Watch Home --> Money Home --> Policy Verify --> Audit Money --> Audit Policy --> Audit
ApproveIssues the unique code and freezes legal name and registration number.
RejectMust name the document and quote a note. Applicant can fix and re-apply (3 times, then a phone call).
Looking up a patientIs itself an audit event. Admin sees little health detail on purpose.

10 — What your APIs must host

Both phases Flutter apps and the admin UI are other teams. One CloudFront: doctors.rohimatech.com (distribution d3h3h0bro93hk1). / is docs. /api is the backend, then /patient, /provider, or /admin — plus /internal and /webhooks. The API process is EC2 in Phase 1 and an EKS pod in Phase 2. Prefixes do not change.

flowchart LR subgraph apps [Not_AWS] P[Patient_app] V[Provider_app] A[Admin_UI] end subgraph edge [Always_the_same] CF[doctors_rohimatech_com] APIGW[DBP-Api_Gateway] Cog[DBP-Users] end subgraph api [API_process_EC2_or_EKS] PatAPI["/api/patient/*"] ProvAPI["/api/provider/*"] AdminAPI["/api/admin/*"] Jobs["/api/internal/*"] Hooks["/api/webhooks/*"] end P --> CF V --> CF A --> CF CF --> APIGW APIGW --> Cog APIGW --> PatAPI APIGW --> ProvAPI APIGW --> AdminAPI APIGW --> Jobs APIGW --> Hooks
Idempotency-KeyRequired on every POST that moves money or the queue. Stored in DynamoDB, not Redis.
ClocksEventBridge → SNS → /api/internal/jobs. Same URL in both phases. Gateway posts /api/webhooks/payments.
Phase 1 is not EKSPersonal-dev compute is one EC2. Production aim is EKS.

11 — Shared AWS (do not rebuild for Phase 2)

Both phases These services are the product. Only the box that runs the container changes. If the API only talks to these names via env vars, the cutover is a Terraform module swap.

flowchart TB CF[CloudFront] APIGW[HTTP_API_Gateway] Cog[Cognito] Image[ECR_dbp_api_image] RDS[RDS_Postgres] DDB[DynamoDB] S3[S3] EB[EventBridge_Scheduler] SNS[SNS] P1[Phase1_EC2] P2[Phase2_EKS] CF --> APIGW APIGW --> Cog Image --> P1 Image --> P2 APIGW -->|phase1_HTTP_EIP| P1 APIGW -->|phase2_VPC_Link| P2 P1 --> RDS P1 --> DDB P1 --> S3 P2 --> RDS P2 --> DDB P2 --> S3 EB --> SNS --> APIGW
AWS servicePhase 1Phase 2
CloudFrontCreatedKeep
HTTP API Gateway + CognitoHTTP integration to EC2 EIPSame API, integration → VPC Link
ECR image dbp-api (lowercase required)Python 3.14 on AL2023, process DBP-ApiSame image tag on EKS
RDS / DynamoDB / S3CreatedKeep (maybe larger RDS)
EventBridge + SNSCreatedKeep
EC2 + Elastic IPCreatedDestroy after cutover
EKS + NLBNot createdCreated

12 — Phase 1 developing: CloudFront → API Gateway → EC2

Phase 1 Personal-developer stack. No EKS control plane. API Gateway HTTP API proxies to one machine. This is a supported AWS path: HTTP integration, not VPC Link.

1. CloudFrontdoctors.rohimatech.com — / is docs, /api is the backend then /patient /provider /admin. TLS. Cache queue later.
2. API GatewayDBP-Api + Cognito DBP-Users. HTTP proxy to the Elastic IP.
3. One EC2DBP-Api on t4g.small. Docker image dbp-api, Python 3.14, port 8080.
4. DataRDS + DynamoDB + S3. Identical to Phase 2.
5. ClocksEventBridge → SNS → /api/internal/jobs.
6. Not hereNo EKS, NLB, NAT, WAF, KMS, Redis.
flowchart TB Apps[Apps] CF[CloudFront] GW[HTTP_API_Gateway] Cog[Cognito] EIP[Elastic_IP_port_8080] EC2[EC2_t4g_small_Docker] RDS[RDS_micro] DDB[DynamoDB] S3[S3] EB[EventBridge] SNS[SNS] Apps --> CF CF -->|cache_hit| Apps CF -->|miss_or_write| GW GW --> Cog GW -->|HTTP_proxy| EIP --> EC2 EC2 --> RDS EC2 --> DDB EC2 --> S3 EC2 --> EB EB --> SNS SNS -->|HTTPS_jobs| GW
sequenceDiagram participant App participant CloudFront participant APIGateway participant Cognito participant EC2 participant RDS participant DynamoDB App->>CloudFront: HTTPS CloudFront->>APIGateway: origin APIGateway->>Cognito: verify_JWT Cognito-->>APIGateway: ok APIGateway->>EC2: HTTP_to_Elastic_IP_8080 EC2->>RDS: bookings EC2->>DynamoDB: hold_or_queue EC2-->>App: JSON
NamesCapitals allowed: DBP-Vpc, DBP-Api-Sg, DBP-Holds. Forbidden: dbp-postgres, dbp-api, dbp-kyc-….
Bill while codingAbout $25–35/month if EC2 + RDS stay up. No $73 Kubernetes tax.
Elastic IPRequired so Stop then Start does not change the API Gateway URL.
EC2 SGPort 8080 is reachable; Cognito and X-Jobs-Secret are the real locks in Phase 1.

13 — Phase 2 production: CloudFront → API Gateway → EKS

Phase 2 Same door, same API contract. Only the hop after API Gateway changes: VPC Link → NLB → pod. This is the earlier production proposal.

flowchart TB Apps[Apps] CF[CloudFront] GW[HTTP_API_Gateway] Cog[Cognito] NLB[NLB] EKSCP[EKS_control_plane] Pod[API_pod_same_image] RDS[RDS] DDB[DynamoDB] S3[S3] EB[EventBridge] SNS[SNS] Apps --> CF CF --> GW GW --> Cog GW -->|VPC_Link| NLB --> Pod EKSCP -.-> Pod Pod --> RDS Pod --> DDB Pod --> S3 EB --> SNS --> GW
sequenceDiagram participant App participant CloudFront participant APIGateway participant Cognito participant NLB participant Pod participant RDS participant DynamoDB App->>CloudFront: HTTPS CloudFront->>APIGateway: origin APIGateway->>Cognito: verify_JWT Cognito-->>APIGateway: ok APIGateway->>NLB: VPC_Link NLB->>Pod: HTTP Pod->>RDS: bookings Pod->>DynamoDB: hold_or_queue Pod-->>App: JSON
EKS control planeAWS-managed Kubernetes brain. ~$73/month. Not on the request path. Worth it only when you want production scheduling and more than one node.
EC2 from Phase 1Turned off and destroyed after the flip. Do not run both origins for long.

14 — Smooth cutover (how the code stays aligned)

Both phases Infra is different. The process is not. If you follow this, Phase 2 is a module apply + one API Gateway edit.

flowchart LR Code[API_source] Docker[Dockerfile] ECR[ECR_dbp_api] Env[Env_vars_only] IAM[Same_IAM_policy] P1[EC2_instance_role] P2[EKS_IRSA] Code --> Docker --> ECR Code --> Env IAM --> P1 IAM --> P2 ECR --> P1 ECR --> P2
RuleWhy it makes Phase 2 easy
One image, listen on PORTdocker run and a Kubernetes container are the same binary.
No Kubernetes API in codeNothing to rip out later.
DATABASE_URL, table names, bucket, Cognito IDs from envPhase 2 keeps the same RDS / DDB / S3 / user pool.
AWS SDK default credential chainEC2 instance profile now, IRSA later, same policy JSON.
IaC: shared + compute-ec2 XOR compute-eksOnly the API Gateway integration URI is the switch.
SNS always calls /api/internal/jobsClocks never point at an EC2 IP or a pod IP.
Cutover dayApply compute-eks → deploy the same image tag → flip API Gateway to VPC Link → health check → stop EC2 → destroy compute-ec2.
Do notPut Postgres only on the EC2 disk. Use RDS from day one or you will migrate data by hand.

15 — Start / Stop / Destroy

Phase 1 Three operations on tagged resources Project=dbp. Use these instead of clicking around the console.

flowchart TD Start[START] Stop[STOP] Destroy[DESTROY] Start --> EC2up[Start_EC2] Start --> RDSup[Start_RDS] Start --> EBon[Enable_EventBridge_group] Start --> Health[GET_internal_health] Stop --> EBoff[Disable_EventBridge_group] Stop --> EC2down[Stop_EC2] Stop --> RDSdown[Stop_RDS] Stop --> Keep[Keep_CF_APIGW_Cognito_DDB_S3] Destroy --> Snap[Optional_RDS_snapshot] Destroy --> TF[terraform_destroy] Destroy --> Zero[Bill_goes_to_zero]
CommandWhat happensWhat you still pay
make startEC2 + RDS start. Elastic IP still attached. Clocks enabled. API Gateway URL unchanged.Instance hours again.
make stopClocks disabled first (so SNS does not retry a dead box). EC2 stopped. RDS stopped.EIP (~$3.6) + RDS storage (~$2) + pennies. No EKS bill.
make destroyEmpty S3 if needed, then destroy the stack. Cognito users and DDB items go away.$0
RDS stop limitAWS auto-starts a stopped RDS instance after 7 days. Re-run stop, or destroy if you are away longer.
Phase 2 equivalentsScale the node to 0 (control plane still ~$73) or delete the cluster to truly stop paying for EKS.

16 — Login / OTP: Cognito consumption

Both phases Cognito holds the user. Tiny Lambdas send the OTP through MSG91 or WhatsApp. DynamoDB counts attempts. RDS gets a PatientProfile only after the first successful verify.

flowchart TD App[App_sends_phone] CF[CloudFront] GW[API_Gateway] Cog[Cognito_custom_auth] L[CreateAuthChallenge_Lambda] Vendor[MSG91_or_WhatsApp] Lock[DynamoDB_OtpLock] Verify[VerifyAuthChallenge_Lambda] JWT[Cognito_issues_JWT] Pod[API_GET_patient_me] RDS[RDS_PatientProfile] App --> CF --> GW --> Cog Cog --> L --> Vendor L --> Lock App --> Verify Verify --> Lock Verify --> JWT JWT --> Pod --> RDS
ConsumedCognito DBP-Users, 2–3 Lambdas, DynamoDB DBP-Otp-Lock, then DBP-Api (EC2 or EKS) + RDS for /api/patient/me or /api/provider/memberships.
Not consumedCognito SMS, SNS SMS, ElastiCache.

17 — Hold → pay → token: what each service stores

Both phases A seat lives in DynamoDB for 8 minutes. The exact expiry is an EventBridge one-time schedule, not DynamoDB TTL. The booking and token number live in RDS only after payment capture.

flowchart TD Search[GET_patient_search] Hold[POST_patient_holds] Pay[Payment_app] WH[POST_webhooks_payments] Book[POST_patient_bookings] Exp[EventBridge_8_min] Search --> RDS Hold --> DDBHold[DynamoDB_Holds] Hold --> DDBIdem[DynamoDB_Idempotency] Hold --> Exp Exp -->|SNS_expire| Release[API_release_seat] Pay --> Gateway[Razorpay_or_Cashfree] Gateway --> CF2[CloudFront_webhook] CF2 --> WH WH --> Book Book --> DDBHold Book --> RDSBook[RDS_Booking_and_Token]
Failed paymentDoes not consume a release. Hold row stays. Patient retries inside the same 8 minutes.
CaptureRDS write is the only moment a token number exists.
DynamoDB TTLMay delete the hold row later as garbage. Must not be the product clock.

18 — Live queue: CloudFront cache vs DynamoDB

Both phases High-volume path. Patients poll every ~10s. Most polls should never reach EC2/EKS or RDS.

flowchart TD Poll[GET_patient_sessions_queue] CF[CloudFront] Hit[Cache_HIT_2_to_3s] Miss[Cache_MISS] GW[API_Gateway] Pod[API_process] View[DynamoDB_QueueView] Desk[Front_desk_call_next] Poll --> CF CF --> Hit Hit --> Poll CF --> Miss Miss --> GW --> Pod --> View View --> CF Desk --> Pod Pod -->|overwrite_snapshot| View Pod -->|next_poll_sees_new_value| CF
Cache hitConsumes CloudFront only. This is how 50k bookings/day stay cheap.
Cache missConsumes API Gateway + EC2 or EKS + DynamoDB. Still does not hit RDS for now_serving.
Desk writecall-next / skip / check-in always hits the API process + DynamoDB + RDS token state. CloudFront cache key ignores Authorization.

19 — Background clocks: EventBridge + SNS

Both phases No extra worker. AWS wakes the same API via CloudFront. SNS retries if the process returns 5xx.

flowchart LR subgraph create [API_creates_a_clock] Hold[Hold_created] Unavail[Doctor_unavailable] Night[Nightly_rule] end subgraph awsClock [AWS_clock] EB[EventBridge_Scheduler] SNS[SNS_topic] end subgraph consume [Same_API_process] Jobs["POST /api/internal/jobs"] RDS2[RDS] DDB2[DynamoDB] Push[FCM_or_SMS_vendor] end Hold --> EB Unavail --> EB Night --> EB EB --> SNS SNS --> Jobs Jobs --> RDS2 Jobs --> DDB2 Jobs --> Push
ClockWhen EventBridge firesSNS topicWhat the API consumes next
Hold expiryExactly at expires_atDBP-HoldsDynamoDB DBP-Holds delete; RDS unchanged if unpaid
Clinic 6hT+6h if no proposalDBP-ReassignRDS refunds; notify topic
Patient 4hT+4h per offerDBP-ReassignRDS refund or leave moved booking
MaterialiseNightlyDBP-OpsRDS insert sessions
NotifyImmediate publishDBP-NotifyOutbound FCM / WhatsApp from the public EC2 or EKS node

20 — Files and payment webhooks

Both phases KYC bytes go straight to S3. The API only mints a signed URL. Payments never trust the app — the gateway calls CloudFront.

flowchart TD subgraph kyc [Verification_docs] App1[Provider_app] Pod1[API_presign] S3[S3_dbp_dev_docs] App1 -->|GET_signed_PUT| Pod1 Pod1 --> S3 App1 -->|PUT_file_direct| S3 Admin[Admin_reviews] -->|GET_signed| S3 end subgraph pay [Payment] App2[Patient_checkout] Razor[Payment_gateway] CF3[CloudFront_no_cache] GW3[API_Gateway_no_Cognito] Pod2[API_capture] RDS3[RDS_token] App2 --> Razor Razor --> CF3 --> GW3 --> Pod2 --> RDS3 end
S3SSE-S3 only. Objects stay in Standard. No Glacier transition. No KMS key.
WebhookPOST /api/webhooks/payments. CloudFront cache off. Signature verified in the API. Then the same hold → booking path.
Why compute is public in Phase 1No NAT. EC2 (later the EKS node) must reach the payment gateway, FCM, SMS vendor, and ECR by itself.

21 — Bill by phase

Mumbai, light personal use. Phase 1 is what you run now. Phase 2 is only when production needs Kubernetes.

ResourcePhase 1 codingPhase 1 stoppedPhase 1 destroyedPhase 2 prod-small
EKS control planeNot created~ $73 always on
NLBNot created~ $16
Compute1 × t4g.small ~ $12$0 (EC2 stopped)$0Node(s) ~ $30
Elastic IPFree while EC2 is running~ $3.6 if kept$0Not needed
RDS t4g.micro~ $12~ $2 storage$0~ $12+ (Multi-AZ later)
CloudFront + APIGW + Cognito + DDB + S3 + clocksPenniesPennies$0Grows with traffic
NAT / WAF / Redis / KMSNot createdOptional later
Total (order of magnitude)~$25–35~$8–12$0~$110–140
Personal developerStay on Phase 1. Use Stop after hours. Use Destroy if you will not touch it for weeks.
Why Phase 2 costs moreEKS control plane + NLB. That is the production tax, not a developing tax.
Regionap-south-1 only. Health and KYC stay in India.