Quickstart
Install
Section titled “Install”bun add @aquienpz/sdk @aquienpz/asset-clientBoth packages ship the same URL builders — @aquienpz/sdk re-exports
@aquienpz/asset-client. Reach for the SDK’s AquienpzClient only when you
also upload; for rendering, the client alone is smaller and needs no key.
Upload one image
Section titled “Upload one image”/** * Upload one image, end to end. * * This file is REAL: `bun run check` type-checks it in CI against the same * workspace version of the SDK this site documents. If a signature changes, * the build fails instead of the website quietly lying. */import { AquienpzClient } from "@aquienpz/sdk";
// The `amk_rt_*` runtime key is a WRITE key to a paid platform. It lives on the// server, in a secret — never in a client bundle, never in `NEXT_PUBLIC_*`.const nt = new AquienpzClient({ endpoint: process.env.NITIDA_ENDPOINT ?? "https://assets.example.com", apiKey: process.env.NITIDA_RUNTIME_KEY, tenantCode: "demo", // Required: variant URLs are tenant-prefixed in base36 (`/a/v/<sha>-lg.webp`). tenantId: Number(process.env.NITIDA_TENANT_ID ?? 1),});
export async function uploadImage(file: File) { const result = await nt.upload(file, { // ⚠️ "original" is non-negotiable at FIRST ingest. A variant you do not ask // for here can never be recovered later: the cleanup job reaps `raw/`, and // `regenerate()` then has nothing left to read from. presets: ["original", "thumb", "md", "lg"], // Browser-only; in Node/Bun it no-ops with a warning and uploads raw bytes. compress: true, });
// `upload` returns ids and the canonical URL — not a full asset. Build the // other URLs from the sha. return { assetId: result.assetId, canonical: result.cdnUrl, display: nt.urlFor({ sha: result.sha256 }, "lg"), };}Render it
Section titled “Render it”/** * Build image URLs without uploading anything. * * `@aquienpz/asset-client` has no network and no key — it is pure URL * construction. Import it directly when all you do is render. */import { getTransformSrcSet, getTransformUrl, setCdnBase, setTenantId, type TransformWidth,} from "@aquienpz/asset-client";
// The builders read PROCESS-GLOBAL config. Pin it once where your helpers live,// so every module that imports a builder is configured.setCdnBase(process.env.NEXT_PUBLIC_NITIDA_CDN ?? "https://8ok.uk");setTenantId(Number(process.env.NEXT_PUBLIC_NITIDA_TENANT_ID ?? 1));
// Lock the output format to your project's policy rather than using `auto`.const FORMAT = "webp" as const;
/** * ⚠️ The width MUST be on the unsigned ladder. Any other width is an HTTP 400 * at the edge — that is a deliberate DoS guard, not a bug. Importing * `TransformWidth` turns an off-ladder width into a COMPILE error, which is * where you want to find out. */export const imageUrl = (sha: string, width: TransformWidth) => getTransformUrl({ sha }, { format: FORMAT, width });
export const imageSrcSet = (sha: string, widths: readonly TransformWidth[]) => getTransformSrcSet({ sha }, widths, { format: FORMAT });
/** Fixed-aspect cards and thumbnails want a cover crop. */export const thumbUrl = (sha: string, width: TransformWidth) => getTransformUrl( { sha }, { format: FORMAT, width, fit: "cover", gravity: "auto" }, );Widths are a ladder, not a number
Section titled “Widths are a ladder, not a number”A width outside TRANSFORM_WIDTHS answers HTTP 400 at the edge. That is a
deliberate guard — an open resize endpoint is a denial-of-service amplifier —
not a bug to work around. Import the TransformWidth type and an off-ladder
width becomes a compile error instead of a production 400:
160 · 240 · 256 · 320 · 400 · 480 · 600 · 640 · 800 · 9601080 · 1200 · 1280 · 1440 · 1600 · 1920 · 2560 · 3840Two URL shapes, and they are not interchangeable
Section titled “Two URL shapes, and they are not interchangeable”| shape | tenant-scoped | |
|---|---|---|
Stored variants — urlFor, srcSetFor, cdnUrl |
<cdn>/<tenantId b36>/v/<sha>-<preset>.<ext> |
yes |
On-the-fly transforms — transform, transformSrcSet |
<cdn>/t/<dsl>/<sha>.<ext> |
no |
Transforms are content-addressed by sha and resize from the source on demand,
so they carry no tenant segment. Variants are pre-generated per tenant and do.
Using /t/ for a stored video answers 410 — see
Video and HLS.