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”BiomAI and BYOK rate limits are consumed after local file validation and before the Gemini call, so attempts that reach the external API path count even if extraction fails or times out. Non-BYOK BiomAI is also protected by internal shared daily capacity limits. BiomJSON is local and only consumes quota after successful JSON validation/processing.
| Scenario | Bucket | Public default |
|---|---|---|
| PDF/image, no BYOK | biomai | 15/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 an unedited 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. Manual web edits are submitted as new latest-state BiomDIRECT payloads with source_app: "BiomAPI Webapp". If the JSON has no recognizable BiomAI provenance, it’s attributed as BiomDIRECT.
Integrations can identify direct/manual JSON sources with metadata.extraction.source_app and source_version:
{ "metadata": { "schema_version": "1.0.0", "app_version": "2.0.22", "extraction": { "method": "BiomDIRECT", "timestamp": "2026-05-24T12:00:00Z", "input_schema_version": "1.0.0", "source_app": "BiomLINK", "source_version": "0.1.0" } }}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": "2.0.22", "extraction": { "method": "BiomAI", "timestamp": "2025-01-15T10:30:00Z", "byok": false, "llm": "gemini-3.5-flash", "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.
Export requests are bounded to keep public transformation endpoints lightweight:
| Limit | Default | Environment variable |
|---|---|---|
| JSON request body size | 5 MB | EXPORT_MAX_BODY_SIZE |
| Responses per request | 1000 | EXPORT_MAX_RESPONSES |
The Content-Length header is required. The body-size limit is checked from Content-Length before JSON parsing, and the response-count limit is checked after parsing.
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).
Errors
Section titled “Errors”| Code | Condition |
|---|---|
400 | Invalid Content-Length header or empty response list |
411 | Missing Content-Length header |
413 | Request body exceeds EXPORT_MAX_BODY_SIZE or response count exceeds EXPORT_MAX_RESPONSES |
500 | CSV generation failed |
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.
The same export limits as /csv apply: EXPORT_MAX_BODY_SIZE caps the incoming JSON request body, and EXPORT_MAX_RESPONSES caps the number of response objects per archive.
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”Lightweight API status and deployment metadata. Public, no authentication required.
Use this endpoint for frequent polling when integrations need the BiomPIN database instance identifier. It does not call Gemini, open a database session, count BiomPIN records, or emit analytics.
Response
Section titled “Response”{ "status": "ok", "gemini_llm": "gemini-3.5-flash", "environment": "production", "app_version": "2.0.22", "schema_version": "1.0.0", "db_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}| Field | Description |
|---|---|
status | Always "ok" if the API process can serve the request |
gemini_llm | The Gemini model configured on the server |
environment | Deployment environment |
app_version | Running BiomAPI package version |
schema_version | Current response schema version |
db_id | BiomPIN database instance identifier |
Performance
Section titled “Performance”| Field | Cost |
|---|---|
db_id | In-memory value initialized at startup; no per-request DB lookup |
environment, app_version, schema_version, gemini_llm | Configuration/package metadata reads |
GET /api/v1/health
Section titled “GET /api/v1/health”Active health check. Public, no authentication required.
Use this endpoint for monitoring when you need live Gemini and database readiness. Avoid high-frequency polling because it may perform external API and database work.
Response
Section titled “Response”{ "status": "operational", "gemini_api_connected": true, "db_connected": true, "db_records": 42, "gemini_llm": "gemini-3.5-flash", "environment": "production", "app_version": "2.0.22", "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; this may call Gemini and is cached for 60 seconds |
db_connected | Whether the BiomPIN SQLite database manager is initialized |
db_records | Total BiomPIN records from SELECT COUNT(*) FROM share |
gemini_llm | The Gemini model configured on the server |
environment | Deployment environment |
app_version | Running BiomAPI package version |
schema_version | Current response schema version |
db_id | BiomPIN database instance identifier |
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) |