This is the full developer documentation for BiomAPI # BiomAPI > Extract structured biometry data from optical biometry reports — then share securely with a BiomPIN. ## Core Technologies [Section titled “Core Technologies”](#core-technologies) * **BiomJSON** - Biometry data structure plus metadata in JSON validated with versioned Pydantic schemas for data integrity * **BiomAI** - LLM-powered data extraction from PDFs and images using Google’s Gemini models * **BiomDIRECT** - Direct data entry (manual transcription, automated scripts, EHR exports) * **BiomPIN** - Optional secure temporary sharing of biometry data with AES-256-GCM encryption + Argon2id key derivation ## Key Features [Section titled “Key Features”](#key-features) * **Intelligent File Routing** - Automatic engine selection based on file extension * **Supported Formats** - PDF, JPG, PNG, GIF, BMP, JSON * **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 * **BYOK** - Supply your own Gemini API key via `X-Gemini-API-Key` header to use your personal quota * **Secure Sharing** - Optional BiomPIN generation with automatic expiration (31 days by default) * **Zero-Knowledge Encryption** - BiomPIN data is encrypted at rest, keys are not stored * **CSV Export** - Export results as a by-eye CSV (two rows per report: OD + OS) # Authentication & BYOK > How to authenticate with BiomAPI and use your own Gemini key. ## Overview [Section titled “Overview”](#overview) All BiomAPI endpoints are **publicly accessible** — authentication is optional and provides higher rate limits, not access control. | Scenario | Rate limit | | --------------- | ----------------------------------------------------- | | No key | Per-IP daily limits (30 BiomAI calls/day) | | BiomAPI key | Per-user custom quotas | | BYOK Gemini key | Unlimited BiomAI (your Gemini quota), separate bucket | | Both keys | Your Gemini quota + custom retrieve quota | *** ## BiomAPI key [Section titled “BiomAPI key”](#biomapi-key) Pass your key as a Bearer token or query parameter: ```bash # Bearer token (recommended) curl -H "Authorization: Bearer biom_your_key_here" ... # Query parameter curl "https://biomapi.com/api/v1/biom/process?api_key=biom_your_key_here" ... ``` Key format: `biom_` + 43 random base64url characters (256-bit entropy). GitHub Secret Scanning compatible. ### Check your usage [Section titled “Check your usage”](#check-your-usage) ```bash curl -H "Authorization: Bearer biom_your_key" \ https://biomapi.com/api/v1/biom/usage ``` *** ## BYOK — Bring Your Own Gemini Key [Section titled “BYOK — Bring Your Own Gemini Key”](#byok--bring-your-own-gemini-key) Supply your own Google Gemini API key to use your personal quota. The server’s shared BiomAI quota is not consumed. ```bash curl -X POST https://biomapi.com/api/v1/biom/process \ -H "X-Gemini-API-Key: AIza_your_gemini_key" \ -F "file=@report.pdf" ``` **BYOK details:** * Applies to `POST /api/v1/biom/process` (PDF/image files only) * Ignored for JSON uploads — BiomJSON doesn’t call the LLM * Tracked under the `biomai_byok` rate limit bucket (separate from `biomai`) * Validate your key: `GET /api/v1/biom/usage?validate=true` *** ## Rate limit windows [Section titled “Rate limit windows”](#rate-limit-windows) All limits use a **24-hour sliding window** (not a midnight reset). Usage timestamps roll off 24 hours after they were recorded. See [Rate Limits](../rate-limits/) for full details. # BiomPIN > Secure, temporary biometry data sharing with AES-256-GCM encryption. ## What is BiomPIN? [Section titled “What is BiomPIN?”](#what-is-biompin) BiomPIN is an optional secure sharing system. After processing, BiomAPI can generate a short memorable PIN (`lunar-rocket-731904`) that links to an encrypted copy of the result. Share it with a colleague, open it in the ESCRS calculator, or retrieve it later. Data auto-expires after 31 days. No biometry data is stored unencrypted. *** ## PIN format [Section titled “PIN format”](#pin-format) ```plaintext word-word-123456 ``` * Two BIP-39 words — identify the share (stored as primary key in DB) * Six-digit numeric PIN — the encryption secret (never stored) The URL format is `https://biomapi.com/pin/lunar-rocket-731904` — the full PIN including the numeric suffix is required to decrypt. *** ## Security model [Section titled “Security model”](#security-model) | Layer | Implementation | | ---------------------- | -------------------------------------------------------------------------- | | Key derivation | Argon2id (memory-hard): `time_cost=3`, `memory_cost=32MB`, `parallelism=1` | | Salt | `SHA-256(share_id)[:16]` — deterministic from the word pair, not stored | | Encryption | AES-256-GCM: payload = `nonce (12 bytes) + ciphertext` | | Brute force protection | After 3 wrong numeric PINs, the record is **permanently deleted** | The server cannot decrypt stored data without the numeric PIN — which is never stored. *** ## Generating a BiomPIN [Section titled “Generating a BiomPIN”](#generating-a-biompin) BiomPIN is generated by default on every `POST /api/v1/biom/process` call. To disable: ```bash curl -X POST .../process \ -F "file=@report.pdf" \ -F "create_biompin=false" ``` In the web UI, use the “Share” toggle before processing. *** ## Retrieving data [Section titled “Retrieving data”](#retrieving-data) ### Via API [Section titled “Via API”](#via-api) ```bash GET /api/v1/biom/retrieve?biom_pin=lunar-rocket-731904 ``` ### Via direct URL [Section titled “Via direct URL”](#via-direct-url) ```plaintext https://biomapi.com/pin/lunar-rocket-731904 ``` The web app pre-fills the PIN and auto-retrieves the data. *** ## Expiry and cleanup [Section titled “Expiry and cleanup”](#expiry-and-cleanup) * Default expiry: **744 hours (31 days)** from creation * Expired records are purged automatically after each new store operation * Records are permanently destroyed after 3 failed PIN attempts * The `db_id` field in responses identifies the database instance; it changes if the DB is wiped ### Handling stale history [Section titled “Handling stale history”](#handling-stale-history) If the BiomAPI database is reset, existing PINs become invalid. Client apps should call `GET /api/v1/status` on startup and compare `db_id` to detect this: ```js const status = await fetch('/api/v1/status').then(r => r.json()); if (status.db_id !== storedDbId) { // Purge local history — all pins from the old instance are gone } ``` The web app and [History SDK](../sdk/) handle this automatically. # API Endpoints > Complete reference for all BiomAPI REST endpoints. Base URL: `https://biomapi.com` Interactive docs (Swagger UI): [`/apidocs`](/apidocs) *** ## POST /api/v1/biom/process [Section titled “POST /api/v1/biom/process”](#post-apiv1biomprocess) Process a biometry file and return structured measurements. ### File routing [Section titled “File routing”](#file-routing) The engine is selected automatically based on the file extension — no manual selection needed: | Extension | Engine | What happens | | ----------------------- | -------- | -------------------------------------------------- | | PDF, JPG, PNG, GIF, BMP | BiomAI | Bytes sent to Gemini LLM for structured extraction | | JSON | BiomJSON | Schema validation + metadata preservation | Unsupported extensions are rejected with 400 before any processing occurs. ### Request [Section titled “Request”](#request) **Multipart form data:** | Field | Type | Required | Default | Description | | --------- | ---- | -------- | ------- | ------------------------------------- | | `file` | file | Yes | — | Biometry report file | | `biompin` | bool | No | `false` | Generate a BiomPIN for secure sharing | **Headers:** | Header | Description | | ----------------------------- | ---------------------------------------------------------- | | `Authorization: Bearer ` | Optional BiomAPI key — unlocks higher per-user rate limits | | `X-Gemini-API-Key: ` | Optional BYOK Gemini key — uses your own Google quota | ### BiomPIN generation [Section titled “BiomPIN generation”](#biompin-generation) BiomPIN is **off by default** (`biompin=false`). Set `biompin=true` to receive a secure sharing link in the response. If generation fails (e.g., DB unavailable), processing still succeeds and the `biompin` field is `null`. ### BYOK behavior [Section titled “BYOK behavior”](#byok-behavior) When `X-Gemini-API-Key` is provided, extraction uses your personal Gemini quota instead of the shared server quota. The request is tracked under the `biomai_byok` rate limit bucket (separate from `biomai`). BYOK has no effect on JSON uploads — BiomJSON doesn’t call the LLM. ### Rate limiting [Section titled “Rate limiting”](#rate-limiting) Rate limits are applied **after** a successful extraction. Files that fail validation or cause LLM errors do not count against your quota. | Scenario | Bucket | Public default | | ------------------ | ------------- | -------------- | | PDF/image, no BYOK | `biomai` | 30/day | | PDF/image, BYOK | `biomai_byok` | 1,000/day | | JSON upload | `biomjson` | 300/day | ### Round-trip re-upload (JSON) [Section titled “Round-trip re-upload (JSON)”](#round-trip-re-upload-json) When you re-upload a JSON that originally came from a BiomAI extraction, BiomJSON preserves the original LLM metadata (token counts, model name, timing). The `input_schema_version` field is populated with the schema version of the uploaded JSON, so you can detect if it was produced by an older BiomAPI version. If the JSON has no recognizable BiomAI provenance, it’s attributed as `BiomDIRECT`. ### Posterior keratometry [Section titled “Posterior keratometry”](#posterior-keratometry) The `extra_data.posterior_keratometry` field is populated only for devices that support PK extraction (Anterion, EyestarES900, IOLMaster700, MS39, PentacamAXL). For other devices, `extra_data` is `null`. ### Response [Section titled “Response”](#response) ```json { "data": { "biometer": { "device_name": "IOLMaster700", "manufacturer": "Zeiss" }, "patient": { "name": "JD", "id": "12345", "date_of_birth": "1965-03-15", "gender": "Male" }, "right_eye": { "AL": 23.45, "ACD": 3.12, "K1_magnitude": 43.25, "K1_axis": 5, "K2_magnitude": 44.50, "K2_axis": 95, "WTW": 11.8, "LT": 4.52, "CCT": 545, "lens_status": "Phakic", "post_refractive": "None", "keratometric_index": 1.3375 }, "left_eye": { "...": "..." } }, "extra_data": { "notes": null, "posterior_keratometry": { "pk_device_name": "IOLMaster700", "right_eye": { "PK1_magnitude": 6.12, "PK1_axis": 8, "PK2_magnitude": 6.45, "PK2_axis": 98 }, "left_eye": { "PK1_magnitude": 6.08, "PK1_axis": 172, "PK2_magnitude": 6.38, "PK2_axis": 82 } } }, "metadata": { "schema_version": "1.0.0", "app_version": "0.9.8.1", "extraction": { "method": "BiomAI", "timestamp": "2025-01-15T10:30:00Z", "filename": "report.pdf", "byok": false, "llm": "gemini-flash-latest", "llm_api_metrics": { "prompt_token_count": 1500, "cached_content_token_count": null, "candidates_token_count": 200, "thoughts_token_count": null, "total_token_count": 1700, "cache_hit_ratio": null }, "llm_performance": { "llm_response_time_seconds": 2.5, "retry_attempts": 0, "total_retry_delay_seconds": 0.0 } } }, "biompin": { "pin": "lunar-rocket-731904", "expires_at": "2025-02-15T10:30:00Z", "db_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } } ``` See [Response Schema](./response-schema/) for the complete field reference. ### Errors [Section titled “Errors”](#errors) | Code | Condition | | ----- | ------------------------------------------------------------------- | | `400` | Empty file, unsupported extension, or invalid file content | | `422` | JSON schema mismatch (major version) or Pydantic validation failure | | `429` | Rate limit exceeded for the applicable engine bucket | | `504` | BiomAI extraction exceeded `BIOMAI_TIMEOUT_SECONDS` (default 30 s) | *** ## GET /api/v1/biom/retrieve [Section titled “GET /api/v1/biom/retrieve”](#get-apiv1biomretrieve) Retrieve encrypted biometry data using a BiomPIN code. ### How it works [Section titled “How it works”](#how-it-works) BiomPIN codes have two parts: `word-word` (the share ID, stored in the database) and `-123456` (the 6-digit numeric PIN, the encryption key, never stored). The server derives the AES-256-GCM decryption key from the numeric PIN using Argon2id. Without the full PIN including the numeric suffix, the data cannot be decrypted. Rate limiting for this endpoint is applied **before** retrieval (unlike `/process`). This prevents quota exhaustion via wrong PIN attempts, though the primary brute-force protection is the auto-destroy mechanism. ### Request [Section titled “Request”](#request-1) ```plaintext GET /api/v1/biom/retrieve?biom_pin=lunar-rocket-731904 ``` | Parameter | Required | Description | | ---------- | -------- | ----------------------------------------------------- | | `biom_pin` | Yes | Full PIN including numeric suffix: `word-word-123456` | ### Wrong PIN attempts [Section titled “Wrong PIN attempts”](#wrong-pin-attempts) Each wrong numeric PIN increments a counter. After **3 failed attempts**, the database record is **permanently deleted** — the data is gone, and all subsequent requests for that share ID return 404. This is intentional: it prevents offline brute-forcing by destroying the ciphertext. Note that rate limiting is also applied per-IP/per-user, but the 3-attempt destruction is tracked in the database independently of rate limit state. ### Response [Section titled “Response”](#response-1) Same `StandardAPIResponse` shape as `/process`. The `biompin` field reflects the original PIN info (pin, expiry, db\_id). ### Errors [Section titled “Errors”](#errors-1) | Code | Condition | | ----- | -------------------------------------------------------------------------- | | `400` | PIN doesn’t match the `word-word-123456` format | | `404` | PIN not found, already expired, or destroyed after too many wrong attempts | | `429` | Rate limit exceeded | Caution After 3 wrong numeric PIN attempts the record is **permanently deleted** and returns 404. There is no lockout period — the data is gone. *** ## POST /api/v1/biom/csv [Section titled “POST /api/v1/biom/csv”](#post-apiv1biomcsv) Generate a by-eye CSV from an array of `StandardAPIResponse` objects. No authentication required. No rate limiting. Pure data transformation — no LLM calls. ### By-eye format [Section titled “By-eye format”](#by-eye-format) The CSV produces **two rows per report**: one for the right eye (`right_eye=1`) and one for the left eye (`right_eye=0`). Each row includes patient info, device info, and all 12 biometric fields as flat columns. ### Request body [Section titled “Request body”](#request-body) ```json { "responses": [ { "json_data": { "...": "StandardAPIResponse object" }, "filename": "report1.pdf" }, { "json_data": { "...": "StandardAPIResponse object" }, "filename": "report2.json" } ] } ``` ### Response [Section titled “Response”](#response-2) `text/csv` file download (`Content-Disposition: attachment; filename=biometry_export.csv`). *** ## POST /api/v1/biom/export [Section titled “POST /api/v1/biom/export”](#post-apiv1biomexport) Generate a ZIP archive containing a by-eye CSV and individual JSON files per report. No authentication required. No rate limiting. ### Archive contents [Section titled “Archive contents”](#archive-contents) | File | Description | | ------------------------------------ | ------------------------------------------------- | | `biomapi-export-YYYYMMDD-HHMMSS.csv` | By-eye CSV of all results (same format as `/csv`) | | `biomapi-{patientId}-{device}.json` | One JSON file per report | **Smart filenames:** JSON filenames are derived from `data.patient.id` (slugified) and `data.biometer.device_name`. If no patient ID is present, the current date is used instead. Duplicate filenames within the same archive are disambiguated with a numeric suffix. ### Request body [Section titled “Request body”](#request-body-1) Same structure as `/csv`: `{ "responses": [...] }`. ### Response [Section titled “Response”](#response-3) `application/zip` file download. *** ## GET /api/v1/biom/usage [Section titled “GET /api/v1/biom/usage”](#get-apiv1biomusage) Returns current rate limit usage and API key validity for the caller. No rate limiting applied to this endpoint — it’s a read-only in-memory query. ### What it returns [Section titled “What it returns”](#what-it-returns) Public callers see per-IP usage. Authenticated callers see per-user usage. The `engines` section shows used/limit/resets for all four engine buckets regardless of which ones have been used. The `keys` section reports validity of the BiomAPI key (instant, in-memory check) and optionally the Gemini BYOK key. ### Validating your Gemini key [Section titled “Validating your Gemini key”](#validating-your-gemini-key) Pass `?validate=true` to test the `X-Gemini-API-Key` header against the live Gemini API. This adds \~1–2 seconds of latency. Without `validate=true`, the `gemini` key entry only reports `"provided": true` — no validity check is performed. ### Request [Section titled “Request”](#request-2) ```plaintext GET /api/v1/biom/usage GET /api/v1/biom/usage?validate=true ``` | Query parameter | Default | Description | | --------------- | ------- | ------------------------------------------------------------- | | `validate` | `false` | Also validate `X-Gemini-API-Key` against Gemini API (\~1–2 s) | ### Response [Section titled “Response”](#response-4) ```json { "auth_type": "authenticated", "user_id": "user1", "engines": { "biomai": { "used": 5, "limit": 300, "resets_in_seconds": 72400 }, "biomai_byok": { "used": 0, "limit": 3000, "resets_in_seconds": 72400 }, "biomjson": { "used": 2, "limit": 3000, "resets_in_seconds": 72400 }, "retrieve": { "used": 12, "limit": 10000, "resets_in_seconds": 72400 } }, "keys": { "biomapi": { "provided": true, "valid": true, "user_id": "user1" }, "gemini": { "provided": true, "valid": true } } } ``` **`keys` field details:** | Scenario | `biomapi` value | `gemini` value | | --------------------------------------------- | ----------------------------------------------------- | ------------------------------------ | | No keys provided | `{"provided": false}` | `{"provided": false}` | | BiomAPI key provided, valid | `{"provided": true, "valid": true, "user_id": "..."}` | — | | BiomAPI key provided, invalid | `{"provided": true, "valid": false}` | — | | Gemini key provided, `validate=false` | — | `{"provided": true}` | | Gemini key provided, `validate=true`, valid | — | `{"provided": true, "valid": true}` | | Gemini key provided, `validate=true`, invalid | — | `{"provided": true, "valid": false}` | `resets_in_seconds` is the time until the oldest tracked request in the sliding window rolls off — i.e., the earliest your limit could recover by one slot. *** ## GET /api/v1/status [Section titled “GET /api/v1/status”](#get-apiv1status) Health check. Public, no authentication required. ### Response [Section titled “Response”](#response-5) ```json { "status": "operational", "gemini_api_connected": true, "db_connected": true, "db_records": 42, "gemini_llm": "gemini-flash-latest", "environment": "production", "app_version": "0.9.8.1", "schema_version": "1.0.0", "db_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } ``` | Field | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------- | | `status` | `"operational"` (all healthy), `"degraded"` (one of Gemini/DB unavailable), `"error"` (both unavailable) | | `gemini_api_connected` | Whether the server’s Gemini API key is valid and reachable | | `db_connected` | Whether the BiomPIN SQLite database is healthy | | `db_records` | Number of active (non-expired) BiomPIN records | | `gemini_llm` | The Gemini model configured on the server | | `db_id` | Persistent deployment identifier — changes when the BiomPIN database is wiped | ### Using `db_id` for history management [Section titled “Using db\_id for history management”](#using-db_id-for-history-management) `db_id` is stable across server restarts but changes when the BiomPIN database is reset (e.g., via `BIOMPIN_RESET_DB=true`). If you maintain a local BiomPIN history, compare this value on startup to detect a database wipe: ```js const status = await fetch('/api/v1/status').then(r => r.json()); if (status.db_id !== storedDbId) { // All saved PINs from the old instance are gone — clear local history } ``` The BiomAPI web app does this automatically via the History tab. *** ## Web routes [Section titled “Web routes”](#web-routes) | Route | Description | | -------------------- | -------------------------------------------------------------------- | | `GET /` | Main web application (SPA) | | `GET /pin/{biompin}` | Direct BiomPIN access — pre-fills the BiomPIN tab and auto-retrieves | | `GET /docs/` | This documentation site | | `GET /apidocs` | Swagger UI (interactive API explorer) | | `GET /openapi.json` | OpenAPI 3.0 spec (machine-readable, powers Swagger UI) | | `GET /llms.txt` | LLM-readable site index (also `/llms-full.txt`, `/llms-small.txt`) | # Rate Limits > Per-engine daily quotas for public and authenticated access. ## Four engine buckets [Section titled “Four engine buckets”](#four-engine-buckets) BiomAPI tracks usage across four independent engine types: | Engine | What it covers | | ------------- | ----------------------------------------------------- | | `biomai` | PDF/image extraction using shared Gemini quota | | `biomai_byok` | PDF/image extraction using your own Gemini key (BYOK) | | `biomjson` | JSON upload validation (no LLM call) | | `retrieve` | BiomPIN retrieval | *** ## Public limits (per IP per day) [Section titled “Public limits (per IP per day)”](#public-limits-per-ip-per-day) | Engine | Default limit | | ------------- | ------------- | | `biomai` | 30 | | `biomai_byok` | 1,000 | | `biomjson` | 300 | | `retrieve` | 1,000 | *** ## Authenticated limits (per user per day) [Section titled “Authenticated limits (per user per day)”](#authenticated-limits-per-user-per-day) Configured per API key in `API_KEYS_JSON`. Typical quota: | Engine | Default | | ------------- | ------- | | `biomai` | 300 | | `biomai_byok` | 3,000 | | `biomjson` | 3,000 | | `retrieve` | 10,000 | Authenticated requests are tracked by user ID, not IP. Both IP and user counters are maintained; whichever applies is checked. *** ## Rate limit window [Section titled “Rate limit window”](#rate-limit-window) All limits use a **24-hour sliding window** (not a midnight calendar reset). Usage timestamps roll off exactly 24 hours after they were recorded, giving a continuously refreshing window. *** ## Rate limits are applied after processing [Section titled “Rate limits are applied after processing”](#rate-limits-are-applied-after-processing) Rate limits are consumed only after a **successful** extraction. Files that fail validation, parse errors, or LLM failures do not count against your quota — only successful results do. *** ## Checking your usage [Section titled “Checking your usage”](#checking-your-usage) ```bash GET /api/v1/biom/usage ``` ```json { "auth_type": "public", "user_id": null, "engines": { "biomai": { "used": 5, "limit": 30, "resets_in_seconds": 72400 }, "biomai_byok": { "used": 0, "limit": 1000, "resets_in_seconds": 72400 }, "biomjson": { "used": 2, "limit": 300, "resets_in_seconds": 72400 }, "retrieve": { "used": 12, "limit": 1000, "resets_in_seconds": 72400 } }, "keys": { "biomapi": { "provided": false }, "gemini": { "provided": false } } } ``` *** ## Rate limit errors [Section titled “Rate limit errors”](#rate-limit-errors) When a limit is exceeded, the API returns HTTP `429 Too Many Requests`: ```json { "success": false, "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded for biomai engine" } } ``` The `resets_in_seconds` field in `/usage` tells you how long until the oldest tracked request rolls off. # Response Schema > Complete field reference for StandardAPIResponse — the shape returned by all BiomAPI endpoints. All successful responses from `POST /api/v1/biom/process` and `GET /api/v1/biom/retrieve` return a `StandardAPIResponse` object with four top-level fields. For annotated examples, see the [Swagger UI at /apidocs](/apidocs). *** ## Top-level structure [Section titled “Top-level structure”](#top-level-structure) ```plaintext StandardAPIResponse ├── data: BiometryReport (always present) ├── extra_data: ExtraReport | null (present for PK-capable devices) ├── metadata: ResponseMetadata (always present) └── biompin: BiomPINInfo | null (present when BiomPIN was requested) ``` *** ## `data` — BiometryReport [Section titled “data — BiometryReport”](#data--biometryreport) Core biometry data. Always present. ### `data.biometer` [Section titled “data.biometer”](#databiometer) | Field | Type | Required | Values | | -------------- | ---- | -------- | ----------------------------------- | | `device_name` | enum | Yes | See [Device names](#device-names) | | `manufacturer` | enum | Yes | See [Manufacturers](#manufacturers) | ### `data.patient` [Section titled “data.patient”](#datapatient) All patient fields are optional — they may be `null` if not present in the source file. | Field | Type | Description | | --------------- | ----------------------- | ----------------------------------------------------------------------------------------------- | | `name` | string \| null | Automatically converted to all-caps initials for privacy (e.g., `"John Douglas Doe"` → `"JDD"`) | | `id` | string \| null | Patient ID or medical record number | | `date_of_birth` | date (ISO 8601) \| null | e.g., `"1965-03-15"` | | `gender` | enum \| null | `"Male"` or `"Female"` | ### `data.right_eye` / `data.left_eye` — BiometricData [Section titled “data.right\_eye / data.left\_eye — BiometricData”](#dataright_eye--dataleft_eye--biometricdata) All measurement fields are optional (`null` if not measurable or not present in the source). | Field | Unit | Valid range | Description | | -------------------- | ------------ | ------------- | ------------------------------------------------------------------------ | | `AL` | mm | 14 – 40 | Axial length | | `ACD` | mm | 1 – 6 | Anterior chamber depth | | `K1_magnitude` | D | 20 – 99 | Flat keratometry power | | `K1_axis` | ° | 0 – 180 | K1 axis | | `K2_magnitude` | D | 20 – 99 | Steep keratometry power | | `K2_axis` | ° | 0 – 180 | K2 axis | | `WTW` | mm | 7 – 15 | White-to-white corneal diameter | | `LT` | mm | 0.1 – 7 | Crystalline lens thickness | | `CCT` | µm | 200 – 900 | Central corneal thickness | | `lens_status` | enum \| null | — | See [Lens status](#lens-status) | | `post_refractive` | enum \| null | — | See [Post-refractive](#post-refractive) | | `keratometric_index` | — | 1.330 – 1.338 | Index of refraction used for keratometry conversion (typically `1.3375`) | *** ## `extra_data` — ExtraReport [Section titled “extra\_data — ExtraReport”](#extra_data--extrareport) Optional additional data. `null` for devices that don’t support posterior keratometry extraction. | Field | Type | Description | | ----------------------- | ---------------------------- | --------------------------------------- | | `notes` | string \| null | Clinical notes or free-text annotations | | `posterior_keratometry` | PosteriorKeratometry \| null | PK measurements (device-dependent) | ### `extra_data.posterior_keratometry` [Section titled “extra\_data.posterior\_keratometry”](#extra_dataposterior_keratometry) Present only for devices that support PK extraction: Anterion, EyestarES900, IOLMaster700, MS39, PentacamAXL. | Field | Type | Description | | ---------------- | ------------------------ | ---------------------------------------------------------------- | | `pk_device_name` | enum \| null | Source device for PK measurement — see [PK devices](#pk-devices) | | `right_eye` | PosteriorKeratometryData | Right eye PK measurements | | `left_eye` | PosteriorKeratometryData | Left eye PK measurements | ### PosteriorKeratometryData (per eye) [Section titled “PosteriorKeratometryData (per eye)”](#posteriorkeratometrydata-per-eye) | Field | Unit | Valid range | Description | | --------------- | ---- | ----------- | --------------------------------- | | `PK1_magnitude` | D | 2 – 8 | Posterior flat keratometry power | | `PK1_axis` | ° | 0 – 180 | PK1 axis | | `PK2_magnitude` | D | 2 – 8 | Posterior steep keratometry power | | `PK2_axis` | ° | 0 – 180 | PK2 axis | *** ## `metadata` — ResponseMetadata [Section titled “metadata — ResponseMetadata”](#metadata--responsemetadata) Provenance and version information. Always present. | Field | Type | Description | | ---------------- | ------------------------------------ | ------------------------------------------------- | | `schema_version` | string | `BiometryReport` schema version (e.g., `"1.0.0"`) | | `app_version` | string | BiomAPI server version (e.g., `"0.9.8.1"`) | | `extraction` | BiomAIMetadata \| BiomDIRECTMetadata | Discriminated union — see below | ### `metadata.extraction` — discriminated union [Section titled “metadata.extraction — discriminated union”](#metadataextraction--discriminated-union) The type is determined by `method`: | `method` value | Type | When | | -------------- | ------------------ | ----------------------------------------------------- | | `"BiomAI"` | BiomAIMetadata | Extracted from PDF or image by Gemini LLM | | `"BiomDIRECT"` | BiomDIRECTMetadata | Manual entry, script, EHR export, or any non-LLM JSON | ### Common base fields (both types) [Section titled “Common base fields (both types)”](#common-base-fields-both-types) | Field | Type | Description | | ----------- | ---------------------------- | -------------------------------------- | | `method` | `"BiomAI"` \| `"BiomDIRECT"` | Discriminator | | `timestamp` | datetime (ISO 8601) | When the extraction was performed | | `filename` | string \| null | Original filename of the uploaded file | ### BiomAIMetadata (additional fields) [Section titled “BiomAIMetadata (additional fields)”](#biomaimetadata-additional-fields) | Field | Type | Description | | ---------------------- | -------------------------- | --------------------------------------------------------------------------------------------------- | | `byok` | bool | `true` if the user supplied their own Gemini key | | `llm` | string | Gemini model used (e.g., `"gemini-flash-latest"`) | | `llm_api_metrics` | LLMApiUsageMetrics \| null | Token usage from the Gemini API | | `llm_performance` | LLMPerformanceMetrics | Timing and retry counts | | `input_schema_version` | string \| null | Schema version of the uploaded JSON on round-trip re-upload; `null` for fresh PDF/image extractions | #### LLMApiUsageMetrics [Section titled “LLMApiUsageMetrics”](#llmapiusagemetrics) | Field | Type | Description | | ---------------------------- | ------------------- | ---------------------------------------------- | | `prompt_token_count` | int \| null | Total input tokens (includes cached) | | `cached_content_token_count` | int \| null | Tokens served from cache | | `candidates_token_count` | int \| null | Output tokens generated | | `thoughts_token_count` | int \| null | Internal thinking tokens (if thinking enabled) | | `total_token_count` | int \| null | Sum of all tokens | | `cache_hit_ratio` | float (0–1) \| null | Cached / total prompt tokens | #### LLMPerformanceMetrics [Section titled “LLMPerformanceMetrics”](#llmperformancemetrics) | Field | Type | Description | | --------------------------- | ----- | -------------------------------------- | | `llm_response_time_seconds` | float | Time for the Gemini API call (seconds) | | `retry_attempts` | int | Number of retries due to 429/503 | | `total_retry_delay_seconds` | float | Total wait time spent on retries | ### BiomDIRECTMetadata (additional fields) [Section titled “BiomDIRECTMetadata (additional fields)”](#biomdirectmetadata-additional-fields) | Field | Type | Description | | ---------------------- | ------ | ------------------------------------------------------ | | `input_schema_version` | string | Schema version of the input payload (always populated) | *** ## `biompin` — BiomPINInfo [Section titled “biompin — BiomPINInfo”](#biompin--biompininfo) Present when a BiomPIN was generated (`biompin=true` in the request). `null` otherwise. | Field | Type | Description | | ------------ | ----------------- | ------------------------------------------------------------------ | | `pin` | string | Full PIN code: `word-word-123456` | | `expires_at` | string (ISO 8601) | When the PIN expires (default: 31 days from creation) | | `db_id` | string | Database instance identifier — use to detect stale history entries | *** ## Enum reference [Section titled “Enum reference”](#enum-reference) ### Device names [Section titled “Device names”](#device-names) | Value | Device | Manufacturer | | ---------------- | ------------------- | ------------ | | `"Aladdin"` | Aladdin | Topcon | | `"Anterion"` | Anterion | Heidelberg | | `"Argos"` | Argos | Alcon | | `"IOLMaster700"` | IOLMaster 700 | Zeiss | | `"LenstarLS900"` | Lenstar LS 900 | HaagStreit | | `"EyestarES900"` | Eyestar ES 900 | HaagStreit | | `"MS39"` | MS-39 | CSO | | `"OA-2000"` | OA-2000 | Tomey | | `"PentacamAXL"` | Pentacam AXL | Oculus | | `"Other"` | Unrecognized device | — | ### Manufacturers [Section titled “Manufacturers”](#manufacturers) `"Alcon"`, `"CSO"`, `"HaagStreit"`, `"Heidelberg"`, `"Oculus"`, `"Tomey"`, `"Topcon"`, `"Zeiss"`, `"Other"` ### Lens status [Section titled “Lens status”](#lens-status) | Value | Description | | ---------------- | -------------------------------------------------- | | `"Phakic"` | Natural crystalline lens present | | `"Phakic IOL"` | Natural lens present with an additional phakic IOL | | `"Pseudophakic"` | Natural lens replaced with IOL | | `"Aphakic"` | No lens present | ### Post-refractive [Section titled “Post-refractive”](#post-refractive) | Value | Description | | --------------------- | -------------------------------------------------- | | `"None"` | No prior refractive surgery | | `"Myopic LVC"` | Myopic laser vision correction (LASIK, PRK, LASEK) | | `"Hyperopic LVC"` | Hyperopic laser vision correction | | `"Radial Keratotomy"` | Radial keratotomy | ### PK devices [Section titled “PK devices”](#pk-devices) `"Anterion"`, `"EyestarES900"`, `"Galilei"`, `"IOLMaster700"`, `"MS39"`, `"Pentacam"`, `"Other"` # CLI > Zero-dependency command-line client for BiomAPI — works standalone and as an AI agent skill. The BiomAPI CLI is a zero-dependency Python script that calls the REST API and formats results for the terminal. It also works as an **Agent Skill** for Claude Code and Codex CLI, letting AI assistants process biometry reports directly in conversation. ## Supported devices [Section titled “Supported devices”](#supported-devices) | Device | Manufacturer | PK Support | | -------------- | ------------ | ---------- | | Aladdin | Topcon | — | | Anterion | Heidelberg | Yes | | Argos | Alcon | — | | EyeStar ES900 | Haag-Streit | Yes | | IOLMaster 700 | Zeiss | Yes | | Lenstar LS 900 | Haag-Streit | — | | MS-39 | CSO | Yes | | OA-2000 | Tomey | — | | Pentacam AXL | Oculus | Yes | PK Support = posterior keratometry extraction for toric IOL formulas. *** ## Installation [Section titled “Installation”](#installation) ### Standalone [Section titled “Standalone”](#standalone) Download `biomapi.py` — no pip, no dependencies, pure Python 3.11+: ```bash python biomapi.py status python biomapi.py process report.pdf ``` ### Claude Code plugin [Section titled “Claude Code plugin”](#claude-code-plugin) ```bash /plugin marketplace add mglraimundo/biomapi-cli /plugin install biomapi-cli@mglraimundo-biomapi-cli ``` ### Codex CLI [Section titled “Codex CLI”](#codex-cli) ```bash cp -r skills/biomapi ~/.codex/skills/ ``` Source repo: [mglraimundo/biomapi-cli](https://github.com/mglraimundo/biomapi-cli) *** ## Configuration [Section titled “Configuration”](#configuration) Interactive setup (saves to `~/.config/biomapi/config`): ```bash python biomapi.py configure ``` Or set directly: ```bash python biomapi.py configure --key biom_your_key_here python biomapi.py configure --gemini-key AIza_your_key python biomapi.py configure --show ``` Priority: CLI flags > environment variables (`BIOMAPI_KEY`, `GEMINI_API_KEY`) > config file. *** ## Commands [Section titled “Commands”](#commands) ### `process` [Section titled “process”](#process) ```bash python biomapi.py process report.pdf python biomapi.py process *.pdf # Multiple files (concurrent) python biomapi.py process report.pdf --no-pin ``` Output includes patient name (as acronym), BiomPIN, BiomAPI URL, and ESCRS IOL Calculator link. ### `retrieve` [Section titled “retrieve”](#retrieve) ```bash python biomapi.py retrieve lunar-rocket-731904 ``` ### `csv` [Section titled “csv”](#csv) Generate a by-eye CSV from one or more BiomPINs: ```bash python biomapi.py csv lunar-rocket-731904 solar-river-482910 > export.csv ``` ### `usage` [Section titled “usage”](#usage) ```bash python biomapi.py usage python biomapi.py usage --validate # Also verifies your Gemini key ``` ### `status` [Section titled “status”](#status) ```bash python biomapi.py status ``` ### `configure` [Section titled “configure”](#configure) ```bash python biomapi.py configure --key biom_... python biomapi.py configure --gemini-key AIza_... python biomapi.py configure --show ``` *** ## Access tiers [Section titled “Access tiers”](#access-tiers) | `BIOMAPI_KEY` | `GEMINI_API_KEY` | `process` limit | `retrieve` limit | | ------------- | ---------------- | ----------------------------- | ---------------- | | — | — | 30/day per IP | 1,000/day per IP | | ✓ | — | Custom quota | Custom quota | | — | ✓ | Unlimited (your Gemini quota) | 1,000/day per IP | | ✓ | ✓ | Unlimited (your Gemini quota) | Custom quota | # Getting Started > Set up and use BiomAPI in minutes — web interface, REST API, or CLI. ## Web interface [Section titled “Web interface”](#web-interface) Visit the BiomAPI web app and upload a biometry PDF or image. No account required — just drop a file and get structured measurements instantly. * **Upload tab** — single file, automatic engine selection * **Batch tab** — up to many files processed concurrently * **BiomPIN tab** — retrieve a previously shared result * **Transcribe tab** — manual data entry (BiomDIRECT) * **History tab** — previously retrieved BiomPINs (stored locally) ## REST API [Section titled “REST API”](#rest-api) ### Process a file [Section titled “Process a file”](#process-a-file) ```bash curl -X POST https://biomapi.com/api/v1/biom/process \ -F "file=@report.pdf" ``` Response: ```json { "data": { "biometer": { "device_name": "IOLMaster700", "manufacturer": "Zeiss" }, "patient": { "name": "JD", "id": "12345", "date_of_birth": "1965-03-15", "gender": "Male" }, "right_eye": { "AL": 23.45, "ACD": 3.12, "K1_magnitude": 43.25, "K1_axis": 5, "K2_magnitude": 44.50, "K2_axis": 95, "WTW": 11.8, "LT": 4.52, "CCT": 545, "lens_status": "Phakic", "post_refractive": "None", "keratometric_index": 1.3375 }, "left_eye": { "..." : "..." } }, "extra_data": null, "metadata": { "schema_version": "1.0.0", "app_version": "0.9.8.1", "extraction": { "method": "BiomAI", "timestamp": "2025-01-15T10:30:00Z", "filename": "report.pdf", "byok": false, "llm": "gemini-flash-latest", "llm_api_metrics": { "total_token_count": 1700, "..." : "..." }, "llm_performance": { "llm_response_time_seconds": 2.5, "retry_attempts": 0, "total_retry_delay_seconds": 0.0 } } }, "biompin": { "pin": "lunar-rocket-731904", "expires_at": "2025-02-15T10:30:00Z", "db_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } } ``` See [Response Schema](../api/response-schema/) for the complete field reference. ### Retrieve by BiomPIN [Section titled “Retrieve by BiomPIN”](#retrieve-by-biompin) ```bash curl "https://biomapi.com/api/v1/biom/retrieve?biom_pin=lunar-rocket-731904" ``` ### Check API status [Section titled “Check API status”](#check-api-status) ```bash curl https://biomapi.com/api/v1/status ``` ## CLI [Section titled “CLI”](#cli) Install the zero-dependency CLI and process files from the terminal: ```bash python biomapi.py process report.pdf ``` See the [CLI guide](../cli/) for installation and full usage. ## Authentication [Section titled “Authentication”](#authentication) All endpoints work without an API key (with lower rate limits). For production integrations, pass your BiomAPI key to get higher limits: ```bash curl -X POST https://biomapi.com/api/v1/biom/process \ -H "Authorization: Bearer biom_your_key_here" \ -F "file=@report.pdf" ``` See [Authentication](../api/authentication/) for details. ## BYOK (Bring Your Own Gemini Key) [Section titled “BYOK (Bring Your Own Gemini Key)”](#byok-bring-your-own-gemini-key) Use your own Google Gemini API key to bypass the shared quota entirely: ```bash curl -X POST https://biomapi.com/api/v1/biom/process \ -H "X-Gemini-API-Key: AIza_your_key" \ -F "file=@report.pdf" ``` # How It Works > Architecture and design principles behind BiomAPI — engines, security, rate limiting, and response structure. ## Overview [Section titled “Overview”](#overview) BiomAPI receives a biometry file, routes it to the appropriate extraction engine, returns structured data in a standardized response, and optionally encrypts and stores it under a BiomPIN for secure sharing. Every successful response follows the same `StandardAPIResponse` shape regardless of which engine processed the file. *** ## File Routing [Section titled “File Routing”](#file-routing) When you `POST /api/v1/biom/process`, the server inspects the file extension and selects one of two engines: | Extension | Engine | What happens | | ----------------------- | -------- | ----------------------------------------- | | PDF, JPG, PNG, GIF, BMP | BiomAI | LLM extraction via Gemini | | JSON | BiomJSON | Schema validation + metadata preservation | Routing is purely by extension — no content sniffing. Unsupported extensions are rejected immediately with 400. *** ## The Three Processing Paths [Section titled “The Three Processing Paths”](#the-three-processing-paths) ### BiomAI — LLM Extraction [Section titled “BiomAI — LLM Extraction”](#biomai--llm-extraction) BiomAI transmits the file bytes directly to the Google Gemini API alongside an extraction prompt. Gemini responds with a structured `BiometryReport` JSON. The server validates this through Pydantic and wraps it in a `StandardAPIResponse`. * Images are sent at HIGH resolution; PDFs at MEDIUM * If Gemini returns 429 or 503 (overload), the server retries up to 3 times with exponential backoff and jitter before returning an error to the caller * A hard server-side timeout is applied (default 30 s); breach returns 504 * BYOK: pass `X-Gemini-API-Key` to use your own Gemini quota — the server creates a temporary extraction client with your key and tracks usage under a separate `biomai_byok` rate limit bucket ### BiomJSON — Validation and Round-Trips [Section titled “BiomJSON — Validation and Round-Trips”](#biomjson--validation-and-round-trips) BiomJSON validates JSON payloads against the `BiometryReport` schema. It’s the engine for the round-trip workflow: extract a PDF with BiomAI → download JSON → edit locally → re-upload. **Metadata preservation:** When a re-uploaded JSON contains BiomAI provenance metadata, BiomJSON reconstructs and preserves it — you don’t lose the original LLM metrics (model, token counts, timing) just because the data passed through an editor. The `input_schema_version` field is populated with the schema version declared in the uploaded JSON, making schema drift detectable. If the uploaded JSON has no recognizable BiomAI provenance (or comes from a different origin), it’s attributed as `BiomDIRECT`. **Schema versioning:** BiomJSON checks that the major version of the uploaded JSON’s `schema_version` matches the server’s current version. Minor/patch differences are tolerated; major version mismatch returns 422. ### BiomDIRECT — Direct Data Entry [Section titled “BiomDIRECT — Direct Data Entry”](#biomdirect--direct-data-entry) BiomDIRECT is an attribution label, not a separate engine. Any biometry data not extracted by the Gemini LLM is tagged `method: "BiomDIRECT"` in the response metadata: * Manual transcription via the web UI Transcribe tab * JSON constructed by an external script, EHR export, or automated pipeline * Re-uploaded JSONs without BiomAI provenance BiomDIRECT metadata is minimal: `method`, `timestamp`, `filename`, and `input_schema_version`. This creates a complete audit trail — every result is attributable to either an LLM run or a direct data construction. *** ## BiomPIN — Secure Sharing [Section titled “BiomPIN — Secure Sharing”](#biompin--secure-sharing) After any successful extraction, the server can optionally encrypt the full response and store it under a PIN. BiomPIN is opt-in (pass `biompin=true` in the request). ### Two-part PIN design [Section titled “Two-part PIN design”](#two-part-pin-design) ```plaintext word-word - 123456 └──────┘ └────┘ share_id numeric PIN (stored) (never stored) ``` The `word-word` share ID is the database primary key — it’s stored in plain text and used to look up the record. The 6-digit numeric PIN is the encryption secret; it is **never stored** anywhere on the server. The decryption key is derived from the numeric PIN using Argon2id (a memory-hard key derivation function), with the SHA-256 hash of the share ID as salt. Because the numeric PIN is never stored, the server cannot decrypt data without it. Even full database read access doesn’t compromise stored biometry data. ### Brute-force protection [Section titled “Brute-force protection”](#brute-force-protection) After **3 wrong numeric PIN attempts**, the database record is **permanently deleted**. This eliminates the stored ciphertext, making further brute-force attempts pointless. The response is 404 — there is no lockout period. ### Expiry and cleanup [Section titled “Expiry and cleanup”](#expiry-and-cleanup) Records expire after 744 hours (31 days) by default. Expired records are purged lazily after each new store operation — there is no background cleanup process. *** ## Rate Limiting [Section titled “Rate Limiting”](#rate-limiting) BiomAPI tracks usage across four independent engine buckets: | Bucket | Covers | | ------------- | ------------------------------------------------ | | `biomai` | PDF/image extraction, shared server Gemini quota | | `biomai_byok` | PDF/image extraction, user-supplied Gemini key | | `biomjson` | JSON validation — no LLM call | | `retrieve` | BiomPIN retrieval | **Post-processing application:** Rate limits are consumed only after a *successful* operation. A file that fails validation, triggers an LLM error, or times out does not count against your quota. This is intentional — failed attempts shouldn’t penalize legitimate usage. **Dual tracking:** Every request is tracked by both the client IP and the authenticated user ID (if present). Public callers share per-IP limits; authenticated callers have custom per-user quotas. Both are checked independently. **Sliding window:** The window is a continuous 24 hours (not a midnight calendar reset). Usage timestamps roll off exactly 24 hours after recording. *** ## Response Structure [Section titled “Response Structure”](#response-structure) Every successful response is a `StandardAPIResponse` with four top-level fields: ```plaintext data → BiometryReport (biometer, patient, right_eye, left_eye) extra_data → ExtraReport | null (notes, posterior_keratometry) metadata → ResponseMetadata (schema_version, app_version, extraction) biompin → BiomPINInfo | null (pin, expires_at, db_id) ``` **Why `data` and `extra_data` are separate:** `BiometryReport` in `data` contains the 12 core measurements present on virtually every device. `extra_data` holds optional, device-dependent fields — currently posterior keratometry (PK1/PK2) and notes. This separation means adding new optional fields doesn’t require bumping the core schema version. **Why the metadata discriminated union:** `metadata.extraction` is either `BiomAIMetadata` (with full LLM metrics) or `BiomDIRECTMetadata` (minimal). Clients can branch on `method` to decide whether to surface token usage, processing time, etc. The type is determined by what actually happened, not by the endpoint called. **The `db_id` field:** `biompin.db_id` (and `GET /api/v1/status`’s `db_id`) identifies the database instance. It’s stable across server restarts but changes when the BiomPIN database is wiped. Client apps should check this value on startup to detect a database reset and purge stale local history entries. The BiomAPI web app handles this automatically. # LLMs & Coding Agents > Machine-readable API resources for LLMs and coding agents integrating with BiomAPI. BiomAPI exposes several machine-readable resources designed for LLMs and coding agents building integrations. *** ## OpenAPI spec [Section titled “OpenAPI spec”](#openapi-spec) FastAPI auto-generates a full OpenAPI 3.0 spec at: ```plaintext GET /openapi.json ``` This is the canonical machine-readable description of every endpoint, request/response schema, authentication scheme, and error code. Feed it to any LLM or agent that needs to understand the API before writing code against it. The [interactive Swagger UI](/apidocs) is built from the same spec. *** ## `llms.txt` — site index for LLMs [Section titled “llms.txt — site index for LLMs”](#llmstxt--site-index-for-llms) Three variants of the documentation site index are available at the root: | URL | Contents | | ------------------------------------------------------- | ------------------------------------------------------- | | [`/llms.txt`](https://biomapi.com/llms.txt) | Short index — page titles and descriptions | | [`/llms-full.txt`](https://biomapi.com/llms-full.txt) | Full page content in plain text | | [`/llms-small.txt`](https://biomapi.com/llms-small.txt) | Condensed version optimized for smaller context windows | These follow the [llms.txt convention](https://llmstxt.org/) and are auto-generated from the documentation source by the Starlight build. Use them to give an LLM a fast overview of what BiomAPI does and how to use it without browsing individual pages. *** ## CLI as an agent skill [Section titled “CLI as an agent skill”](#cli-as-an-agent-skill) The BiomAPI CLI (`biomapi.py`) is distributed as an **Agent Skill** — a structured plugin that Claude Code and Codex CLI can install and invoke directly in conversation. ### Claude Code [Section titled “Claude Code”](#claude-code) ```bash /plugin marketplace add mglraimundo/biomapi-cli /plugin install biomapi-cli@mglraimundo-biomapi-cli ``` Once installed, the agent can call `biomapi process`, `biomapi retrieve`, `biomapi csv`, and other commands without leaving the conversation. ### Codex CLI [Section titled “Codex CLI”](#codex-cli) ```bash cp -r skills/biomapi ~/.codex/skills/ ``` ### What the skill provides [Section titled “What the skill provides”](#what-the-skill-provides) The skill bundles: * `biomapi.py` — zero-dependency Python script (only stdlib; no pip) * `SKILL.md` — agent-readable capability description and invocation guide * `reference.md` — API reference formatted for agent context Source: [mglraimundo/biomapi-cli](https://github.com/mglraimundo/biomapi-cli) # Web App > How to use the BiomAPI web interface — tabs, results, editing, and downloads. The BiomAPI web app is a single-page application available at the root URL (`/`). No account required — drop a file and get structured results instantly. *** ## Input Tabs [Section titled “Input Tabs”](#input-tabs) The top section has five tabs for different input methods. ### Upload [Section titled “Upload”](#upload) Process a single biometry file. Drag and drop, click to browse, or paste a file. The extraction engine is selected automatically by file extension: * **PDF, JPG, PNG, GIF, BMP** → BiomAI (LLM extraction) * **JSON** → BiomJSON (schema validation, preserves original metadata) **Share toggle** — enables BiomPIN generation for the result. Off by default. ### Batch [Section titled “Batch”](#batch) Process multiple files at once. Drop a folder or select several files together. Files are processed concurrently up to the server’s concurrency limit. Each file gets an individual result card with inline progress. Results are added to the results panel as they complete. After batch processing, the **Export** button downloads a ZIP with all results: * A by-eye CSV (`biomapi-export-YYYYMMDD-HHMMSS.csv`) * Individual JSON files per report ### BiomPIN [Section titled “BiomPIN”](#biompin) Retrieve a previously processed result using a BiomPIN code (`word-word-123456`). The 6-digit numeric suffix is the decryption key — entering only the `word-word` portion returns an error. You can also navigate directly to `https://biomapi.com/pin/word-word-123456` — the app pre-fills the PIN field and retrieves automatically. Caution After 3 wrong numeric PIN attempts, the record is permanently deleted and returns 404. ### Transcribe [Section titled “Transcribe”](#transcribe) Manual data entry (BiomDIRECT). Fill in device info, patient demographics, and per-eye measurements directly. Useful when a PDF isn’t available or for entering data from a printout. Results from Transcribe are tagged `method: "BiomDIRECT"` in the response metadata. All measurement fields are optional — enter only what you have. ### History [Section titled “History”](#history) Recently retrieved BiomPINs are saved in your browser’s `localStorage`. History entries show the patient name/ID, device, expiry time, and PIN code. Click any entry to retrieve it again. **Stale detection:** If the BiomAPI database is reset, stored PINs become invalid. On startup, the web app compares the `db_id` from `GET /api/v1/status` against the value stored with each history entry. Entries from a previous database instance are automatically removed. *** ## Results Display [Section titled “Results Display”](#results-display) After any successful processing or retrieval, the results panel shows: ### Data tab [Section titled “Data tab”](#data-tab) Two-column table (OD / OS) with all extracted fields: * Biometer: device name, manufacturer * Patient: name (as initials), ID, date of birth, gender * Per-eye: AL, ACD, K1/K2 (magnitude + axis), WTW, LT, CCT, lens status, post-refractive, keratometric index * Posterior keratometry (PK1/PK2) if available ### Metadata tab [Section titled “Metadata tab”](#metadata-tab) Extraction provenance: * `method` — BiomAI or BiomDIRECT * `timestamp`, `filename` * For BiomAI results: LLM model, token usage (prompt, candidates, cached, thinking), response time, retry count ### BiomPIN card [Section titled “BiomPIN card”](#biompin-card) If a BiomPIN was generated, the card shows: * The full PIN (`word-word-123456`) — click to copy * A QR code — scan with a mobile device to open the result directly * Expiry date ### ESCRS button [Section titled “ESCRS button”](#escrs-button) Opens the ESCRS IOL power calculation platform pre-filled with the extracted biometry data. *** ## Edit Modal [Section titled “Edit Modal”](#edit-modal) Click **Edit** on any result to open the full edit form. All fields are editable: * **Device**: `device_name` (required), `manufacturer` (required) * **Patient**: name, ID, date of birth, gender (all optional) * **Per eye**: all 12 measurement fields (all optional) * **Notes** and posterior keratometry **Unsaved changes protection** — if you try to close the modal with unsaved changes, a confirmation dialog appears. Submitting the edit re-processes the data and returns a new result tagged `BiomDIRECT`. If the original result was BiomAI-extracted, the original LLM metadata is preserved in `input_schema_version`. *** ## Status Bar [Section titled “Status Bar”](#status-bar) The fixed bar at the bottom of the screen shows your current authentication and usage state. | Element | Description | | ------------------ | ------------------------------------------------------------------------------------- | | **BiomAPI key** | Enter your key for higher per-user rate limits. Stored in `localStorage`. | | **Gemini key** | BYOK — uses your own Gemini quota for PDF/image extraction. Stored in `localStorage`. | | **Usage counters** | Live per-engine used/limit display, updated after each request. | | **Auth status** | Shows whether you’re authenticated (green) and your user ID. | Both keys are validated by the app via `GET /api/v1/biom/usage` when entered. A green indicator means the key is valid; red means invalid or not provided. *** ## Downloads [Section titled “Downloads”](#downloads) ### Single JSON [Section titled “Single JSON”](#single-json) Download the full `StandardAPIResponse` JSON for any individual result using the **Download** button on the result card. ### ZIP export [Section titled “ZIP export”](#zip-export) From the Batch tab after processing, click **Export** to download a ZIP containing: * `biomapi-export-YYYYMMDD-HHMMSS.csv` — by-eye CSV (two rows per report: OD + OS) * Individual JSON files with smart filenames: `biomapi-{patientId}-{device}.json`