Skip to content

Quickstart

Terminal window
bun add @aquienpz/sdk @aquienpz/asset-client

Both 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-an-image.ts
/**
* 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"),
};
}
image-urls.ts
/**
* 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" },
);

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 · 960
1080 · 1200 · 1280 · 1440 · 1600 · 1920 · 2560 · 3840

Two URL shapes, and they are not interchangeable

Section titled “Two URL shapes, and they are not interchangeable”
shape tenant-scoped
Stored variantsurlFor, srcSetFor, cdnUrl <cdn>/<tenantId b36>/v/<sha>-<preset>.<ext> yes
On-the-fly transformstransform, 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.