Use the Store from Your App
Any app — a paint canvas, a video editor, a 3D suite, a music tool — can pull .azp extensions straight from the azphalt.store marketplace (or any conforming repository) with a small client SDK. azphalt.store implements the normative Repository API, so your app talks to it the same way it would talk to any self-hosted registry — no store-specific code.
1. Install the client
npm install @azphalt/repository-client @azphalt/azp @azphalt/azdk@azphalt/repository-client— browse, search, and download from a repository.@azphalt/azp— read the.azpcontainer bytes you download.@azphalt/azdk— the shared types (manifest, asset types, etc.).
2. Browse & search azphalt.store
import { RepositoryClient } from "@azphalt/repository-client";
const store = new RepositoryClient({ url: "https://azphalt.store" });
// Everything the store carries (paged):
const { packages, total } = await store.search();
// Or filter to just what your app can actually use — say a paint app that only wants brushes & LUTs:
const brushes = await store.search({ types: ["brush", "lut"], q: "ink" });
console.log(`Found ${brushes.total} matching packs.`);
// `kind` narrows by package kind. A host with no code sandbox takes only data:
const assetsOnly = await store.search({ kind: ["asset"] });
// A workflow-aware host can browse its declarative package lanes directly:
const workflowsAndRoles = await store.search({ kind: ["workflow", "role"] });Each result carries ranking and preview metadata (downloads, rating, priceStatus, preview, …) so you can render a gallery without a per-package round-trip. Read the full record — version history, capabilities, the latest manifest — with getPackage:
const detail = await store.getPackage("com.foldlab.filmluts");Scope the catalog to your app
If you pass your app's reverse-DNS id, the store returns global packages plus those an author scoped to your app (targetApps) — the mechanism companions, app-specific extensions, workflows, and roles use:
const store = new RepositoryClient({ url: "https://azphalt.store", app: "com.hereliesaz.graffux" });Workflow and role hosts
kind:"workflow" and kind:"role" are declarative package lanes. Do not hand them to the code extension runtime.
For a workflow package, verify the .azp container first, then check workflow.format, resolve dependencies, evaluate symbolic host-permission requests, and only then import/launch the definitions through your own workflow engine. Declarative screens are rendered by your host's already-compiled UI; the package may not ship executable UI/runtime payloads.
For a role package, verify the container and role.format, then import the declared role payload only through your host's explicit role/persona model. Installation is not a permission grant.
Use the package kind and your app-scoped catalog query to decide whether these packages belong in your host at all. See Workflow packages and Role packages.
3. Download a free package
Free packages are open — no auth, no fee. download returns the raw .azp bytes; read them with readAzp:
import { readAzp } from "@azphalt/azp";
const bytes = await store.download("com.foldlab.filmluts", "1.0.0");
const { manifest, payload } = readAzp(bytes);
// payload["assets/portra.cube"] is the LUT, ready to load into your engine.Big files? Downloads are resumable and parallel. When the repository supports byte ranges (it advertises Accept-Ranges), download fetches the file in chunks — retrying any chunk that drops (so a flaky connection resumes the exact bytes it lost instead of restarting) and running several in parallel. This matters most for large model packs. Tune it and track progress:
const bytes = await store.download("com.azphalt.model.sherpa-onnx", "1.0.0", {
concurrency: 6, // parallel range requests (default 4; 1 = sequential)
chunkSize: 16 * 1024 * 1024,
retries: 5, // per-chunk retries — this is the "resume"
signal: abortController.signal,
onProgress: (received, total) => setProgress(received / total),
});If the repository doesn't support ranges, download transparently falls back to a single request — same call, same result.
4. Paid packages: the entitlement token
A consigned (paid) package's bytes are gated. The store issues a signed, offline-verifiable entitlement token when a buyer completes checkout; you send it as a Bearer credential on download. The gate is exactly the Repository API contract:
401— no token (or an unrecognized one): the user hasn't authenticated to the store.402— a real token that doesn't license this package: they need to buy it.200— a valid entitlement: the bytes.
// After the user buys the package on the store, hand its token to the client:
store.setToken(userEntitlementToken);
try {
const bytes = await store.download("com.hereliesaz.halftone", "1.2.0");
// …install it…
} catch (err) {
// The client throws on 401/402 — send the user to the store's checkout for this package.
}Where the token comes from: the buyer purchases on azphalt.store (Stripe checkout), and the store returns the entitlement on the success page and via /purchases (their license library). Because the token is a signed claim — not an opaque session — your app can also verify it offline against the repository's public key, published in the index (step 5). Nothing about the paid lane leaks into the free registry: money lives only on listings.
5. Trust the store once (offline verification)
On first contact, read the repository index. When the store issues entitlements it advertises its Ed25519 signing key there; add it to your trust store and you can verify this store's tokens — and any author it counter-signs — without a network round-trip on every download (see the spec's Trust bootstrap).
const index = await store.getIndex();
// index.name, index.version, index.signingKeys?.[0]?.publicKeyReading an .azp you already have
You don't need the network to open a package a user dragged in — an .azp is a signed ZIP container:
import { readAzp } from "@azphalt/azp";
import { readFileSync } from "node:fs";
const { manifest, payload } = readAzp(readFileSync("explosion-pack.azp"));
console.log(manifest.name, manifest.assets);Self-hosting your own repository
azphalt.store is one repository, not the only one — the API is neutral and self-hostable. To run your own catalog (free or paid) that the same client consumes, run the reference server in apps/repository-server (it is not published to npm; npm create azphalt offers a Repository Server template to start from), or adopt the standard directly — see Adopting the Standard.
Next
- The full contract: Repository API.
- Run extension code safely in your app: the Capability model and UI schema.
- Launch external companion apps over a handoff: Companion Apps.
- Import declarative workflows and roles safely: Workflow packages and Role packages.
- Let a web storefront hand your app a package: Web Handoff. Claiming
azphalt://installand accepting the.azpmedia type is what makes "Install" on a web page reach your app instead of dead-ending — and listing your app puts it in the storefront's "get a host" fallback for users who have none.