The zed-pkg registry REST API, in Rust: package and
version metadata in Postgres through the canonical zed-orm-core boundary and
the legacy compatibility migration crate. Production schema changes run only
through the explicit zed-api-server migrate release command. Artifact
archives (tar.gz/zip) live in bounded process memory for disposable
certification, local disk for development/self-hosting, or any S3-compatible
object storage—Cloudflare R2 in production and AWS S3 or MinIO as alternatives.
This is the service zed publish and zed install talk to, and the whole
stack is self-hostable for private registries.
| Method | Path | Notes |
|---|---|---|
| GET | /healthz |
liveness + db status |
| GET | /v1/packages/{org}/{name} |
PackageMetadata |
| GET | /v1/packages/{org}/{name}/versions/{version} |
VersionMetadata |
| PUT | /v1/packages/{org}/{name}/versions/{version} |
publish: multipart meta (PublishMeta JSON) + artifact (bytes); bearer token; versions are immutable |
| GET | /v1/artifacts/{sha256} |
302 presigned URL (s3), streamed bytes (local), or zero-copy response bytes (memory) |
| GET | /v1/search?q= |
name/description match, cap 50 |
| POST | /v1/orgs |
claim a namespace; bearer token |
| GET | /v1/files/{org}/{name}/{version}/{path} |
unpkg-style: serve one file out of an artifact, immutable cache headers |
Errors are JSON ApiError { code, message } — codes include not_found,
unauthorized, org_not_found, org_taken, version_exists,
sha256_mismatch, tag_not_found, invalid_manifest, invalid_multipart,
invalid_binary_artifact, and vcs_commit_mismatch.
api.zpkg.net serves the complete API. registry.zpkg.net reaches this same
process but is constrained by a fail-closed Host state machine to the machine
registry routes above (plus the rest of the checked-in machine OpenAPI). It
cannot reach browser/account, auth, docs, admin, or unknown routes, including
account compatibility routes that happen to live below /v1.
Publish pipeline: bearer token -> manifest validation -> URL/manifest agreement -> server-side sha256 recomputation -> org ownership -> VCS tag verification (policy below) -> immutability check -> store artifact -> record version.
Native binary ZIP layout, server verification, R2 race recovery, and the
coordinated multi-platform route/data-model boundary are specified in
docs/binary-artifact-publication.md.
Long-running and release commands resolve non-secret settings through the
pinned flags-2-env Rust binding
before tracing, network, storage, or database effects. Every option maps to the
environment variable in .cli-flags.toml; command-line
values take precedence, and unknown options fail closed. Run
zed-api-server --help (or zed-api-server serve --help) for the audited
option list.
Credentials remain environment-only: DATABASE_URL,
SHARED_AUTH_SERVICE_CREDENTIAL, FIDUCIA_INTERNAL_SECRET, GITHUB_TOKEN,
and AWS credential variables deliberately have no command-line form. The
existing create-token and revoke-token subcommands retain their own private
argument parsers.
| Var | Default | Notes |
|---|---|---|
BIND_ADDR |
0.0.0.0:8080 |
|
DATABASE_URL |
required | Postgres runtime credential; DML only in production |
AUTO_MIGRATE |
false |
transitional disposable-local escape hatch only; production/Kubernetes must run the reviewed zed-api-server migrate release job |
STORAGE_BACKEND |
local |
memory, local, or s3 |
STORAGE_MEMORY_MAX_BYTES |
268435456 |
hard total for the process-memory backend; must be greater than zero |
STORAGE_LOCAL_DIR |
.data/artifacts |
local backend |
ZED_MAX_BINARY_ARCHIVE_BYTES |
1073741824 |
binary ZIP outer-byte limit; may only lower the v1 ceiling |
ZED_MAX_BINARY_EXPANDED_BYTES |
2147483648 |
total expanded-byte limit; may only lower the v1 ceiling |
ZED_MAX_BINARY_ENTRIES |
200000 |
entry-count limit; may only lower the v1 ceiling |
ZED_MAX_BINARY_COMPRESSION_RATIO |
1000 |
per-entry ratio limit; may only lower the v1 ceiling |
S3_BUCKET |
required for s3 | |
S3_ENDPOINT_URL |
unset | set for R2/MinIO |
S3_REGION |
auto |
R2 uses auto |
S3_FORCE_PATH_STYLE |
true |
MinIO needs it |
PUBLIC_BASE_URL |
http://localhost:8080 |
used in download URLs |
ZED_REGISTRY_ID |
registry:zpkg-primary |
stable logical graph identity; set once and do not derive it from an ingress alias |
ZED_VERIFY_TAGS |
off |
off or github |
GITHUB_TOKEN |
unset | raises tag-check rate limits |
MAX_ARTIFACT_BYTES |
104857600 |
request body cap |
ZED_ARTIFACT_SERVE_MEMORY_BUDGET_BYTES |
268435456 |
memory budget for concurrently buffered artifact/file responses |
ZED_GRAPH_SERVE_MEMORY_BUDGET_BYTES |
268435456 |
independent memory budget for concurrent dependency-graph encoders |
RUST_LOG |
info |
memory is intentionally process-local and disposable: every restart clears
all archives, so it must remain single-replica and paired with equally
throwaway metadata during certification. See
docs/memory-publish-certification.md
for the zed r2g + real-server test contract and the Kubernetes promotion
boundary.
This process is the sole runtime writer for the shared registry schema. The web server receives a separate read-only identity and sends every mutation through this API over private-cluster HTTP. Production migrations run once as a reviewed release step with a dedicated migrator identity; normal API replicas do not receive DDL privileges and do not migrate on boot.
The shared-library rollout, role split, migration evidence, and exact
zed-orm-core pin are documented in
docs/database-boundary.md. The disposable Docker
Compose stack explicitly opts into the legacy SeaORM boot migration while the
production release path invokes the explicit migration command. That boot-time
exception is not valid for Kubernetes or durable self-hosted deployments.
| Setting | Value |
|---|---|
S3_ENDPOINT_URL |
https://<account_id>.r2.cloudflarestorage.com |
S3_REGION |
auto |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY |
R2 API token pair |
| Bucket | see zed-infra/terraform/cloudflare |
ZED_VERIFY_TAGS=github enforces the provenance rule server-side: the
declared backing repo must have the tag the client publishes from
(tag_not_found otherwise). github.com is implemented in src/verify.rs;
GitLab/Bitbucket/Codeberg/SourceHut and hg hosts are the marked extension
point, and unverifiable hosts (self-hosted forges) are allowed through with a
warning so they are not locked out. The CLI independently verifies tags
client-side either way.
# disposable local stack: postgres + minio + api (from the parent directory)
# docker-compose.yml explicitly enables the legacy local boot migration.
docker compose -f zed-api-server.rs/docker-compose.yml up --build
# or bare, after applying the reviewed schema to your own Postgres
DATABASE_URL=postgres://zed:zed@localhost:5432/zed \
cargo run -- migrate
DATABASE_URL=postgres://zed:zed@localhost:5432/zed \
STORAGE_BACKEND=memory \
STORAGE_MEMORY_MAX_BYTES=268435456 cargo run -- \
--bind-addr 127.0.0.1:8080 \
--rust-log info
# mint a token (printed once)
DATABASE_URL=... cargo run -- create-token --name ci --org acme
# then, from a package directory
zed org claim acme --registry http://localhost:8080 --token zpkg_...
zed publish --registry http://localhost:8080 --token zpkg_...Equivalent curl publish:
curl -X PUT http://localhost:8080/v1/packages/acme/demo/versions/0.1.0 \
-H "Authorization: Bearer zpkg_..." \
-F 'meta={"manifest":{...},"vcs_tag":"v0.1.0","sha256":"...","size":123,"format":"tar.gz"};type=application/json' \
-F 'artifact=@.zed/pack/acme-demo-0.1.0.tar.gz'Clone side by side with zed-interfaces (path dependency). cargo test runs
without Postgres or network: handler publication/immutability paths use a real
in-memory SQLite database, and storage tests exercise the bounded process-memory
backend directly. The compose and cross-repository E2E suites still cover the
full Postgres/API/web/CLI stack.
formal/fm.toml and
formal/latest_selection.fm.toml define
schema-v1 fmctl gates for publication finalization and deterministic
latest-version selection across publish/yank transitions. Their Quint models
exhaustively check immutable publication, atomic visibility, fail-closed
authorization, committed artifact availability, and maximal non-yanked
selection. See formal/README.md for bounds, witnesses,
the concurrency finding behind retained failed-upload blobs, and an explicit
account of what is not proved.
MIT