Skip to main content

dcm-anon-vault

A hosted, single-tenant DICOM pseudonymization API. Upload DICOM files over HTTP, get back a ZIP of pseudonymized outputs plus a tamper-evident audit log. Stripe billing is built in for pay-as-you-go tier enforcement.

Wording note — In DICOM and clinical-research practice the process is colloquially called "anonymization" (and the upstream engine ships as dcm-anonymizer). Under EU GDPR (Recital 26 + WP29 Opinion 05/2014 + EDPB Guidelines 01/2025) the OUTPUT of PS3.15 Basic Profile is pseudonymized personal data, NOT anonymized. We use "pseudonymization" throughout this README and in all customer-facing copy; "anonymize" appears only as the technical verb on DICOM tags.


1. What it is

dcm-anon-vault wraps the dcm-anonymizer engine (PS3.15 Basic Confidentiality Profile, PyPI: dcm-anonymizer) in a FastAPI service. You deploy one instance per customer (single-tenant), point it at a SQLite volume, and give each customer an API key. The service enforces per-tier file quotas, logs every pseudonymization event with a SHA-256 audit chain, and integrates with Stripe for upgrade billing.

All PHI scrubbing is done by dcm-anonymizer, which implements the DICOM PS3.15 Basic Application Level Confidentiality Profile. No data ever leaves the machine you deploy on.

UID re-mapping is deterministic per customer (we use SHA-256(api_key) as the engine salt), so re-running the same source study produces the same target SOPInstanceUID / PatientID — enabling longitudinal cohort linkage that random UIDs would destroy.


2. Why hosted vs running the CLI locally

Concern CLI (pip install dcm-anonymizer) Vault (hosted)
Audit log retention You manage the JSON file Persisted to SQLite, queryable
Multi-user access Manual key sharing Per-customer API keys + tiers
Billing Manual invoicing Stripe Checkout built in
Deployment Per-workstation install Deploy once on Fly.io or your VPS
CI / pipeline integration Possible but fragile POST /v1/anonymize from any client

If you process fewer than 50 DICOMs per month, the free tier is sufficient. If you need auditability across a team, or want to gate access by subscription, the hosted vault is the right tool.


3. Tiers (subject to change pre-1.0)

Tier Price Quota Isolation
Free €0/mo 50 files/month Shared single-tenant
Pro €99/mo Fair-use 10K files/mo Single-tenant (your own deploy)
Annual €999/yr Fair-use 10K files/mo Same as Pro; ~17 % off
Enterprise Contact SLA, BAA, async jobs, SSO Isolated VPS, dedicated support

Free-tier customers receive 429 Too Many Requests with a Retry-After header once they hit the monthly cap. Upgrading via Stripe Checkout flips the tier in the database on signed-webhook receipt.

A 14-day Pro trial is enabled by default (STRIPE_TRIAL_DAYS=14).


4. Deploy in 5 minutes on Fly.io

Prerequisites: flyctl installed and authenticated.

git clone https://github.com/Ces107/dcm-anon-vault
cd dcm-anon-vault

fly apps create dcm-anon-vault
fly volumes create vault_data --size 1 --region cdg

fly secrets set \
  DCM_API_KEYS="customer1:$(openssl rand -hex 32)" \
  STRIPE_API_KEY="sk_test_REPLACE_ME" \
  STRIPE_PRICE_ID="price_REPLACE_ME" \
  STRIPE_PRICE_ID_ANNUAL="price_REPLACE_ME" \
  STRIPE_WEBHOOK_SECRET="whsec_REAL_SECRET_HERE"

fly deploy
curl https://dcm-anon-vault.fly.dev/health

Required Stripe configuration before any paid customer:

  • Monthly price → STRIPE_PRICE_ID.
  • Optional annual price → STRIPE_PRICE_ID_ANNUAL.
  • Webhook endpoint at https://<your-app>/v1/billing/webhook pointing at the events: checkout.session.completed. Copy the signing secret into STRIPE_WEBHOOK_SECRET — the service refuses to process unsigned events.

5. API quick reference

# Pseudonymize one or more DICOMs (returns ZIP)
curl -X POST https://<host>/v1/anonymize \
  -H "X-API-Key: <your-key>" \
  -F "files=@scan.dcm" \
  --output result.zip

# Check usage / quota for the current UTC month
curl https://<host>/v1/usage -H "X-API-Key: <your-key>"

# Start a Stripe Checkout upgrade (monthly or annual)
curl -X POST https://<host>/v1/billing/checkout-session \
  -H "X-API-Key: <your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "success_url":"https://example.com/success",
    "cancel_url":"https://example.com/cancel",
    "plan":"annual",
    "customer_email":"buyer@example.com"
  }'

Response headers from /v1/anonymize carry X-Files-Processed, X-Files-Failed, X-Files-Rejected-BurnedIn, and X-Audit-Sha256.


6. Local development

cp .env.example .env          # fill in real values
pip install -e ".[dev]"
uvicorn dcm_anon_vault.app:app --reload --port 8080

python -m pytest -q
python -m ruff check src tests
python -m mypy --strict src

7. Scope, disclaimers, regulatory posture

This is a research utility. It is NOT a medical device.

dcm-anon-vault is intended for the preparation of DICOM datasets for research, software development, and educational use. It is not intended to inform clinical diagnosis or therapeutic decisions and is not a medical device under Regulation (EU) 2017/745 (MDR) Art 2(1) nor under 21 CFR Part 820. If you intend to use it as a pre-processing step in a clinical pipeline, the obligation to perform conformity assessment falls on you as the deployer / modifier.

GDPR posture. PS3.15 Basic Profile is a pseudonymization operation, not anonymization, per EDPB Guidelines 01/2025 on Pseudonymisation and WP29 Opinion 05/2014. Output remains personal data and must be handled accordingly. The hosted plane receives and briefly processes raw PHI on the customer's behalf, which makes the operator a processor under GDPR Art 4(8). A Data Processing Agreement (DPA) is required before any EU customer can be onboarded; we publish a template DPA at /legal/dpa (work in progress).

HIPAA posture. Receiving raw DICOM from a US Covered Entity makes the operator a Business Associate by operation of law (45 CFR 160.103), regardless of contract. Until a BAA programme is in place, the hosted service is not available to US Covered Entities. Self-host the service inside your own HIPAA-compliant environment instead.

Storage at rest. The audit log is stored in SQLite without disk-level encryption. Customers requiring encryption-at-rest should deploy on an encrypted volume (LUKS, KMS-backed EBS, Fly encrypted volumes) and / or substitute Postgres with TDE via DCM_DB_URL.

What this software does NOT do. No burned-in pixel-data PHI removal; we reject files declaring BurnedInAnnotation==YES with HTTP 422 rather than silently leak PHI. No SR (Structured Report) text-item deep redaction (only the engine's PS3.15 actions). No IHE BIR / ATNA audit message emission. No KMS-backed encryption. No Conformance Statement (PS3.4 §2.2) — write to us if you need one for a hospital procurement evaluation.

This README is the canonical scope statement. Marketing copy MUST match it.

Contact: plusultra.dev@proton.me · Issues: https://github.com/Ces107/dcm-anon-vault/issues


Copyright © 2026 César Pereiro García — MIT License. See NOTICE.md for upstream attribution; SECURITY.md for vulnerability reporting; CHANGELOG.md for release history.

Metadata

Release files for dcm-anon-vault 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dcm-anon-vault 0.2.1
File Size Uploaded
dcm_anon_vault-0.2.1.tar.gz 25.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dcm-anon-vault 0.2.1
File Interpreter ABI Platform
dcm_anon_vault-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 47.0 kB

Release files / dcm_anon_vault-0.2.1.tar.gz

Download URL dcm_anon_vault-0.2.1.tar.gz
Size 25.9 kB
Tags Source
SHA-256 checksum
How to use checksums
dfffda4a94e310db5cfdf9e41ab85b5fc49567779f3554ba4e9f865195bbe756
BLAKE2b-256 checksum
How to use checksums
bad5949dd62b7b402ecbaafb2d4e7d8242ababc09909d87755e66e6cbca44e2b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.0

Release files / dcm_anon_vault-0.2.1-py3-none-any.whl

Download URL dcm_anon_vault-0.2.1-py3-none-any.whl
Size 21.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5c2466cba2fef463c2618a211bc0ef00cf5fc1cef1b3fdc3b6865e0adfd8e429
BLAKE2b-256 checksum
How to use checksums
4c4f0ae70066000ea50a85a52c1817a85912348328154b54fe153b89da251aa8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.0

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page