{"openapi":"3.1.0","info":{"title":"BiomAPI","description":"**BiomAPI** is a web platform for extracting and securely sharing biometry data.\n\n## Core Technologies\n\n* **BiomJSON** - Biometry data structure plus metadata in JSON validated with versioned Pydantic schemas for data integrity\n* **BiomAI** - LLM-powered data extraction from PDFs and images using Google's Gemini models\n* **BiomDIRECT** - Direct data entry (manual transcription, automated scripts, EHR exports)\n* **BiomPIN** - Optional secure temporary sharing of biometry data with AES-256-GCM encryption + Argon2id key derivation\n\n## Key Features\n\n* **Intelligent File Routing** - Automatic engine selection based on file extension\n* **Supported Formats** - PDF, JPG, PNG, GIF, BMP, JSON\n* **Optional Authentication** - API key via Bearer token or `?api_key=` query parameter; unauthenticated access is public with lower rate limits, authenticated access unlocks higher per-user quotas\n* **Explicit BiomAI Routing** - Use deployment-managed credentials with optional per-key server-default retry fallback, or isolated BYOK credentials\n* **Secure Sharing** - Optional BiomPIN generation with automatic expiration (31 days by default)\n* **Zero-Knowledge Encryption** - BiomPIN data is encrypted at rest, keys are not stored\n* **Spreadsheet Export** - Export results as matching by-eye CSV and XLSX files (two rows per report: OD + OS)\n\n## API Endpoints\n\n### Processing & Retrieval\n* `POST /api/v1/biom/process` - Process biometry file with optional BiomPIN generation\n  - PDF/Images → **BiomAI** engine (LLM extraction)\n  - JSON files → **BiomJSON** engine (validation)\n  - Returns structured biometry data + optional BiomPIN for sharing\n\n* `GET /api/v1/biom/retrieve` - Retrieve encrypted data using BiomPIN code\n  - Format: `?biom_pin=word-word-123456`\n  - Rate-limited per IP to prevent enumeration attacks\n  - Returns complete biometry data if PIN valid and not expired\n\n* `POST /api/v1/biom/feedback` - Rate a successful BiomAI extraction as accurate or inaccurate\n  - Uses the original processing request ID; no opt-in header required\n  - Available when analytics is enabled, with no files or extracted data attached\n  - No authentication required and no extraction quota consumed\n\n### Export\n* `POST /api/v1/biom/export` - Generate a ZIP export from an array of `StandardAPIResponse` objects\n  - No authentication required, no rate limiting (pure data transformation, no LLM calls)\n  - Bounded by `EXPORT_MAX_BODY_SIZE` and `EXPORT_MAX_RESPONSES`\n  - `formats` selects any combination of `csv`, `xlsx`, and `json`; all three are included by default\n  - CSV and XLSX contain the same by-eye data (two rows per report: OD + OS)\n  - Smart filenames derived from patient ID and device name\n\n### Usage & Status\n* `GET /api/v1/biom/usage` - Current rate limit usage per engine for the caller\n  - Returns used/limit/resets_in_seconds per engine (biomai, biomai_byok, biomjson, retrieve)\n  - No rate limiting on this endpoint\n\n* `GET /api/v1/status` - Lightweight API status and deployment metadata\n  - Public endpoint (no authentication required)\n  - Returns `db_id`, configured model, environment, app version, and schema version\n  - Does not call Gemini, open a database session, count records, or emit analytics\n\n* `GET /api/v1/health` - Active API health check\n  - Public endpoint (no authentication required)\n  - Returns Gemini API status, BiomPIN database status, record count, default model\n\n## Authentication\n\n`POST /api/v1/biom/process`, `GET /api/v1/biom/retrieve`, and `GET /api/v1/biom/usage` support **optional authentication** via API key (Bearer token or query parameter):\n- **Unauthenticated**: Lower rate limits, suitable for web interface and testing\n- **Authenticated**: Higher rate limits for production integrations\n- Authentication provides tiered access, not access control (all endpoints are public)\n\n`POST /api/v1/biom/feedback`, `POST /api/v1/biom/export`, `GET /api/v1/status`, and `GET /api/v1/health` are fully public with no authentication support.\n\n## BYOK (Bring Your Own Key)\n\nSupply your own Google Gemini API key to use your personal quota instead of the server-selected credential lane:\n\n- **Header**: `X-Gemini-API-Key: <your-gemini-api-key>`\n- **Supported on**: `POST /api/v1/biom/process` (PDF/image files only) and `GET /api/v1/biom/usage`\n- **Precedence**: BYOK overrides deployment-managed routing for that request\n- **Rate limit**: BYOK requests use the `biomai_byok` bucket — separate from the default `biomai` bucket\n- **No effect on JSON uploads**: BiomJSON validation does not call the LLM; the key is ignored for `.json` files\n- **Key validation**: Pass `?validate=true` to `/api/v1/biom/usage` to verify your Gemini key is valid (~1-2s latency)\n\n## Rate Limit Accounting\n\nBiomAI and BYOK limits protect external Gemini cost. They are consumed after local file validation, credential resolution, and before the Gemini call, so attempts that reach the LLM path count even if extraction fails or times out. Deployment-managed BiomAI is protected by internal capacity limits. BiomJSON is local and consumes quota only after successful JSON validation/processing.\n","version":"2.2.15"},"paths":{"/api/v1/status":{"get":{"tags":["BiomAPI"],"summary":"Get Api Status","description":"Get lightweight API status and deployment metadata.\n\nThis endpoint is public (no API key required) and intentionally cheap for\nfrequent polling by external integrations. It does not call Gemini, query\nthe database, or emit analytics.","operationId":"getStatus","responses":{"200":{"description":"API status","content":{"application/json":{"schema":{"type":"string"},"example":{"status":"ok","gemini_llm":"gemini-3.8-flash","environment":"production","app_version":"0.9.8.1","schema_version":"1.1.0","db_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}}}}}},"/api/v1/health":{"get":{"tags":["BiomAPI"],"summary":"Get Api Health","description":"Get active API health and connectivity.\n\nThis endpoint is public (no API key required) for health monitoring.\nReturns comprehensive information about API health, including:\n- Gemini API connectivity status\n- BiomPIN database connectivity\n- Default model configuration\n- Environment and version info","operationId":"getHealth","responses":{"200":{"description":"API health","content":{"application/json":{"schema":{"type":"string"},"example":{"status":"operational","gemini_api_connected":true,"db_connected":true,"db_records":42,"gemini_llm":"gemini-3.8-flash","environment":"production","app_version":"0.9.8.1","schema_version":"1.1.0","db_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}}}}}},"/api/v1/biom/process":{"post":{"tags":["BiomAPI"],"summary":"Process Biometry File","description":"Biometry processing endpoint with intelligent routing.\n\nAutomatically routes to appropriate engine based on file extension:\n- **PDF/Images** → BiomAI (LLM extraction)\n- **JSON** → BiomJSON (validation/reprocessing)\n\nJSON files must contain a top-level `data` object with the biometry report.\nOptional `extra_data` and `metadata` belong alongside `data`; metadata may be\nomitted for BiomDIRECT input. Saved StandardAPIResponse objects can be\nre-uploaded. Bare reports or misplaced report fields return HTTP 422.\nSee [JSON input / BiomDIRECT](/docs/api/json-input/) for complete examples.\n\nSupports both public (rate-limited) and authenticated (higher limits) access.\n\n- **file**: Biometry file (PDF, image, or JSON)\n- **biompin**: Whether to create a BiomPIN for secure sharing (default: false)\n- **service_tier**: `standard` (1 credit) or `slow` (0.5 credit) for PDF/images\n- **user_id**: User ID (from API key authentication) for higher rate limits\n- **X-Gemini-API-Key** *(header, optional)*: Bring Your Own Key — supply your own Gemini API\n  key to use your personal quota. BYOK requests use a separate rate limit bucket and never\n  fall back to server credentials if the key fails.\n\n**Rate Limits** (daily credits, tiered by processing cost):\n- BiomAI (PDF/images): public or authenticated `biomai` quota\n- Standard BiomAI costs 1 credit; Slow BiomAI costs 0.5 credit\n- BiomJSON (JSON validation): public or authenticated `biomjson` quota\n- BYOK requests (`X-Gemini-API-Key` header): separate `biomai_byok` quota\n\nUse `GET /api/v1/biom/usage` to obtain the limits that apply to the caller.\n\nBiomAI/BYOK quota is consumed after local file validation and credential resolution, and\nbefore the Gemini call, so attempts that reach the external LLM path count even if extraction\nfails or times out. Deployment-managed BiomAI is also protected by internal capacity limits.\nBiomJSON quota is consumed only after successful local JSON validation/processing.\n\n**BiomPIN** optionally creates a secure, temporary link for sharing results.\n\n**Accuracy feedback**: when analytics is enabled, successful BiomAI extractions\n(including BYOK) can be rated via `POST /api/v1/biom/feedback` using the original\n`metadata.request_id`. No opt-in header is needed. JSON processing is not eligible.\n\nReturns standardized biometry data with engine-specific metadata.","operationId":"processBiometryFile","parameters":[{"name":"X-Gemini-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"**BYOK** — Supply your own Gemini API key to use your personal Google quota. When provided, it takes precedence over deployment-managed routing and the request is processed under the `biomai_byok` rate limit bucket (separate from the `biomai` bucket). Has no effect on JSON uploads (BiomJSON engine does not call the LLM).","title":"X-Gemini-Api-Key"},"description":"**BYOK** — Supply your own Gemini API key to use your personal Google quota. When provided, it takes precedence over deployment-managed routing and the request is processed under the `biomai_byok` rate limit bucket (separate from the `biomai` bucket). Has no effect on JSON uploads (BiomJSON engine does not call the LLM)."}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_processBiometryFile"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"string","$ref":"#/components/schemas/StandardAPIResponse"}},"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Invalid file type or empty file","content":{"application/json":{"example":{"detail":"Invalid file type. Supported: pdf, png, jpg, jpeg, json"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error - malformed data or unsupported format","content":{"application/json":{"example":{"detail":"JSON validation failed: missing required field 'device'"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"detail":"Biomai credit limit exceeded: 25 credits/day"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds until a request slot becomes available","schema":{"type":"integer"}}}},"500":{"description":"Processing error - LLM extraction failed","content":{"application/json":{"example":{"detail":"LLM extraction failed: unable to parse biometry data from file"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Gateway timeout - BiomAI extraction exceeded time limit","content":{"application/json":{"example":{"detail":"BiomAI extraction timed out after 70 seconds"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]},{"GeminiApiKey":[]},{}]}},"/api/v1/biom/retrieve":{"get":{"tags":["BiomAPI"],"summary":"Retrieve With Biompin","description":"Retrieve and decrypt biometry data using BiomPIN secure sharing system.\n\nSupports both public (rate-limited) and authenticated (higher limits) access.\n\n- **biom_pin**: BiomPIN code in format word-word-123456 (query parameter)\n- **user_id**: User ID (from API key authentication) for higher rate limits\n\n**Public Access**: Stricter per-IP rate limits to prevent enumeration attacks\n**Authenticated Access**: Higher per-user rate limits with valid API key\n\n**BiomPIN** enables secure, temporary sharing of biometry data without permanent storage.\nData is encrypted and automatically expires after the configured time period.\n\nThis endpoint uses GET method and can be accessed directly via URL:\n`/api/v1/biom/retrieve?biom_pin=lunar-rocket-731904`","operationId":"retrieveWithBiomPIN","parameters":[{"name":"biom_pin","in":"query","required":true,"schema":{"type":"string","description":"BiomPIN code (e.g., lunar-rocket-731904)","title":"Biom Pin"},"description":"BiomPIN code (e.g., lunar-rocket-731904)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"string","$ref":"#/components/schemas/StandardAPIResponse"}}}},"400":{"description":"Invalid BiomPIN format","content":{"application/json":{"example":{"detail":"Invalid BiomPIN format. Expected: word-word-123456"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"BiomPIN not found or expired","content":{"application/json":{"example":{"detail":"BiomPIN not found or expired!"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Missing required biom_pin query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"detail":"Retrieve credit limit exceeded: 5000 credits/day"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"description":"Seconds until a request slot becomes available","schema":{"type":"integer"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]},{"GeminiApiKey":[]},{}]}},"/api/v1/biom/export":{"post":{"tags":["BiomAPI"],"summary":"Generate Export Zip","description":"Generate a ZIP export from an array of `StandardAPIResponse` objects.\n\nThe `formats` array selects any combination of:\n- `csv`: a combined by-eye CSV with all results\n- `xlsx`: a formatted workbook with the same by-eye data\n- `json`: one JSON file per report\n\nNo authentication required, no rate limiting — pure data transformation, no LLM calls.\nExport requests are capped by `EXPORT_MAX_BODY_SIZE` before JSON parsing and\n`EXPORT_MAX_RESPONSES` after parsing.","operationId":"generateExportZIP","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportRequest"}}},"required":true},"responses":{"200":{"description":"ZIP file download","content":{"application/zip":{}}},"400":{"description":"Invalid Content-Length, empty response list, or empty formats array","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"411":{"description":"Export request requires a Content-Length header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Export request exceeds configured size or response-count limits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Invalid request body, including missing fields or unsupported export formats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Export generation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/biom/usage":{"get":{"tags":["BiomAPI"],"summary":"Get Usage","description":"Return current rate limit usage and key validity for the caller.\n\nPublic callers see per-IP usage; authenticated callers see per-user usage.\nAlways includes a `keys` section with BiomAPI key validity (instant, in-memory).\nPass `?validate=true` to also validate the Gemini BYOK key (adds ~1-2s latency).\nNo rate limiting applied to this endpoint — quota usage is a read-only database query.","operationId":"getUsage","parameters":[{"name":"validate","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Validate"}},{"name":"X-Gemini-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"**BYOK** — Your Gemini API key. Included in the `keys.gemini` section of the response. Pass `?validate=true` to also verify the key against the Gemini API (~1-2s latency); omit it to read quota usage without a Gemini connectivity check.","title":"X-Gemini-Api-Key"},"description":"**BYOK** — Your Gemini API key. Included in the `keys.gemini` section of the response. Pass `?validate=true` to also verify the key against the Gemini API (~1-2s latency); omit it to read quota usage without a Gemini connectivity check."}],"responses":{"200":{"description":"Current rate limit usage and key validity for the caller","content":{"application/json":{"schema":{"type":"string"},"examples":{"public":{"summary":"Public (unauthenticated) caller","value":{"auth_type":"public","engines":{"biomai":{"used":0,"limit":25},"biomai_byok":{"used":0,"limit":5000},"biomjson":{"used":0,"limit":1000},"retrieve":{"used":0,"limit":5000}},"keys":{"biomapi":{"provided":false},"gemini":{"provided":false}}}}}}}},"422":{"description":"Invalid validate query parameter; a boolean value is required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]},{"GeminiApiKey":[]},{}]}},"/api/v1/biom/feedback":{"post":{"tags":["BiomAPI"],"summary":"Rate BiomAI extraction accuracy","description":"Submit an optional accuracy rating without report content or patient information.\n\nWhen analytics is enabled, every successful BiomAI PDF/image extraction,\nincluding BYOK, is eligible without an opt-in header. Use the **original**\nprocessing response's `metadata.request_id`, not a retrieval request ID.\nJSON validation and manual edits do not create eligible references.\n\nReferences remain eligible for up to **24 hours**, subject to a 10,000-entry\nprocess-local limit. Eviction or a server restart expires the reference.\nThe first rating is final; repeating the same rating is idempotent.\n\nAnalytics use `request_id` for this HTTP request (`X-Request-ID`) and\n`process_request_id` for the original extraction process. The submitted `rating`\nbecomes `outcome` (`accurate` or `inaccurate`) in the analytics event.\n\nNo API key is required and ratings do not consume extraction quotas. Send\nonly `request_id` and `rating` as JSON (maximum **256 bytes**). Success means\nthe rating was handed to the analytics SDK, not confirmed delivered.","operationId":"submitBiomAIFeedback","requestBody":{"content":{"application/json":{"schema":{"properties":{"request_id":{"type":"string","format":"uuid","title":"Request Id","description":"Original metadata.request_id from a successful BiomAI processing response"},"rating":{"type":"string","enum":["accurate","inaccurate"],"title":"Rating","description":"User's assessment of extraction accuracy"}},"additionalProperties":false,"type":"object","required":["request_id","rating"],"title":"ExtractionFeedback"},"example":{"request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","rating":"accurate"}}},"required":true},"responses":{"200":{"description":"Rating accepted, or the same rating was already submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionFeedbackResponse"},"example":{"success":true}}}},"404":{"description":"Unknown, ineligible, expired, or evicted extraction reference","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"NOT_FOUND","message":"Feedback is no longer available for this extraction","request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","timestamp":"2026-08-28T12:00:00+00:00"}}}}},"409":{"description":"This extraction already has a different rating","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"HTTP_ERROR","message":"This extraction has already been rated","request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","timestamp":"2026-08-28T12:00:00+00:00"}}}}},"413":{"description":"Request body exceeds 256 bytes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"PAYLOAD_TOO_LARGE","message":"Feedback request too large","request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","timestamp":"2026-08-28T12:00:00+00:00"}}}}},"415":{"description":"Content-Type must be application/json","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"HTTP_ERROR","message":"Feedback must be JSON","request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","timestamp":"2026-08-28T12:00:00+00:00"}}}}},"422":{"description":"Invalid request ID, rating, JSON, or unexpected fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"REQUEST_VALIDATION_ERROR","message":"Provide only an extraction request ID and rating","request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","timestamp":"2026-08-28T12:00:00+00:00"}}}}},"503":{"description":"Feedback analytics is temporarily unavailable; retry is safe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INTERNAL_ERROR","message":"Feedback is temporarily unavailable","request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","timestamp":"2026-08-28T12:00:00+00:00"}}}}}}}}},"components":{"schemas":{"BiomDIRECTMetadata":{"properties":{"method":{"type":"string","const":"BiomDIRECT","title":"Method","default":"BiomDIRECT"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"input_schema_version":{"type":"string","title":"Input Schema Version","description":"Schema version of the input payload (server current version if no metadata block was present)"},"source_app":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source App","description":"Client-declared source application for direct/manual data"},"source_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Version","description":"Client-declared source application version"}},"type":"object","required":["input_schema_version"],"title":"BiomDIRECTMetadata","description":"Provenance metadata for directly provided biometry data.\n\nPresent when `method = \"BiomDIRECT\"` — the data was directly constructed\nor manually edited: web transcription, user edits after an initial extraction,\nautomated scripts, EHR exports, or any other path that bypasses AI extraction.\nAlso assigned to any uploaded JSON that cannot be attributed to a BiomAI run.\n\n`input_schema_version` records the schema version the input payload claimed\n(or the server's current version if no metadata block was present). Always\npopulated — enables future migration chain logic and complete audit trail.\n`source_app` and `source_version` are client-declared provenance fields, not\nauthentication or trust signals."},"BiomPINInfo":{"properties":{"pin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pin","description":"BiomPIN code for secure sharing"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"ISO timestamp when BiomPIN expires"},"db_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Db Id","description":"Database instance ID — use with history-storage.js to keep BiomPIN history in sync across integrations"}},"type":"object","title":"BiomPINInfo","description":"BiomPIN information for secure sharing"},"BiometerInfo":{"properties":{"device_name":{"$ref":"#/components/schemas/DeviceName","description":"Name of the biometer device"},"manufacturer":{"$ref":"#/components/schemas/DeviceManufacturer","description":"Manufacturer of the biometer device"}},"type":"object","required":["device_name","manufacturer"],"title":"BiometerInfo","description":"Information about the optical biometer device used"},"BiometricData":{"properties":{"AL":{"anyOf":[{"type":"number","maximum":40.0,"minimum":14.0},{"type":"null"}],"title":"Al","description":"Axial Length in mm"},"ACD":{"anyOf":[{"type":"number","maximum":6.0,"minimum":1.0},{"type":"null"}],"title":"Acd","description":"Anterior Chamber Depth in mm"},"K1_magnitude":{"anyOf":[{"type":"number","maximum":99.0,"minimum":20.0},{"type":"null"}],"title":"K1 Magnitude","description":"K1 keratometry power in diopters"},"K1_axis":{"anyOf":[{"type":"integer","maximum":180.0,"minimum":0.0},{"type":"null"}],"title":"K1 Axis","description":"K1 axis in degrees"},"K2_magnitude":{"anyOf":[{"type":"number","maximum":99.0,"minimum":20.0},{"type":"null"}],"title":"K2 Magnitude","description":"K2 keratometry power in diopters"},"K2_axis":{"anyOf":[{"type":"integer","maximum":180.0,"minimum":0.0},{"type":"null"}],"title":"K2 Axis","description":"K2 axis in degrees"},"WTW":{"anyOf":[{"type":"number","maximum":17.0,"minimum":7.0},{"type":"null"}],"title":"Wtw","description":"White to White diameter in mm"},"LT":{"anyOf":[{"type":"number","maximum":7.0,"minimum":0.1},{"type":"null"}],"title":"Lt","description":"Lens Thickness in mm"},"CCT":{"anyOf":[{"type":"integer","maximum":900.0,"minimum":200.0},{"type":"null"}],"title":"Cct","description":"Central Corneal Thickness in micrometers"},"lens_status":{"anyOf":[{"$ref":"#/components/schemas/LensStatus"},{"type":"null"}],"description":"Lens status: Phakic, Pseudophakic, or Aphakic"},"post_refractive":{"anyOf":[{"$ref":"#/components/schemas/PostRefractive"},{"type":"null"}],"description":"Post-refractive status: None, Myopic LVC, Hyperopic LVC, or Radial Keratotomy"},"keratometric_index":{"anyOf":[{"type":"number","maximum":1.338,"minimum":1.33},{"type":"null"}],"title":"Keratometric Index","description":"Keratometric index (n or nk), typically 1.3375"}},"type":"object","title":"BiometricData","description":"Biometric measurements for one eye"},"BiometryReport":{"properties":{"biometer":{"$ref":"#/components/schemas/BiometerInfo","description":"Information about the biometer device used"},"exam_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Exam Date","description":"Date the biometry exam was performed, not the patient's date of birth"},"patient":{"$ref":"#/components/schemas/PatientData"},"right_eye":{"$ref":"#/components/schemas/BiometricData","description":"Right eye biometric measurements"},"left_eye":{"$ref":"#/components/schemas/BiometricData","description":"Left eye biometric measurements"}},"type":"object","required":["biometer","patient","right_eye","left_eye"],"title":"BiometryReport","description":"Biometry report for LLM extraction - contains only the data fields without metadata"},"Body_processBiometryFile":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"biompin":{"type":"boolean","title":"Biompin","default":false},"stream":{"type":"boolean","title":"Stream","description":"Return progress events and a complete validated result as server-sent events.","default":false},"service_tier":{"type":"string","enum":["standard","slow"],"title":"Service Tier","description":"BiomAPI processing tier for PDF/image uploads. Slow costs 0.5 BiomAI credit and may have substantially higher latency. Has no effect on JSON uploads.","default":"standard"}},"type":"object","required":["file"],"title":"Body_processBiometryFile"},"DeviceManufacturer":{"type":"string","enum":["Alcon","CSO","HaagStreit","Heidelberg","Oculus","Tomey","Topcon","Zeiss","Other"],"title":"DeviceManufacturer","description":"Device manufacturer options"},"DeviceName":{"type":"string","enum":["Aladdin","Anterion","Argos","MS39","IOLMaster700","LenstarLS900","EyestarES900","OA-2000","PentacamAXL","Other"],"title":"DeviceName","description":"Device name options"},"ErrorDetail":{"properties":{"code":{"type":"string","title":"Code","description":"Stable error code"},"message":{"type":"string","title":"Message","description":"Error message describing what went wrong"},"request_id":{"type":"string","title":"Request Id","description":"Server-generated request correlation ID"},"timestamp":{"type":"string","title":"Timestamp","description":"ISO timestamp when the error was created"},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details","description":"Optional structured error details"}},"type":"object","required":["code","message","request_id","timestamp"],"title":"ErrorDetail","description":"Standard error payload details."},"ErrorResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Always false for error responses","default":false},"error":{"$ref":"#/components/schemas/ErrorDetail","description":"Structured error information"}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Standard error response format","examples":[{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"BiomAI credit limit exceeded","request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","timestamp":"2026-05-24T12:00:00+00:00"},"success":false},{"error":{"code":"FILE_VALIDATION_ERROR","message":"File extension is .pdf but content does not look like a PDF","request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","timestamp":"2026-05-24T12:00:00+00:00"},"success":false}]},"ExportRequest":{"properties":{"responses":{"items":{"$ref":"#/components/schemas/ExportResponseItem"},"type":"array","title":"Responses"},"formats":{"items":{"type":"string","enum":["csv","xlsx","json"]},"type":"array","title":"Formats","default":["csv","xlsx","json"]}},"type":"object","required":["responses"],"title":"ExportRequest"},"ExportResponseItem":{"properties":{"json_data":{"additionalProperties":true,"type":"object","title":"Json Data"},"filename":{"type":"string","title":"Filename"}},"type":"object","required":["json_data","filename"],"title":"ExportResponseItem"},"ExtraReport":{"properties":{"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Additional notes"},"posterior_keratometry":{"anyOf":[{"$ref":"#/components/schemas/PosteriorKeratometry"},{"type":"null"}],"description":"Posterior keratometry measurements"}},"type":"object","title":"ExtraReport","description":"Additional optional biometry data","examples":[{"notes":"Patient has mild dry eye","posterior_keratometry":{"left_eye":{"PK1_axis":172,"PK1_magnitude":6.08,"PK2_axis":82,"PK2_magnitude":6.38},"pk_device_name":"IOLMaster700","right_eye":{"PK1_axis":8,"PK1_magnitude":6.12,"PK2_axis":98,"PK2_magnitude":6.45}}}]},"ExtractionFeedbackResponse":{"properties":{"success":{"type":"boolean","const":true,"title":"Success","default":true}},"type":"object","title":"ExtractionFeedbackResponse"},"Gender":{"type":"string","enum":["Male","Female"],"title":"Gender","description":"Patient gender options"},"LensStatus":{"type":"string","enum":["Phakic","Phakic IOL","Pseudophakic","Aphakic"],"title":"LensStatus","description":"Lens status options"},"PKDeviceName":{"type":"string","enum":["Anterion","EyestarES900","Galilei","IOLMaster700","MS39","Pentacam","Other"],"title":"PKDeviceName","description":"Posterior keratometry device name options"},"PatientData":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Patient full name"},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Patient ID or medical record number"},"date_of_birth":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Date Of Birth","description":"Date of birth"},"gender":{"anyOf":[{"$ref":"#/components/schemas/Gender"},{"type":"null"}],"description":"Patient gender"}},"type":"object","title":"PatientData","description":"Patient demographic information"},"PostRefractive":{"type":"string","enum":["None","Myopic LVC","Hyperopic LVC","Radial Keratotomy"],"title":"PostRefractive","description":"Post-refractive surgery options"},"PosteriorKeratometry":{"properties":{"pk_device_name":{"anyOf":[{"$ref":"#/components/schemas/PKDeviceName"},{"type":"null"}],"description":"Shared device used for PK and TK measurements"},"right_eye":{"$ref":"#/components/schemas/PosteriorKeratometryData","description":"Right eye posterior keratometry"},"left_eye":{"$ref":"#/components/schemas/PosteriorKeratometryData","description":"Left eye posterior keratometry"}},"type":"object","title":"PosteriorKeratometry","description":"Posterior keratometry measurements from specialized devices","examples":[{"left_eye":{"PK1_axis":172,"PK1_magnitude":6.08,"PK2_axis":82,"PK2_magnitude":6.38},"pk_device_name":"IOLMaster700","right_eye":{"PK1_axis":8,"PK1_magnitude":6.12,"PK2_axis":98,"PK2_magnitude":6.45}}]},"PosteriorKeratometryData":{"properties":{"PK1_magnitude":{"anyOf":[{"type":"number","maximum":10.0,"minimum":2.0},{"type":"null"}],"title":"Pk1 Magnitude","description":"PK1 power in diopters"},"PK1_axis":{"anyOf":[{"type":"integer","maximum":180.0,"minimum":0.0},{"type":"null"}],"title":"Pk1 Axis","description":"PK1 axis in degrees"},"PK2_magnitude":{"anyOf":[{"type":"number","maximum":10.0,"minimum":2.0},{"type":"null"}],"title":"Pk2 Magnitude","description":"PK2 power in diopters"},"PK2_axis":{"anyOf":[{"type":"integer","maximum":180.0,"minimum":0.0},{"type":"null"}],"title":"Pk2 Axis","description":"PK2 axis in degrees"},"TK1_magnitude":{"anyOf":[{"type":"number","maximum":99.0,"minimum":20.0},{"type":"null"}],"title":"Tk1 Magnitude","description":"TK1 power in diopters"},"TK1_axis":{"anyOf":[{"type":"integer","maximum":180.0,"minimum":0.0},{"type":"null"}],"title":"Tk1 Axis","description":"TK1 axis in degrees"},"TK2_magnitude":{"anyOf":[{"type":"number","maximum":99.0,"minimum":20.0},{"type":"null"}],"title":"Tk2 Magnitude","description":"TK2 power in diopters"},"TK2_axis":{"anyOf":[{"type":"integer","maximum":180.0,"minimum":0.0},{"type":"null"}],"title":"Tk2 Axis","description":"TK2 axis in degrees"}},"type":"object","title":"PosteriorKeratometryData","description":"Posterior keratometry measurements for one eye","examples":[{"PK1_axis":8,"PK1_magnitude":6.12,"PK2_axis":98,"PK2_magnitude":6.45}]},"PublicBiomAIMetadata":{"properties":{"method":{"type":"string","const":"BiomAI","title":"Method","default":"BiomAI"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"byok":{"type":"boolean","title":"Byok","description":"Whether the user provided their own Gemini API key","default":false},"llm":{"type":"string","title":"Llm","description":"LLM model used for extraction (e.g., gemini-3.8-flash)"},"input_schema_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Input Schema Version","description":"Schema version of the uploaded JSON on round-trip re-upload; null for fresh extractions"}},"type":"object","required":["llm"],"title":"PublicBiomAIMetadata","description":"User-facing provenance metadata for BiomAI responses.\n\nOperational diagnostics such as token counts, retry timings, and\npreprocessing details stay on the internal BiomAIMetadata object for\nanalytics/logging, but are intentionally omitted from public API payloads."},"ResponseMetadata":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Server-generated request correlation ID for this response"},"schema_version":{"type":"string","title":"Schema Version","description":"BiometryReport schema version"},"app_version":{"type":"string","title":"App Version","description":"BiomAPI application version"},"extraction":{"oneOf":[{"$ref":"#/components/schemas/PublicBiomAIMetadata"},{"$ref":"#/components/schemas/BiomDIRECTMetadata"}],"title":"Extraction","description":"Data provenance record. Discriminated by `method`:\n\n- **`BiomAI`** — extracted from a PDF or image by the Gemini LLM; includes the public model name and BYOK status. Operational LLM diagnostics are tracked internally and omitted from public payloads.\n- **`BiomDIRECT`** — data was directly provided or manually edited (web transcription, BiomAPI Webapp edits, automated script, EHR export, or any direct construction); may include client-declared source fields.","discriminator":{"propertyName":"method","mapping":{"BiomAI":"#/components/schemas/PublicBiomAIMetadata","BiomDIRECT":"#/components/schemas/BiomDIRECTMetadata"}}}},"type":"object","required":["request_id","schema_version","app_version","extraction"],"title":"ResponseMetadata","description":"Metadata attached to biometry processing and retrieval responses."},"StandardAPIResponse":{"properties":{"data":{"$ref":"#/components/schemas/BiometryReport","description":"Extracted biometry data"},"extra_data":{"anyOf":[{"$ref":"#/components/schemas/ExtraReport"},{"type":"null"}],"description":"Additional optional biometry data"},"metadata":{"$ref":"#/components/schemas/ResponseMetadata","description":"Response metadata"},"biompin":{"anyOf":[{"$ref":"#/components/schemas/BiomPINInfo"},{"type":"null"}],"description":"BiomPIN information (null when not requested)"}},"type":"object","required":["data","metadata"],"title":"StandardAPIResponse","description":"Biometry response returned by POST /api/v1/biom/process and GET /api/v1/biom/retrieve.","examples":[{"biompin":{"db_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","expires_at":"2025-01-18T10:30:00Z","pin":"lunar-rocket-731904"},"data":{"biometer":{"device_name":"IOLMaster700","manufacturer":"Zeiss"},"exam_date":"2025-01-15","left_eye":{"ACD":3.08,"AL":23.52,"CCT":542,"K1_axis":175,"K1_magnitude":43.0,"K2_axis":85,"K2_magnitude":44.25,"LT":4.48,"WTW":11.9,"keratometric_index":1.3375,"lens_status":"Phakic","post_refractive":"None"},"patient":{"date_of_birth":"1965-03-15","gender":"Male","id":"12345","name":"JD"},"right_eye":{"ACD":3.12,"AL":23.45,"CCT":545,"K1_axis":5,"K1_magnitude":43.25,"K2_axis":95,"K2_magnitude":44.5,"LT":4.52,"WTW":11.8,"keratometric_index":1.3375,"lens_status":"Phakic","post_refractive":"None"}},"extra_data":{"posterior_keratometry":{"left_eye":{"PK1_axis":172,"PK1_magnitude":6.08,"PK2_axis":82,"PK2_magnitude":6.38},"pk_device_name":"IOLMaster700","right_eye":{"PK1_axis":8,"PK1_magnitude":6.12,"PK2_axis":98,"PK2_magnitude":6.45}}},"metadata":{"app_version":"0.9.8.1","extraction":{"byok":false,"llm":"gemini-3.8-flash","method":"BiomAI","timestamp":"2025-01-15T10:30:00Z"},"request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","schema_version":"1.1.0"}},{"description":"Response from uploading a minimal BiomDIRECT JSON. The input payload only needs `biometer` (required), `patient`, `right_eye`, and `left_eye` (all fields optional). No metadata block is required — the server stamps method, timestamp, and input_schema_version automatically.\n\n**Minimum valid input payload:**\n```json\n{\n  \"biometer\": {\"device_name\": \"IOLMaster700\", \"manufacturer\": \"Zeiss\"},\n  \"patient\": {},\n  \"right_eye\": {},\n  \"left_eye\": {}\n}\n```","summary":"BiomDIRECT — minimum valid payload response","value":{"data":{"biometer":{"device_name":"IOLMaster700","manufacturer":"Zeiss"},"left_eye":{"ACD":3.08,"AL":23.52,"K1_axis":175,"K1_magnitude":43.0,"K2_axis":85,"K2_magnitude":44.25},"patient":{},"right_eye":{"ACD":3.12,"AL":23.45,"K1_axis":5,"K1_magnitude":43.25,"K2_axis":95,"K2_magnitude":44.5}},"metadata":{"app_version":"0.9.8.1","extraction":{"input_schema_version":"1.0.0","method":"BiomDIRECT","source_app":"BiomAPI Webapp","source_version":"0.9.8.1","timestamp":"2025-01-15T10:30:00Z"},"request_id":"3f5e3c51-d0b9-45fd-a7f4-6d7a3b592f42","schema_version":"1.1.0"}}}]}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"Enter your BiomAPI key as a Bearer token"},"ApiKeyAuth":{"type":"apiKey","in":"query","name":"api_key","description":"BiomAPI key as a query parameter"},"GeminiApiKey":{"type":"apiKey","in":"header","name":"X-Gemini-API-Key","description":"**BYOK** — Your own Google Gemini API key. When supplied, PDF/image processing uses your personal Gemini quota, overrides deployment-managed routing, and counts against the `biomai_byok` rate limit bucket."}}}}