API Endpoints
Base URL: https://biomapi.com
Interactive docs (Swagger UI): /apidocs
POST /api/v1/biom/process
Section titled “POST /api/v1/biom/process”Process a biometry file and return structured measurements.
File routing
Section titled “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”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 <key> | Optional BiomAPI key — unlocks higher per-user rate limits |
X-Gemini-API-Key: <key> | Optional BYOK Gemini key — uses your own Google quota |
BiomPIN generation
Section titled “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”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 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)”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”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”{ "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 for the complete field reference.
Errors
Section titled “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”Retrieve encrypted biometry data using a BiomPIN code.
How it works
Section titled “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”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”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”Same StandardAPIResponse shape as /process. The biompin field reflects the original PIN info (pin, expiry, db_id).
Errors
Section titled “Errors”| 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 |
POST /api/v1/biom/csv
Section titled “POST /api/v1/biom/csv”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”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”{ "responses": [ { "json_data": { "...": "StandardAPIResponse object" }, "filename": "report1.pdf" }, { "json_data": { "...": "StandardAPIResponse object" }, "filename": "report2.json" } ]}Response
Section titled “Response”text/csv file download (Content-Disposition: attachment; filename=biometry_export.csv).
POST /api/v1/biom/export
Section titled “POST /api/v1/biom/export”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”| 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”Same structure as /csv: { "responses": [...] }.
Response
Section titled “Response”application/zip file download.
GET /api/v1/biom/usage
Section titled “GET /api/v1/biom/usage”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”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”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”GET /api/v1/biom/usageGET /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”{ "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”Health check. Public, no authentication required.
Response
Section titled “Response”{ "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”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:
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”| 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) |