Skip to content
Registry StackDocsDocumentation preview

Use your own spreadsheet

View as Markdown

Use this tutorial after running the sample registry API. You will derive every source and access value from a workbook you control, validate the project, and prove denied and allowed behavior on the resulting route.

Outcome
A protected local API over one reviewed spreadsheet, with the route, returned fields, purpose, and local caller derived from your own decisions.
Time
About 30 minutes for a small worksheet
Level
Country implementer
Prerequisites
Linux amd64Registryctl 0.15.0 with its matching image lockDocker Engine with Compose v2curlA reviewed non-personal XLSX workbookA text editor

Use a synthetic, public, or otherwise approved non-personal workbook for this local evaluation. Do not use live country data, personal data, production credentials, or an unreviewed export.

Make a reviewed derivative that contains:

  • one selected worksheet with one header row;
  • exactly two columns for this first adaptation;
  • one stable, non-empty, unique text key;
  • one text field approved for disclosure;
  • no formulas in the selected worksheet; and
  • at least one approved row whose key you can use as a local principal.

Registry Stack does not evaluate formulas, follow external workbook links, or infer relationships between sheets. Review the XLSX readiness contract before adapting a larger or less regular workbook.

Write down the decisions you will substitute for the angle-bracket placeholders in this tutorial:

PlaceholderDerive it from
<entity-id> and <key-field>Stable lower-snake-case names for the selected worksheet and its unique key
<released-field>A lower-snake-case name for the second, approved column
<key-header> and <released-header>The two exact workbook header cells, including spaces and capitalization
<worksheet-name>The exact worksheet tab name
<matching-key-value>One approved, non-personal key cell that exists in the worksheet
<purpose>A reviewed identifier for why callers may use this API
<registry-id> and <service-id>Stable identifiers for the authored registry and its records policy

The examples below quote exact workbook header cells. Keep the angle brackets only while reading. Replace every placeholder in the authored files before running validation.

Set the two values reused by shell commands in this tutorial. Replace the quoted placeholders with your actual identifiers:

Terminal window
entity_id='<entity-id>'
request_purpose='<purpose>'

These assignments print nothing. Keep using the same shell for the remaining commands.

Create a new project from the maintained spreadsheet starter:

Terminal window
registryctl init --from spreadsheet --project-dir reviewed-registry
cd reviewed-registry

Registryctl prints:

Initialized Registry Stack project "fictional-public-works-registry".
Directory: reviewed-registry
Starter: spreadsheet (Registry Stack 0.15.0)
Starter content: matches bundled digest
Editor support: VS Code and Zed (reviewed-registry/.registry-stack-editor/manifest.json)

Registryctl follows those lines with a Next section that tells you to enter the directory, check the local project, and start Relay. The starter is the reviewed baseline you will adapt. The cd command prints nothing.

Copy your workbook into the project and give the entity file its authored name:

Terminal window
cp /path/to/your-reviewed-workbook.xlsx data/reviewed-source.xlsx
mv entities/projects.yaml "entities/${entity_id}.yaml"

These commands print nothing when they succeed. Registryctl reads the copied workbook but never writes its cells.

You will edit only these human-owned inputs:

FileDecision it owns
data/reviewed-source.xlsxThe exact approved source bytes
entities/<entity-id>.yamlLogical field names, stable key, strict types, and row limits
environments/local.yamlWorksheet headers, workbook path, and local caller principals
registry-stack.yamlRegistry identity, service, purpose, scopes, filters, and returned fields

Do not edit .registry-stack/. Registryctl generates that directory and may replace it.

Replace entities/<entity-id>.yaml with this strict two-field schema after substituting your three logical names:

version: 1
id: <entity-id>
revision: 1
primary_key: <key-field>
schema:
type: object
additionalProperties: false
required: [<key-field>, <released-field>]
properties:
<key-field>: { type: string, minLength: 1, maxLength: 128 }
<released-field>: { type: string, minLength: 1, maxLength: 256 }
materialization:
max_records: 10000
max_bytes: 16MiB
refresh: manual
retain_generations: 2

primary_key names the stable key. Every selected column is required and explicitly typed. additionalProperties: false makes a new source column a review event instead of silently adding it to the entity.

Do not use a row number or mutable display name as the key. If either column may legitimately be empty or needs a different type or length, make that decision explicitly using the entity definition reference before continuing.

Replace environments/local.yaml with the following content after substituting the entity, worksheet, header, and matching-key values:

version: 1
entities:
<entity-id>:
provider:
type: xlsx
project_file: data/reviewed-source.xlsx
path: /var/lib/registry/reviewed-source.xlsx
sheet: "<worksheet-name>"
header_row: 1
columns:
<key-field>: "<key-header>"
<released-field>: "<released-header>"
source_revision: reviewed-source-v1
generation: "1"
relay:
origin: https://records-relay.internal.invalid
issuer: https://workload-issuer.internal.invalid
jwks_url: https://workload-issuer.internal.invalid/.well-known/jwks.json
audience: registry-relay
allowed_clients: [local-reviewed-client]
local_api_keys:
match_principal: "<matching-key-value>"
no_match_principal: registryctl_local_no_match
scopes: [<entity-id>:metadata, <entity-id>:rows]
deployment:
profile: local
relay: { service: records-relay }

The keys on the left side of columns are the logical field names from the entity schema. The quoted values on the right are exact header cells in the workbook.

Set match_principal to an approved key cell that exists in the workbook. Keep registryctl_local_no_match absent from the key column. These two principals let you prove one-row and no-row behavior without creating production identities.

Local API keys exist only for this local profile. Registryctl writes their raw values to a private runtime directory. A deployed environment must use its reviewed identity provider and secret management.

Keep the version and starter block that Registryctl created at the top of registry-stack.yaml. Replace the registry, entities, and services sections beneath it with:

registry:
id: <registry-id>
entities:
<entity-id>:
file: entities/<entity-id>.yaml
services:
<service-id>:
kind: records_api
entity: <entity-id>
title: Reviewed local records
description: Protected view of the approved workbook derivative.
owner: Local evaluation team
sensitivity: confidential
access_rights: restricted
update_frequency: as_needed
api:
scopes:
metadata: <entity-id>:metadata
rows: <entity-id>:rows
aggregate: <entity-id>:aggregate
evidence_verification: <entity-id>:evidence_verification
purposes: [<purpose>]
projection: [<key-field>, <released-field>]
pagination: { default_limit: 50, max_limit: 100 }
filters: { <key-field>: [eq] }
required_principal_filters: [<key-field>]
standards: { ogc_features: false, sp_dci: false }

This policy derives the API contract:

  • the request purpose header is Data-Purpose: <purpose>;
  • the returned object contains only <key-field> and <released-field>;
  • the allowed filter is equality on <key-field>; and
  • the required key filter is bound to trusted caller identity.

The service identifier owns the policy but does not appear in the URL. Registryctl derives the records URL from the entity identifier:

http://127.0.0.1:4242/v1/datasets/<entity-id>/entities/<entity-id>/records

A caller-supplied query value cannot satisfy or replace the required principal filter. Relay obtains that value from authenticated gateway identity. A field is not readable merely because it exists in the workbook or entity schema. It must also appear in the reviewed projection.

Ask Registryctl to validate the workbook, authored policy, generated configuration, release files, listener, and Docker runtime:

Terminal window
registryctl doctor --profile local

A successful result starts with:

Registry Stack doctor: ok
Project: <absolute-project-path>
Profile: local
registry-relay: ok (0 errors, 0 warnings)

<absolute-project-path> is the path to reviewed-registry on your computer. If the check fails, correct the workbook or one of the three authored YAML files named in the diagnostic. Do not edit .registry-stack/.

A successful check proves that:

  • the selected worksheet and headers exist;
  • every selected row matches the strict field schema;
  • required keys are present and unique;
  • formulas, unexpected columns, unsafe paths, and invalid values were rejected;
  • the route, scopes, purpose, filters, and projection form a valid Relay policy; and
  • the matching release runtime can start on the loopback listener.

Start Relay:

Terminal window
registryctl start

After Docker progress, Registryctl prints exactly:

PASS readiness: compiled workbook source is ready
Relay API: http://127.0.0.1:4242
API docs: http://127.0.0.1:4242/docs

Relay parses the workbook again during its initial load. The listener is limited to 127.0.0.1:4242. The generated configuration and workbook are mounted read-only.

Do not run registryctl smoke for this adapted project. In v0.15, that command checks the unmodified starter’s projects route and sample purpose. Use the derived requests below to check your authored route and purpose.

Substitute your entity and purpose in this request:

Terminal window
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' \
-H "Data-Purpose: ${request_purpose}" \
"http://127.0.0.1:4242/v1/datasets/${entity_id}/entities/${entity_id}/records"

Relay prints exactly:

HTTP 401

This proves that the protected route rejects a missing credential. A valid purpose header does not substitute for authenticated caller identity.

Load the private local keys without printing them:

Terminal window
set -a
. .registry-stack/runtime/local/secrets/local.env
set +a

These commands print nothing. Do not print, paste, commit, or retain the raw keys outside this disposable local runtime.

Substitute the same entity and purpose:

Terminal window
curl -sS -w '\nHTTP %{http_code}\n' \
-H "Authorization: Bearer $REGISTRYCTL_LOCAL_RELAY_MATCH_KEY_RAW" \
-H "Data-Purpose: ${request_purpose}" \
"http://127.0.0.1:4242/v1/datasets/${entity_id}/entities/${entity_id}/records"

Relay returns this structure using your actual field names and row values. JSON whitespace and object-key order may differ:

{
"data": [
{
"<key-field>": "<matching-key-value>",
"<released-field>": "<value-from-that-row>"
}
],
"pagination": {
"has_more": false
}
}
HTTP 200

This proves that the credential authenticates the local caller, the purpose and scopes authorize the operation, the principal-bound key selects exactly the matching row, and the projection limits the disclosed fields. The request URL, purpose, key, and response fields all come from the authored values you derived from the workbook and access decision.

Stop the local runtime:

Terminal window
registryctl stop

Docker may print container shutdown progress. Registryctl prints no additional success line after Compose exits successfully.

The authored project and workbook remain. Generated build and runtime directories are disposable and can be regenerated.

You built a loopback-only protected API over one reviewed XLSX worksheet. Its entity and request route come from your logical entity name. Its source mapping comes from exact worksheet headers. Its authorized row comes from the stable key used as local principal identity. Its purpose and returned fields come from explicit service policy.

You validated the workbook and policy, then confirmed the running API’s denied and allowed behavior.

ProblemWhat to change
Wrong worksheet or headerCorrect sheet, header_row, or the exact quoted header mapping.
Missing or unexpected columnAlign the reviewed workbook derivative, entity schema, and column mapping.
Missing or duplicate keyResolve the source identity problem. Registry Stack will not choose one duplicate.
Invalid type or null required valueCorrect the workbook derivative or deliberately change the schema.
Formula or external referenceReplace it with an approved static value in the derivative.
Path traversal or workbook symlinkCopy the reviewed file under data/ and use that regular file.
An unintended field appears in projectionRemove it before building, or obtain the required review.