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 on GitHub.
Publish a spreadsheet as a secured registry API
Use this tutorial to create a local Registry Relay project from a sample Excel workbook.
You will install registryctl, start a protected registry API, prove anonymous access is denied,
read spreadsheet data through a secured route, and open the local API reference.
This tutorial uses synthetic data and local demo credentials. Do not use the generated local keys in production. Estimated time: about 10 minutes after Docker is installed.
Before you start
Section titled “Before you start”Install:
curl,tar, andunameshasumorsha256sumfor release checksum verification- Docker Desktop, OrbStack, Colima, Podman, or another Docker Compose provider
~/.local/binon yourPATH(the installer writes there by default; a new shell cannot findregistryctlotherwise)
Use registryctl 0.1.0 or newer.
The release installer publishes binaries for Linux x86_64, Linux aarch64, and macOS aarch64. Intel macOS does not have a published binary yet. On Intel macOS, install from source with Rust or run the tutorial from a supported Linux environment such as a VM or container.
Install registryctl without cloning a product repository:
curl -fsSL https://raw.githubusercontent.com/jeremi/registry-registryctl/main/install.sh | shThe installer downloads the pinned release selected by REGISTRYCTL_VERSION.
It defaults to v0.1.0.
The container images are published for linux/amd64; on Apple Silicon your Docker provider runs
them under emulation and prints a platform warning, which is harmless here.
If the installer reports that the release asset is unavailable, or if you are on Intel macOS, use
the source install path until the next pinned release is available.
Install Rust with rustup, then build the pinned release tag:
cargo install --git https://github.com/jeremi/registry-registryctl --tag v0.1.0 --lockedConfirm the CLI is available:
registryctl --versionIf your shell cannot find registryctl, add the install directory printed by the installer to your
PATH.
Create a local spreadsheet API project
Section titled “Create a local spreadsheet API project”Create a project from the benefits sample:
registryctl init relay my-first-api --sample benefitscd my-first-apiregistryctl creates:
my-first-api/ registryctl.yaml compose.yaml README.md relay/ config.yaml metadata.yaml data/ benefits_casework.xlsx bruno/ registry-api/ secrets/ local.env output/ .gitignoreThe secrets/local.env file contains local demo bearer keys and matching fingerprints.
The Relay config contains only fingerprint references and commitments.
Start the registry API
Section titled “Start the registry API”Start the local project:
registryctl startThe command starts the published Registry Relay container, waits for health and readiness, and after the Docker Compose progress lines prints:
Relay API: http://127.0.0.1:4242API docs: http://127.0.0.1:4242/docsCheck status:
registryctl statusThe service reports healthz: 200 and ready: 200 when startup is complete.
Run the smoke test
Section titled “Run the smoke test”Run the smoke checks:
registryctl smokeThe smoke test passes with these checks:
PASS healthz is publicPASS ready is publicPASS anonymous dataset request is deniedPASS metadata key can list datasetsPASS metadata key cannot read rowsPASS row read without Data-Purpose returns 400PASS row reader can read filtered recordsPASS anonymous caller can fetch runtime OpenAPIregistryctl writes detailed results to:
output/smoke-results.jsonLoad local demo keys
Section titled “Load local demo keys”Load the generated local keys into your shell:
set -a. secrets/local.envset +aset -a exports every variable the sourced file defines, so later commands such as curl can read
them; set +a turns automatic export back off.
Run the three lines in the same shell session you use for the rest of the tutorial.
The generated principals are labels wired to Relay scope strings in the generated config.
| Principal | Environment variable | What it can do |
|---|---|---|
metadata_reader | METADATA_READER_RAW | Read catalog and schema metadata |
row_reader | ROW_READER_RAW | Read configured entity records with a purpose header |
aggregate_reader | AGGREGATE_READER_RAW | Run configured aggregates, if present |
Prove anonymous access is denied
Section titled “Prove anonymous access is denied”Call a protected route without a credential:
curl -i http://127.0.0.1:4242/v1/datasetsRelay returns 401 Unauthorized.
Call the same route with the metadata key:
curl -i \ -H "Authorization: Bearer $METADATA_READER_RAW" \ http://127.0.0.1:4242/v1/datasetsRelay returns 200 OK and shows the dataset visible to that principal.
Read spreadsheet data through the API
Section titled “Read spreadsheet data through the API”Relay exposes configured entities, not spreadsheet sheet names.
The sample workbook has a Persons sheet, but callers read the person entity.
Read records for one household:
curl -sS -G \ -H "Authorization: Bearer $ROW_READER_RAW" \ -H "Data-Purpose: https://example.local/purpose/tutorial" \ --data-urlencode "household_id=hh-1001" \ http://127.0.0.1:4242/v1/datasets/benefits_casework/entities/person/recordsThe response includes synthetic person records from household hh-1001.
Sensitive source columns such as full names, national identifiers, and addresses are not exposed as
public entity fields in this sample.
Try the same request with the metadata-only key:
curl -i -G \ -H "Authorization: Bearer $METADATA_READER_RAW" \ -H "Data-Purpose: https://example.local/purpose/tutorial" \ --data-urlencode "household_id=hh-1001" \ http://127.0.0.1:4242/v1/datasets/benefits_casework/entities/person/recordsRelay returns 403 Forbidden.
Open the API reference
Section titled “Open the API reference”Open the local API docs:
registryctl openThe command opens the docs page in your default browser. In an environment that cannot launch one, such as a remote shell, it prints the URL instead:
http://127.0.0.1:4242/docsThe tutorial config leaves the docs shell and runtime OpenAPI document public. Protected data routes still require a bearer key and the right scope.
Fetch the runtime OpenAPI document directly:
curl -sS http://127.0.0.1:4242/openapi.jsonExplore requests in Bruno
Section titled “Explore requests in Bruno”registryctl generates a Bruno collection so you can inspect and run the local API requests
visually.
Bruno is optional.
The tutorial and API work without it.
Open the generated collection:
registryctl bruno openIf Bruno is installed, the collection opens. If Bruno is not installed, the command prints the collection path and an install link.
If the Bruno CLI is installed, you can also run the collection:
registryctl bruno runIf bru is not installed, the command prints a fallback and exits without blocking the Relay
workflow.
Clean up
Section titled “Clean up”When you are done:
registryctl stopThis stops the local containers. It does not delete your workbook, generated config, local keys, or smoke results.
- Verify a claim with Registry Notary: add Registry Notary to the project and evaluate a claim without exposing the source row, or run Notary standalone against an API you operate.
- See it live: explore the hosted demo to see the full stack in action.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Resolution |
|---|---|---|
registryctl is not found | The install directory is not on PATH. | Add the directory printed by the installer, usually ~/.local/bin, to PATH. |
| The installer reports an unsupported platform | No binary is published for that OS or CPU yet. | Use a supported Linux or macOS aarch64 machine, or install from source with Cargo. |
registryctl start cannot find Docker | Docker or another Compose provider is not installed or running. | Start Docker Desktop, OrbStack, Colima, Podman, or your supported provider, then run registryctl start again. |
registryctl start fails and the container log shows failed to parse config YAML ... unknown field | The locally cached container image does not match the digest-pinned image in the generated compose.yaml. | Run docker compose pull in the project directory to refresh the pinned images, or re-run registryctl init relay to regenerate the compose file, then registryctl start again. |
A row read returns 403 Forbidden | The key is valid but lacks the row-read scope. | Use ROW_READER_RAW for row reads. |
A row read returns 400 auth.purpose_required | The entity requires a Data-Purpose header. | Send Data-Purpose: https://example.local/purpose/tutorial or another purpose URI. |
| A collection row read returns a filter error | The entity requires at least one configured filter. | Add a filter such as household_id=hh-1001. |