Skip to content
Registry StackDocsDevelopment (unreleased)

Connect an institution API

For the assertion provider

View as Markdown

If you integrate an institution’s existing HTTP API with Registry Evidence, you connect one of its operations here and return a verified answer from it. You start a new project, so you are no longer in the standalone project from your first assertion; if that one is still running, run rstack evidence dev stop . in its directory first, because both use the same ports.

Outcome
A verified coverage-status assertion from a FHIR API.
Time
About 20 minutes
Level
Local development with a synthetic public server
Prerequisites
The Evidence component for rstack, v0.45.0 or later, installed as in your first Evidence assertionRunning DockerPython 3, tar, and jqInternet access

Settle three things about the operation you connect:

  • A fixed request shape. One method and one path template. Selector values fill the path parameters; the caller never chooses the operation, query, or headers.
  • Authentication configuration and, when needed, a secret reference. The reviewed pack endpoint declares the authentication type and the target names the same type plus any reference; check --target refuses the two when they differ. A configured secret provider supplies the credential bytes, and the caller’s own token never reaches your API.
  • The fields the integration may retain. Review exactly which response fields can be retained; nothing else in the response may reach the assertion logic.

Without a real API at hand, you connect a FHIR R4 Coverage read on a synthetic public server. Each step applies to your own API.

For an existing DHIS2 Tracker or OpenCRVS integration, download the source recipe catalog. It includes reviewed exports and synthetic fixture projects. Follow its README to validate the recipe with matching binaries, then import it through the existing source lifecycle. Its contract notes identify the provider shapes you need to check against your deployment.

Download the Evidence example projects into a fresh working directory as evidence-examples.tar.gz, then unpack the FHIR project into a directory of its own, because the next commands write keys and responses into it:

The download uses the Adapter-pack authoring format, which v0.45.0 is the first release to read. Do not pair it with binaries from an older release.

Terminal window
mkdir connect-an-institution-api
tar -xzf evidence-examples.tar.gz -C connect-an-institution-api fhir
mv connect-an-institution-api/fhir connect-an-institution-api/fhir-record-evidence
cd connect-an-institution-api/fhir-record-evidence

Check it and run its fixtures, which need no network:

Terminal window
rstack evidence check . --target local-target.yaml
rstack evidence test . --target local-target.yaml --deny-warnings
Evidence project check: complete
Project: .
Target: local-target.yaml
Deployment inputs missing: signer.publicJwkFile; the compiled-target bundle check was skipped. Supply them and rerun check to prove the target.
0 errors, 0 warnings in 1 file
PASS: check
PASS: fixtures/coverage-status.yaml (8 cases)
PASS: fixtures/healthcare-establishment.yaml (11 cases)
3 passed, 0 failed (19 cases evaluated)
Proof: offline synthetic fixture evaluation; no live dependency readiness was checked

You request fhir-coverage-status, one of its two assertion definitions. check runs before any signing key exists, so it reports the missing public key and skips the compiled-target check. test then wrote disposable, owner-only keys under .generated/secrets; keep that directory out of version control.

Inspect the acquisition and derivation that the checked project will use:

Terminal window
rstack evidence explain . --target local-target.yaml
Definition fhir-coverage-status: urn:example:requirement:coverage-record:v1
Acquisition: Source coverage-record
Source coverage-record: pack fhir-read-through@1; route coverage-record; connection fhir-read-through
Posture: record-transformed
Inputs: coverageId <- selector coverage-record@coverage-by-id.coverage_id
Facts: beneficiary-reference (string), status (string)
Derive: Function derive-coverage-status
Outputs: coverage-record-reports-active

The Source is the project’s thin acquisition binding. It selects one route from the reviewed fhir-read-through@1 pack, supplies coverageId from the fixed selector profile, and names the target-owned connection. The route returns only the two private Facts shown before the derivation Function produces the Boolean Output.

Read the operation from its OpenAPI description

Section titled “Read the operation from its OpenAPI description”

source.openapi.yaml describes the API. Print a source draft for one operation, selecting the two fields the answer needs (--list-pointers instead lists every field it returns):

Terminal window
rstack evidence source suggest --openapi source.openapi.yaml \
--operation 'GET /Coverage/{coverage_id}' --source-id coverage-record \
--select /status --select /beneficiary/reference
evidencectl source suggest: draft for source `coverage-record` (GET /Coverage/{coverage_id})
Derived automatically: none.
Still needs your input (the source block below):
- Set the source origin.
- Review the generated Source posture and default source limits.
- Configure target-owned authentication.
- Complete selector bindings and the pack request mapping.

This print-only draft includes a candidate pack and Source block. It is incomplete until you review the pack, bind every request input, and resolve its draft markers. For another operation, explicit selector mappings generate fixed pack request bindings from profiles already in your project.

The downloaded project uses the reviewed fhir-read-through@1 pack instead of the generated candidate. Its Source remains a small binding in evidence.yaml:

coverage-record:
pack:
id: fhir-read-through
version: '1'
route: coverage-record
connection: fhir-read-through
acquisitionPosture: record-transformed
inputs:
coverageId:
selector:
role: coverage-record
profile: coverage-by-id
field: coverage_id

The selected pack route owns the inputs and Facts schemas for acquisition:

{
"id": "coverage-record",
"evidence": {
"inputsSchema": {
"path": "schemas/inputs-coverage.json",
"digest": "sha256:a70e6414a6baedd4404c53015ce035caa3e3a2528700a259727be4632296f03d"
},
"factsSchema": {
"path": "schemas/facts-coverage.json",
"digest": "sha256:7c93c17ec873a96e80a31fb096260add3abbb64e0b2a3ad9352cde10b4c9775a"
},
"acquisitionPosture": "record-transformed"
}
}

The route fixes the request method, path, and path-parameter source:

{
"to": {
"uri": "http:fhir",
"parameters": {
"httpMethod": "GET",
"path": "/Coverage/{{coverage-id}}",
"pathParameters": {
"coverage-id": {
"exchangeProperty": "coverage-id"
}
},
"operation": "coverage-read"
}
}
}

Its reviewed script projects resourceType, id, status, and beneficiary.reference. A separate validateSchema step enforces provider-coverage.json against that projection. The following lookup step checks the returned record’s identity and returns only status and beneficiary-reference as private Facts. The posture record-transformed records that the API returns a broader record than the answer needs, so the integration claims disclosure minimization only.

The endpoint details live in local-target.yaml, under the connection the Source names:

connections:
fhir-read-through:
endpoints:
fhir:
origin: http://127.0.0.1:8000
authentication:
type: none
attemptTimeoutMilliseconds: 30000
maximumResponseBytes: 65536

The local read-through helper needs no credential, and the pack endpoint declares the same authentication type, none. For an API that expects a fixed Authorization value, change both files to the same type:

  • In adapter-packs/fhir-read-through/pack.yaml, set the endpoint’s authentication to {"type": "static-authorization"}.
  • In local-target.yaml, set type: static-authorization and add a tokenRef to a secret reference, as your first assertion’s target does.

If you change only one of them, rstack evidence check . --target local-target.yaml refuses the pair with evidence.source.authentication-mismatch, which names both types. The configured secret provider supplies the credential bytes. For your own description, rstack evidence init <dir> --openapi <doc> --profile local writes a project and rstack evidence source suggest <dir> writes a pack draft into it.

FHIR servers answer with application/fhir+json; the pack route accepts application/json from the local read-through helper. That helper is project code and not part of Evidence. It re-serves each allowed resource from the synthetic server. In a second terminal, start it and leave it running:

Terminal window
python3 serve-source.py

Send it no real identifiers. Back in the first terminal, pick one synthetic coverage record and its patient; the script writes owner-only subject files in the parent directory:

Terminal window
cd ..
python3 fhir-record-evidence/discover-records.py
cd fhir-record-evidence
Coverage selector file: ready
Organization selector file: ready

Start Registry Evidence and request one assertion

Section titled “Start Registry Evidence and request one assertion”

Register the client you will request as:

Terminal window
rstack evidence access client add fhir-client --access-profile fhir-service --generate-local-key
Added client fhir-client with access profile fhir-service.

Start the gateway and its local token issuer; they run until you stop them:

Terminal window
rstack evidence dev start . --target local-target.yaml --evidence-port 18080 --issuer-port 8082
Evidence ready at http://127.0.0.1:18080
Issuer ready at http://127.0.0.1:8082

Prepare the request from the subject file:

Terminal window
rstack evidence request prepare fhir-coverage-status \
--client fhir-client \
--purpose coverage-record-verification \
--subjects-file ../fhir-coverage-subjects.json \
--name fhir-coverage
Prepared request: .evidence/requests/fhir-coverage/request.json
Prepared verification context: .evidence/requests/fhir-coverage/verification.json
Prepared authorization: .evidence/requests/fhir-coverage/authorization.curl

Send it and verify the signed response offline:

Terminal window
rstack evidence api /v1/evidence \
--client fhir-client \
--input .evidence/requests/fhir-coverage/request.json \
--accept application/jose+json \
--output fhir-coverage.jws.json
rstack evidence verify fhir-coverage.jws.json \
--context .evidence/requests/fhir-coverage/verification.json \
--output fhir-coverage.verified.json
VERIFIED
Terminal window
jq '.supportedValues' fhir-coverage.verified.json
[
{
"providesValueFor": "urn:example:concept:coverage-record-reports-active:v1",
"value": true
}
]

The assertion carries one Boolean, whether the coverage is active, and none of the coverage record.

The audit view reads only a stopped session, so stop the gateway first:

Terminal window
rstack evidence dev stop .
rstack evidence audit show --last-operation
ACCESS AUTHORIZED fhir-coverage-status coverage-record-verification requester=<pseudonym>
DISCLOSURE RELEASED coverage-record-reports-active

Cleaning removes the stopped session’s directory, .evidence/dev, and the audit with it, so read the audit first. The project, its registered client, and the prepared request stay:

Terminal window
rstack evidence dev clean .
Removed stopped local Evidence state

Press Ctrl+C in the read-through helper’s terminal, then remove the subject files:

Terminal window
cd ..
rm -f fhir-coverage-subjects.json fhir-organization-subjects.json

Connect a published SQLite extract