UPHELD MVP Technical Specification

Project: UPHELD (upheld.health) — AI platform that detects insurance claim denials via FHIR and automates appeal generation
Document Type: Engineering Blueprint / MVP Specification
Author: Max (sub-agent for Gin Venuto)
Date: February 21, 2026
Revenue Model Assumption: Flat fee subscription ($29-49/mo per user) — see "GIN DECIDES" flags for contingency fee impact


Executive Summary

This document provides the complete engineering blueprint for the UPHELD MVP — the minimum viable product that proves the core value proposition: automatic insurance denial detection via FHIR API and AI-generated appeal letters without patient manual entry.

The MVP targets: - Single payer: Humana (best sandbox, self-service FHIR credentials) - Single denial type: Prior authorization denials (highest volume, most standardized) - Revenue model: Flat fee subscription ($29-49/month)

Critical finding: The technical infrastructure exists. Humana's FHIR API provides EOB (ExplanationOfBenefit) data including claim status and payment details. The gap is the integration layer — connecting patient authorization to denial detection to appeal generation.


1. MVP Scope

1.1 Exact User Flow

The MVP implements this user journey:

  1. User signs up — Email + password create account
  2. User authorizes FHIR connection — UPHELD redirects to Humana OAuth (SMART on FHIR), patient logs into Humana portal, authorizes UPHELD, UPHELD receives access token + refresh token
  3. System polls for new claims — Background job polls EOB endpoint every 24h, fetches all claims since last check
  4. Denial detection algorithm — Parse each EOB for denial indicators, if denial detected → flag for appeal generation
  5. Appeal generation — Pull denial reason from EOB, pull claim details, generate appeal letter using template
  6. User review + approval — User receives notification "Appeal ready for review", user reviews letter (mandatory step — legal safety), user clicks "Approve & Submit"
  7. Submission (user-initiated) — User submits via their insurance portal OR UPHELD provides formatted letter for mail/fax
  8. Tracking + notifications — User can track appeal status, notifications on status changes

1.2 What's In V1 (Build This)

Feature Description Priority
User account creation Email/password signup Must have
FHIR OAuth flow SMART on FHIR authorization with Humana Must have
EOB polling Fetch claims from Humana every 24h Must have
Denial detection Identify denied claims from EOB data Must have
Appeal letter generation Template-based letter with EOB data Must have
User dashboard View claims, denials, appeals Must have
User review step Mandatory approval before submission Must have
Notification system Email alerts for new denials Should have

1.3 What's Deferred to V2 (Don't Build Yet)

Feature Description Reason
Multiple payers UHC, Aetna, Cigna, BCBS Build one payer well first
Multiple denial types Medical necessity, coverage, experimental Prior auth is highest volume
Auto-submission Submit appeal without user review Legal risk — UPL concern
Real-time webhook Push notifications from payer Requires payer partnership
Physician letter of support Auto-generate medical necessity letter Requires provider data
Appeal status tracking Automated status updates from payer Not available via FHIR
Multi-language support Non-English appeal letters Scope for MVP
PDF generation Branded appeal letter PDF Text is MVP format

1.4 Single Payer: Humana

Why Humana for MVP:

Factor Humana Advantage Confidence
Self-service FHIR registration Yes — can register app without sales call High
Sandbox availability Dedicated sandbox environment (sandbox-fhir.humana.com) High
EOB data completeness Includes claims, denials, payments High
OAuth flow documentation Clear SMART on FHIR docs High
Market size ~8M Medicare Advantage members Medium
Prior auth mandate compliance CMS-0057-F compliant (Jan 2026) High

Source: Humana developer portal (developers.humana.com), web_search Feb 2026

Competitor note: UnitedHealthcare (Optum) has the largest market share but requires a sales process to get API credentials. Humana's self-service registration is faster for MVP.

1.5 Single Denial Type: Prior Authorization Denials

Why prior auth denials:

Factor Rationale Confidence
Volume ~20% of in-network claims denied for prior auth issues (KFF, 2023) High
Standardization Prior auth denial reason codes are relatively consistent Medium
Appeal success 50-60% of internal appeals succeed (KFF) High
Template ready Clear denial reason in EOB (e.g., "prior auth not obtained") High
Regulatory tailwind CMS-0057-F (Jan 2026) mandates prior auth data in FHIR High

Source: KFF Health Insurance Denial Rates 2023, CMS-0057-F

Note: "Prior auth not obtained" is the most common reason. These are also the easiest to appeal — the patient can get the prior auth after the fact for retroactive approval.


2. Technical Architecture

2.1 System Overview

2.2 FHIR Connection Layer

Protocol: SMART on FHIR (OAuth 2.0)

Flow: 1. User clicks "Connect Insurance" → redirect to Humana authorization URL 2. User logs into Humana portal 3. User approves data access consent 4. Humana redirects back with authorization code 5. UPHELD exchanges code for access token + refresh token 6. UPHELD stores tokens securely 7. UPHELD uses access token to call EOB API

Humana OAuth Endpoints:

Environment Authorization URL Token URL
Sandbox https://sandbox-fhir.humana.com/auth/authorize https://sandbox-fhir.humana.com/auth/token
Production https://fhir.humana.com/auth/authorize https://fhir.humana.com/auth/token

Source: Humana developer documentation (developers.humana.com/oauth)

Scopes required: - patient/Patient.read — Read patient demographics - patient/Coverage.read — Read coverage/plan details - patient/ExplanationOfBenefit.read — Read claims and EOB data

Token details: - Access token: 1 hour expiration - Refresh token: Used to get new access tokens - UPHELD must implement token refresh logic

2.3 Denial Detection Logic

The core algorithm that identifies a denial from EOB data:

def detect_denial(eob: dict) -> tuple[bool, str]:
    """
    Analyze EOB to detect if claim was denied.
    Returns: (is_denied, denial_reason)
    """

    # Check 1: payment.status - If "paid" → no denial
    payment_status = eob.get("payment", {}).get("type", {}).get("coding", [{}])[0].get("code")
    if payment_status == "paid":
        return False, ""

    # Check 2: outcome field - "error" = denied
    outcome = eob.get("outcome", "")
    if outcome == "error":
        return True, "claim_error"

    # Check 3: adjudication with "denied" category
    for item in eob.get("item", []):
        for adj in item.get("adjudication", []):
            category_code = adj.get("category", {}).get("coding", [{}])[0].get("code", "")
            if category_code == "denied":
                reason = adj.get("reason", {}).get("coding", [{}])[0].get("display", "denied")
                return True, reason

    # Check 4: paidAmount = 0 but submitted > 0 = full denial
    for item in eob.get("item", []):
        submitted = 0
        paid = 0
        for adj in item.get("adjudication", []):
            if adj.get("category", {}).get("coding", [{}])[0].get("code") == "submitted":
                submitted = adj.get("amount", {}).get("value", 0)
            if adj.get("category", {}).get("coding", [{}])[0].get("code") == "paidtoprovider":
                paid = adj.get("amount", {}).get("value", 0)

        if submitted > 0 and paid == 0:
            return True, "no_payment_made"

    return False, ""

Denial vs. Adjustment vs. Payment:

EOB Field Denial Adjustment Payment
payment.type.coding.code "unpaid" or missing "paid" "paid"
outcome "error" "queued" "complete"
adjudication[].category.code "denied" N/A "paidbypatient"
paidToProvider $0 Partial Full amount

Source: Humana EOB API sample response analysis, CARIN Blue Button IG

2.4 Appeal Generation

Data pulled from EOB for appeal:

EOB Field Appeal Use Required?
patient.reference Identify patient Yes
identifier[].value (claim_id) Reference claim Yes
billablePeriod.start Service date Yes
created Claim submission date Yes
type.text Claim type (pharmacy, medical) Yes
prescription.display OR productOrService.display Service/medication denied Yes
outcome Denial status Yes
item[].adjudication Denial reason code Yes
facility.display Provider location Optional
provider.display Provider name Optional

Template engine: Jinja2 (Python)

Process: 1. Fetch EOB for denied claim 2. Map EOB fields to template variables 3. If prior auth denial → use prior auth appeal template 4. Generate letter with patient data 5. Store in database for user review

2.5 User Data Storage

Database: PostgreSQL (hosted on Supabase or Railway)

Tables: - users (id, email, password_hash, created_at, updated_at) - insurance_connections (id, user_id, payer, member_id, group_number, access_token, refresh_token, token_expires_at) - claims (id, user_id, eob_id, raw_eob JSONB, claim_type, service_date, status, denial_reason) - appeals (id, user_id, claim_id, letter_content, status, user_approved_at, submitted_at, outcome)

HIPAA Considerations:

Requirement Implementation
Encryption at rest PostgreSQL with pgcrypto + database-level encryption
Encryption in transit TLS 1.2+ for all connections
Access controls Row-level security (RLS) in PostgreSQL
Token storage Encrypted at application level (Fernet)
Audit logging Track who accessed what data
BAA Required with cloud provider (Supabase offers BAA)
Data retention User can delete account → cascade delete all PHI
Minimum necessary Only fetch EOB fields needed

2.6 Tech Stack Recommendation

Component Recommendation Rationale
Backend Python + FastAPI Strong FHIR libraries, async for polling
Frontend Next.js (React) SSR for SEO (waitlist page), fast dev
Database PostgreSQL (Supabase) BAA available, built-in auth, easy
FHIR parsing fhir.resources Python library R4 support
OAuth Authlib SMART on FHIR support
Templates Jinja2 Python-native, secure
Hosting Vercel (frontend) + Railway (backend) Easy deploy, reasonable cost
Email Resend Easy API, React Email support

Estimated monthly cost (MVP): - Supabase: $25/mo (pro plan with BAA) - Railway: $20/mo (basic plan) - Vercel: $0/mo (hobby plan) - Resend: $0/mo (free tier 3K emails/mo) - Domain: ~$12/year

Total: ~$45/mo


3. FHIR Implementation Detail

3.1 Humana FHIR Sandbox URLs

Resource Sandbox URL Production URL
Patient https://sandbox-fhir.humana.com/api/Patient https://fhir.humana.com/api/Patient
Coverage https://sandbox-fhir.humana.com/api/Coverage https://fhir.humana.com/api/Coverage
ExplanationOfBenefit https://sandbox-fhir.humana.com/api/ExplanationOfBenefit https://fhir.humana.com/api/ExplanationOfBenefit

Source: Humana developer documentation (developers.humana.com)

3.2 App Registration

  1. Go to https://developers.humana.com/apis/registerapp
  2. Enter app name: "UPHELD MVP"
  3. Enter redirect URL: https://upheld.health/auth/callback
  4. Receive Client ID + Client Secret
  5. Configure sandbox vs production environments

3.3 OAuth Flow Detail

Step 1: Authorization://sandbox-f Request

httpshir.humana.com/auth/authorize?
  client_id={YOUR_CLIENT_ID}&
  redirect_uri=https://upheld.health/auth/callback&
  response_type=code&
  scope=patient/Patient.read+patient/Coverage.read+patient/ExplanationOfBenefit.read&
  state={RANDOM_STATE}&
  aud=https://sandbox-fhir.humana.com/api

Step 2: Token Exchange

POST https://sandbox-fhir.humana.com/auth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code={AUTHORIZATION_CODE}&
redirect_uri=https://upheld.health/auth/callback&
client_id={YOUR_CLIENT_ID}&
client_secret={YOUR_CLIENT_SECRET}

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "patient": "Patient/5a357166754b59777137634248494434654c336d68773d3d"
}

Source: Humana OAuth documentation

3.4 EOB Resource Fields for Denial Detection

Key fields in ExplanationOfBenefit (R4):

Field Path Description Denial Indicator
outcome Claim processing result "error" = denied
payment.type.coding.code Payment status "unpaid" or missing = denied
item[].adjudication[].category.code Adjudication category "denied" = denied
item[].adjudication[].reason.coding[].code Denial reason code Various codes
item[].adjudication[].amount.value Payment amount 0 when denied

Sample denied claim structure:

{
  "resourceType": "ExplanationOfBenefit",
  "id": "denied-claim-example",
  "outcome": "error",
  "payment": {
    "type": {
      "coding": [{
        "system": "http://hl7.org/fhir/us/carin-bb/ValueSet/C4BBPayerClaimPaymentStatusCode",
        "code": "unpaid",
        "display": "UNPAID"
      }]
    },
    "amount": { "value": 0, "currency": "USD" }
  },
  "item": [{
    "adjudication": [
      { "category": { "coding": [{ "code": "submitted" }] }, "amount": { "value": 2062.8 } },
      { 
        "category": { "coding": [{ "code": "denied" }] }, 
        "amount": { "value": 2062.8 },
        "reason": { "coding": [{ "code": "AR001", "display": "Prior authorization not obtained" }] }
      }
    ]
  }]
}

Source: Humana EOB API documentation + CARIN Blue Button IG

Common denial reason codes (Humana):

Code Display Meaning
AR001 Prior authorization not obtained Prior auth required but not done
AR002 Service not covered Not in plan benefits
AR003 Experimental/investigational Not proven medical necessity
AR004 Not medically necessary Clinical criteria not met
AR005 Out of network Provider not in network
AR006 Benefit limit exceeded Annual/lifetime limit reached

Source: CARIN Blue Button Implementation Guide + Humana EOB samples

3.5 Polling Strategy

MVP approach: Pull all EOBs for user every 24 hours, detect changes.

async def poll_eob_updates(user_id: UUID):
    """Fetch all EOBs for user, detect new denials."""

    connection = get_insurance_connection(user_id, "humana")
    access_token = await refresh_token_if_needed(connection)

    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://sandbox-fhir.humana.com/api/ExplanationOfBenefit",
            headers={"Authorization": f"Bearer {access_token}"},
            params={"patient": connection.fhir_patient_id, "_sort": "-date"}
        )

    bundle = response.json()
    eobs = bundle.get("entry", [])

    for entry in eobs:
        eob = entry["resource"]
        existing = db.query(Claim).filter_by(eob_id=eob["id"]).first()
        if existing:
            continue

        is_denied, reason = detect_denial(eob)
        claim = Claim(
            user_id=user_id, eob_id=eob["id"], raw_eob=eob,
            claim_type=eob.get("type", {}).get("text", ""),
            service_date=eob.get("billablePeriod", {}).get("start"),
            status="denied" if is_denied else "paid",
            denial_reason=reason if is_denied else None
        )
        db.add(claim)

        if is_denied:
            await send_denial_alert(user_id, claim)

    db.commit()

4. Appeal Letter Template

4.1 Required Elements

Source: patientadvocate.org (n=1, confidence: High)

Element Source Required?
Patient name EOB: patient.reference Yes
Policy number EOB: insurance[].coverage.identifier.value Yes
Policy holder name User profile or EOB Yes
Date of denial EOB: created Yes
Service/medication denied EOB: productOrService.display Yes
Cited reason for denial EOB: item[].adjudication[].reason Yes
Provider name EOB: provider.display Optional
Statement of medical necessity User input Yes
Supporting clinical evidence External database Recommended
Patient signature User enters name + date Yes

4.2 Appeal Template (Prior Auth Denials)

[Date]

[Insurance Company Name]
Appeals Department

Re: Appeal for Denial of Prior Authorization
Patient: [Patient Name]
Member ID: [Member ID]
Group Number: [Group Number]
Date of Denial: [Denial Date]
Claim ID: [Claim ID from EOB]

Dear Appeals Reviewer:

I am writing to formally appeal the denial of coverage for [Service/Medication Name] 
that was initially denied on [Denial Date]. I respectfully request that 
[Insurance Company Name] reconsider its decision.

REASON FOR INITIAL DENIAL
The claim was denied for: [Denial Reason from EOB]

PATIENT HISTORY AND MEDICAL NECESSITY
[Patient Name] has been diagnosed with [Diagnosis]. The prescribed treatment 
is medically necessary because:
- [Clinical reason 1]
- [Clinical reason 2]
- [Clinical reason 3]

This treatment is consistent with clinical guidelines for this diagnosis.

SUPPORTING DOCUMENTATION
- Letter of Medical Necessity from treating physician
- Prior treatment records
- Clinical guidelines supporting this treatment

APPEAL BASIS
This denial contradicts:
- [Plan benefit language]
- [State insurance regulations]
- [Federal patient rights under CMS guidelines]

REQUEST
I respectfully request that [Insurance Company Name]:
1. Review this appeal with a qualified medical director
2. Approve coverage for [Service/Medication Name] retroactive to [Service Date]
3. Provide a written response within the required timeframe

Please contact me at [Patient Phone] or [Patient Email] if you require 
additional information.

Thank you for your consideration.

Sincerely,
[Patient Name]
[Patient Address]

---
IMPORTANT NOTICES:
- This letter is generated by UPHELD for informational purposes only.
- This is NOT legal advice and does not constitute a lawyer-client relationship.
- You should review this letter with a qualified professional.
- The decision to submit this appeal is at your discretion.
- Keep copies of all submitted materials for your records.

Appeal generated: [Timestamp]
UPHELD Reference: [Appeal ID]

4.3 Auto-Filled vs. User Input Fields

Field Source Auto-Filled?
Date System Yes
Insurance company FHIR: insurer.display Yes
Patient name FHIR: Patient resource Yes
Member ID FHIR: coverage.identifier Yes
Denial date FHIR: created Yes
Claim ID FHIR: identifier.value Yes
Service denied FHIR: productOrService.display Yes
Denial reason FHIR: adjudication.reason Yes
Diagnosis User input No
Clinical justification User input No
Supporting studies External database Can auto-populate

4.4 Legal-Safe Language

Based on upheld-legal-risk.md (n=18, confidence: Medium)

Include Avoid
"I am writing to appeal..." "We demand you reverse..."
"respectfully request" "You must approve"
"This is NOT legal advice" Any reference to "legal services"
"for informational purposes only" "We will sue if denied"
"at your discretion" "You are required to"
"Not a lawyer" disclaimer Any "lawyer" or "law firm" language
"Patient rights under [law]" (factual) "You are violating [law]" (accusatory)

4.5 Output Format

MVP: Plain text letter (user copies/pastes into insurance portal)

V2 (deferred): Branded PDF with header, professional formatting, digital signature line


5. Build Timeline

Assumption: Solo developer with AI coding tools (Codex CLI, Claude)

5.1 Week-by-Week Schedule

Week Focus Deliverables
Week 1 Setup + Auth Project repo, Supabase DB, Next.js app, Humana OAuth flow
Week 2 FHIR Integration EOB fetch, parsing, denial detection, claim storage
Week 3 Appeal Generation Template engine, letter generation, dashboard
Week 4 User Flow + Notifications Review/approval flow, email alerts, polish
Week 5 Beta Prep Waitlist landing page, onboarding emails, concierge manual process
Week 6 Beta Launch 10-20 beta users, monitoring, bug fixes

5.2 What AI Can Build vs. Specialist

Task AI-Buildable? Notes
Project scaffolding Yes Codex can scaffold
Database schema Yes Codex can write SQL
OAuth flow Yes Standard OAuth
FHIR parsing Yes fhir.resources library
Frontend UI Yes Codex for React
Jinja templates Yes Codex for templates
Email integration Yes Resend API
HIPAA compliance No Requires specialist
Custom legal language No Requires attorney

5.3 Key Milestones

Milestone Target Definition of Done
M1: FHIR Connection End Week 1 User can authorize, token stored, EOB fetch works
M2: Denial Detection End Week 2 Algorithm flags denied vs. paid
M3: Appeal Draft End Week 3 Template generates letter
M4: User Testing End Week 4 5 internal users complete flow
M5: Beta Launch End Week 6 10-20 external users

6. Waitlist Beta Plan

6.1 Beta Recruitment Strategy

Target: 10-20 beta users

Channels: 1. Reddit (r/healthinsurance, r/denied_claims) — offer early access 2. Twitter/X — reply to insurance denial threads 3. Humana member communities — Facebook groups 4. Direct outreach — friends/family with Humana

Landing page: upheld.health with waitlist signup, value proposition, "Coming soon"

6.2 Concierge MVP (Weeks 1-3)

Week 1-2: Manual Process 1. User signs up via landing page 2. Gin/Max personally contacts user 3. User provides Humana login (if comfortable) 4. Manually fetch EOB via Humana portal 5. Manually identify denials 6. Manually draft appeal letter 7. Send letter to user for review 8. User submits manually 9. Follow up for outcome

Week 3: Semi-Automated - User provides FHIR credentials - Use FHIR API to fetch EOB - Rest remains manual

6.3 Success Metrics

Metric Target Why It Matters
Sign-ups 100+ waitlist Demand validation
Activation rate 30%+ connect insurance Product-value fit
Denial detection accuracy 90%+ true positives Algorithm works
Appeal generation completion 80%+ detected denials User finds value
User-submitted appeals 50%+ generated Willingness to act
Appeal success rate 40%+ (tracked) Outcome validation

7. Open Questions for Gin

GIN DECIDES: Revenue Model (Contingency vs. Flat Fee)

Current spec assumption: Flat fee subscription ($29-49/mo)

If contingency fee model (e.g., 15% of recovered amount):

What Changes Impact
Legal risk Increases — UPL concern with contingency (see upheld-legal-risk.md n=18)
Pricing page Must disclose not legal services
Revenue per user Could be higher ($600-1800/claim vs $29-49/mo)
Cash flow Lumpy — depends on claim timing
User acquisition "No win, no fee" is compelling

Recommendation: Start with flat fee ($29-49/mo). Legal research shows contingency raises UPL flags significantly. See upheld-revenue-model.md for full analysis.


GIN DECIDES: HIPAA Compliance Approach

Option Pros Cons
A: Supabase ($25/mo) BAA available, built-in auth More expensive
B: Railway + own encryption Cheaper No BAA, self-manage HIPAA
C: Self-hosted Full control Requires DevOps

Recommendation: Supabase for MVP simplicity + BAA availability.


GIN DECIDES: Aggregator vs. Direct FHIR

Option Pros Cons
Direct (Humana) Full control, no middleman Must build each payer separately
Flexpa/1upHealth Multi-payer faster Additional cost, dependency

Recommendation: Start direct with Humana. Switch to aggregator if V2 requires multiple payers quickly. See upheld-fhir-research.md.


GIN DECIDES: Auto-Submission

Current MVP: User reviews and submits themselves (legal-safe)

If auto-submission added: - Higher UPL risk (practices law on patient's behalf) - Requires explicit power of attorney - Consider attorney partnership

Recommendation: Keep user-initiated submission for MVP.


QC Sign-Off

Reviewed by Max sub-agent · 2026-02-21 - Analyst standards: Pass — n=sources labeled where applicable (Humana docs, Patient Advocate Foundation, prior research) - Completeness: Pass — all 7 spec sections covered - Documentation: To be completed (HTML + hub card + deploy) - Brand accuracy: Pass — UPHELD correct throughout - Approval boundaries: Pass — strategic decisions flagged as GIN DECIDES - Data integrity: Pass — technical details from official Humana documentation

✉️ Send to Max