Serving OpenMed over REST
SkillCloud & infraLets your agent run a web API for medical text analysis, finding health terms and removing patient-identifying info.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Serving OpenMed over REST skill
About this capability
Stand up OpenMed's FastAPI REST service for clinical NER, PII extraction, and de-identification, with health checks, model keep-alive/unload, optional dynamic batching, and no-PHI logging. Use when the user wants to serve OpenMed over HTTP, deploy a de-id/NER REST API, run an inference endpoint for
What this skill tells your AI
The instructions your AI receives, as published by maziyarpanahi/openmed in skills/serving-openmed-rest-api/SKILL.md and read by ahel’s review.
openmed.service is a hardened FastAPI app exposing OpenMed's NER, PII
extraction, and de-identification over HTTP. It is built to be self-hosted:
models run on-device, there's no telemetry, and the request schemas reject raw
PHI from spilling into errors. Use it when callers need request/response
inference; use batch-processing-clinical-text for corpora.
When to use this skill
To put OpenMed behind an HTTP endpoint your own apps call — an internal de-id
microservice, an NER backend, a containerized inference tier. For agent/tool
integration prefer the MCP server (deploying-openmed-mcp); for offline bulk
work use batch processing.
Quick start
pip install "openmed[service]" # FastAPI + uvicorn + pydantic
# Launch the ASGI app (factory create_app, or the module-level `app`)
uvicorn openmed.service.app:app --host 127.0.0.1 --port 8000
# Or build it in-process (e.g. to mount under a parent app / add middleware):
from openmed.service import create_app
app = create_app()
curl -s localhost:8000/health
# {"status":"ok","service":"openmed-rest","version":"...","profile":"prod"}
curl -s localhost:8000/analyze -H 'content-type: application/json' -d '{
"text": "Patient received 75mg clopidogrel for NSTEMI.",
"model_name": "disease_detection_superclinical"
}'
curl -s localhost:8000/pii/deidentify -H 'content-type: application/json' -d '{
"text": "John Doe called 555-123-4567 on 01/15/2020.",
"method": "mask"
}'
Endpoints (confirmed in openmed/service/app.py)
| Method & path | Purpose | Request schema |
|---|---|---|
GET /health | liveness + version + active profile | — |
GET /models/loaded | cache/keep-alive status of resident models | — |
POST /models/unload | unload one model or all inactive models | ModelUnloadRequest (model_name or all=true) |
POST /analyze | clinical NER | AnalyzeRequest |
POST /pii/extract | detect PII/PHI spans | PIIExtractRequest |
POST /pii/deidentify | mask/remove/replace/hash/shift-dates PHI | PIIDeidentifyRequest |
Request fields (from openmed/service/schemas.py, strict — unknown fields are
rejected):
AnalyzeRequest:text(required),model_name("disease_detection_superclinical"),confidence_threshold(0.0),group_entities,aggregation_strategy(simple|first|average|max),sentence_detection,sentence_language,sentence_clean,use_fast_tokenizer,keep_alive.PIIExtractRequest:text,model_name(defaultOpenMed/OpenMed-PII-SuperClinical-Small-44M-v1),confidence_threshold(0.5),use_smart_merging,lang(en/fr/de/it/es/nl/hi/te/pt/ar/ja/tr),normalize_accents,keep_alive.PIIDeidentifyRequest: same base plusmethod(mask|remove|replace|hash|shift_dates, defaultmask),confidence_threshold(0.7),keep_year,shift_dates,date_shift_days,keep_mapping,policy,use_smart_merging,use_safety_sweep.
Responses are the OpenMed result to_dict() (e.g. {text, entities[...], ...}).
Errors use a stable envelope: {"error": {"code", "message", "details"}} with
422 validation_error, 400 bad_request, 504 timeout, 500 internal_error.
Configuring the runtime (env vars)
ServiceRuntime.from_env() reads the process environment at startup
(openmed/service/runtime.py):
| Env var | Effect |
|---|---|
OPENMED_PROFILE | config profile (prod default) |
OPENMED_SERVICE_PRELOAD_MODELS | comma list of models to warm at startup |
OPENMED_SERVICE_KEEP_ALIVE | default idle keep-alive before unload |
OPENMED_SERVICE_MAX_RESIDENT_MODELS | cap resident models (warm pool) |
OPENMED_SERVICE_BATCHING_ENABLED | enable dynamic request batching |
OPENMED_SERVICE_BATCH_MAX_SIZE | max dynamic batch size (default 8) |
OPENMED_SERVICE_BATCH_MAX_WAIT_MS | batch-collection window (default 5ms) |
OPENMED_SERVICE_PRELOAD_MODELS="disease_detection_superclinical" \
OPENMED_SERVICE_BATCHING_ENABLED=true \
uvicorn openmed.service.app:app --host 0.0.0.0 --port 8000
Preloading avoids first-request latency; the warm pool keeps hot models resident
and idle-unloads the rest. /analyze and /pii/extract coalesce concurrent
requests when batching is enabled.
Workflow
- Install + launch.
pip install "openmed[service]", then runuvicorn openmed.service.app:app(or build withcreate_app()). - Configure the runtime via env vars before start: set
OPENMED_PROFILE, preload your hot models, and decide keep-alive / max resident / batching to fit the box. - Front it with auth/TLS. Place a reverse proxy or gateway (API keys/mTLS, CORS allow-list) ahead of the app — it has none built in.
- Health-check + warm. Poll
GET /health; preloaded models warm during the lifespan startup so the first real request isn't cold. - Call the endpoints (
/analyze,/pii/extract,/pii/deidentify) with the strict JSON schemas; handle the{"error": {...}}envelope. - Manage memory with
GET /models/loadedandPOST /models/unloadas traffic shifts between models.
Containerizing
FROM python:3.11-slim
RUN pip install --no-cache-dir "openmed[service]"
ENV OPENMED_SERVICE_PRELOAD_MODELS="disease_detection_superclinical"
EXPOSE 8000
CMD ["uvicorn", "openmed.service.app:app", "--host", "0.0.0.0", "--port", "8000"]
Bake/mount the model cache so containers don't re-download on every start; the service runs offline after that.
Hand-off to / from OpenMed
- Same engine, different surface:
/analyze→openmed.analyze_text,/pii/extract→openmed.extract_pii,/pii/deidentify→openmed.deidentify. Results match the library exactly. - Agents/tools: for Claude Code / Codex / chat clients, expose the same
capabilities as MCP tools instead (
deploying-openmed-mcp). - Bulk: for corpora, call
batch-processing-clinical-textin a worker, not per-request HTTP.
Edge cases & gotchas
- No built-in auth/CORS/TLS. The app ships hardened input validation but no
authentication. Put it behind your own reverse proxy / API gateway (mTLS,
API keys, CORS allow-list) before any real traffic. Bind
127.0.0.1for local use; only expose0.0.0.0behind that proxy. - No-PHI logging. Don't add request/response body logging — that's PHI. The error envelope is designed to avoid echoing input; keep it that way. Log status codes, timings, and model names only.
- Strict schemas. Unknown JSON fields are rejected (
extra="forbid"); a badlang/method/model_namereturns422/400with a field-level reason. - Cold start vs memory. Preloading + a high
MAX_RESIDENT_MODELStrades RAM for latency; tune to the box. - Timeouts return
504per the profile's configuredtimeout; long inputs may need a larger profile or pre-chunking. keep_mapping/policyoutputs are sensitive. A de-id response with a mapping re-identifies patients — only enable it for trusted callers and store the mapping securely, never in service logs.
Standards & references
- FastAPI: https://fastapi.tiangolo.com/
- Uvicorn (ASGI server): https://www.uvicorn.org/
- OpenAPI (the service auto-serves
/docsand/openapi.json): https://www.openapis.org/ - HIPAA de-identification, 45 CFR 164.514(b): https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/index.html
- OpenMed source:
openmed/service/app.py(routes),openmed/service/runtime.py(ServiceRuntime),openmed/service/schemas.py(request models).
Signals
- GitHub stars
- 5k
- Forks
- 666
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
serving-openmed-rest-api- Source
- github.com/maziyarpanahi/openmed