The trust-api is the gateway service deployed at each participating healthcare Trust site. It polls the FLIP Central Hub for tasks and coordinates local operations — cohort queries, model training, and imaging project management — without exposing patient data externally.
The trust-api acts as the local orchestrator at each Trust:
- Cohort queries — polls for and executes OMOP SQL queries from the Central Hub, delegates to data-access-api, and returns aggregated statistics
- Imaging projects — creates projects in XNAT via imaging-api in response to approved FL studies
- Service health — a background collector probes the trust stack every
HEALTH_COLLECT_INTERVAL_SECONDS(imaging-api + data-access-api/health, XNAT via its anonymousbuildInfoendpoint, the PACS connector transitively via imaging-api'sping_pacsDIMSE echo, the OMOP database via a raw TCP connect) and attaches the snapshot to the heartbeat; the hub surfaces it on the Connection Status page's per-container drawer. Each PACS echo opens an association to the hospital PACS, so it runs at most every five minutes, the cycles in between report its last result, and a PACS outage can show up to five minutes late - Audit — logs all operations locally for governance purposes
The trust-api polls the flip-api (Central Hub) for tasks. It does not accept inbound requests from the hub or expose an external user interface.
The trust-api is deployed as part of the Trust-side stack. In the local test environment it starts as part of:
make up-trusts # trust services for both mock Trustsor the full stack:
make upAPI documentation (Swagger UI) is available at the port defined by TRUST_API_PORT in this
trust's kit file (trust/.env.<CODE>.<env>; templates: trust/.env.GSTT.development.example,
trust/.env.KCH.development.example, default 8020):
http://localhost:<TRUST_API_PORT>/docs
Key environment variables. Per-trust values (ports, keys, expected trust id) live in this
trust's kit file (trust/.env.<CODE>.<env>); hub-shared values (AES_KEY_BASE64,
CENTRAL_HUB_API_URL) are synced into the kit's Hub-shared block by
make register-trust KIT=<CODE> (live in prod, inherited from the hub .env.<env> in dev):
| Variable | Description |
|---|---|
EXPECTED_TRUST_ID |
Optional self-check. If set, trust-api aborts at startup when the hub-resolved trust id does not match this value |
DATA_ACCESS_API_URL |
Internal URL of the data-access-api |
IMAGING_API_URL |
Internal URL of the imaging-api |
CENTRAL_HUB_API_URL |
URL of the Central Hub API (for task polling) |
TRUST_API_KEY |
Per-trust API key for authenticating with the Central Hub. Lives in this trust's kit file (trust/.env.<CODE>.<env>), written by make register-trust KIT=<CODE> |
AES_KEY_BASE64 |
Base64-encoded AES-256 key shared with the hub; task payloads arrive as AES-256-GCM envelopes (FLIP#1179). Must be byte-identical to the hub's key: a mismatch fails closed, reporting every task as Invalid payload: failed authentication |
POLL_INTERVAL_SECONDS |
Polling frequency in seconds (default: 5) |
COHORT_QUERY_TIMEOUT_SECONDS |
Timeout on trust-api's one cohort-query request, POST /cohort to data-access-api (default: 300). A cohort re-runs against OMOP at every later stage too, but those are other services' calls (imaging-api → /cohort/accession-ids, fl-client → /cohort/dataframe) and this setting does not reach them |
HEALTH_COLLECT_INTERVAL_SECONDS |
How often the health collector probes the trust services (default: 30) |
HEALTH_PROBE_DEGRADED_MS |
A successful probe slower than this reports degraded (default: 1000) |
XNAT_URL |
Internal URL of XNAT for the health probe (default http://xnat-web:8080) |
OMOP_DB_HOST / OMOP_DB_PORT |
OMOP PostgreSQL address for the TCP health probe (defaults omop-db / 5432) |
TRUST_INTERNAL_SERVICE_KEY_HEADER |
Header name for trust-internal service auth (default X-Trust-Internal-Service-Key) |
TRUST_INTERNAL_SERVICE_KEY |
Per-trust plaintext key. Forwarded outbound on every call to imaging-api and data-access-api so those services can authenticate the caller. Minted by register_trust (make register-trust KIT=<CODE>) into this trust's kit file (trust/.env.<CODE>.<env>). |
XNAT_URL, OMOP_DB_HOST/OMOP_DB_PORT, DATA_ACCESS_API_URL, IMAGING_API_URL and
TRUST_INTERNAL_SERVICE_KEY_HEADER are internal-topology constants fixed by the compose network,
so they default in trust_api/config.py and none of them is injected into trust-api's own compose
environment: block — injecting them would let an empty ${VAR} from a kit that omits the line
override the code default. XNAT_URL and OMOP_DB_HOST appear in no kit file at all (the kit's
OMOP_DB_PORT is a different variable, see below). The last three are in
every kit file (trust/.env.example, the GSTT/KCH examples) and in the fl-client's compose block,
because the fl-client reads them from env too and its own code defaults are localhost URLs, not
the trust-network names; the Helm chart also injects them into trust-api itself
(trust/deploy/helm/templates/trust-api.yaml). Override them through the environment
only for a non-standard deployment. Note the kit's own OMOP_DB_PORT is a different variable: it
is the host-published port for the omop-db container, while trust-api probes the container port
over the trust network.
trust-api authenticates outbound in two directions:
- To the Central Hub: every request carries
TRUST_API_KEYin theTRUST_API_KEY_HEADER. The hub validates against theapi_key_hashcolumn on the trust's row in thetrusttable. - To sibling trust services (imaging-api, data-access-api): every request carries
TRUST_INTERNAL_SERVICE_KEYin the configured header. Receivers compare withhmac.compare_digestagainst their own copy of the same per-trust key. See the public security model for the threat model.
trust-api itself does not receive inbound requests from the hub or any external caller — it only polls outbound — so it does not validate an inbound trust-internal header.
The trust-api task poller is designed to run as a single replica per trust. The central hub's task-claim endpoint does not use row-level database locking, so running multiple poller replicas for the same trust would cause duplicate task execution.
If horizontal scaling is needed, the hub endpoint (GET /tasks/pending) must be
updated to use SELECT ... FOR UPDATE SKIP LOCKED to ensure each task is claimed by exactly
one replica.
Tests are split into tests/ (unit-level, no real backing services — tests/routers/, tests/services/, etc.) and tests/integration/ (real OMOP database via the shared trust/deploy/compose/compose.test.yml stack). See Where does my test go? in CONTRIBUTING.md for the placement rule, and trust/README.md for how the cohort-query end-to-end suite is wired.
make local_test # ruff + mypy + unit suite (no Docker required)
make integration_test # ruff + mypy + cohort-query end-to-end suite (Docker required)