← All Articles
April 28, 2026 9 min read AI · Healthcare · Compliance

AI for Healthcare Admin: Automating Prior Authorizations and Intake Forms

Prior authorizations stall care, frustrate doctors, and bury staff in paperwork. AI can’t fix insurance regulation, but it can eliminate the administrative friction — extracting structured data from forms, validating fields, checking coverage rules, and routing decisions to the right team. Here’s how to build it inside HIPAA constraints.

The problem: manual prior authorization processing

The current workflow looks like this:

  1. Patient needs a procedure.
  2. Doctor’s office calls insurance or fills a form.
  3. Insurance manually reviews the request.
  4. It takes 7–14 days to get an approval (or denial).
  5. Patient can’t schedule; the procedure is delayed.

Where time is wasted on the provider side:

Total: 35 minutes per request × hundreds of requests per week = massive overhead.

The automation: an AI document pipeline

The shape of the workflow:

Prior Auth Form (PDF or web form) ↓ Document Upload (encrypted S3) ↓ Claude: Extract structured data ↓ Validation Layer: Check required fields ↓ Plan Lookup: Query coverage rules (DynamoDB) ↓ Decision Logic: Auto-approve if routine, flag if complex ↓ Route to appropriate team ↓ Patient/Doctor notification

Step 1: Encrypted document storage

All patient data is PHI (Protected Health Information). It must be encrypted at rest and in transit.

resource "aws_s3_bucket" "prior_auth_forms" { bucket = "healthcare-prior-auth-forms-prod" tags = { HIPAA = "true" PHI = "true" } } # Enforce encryption at rest resource "aws_s3_bucket_server_side_encryption_configuration" "prior_auth_forms" { bucket = aws_s3_bucket.prior_auth_forms.id rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" kms_master_key_id = aws_kms_key.prior_auth.arn } bucket_key_enabled = true } } # Block public access resource "aws_s3_bucket_public_access_block" "prior_auth_forms" { bucket = aws_s3_bucket.prior_auth_forms.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } # Enable versioning for audit trail resource "aws_s3_bucket_versioning" "prior_auth_forms" { bucket = aws_s3_bucket.prior_auth_forms.id versioning_configuration { status = "Enabled" } }

Every document is encrypted, versioned, and auditable.

Step 2: Extract structured data with Claude

Use Claude to parse forms and extract the fields you need:

import base64 import boto3 import json from anthropic import Anthropic from datetime import datetime s3 = boto3.client('s3') dynamodb = boto3.resource('dynamodb') client = Anthropic() def extract_prior_auth_data(bucket: str, key: str): """Extract structured data from a prior auth form. Logs every step for audit.""" audit_table = dynamodb.Table('prior_auth_audit_log') request_id = f"{datetime.now().isoformat()}-{key}" audit_table.put_item(Item={ 'request_id': request_id, 'timestamp': datetime.now().isoformat(), 'document_key': key, 'action': 'extraction_started', 'status': 'in_progress', }) try: doc = s3.get_object(Bucket=bucket, Key=key) content = doc['Body'].read() prompt = """Extract the following from this prior authorization form: - Patient name, ID, DOB - Insurance plan - Procedure code (CPT) and description - Diagnosis codes (ICD-10) - Requesting provider - Date of service requested - Medical necessity justification - Current medications (if applicable) Return as JSON with these exact field names. If a field is not present, return null.""" response = client.messages.create( model='claude-opus-4-1-20250805', max_tokens=1500, messages=[{ 'role': 'user', 'content': [ {'type': 'text', 'text': prompt}, {'type': 'document', 'source': { 'type': 'base64', 'media_type': 'application/pdf', 'data': base64.b64encode(content).decode(), }}, ], }], ) extracted = json.loads(response.content[0].text) audit_table.put_item(Item={ 'request_id': request_id, 'timestamp': datetime.now().isoformat(), 'document_key': key, 'action': 'extraction_completed', 'status': 'success', 'fields_extracted': list(extracted.keys()), }) return extracted except Exception as e: audit_table.put_item(Item={ 'request_id': request_id, 'timestamp': datetime.now().isoformat(), 'document_key': key, 'action': 'extraction_failed', 'status': 'error', 'error': str(e), }) raise

Every extraction is logged for compliance audits. If there’s ever a question about how a decision was made, the audit log shows what data was extracted and when.

Step 3: Validation layer

Check that all required fields are present and correctly formatted:

def validate_prior_auth(data: dict) -> dict: """Returns {valid: bool, errors: [...], warnings: [...]}.""" errors, warnings = [], [] required = ['patient_id', 'insurance_plan', 'procedure_code', 'diagnosis_codes'] for field in required: if not data.get(field): errors.append(f"Missing required field: {field}") if data.get('patient_id') and len(str(data['patient_id'])) < 5: errors.append("Patient ID appears invalid") if data.get('procedure_code') and not is_valid_cpt_code(data['procedure_code']): errors.append("Invalid CPT code") if data.get('diagnosis_codes'): for code in data['diagnosis_codes']: if not is_valid_icd10(code): errors.append(f"Invalid ICD-10 code: {code}") if data.get('medical_necessity') is None: warnings.append("No medical necessity justification provided") if data.get('date_of_service') and is_emergency_timeframe(data['date_of_service']): warnings.append("Urgent/emergency request - expedited review needed") return { 'valid': len(errors) == 0, 'errors': errors, 'warnings': warnings, }

Invalid requests are flagged. Warnings are noted but don’t block processing.

Step 4: Coverage rules and auto-decision

For routine procedures with standard coverage, make automatic decisions; for everything else, route for review:

def check_coverage(data: dict) -> dict: coverage_table = dynamodb.Table('insurance_coverage_rules') plan = data.get('insurance_plan') procedure = data.get('procedure_code') try: response = coverage_table.get_item(Key={ 'plan_id': plan, 'procedure_code': procedure, }) if 'Item' in response: rule = response['Item'] return { 'covered': rule.get('covered', False), 'requires_auth': rule.get('requires_auth', False), 'reason': rule.get('description', 'Standard coverage'), } # Not in rules -- safe default: require manual auth. return {'covered': None, 'requires_auth': True, 'reason': 'Procedure not in standard coverage rules'} except Exception as e: return {'covered': None, 'requires_auth': True, 'reason': f'Coverage check failed: {e}'} def make_decision(data: dict, validation: dict, coverage: dict) -> dict: if not validation['valid']: return {'decision': 'denied', 'reason': 'Incomplete/invalid submission', 'issues': validation['errors'], 'route_to': 'manual_review'} if coverage['covered'] is False: return {'decision': 'denied', 'reason': 'Procedure not covered under plan', 'route_to': 'patient_notification'} if coverage['requires_auth'] is False: return {'decision': 'approved', 'reason': 'Routine procedure, pre-authorized', 'route_to': 'patient_notification'} return {'decision': 'pending_review', 'reason': 'Requires clinical review', 'route_to': 'clinical_team', 'priority': 'high' if validation['warnings'] else 'normal'}

Real outcome: Roughly 40% of requests auto-approve (routine procedures, standard coverage). The remaining 60% are flagged for human review with the data already extracted, validated, and pre-categorized. Clinical staff stop reviewing 100% of requests — they review the 60% that actually need judgment.

Step 5: Notifications and routing

Decisions get routed to the right team via SNS and DynamoDB queues:

def route_and_notify(request: dict, decision: dict): sns = boto3.client('sns') if decision['route_to'] == 'patient_notification': sns.publish( TopicArn='arn:aws:sns:us-east-1:ACCOUNT:prior-auth-approvals', Message=f""" Prior Authorization: {decision['decision'].upper()} Request ID: {request['request_id']} Patient: {request['patient_name']} Procedure: {request['procedure_description']} Reason: {decision['reason']} """) elif decision['route_to'] == 'clinical_team': review_table = dynamodb.Table('prior_auth_clinical_review') review_table.put_item(Item={ 'request_id': request['request_id'], 'created_at': datetime.now().isoformat(), 'priority': decision.get('priority', 'normal'), 'assigned_to': None, 'status': 'pending', 'data': request, }) sns.publish( TopicArn='arn:aws:sns:us-east-1:ACCOUNT:prior-auth-clinical-review', Message=f"New prior auth for clinical review: {request['request_id']}", ) elif decision['route_to'] == 'manual_review': error_queue = dynamodb.Table('prior_auth_error_queue') error_queue.put_item(Item={ 'request_id': request['request_id'], 'created_at': datetime.now().isoformat(), 'reason': decision['reason'], 'issues': decision['issues'], 'status': 'pending_correction', })

HIPAA considerations

What you need on the operations side:

What Claude’s API gives you:

Real numbers

Manual processing: 35 min per form × 100 forms per week = 58 hours per week = $2,900 per week = roughly $150k per year.

With automation:

Build cost: $12–15k. Payback period: 3–4 months. The system pays for itself before the next quarterly review.

Drowning in prior auth? Three Moons Network builds HIPAA-compliant AI systems for healthcare. Email charles@threemoonsnetwork.net and let’s automate the paperwork.

Get the free AI Readiness Checklist

15 questions to diagnose your team’s AI readiness, where you’ll see ROI fastest, and what to tackle first.

Takes 5 minutes Actionable next steps No sales pitch

No spam. Unsubscribe anytime.

or

Ready to build AI that actually works?

Let’s talk about how SRE discipline transforms AI from a risky experiment into a reliable business system.

Book Your Free Discovery Call