Preview release.These docs are a work in progress. Pages are still being written, links may break, and structure may shift without notice. Treat everything here as a draft and report issues onGitHub.
This guide shows the caller-side OpenFn pattern for Registry Relay: an OpenFn workflow reads protected registry records, metadata, relationships, or aggregate outputs through a scoped Relay credential.
Use this pattern only when the workflow is authorized to read registry data directly. If the workflow needs a governed trust decision, such as whether a farmer is eligible or a certified value such as farmed land size, call Registry Notary instead.
Adaptor
Section titled “Adaptor”The current OpenFn language adaptors for Registry Stack live in:
https://github.com/jeremi/openfn-language-registry-stackWhen that repository is configured as an OpenFn local adaptor repository, Lightning and the worker load the Relay package as:
@openfn/language-registry-relay@localConfigure an OpenFn credential with:
relay_base_url: Registry Relay service base URL.token: bearer token or API key for the Relay caller credential.
The adaptor sends credentials as Authorization: Bearer <token>. It does not
send x-api-key.
Lab credentials
Section titled “Lab credentials”The public Registry Stack lab publishes current demo service URLs, scopes, and tokens at:
https://lab.registrystack.org/api/lab.jsonFor the agriculture Relay examples below:
- use
agri-row-readerfor row reads; - use
agri-aggregate-readerfor aggregate reads; - use
agri-metadatafor dataset discovery; - use
agri-evidence-onlyfor evidence offering discovery.
The lab UI at https://lab.registrystack.org shows the same public demo
credentials.
The examples use NAGDI, a fictional agriculture ministry from the demo lab.
Read one record
Section titled “Read one record”execute( getRecord({ dataset: "agri_registry", entity: "farmer", id: dataValue("farmer_id"), purpose: "https://demo.example.gov/purpose/nagdi/climate-smart-input-support", fields: ["id", "district", "registration_status"], as: "farmer", redactDataPaths: ["farmer_id"], }),
fn((state) => { const farmer = state.data.farmer.record;
return { ...state, data: { ...state.data, decision_input: { farmer_id: farmer.id, district: farmer.district, relay_request_id: state.data.farmer.request_id, }, }, }; }),);List records
Section titled “List records”Collection reads require an explicit limit and at least one filter unless
allowUnfiltered: true is set.
execute( listRecords({ dataset: "agri_registry", entity: "farmer", purpose: "https://demo.example.gov/purpose/nagdi/climate-smart-input-support", filters: { district: "north", "id.in": ["FARMER-1001", "FARMER-1002"], }, fields: ["id", "district", "registration_status"], limit: 50, as: "farmers", }),);Query an aggregate
Section titled “Query an aggregate”execute( queryAggregate({ dataset: "agri_registry", aggregate: "voucher_opportunities_by_district_crop_risk_input", purpose: "https://demo.example.gov/purpose/nagdi/climate-smart-input-support", dimensions: ["district_code"], measures: ["eligible_opportunity_count"], filters: { season: ["2026A"] }, maxRows: 100, as: "district_summary", }),
fn((state) => { const observations = state.data.district_summary.observations;
return { ...state, data: { ...state.data, north_voucher_opportunities: observations.find((row) => row.district_code === "north")?.eligible_opportunity_count ?? 0, }, }; }),);Discovery
Section titled “Discovery”execute( discoverDatasets({ as: "catalog" }), getEntitySchema({ dataset: "agri_registry", entity: "farmer", as: "farmer_schema", }), listEvidenceOfferings({ as: "evidence_offerings" }),);Relay evidence offering routes are discovery only. They tell the workflow which Registry Notary endpoint to use; Relay does not evaluate a claim.
Guardrails
Section titled “Guardrails”The adaptor is intentionally stricter than a generic HTTP helper:
- row, relationship, and aggregate helpers require
purpose; listRecordsrequireslimitand filters unlessallowUnfiltered: true;X-Request-Idusesstate.data.request_idwhen present;traceparentis forwarded whenstate.data.traceparentis present;ETag,Retry-After, request id, and pagination cursors are preserved;- credentials, raw request material, and
configurationare removed from final state; - Problem Details are reduced to
code,status,title, andretryable; the adaptor does not expose Problem Detailsdetail.
Common result branches are succeeded, not_modified, not_found,
auth_failed, forbidden, filter_required, cursor_invalid,
retryable_infrastructure, and failed.