What this is, and when to use it
This is where your product’s media lives. Every image, every video, every PDF — uploaded by a user or shipped by you — stored once, addressed by content, measured per tenant, and delivered from a CDN in whatever shape the page needs.
A modern Cloudinary, built agent-first.
The four jobs
Section titled “The four jobs”1 · Store everything, once. Content-addressed by SHA-256, so the same bytes are the same asset no matter who uploaded them or what they called the file. Deduplication is not a feature you turn on; it is what an address is.
2 · Get bytes off a device, reliably. Multipart with resume across a reload, progress per file, compression before anything leaves the phone. On mobile, a native background session so the upload survives a locked screen.
3 · Serve whatever shape the page needs. One upload, many outputs: a 256px thumbnail, a 1920px hero, a square crop, a different format — on demand behind a CDN, not pre-generated by you and not stored five times.
4 · Do the video work you do not want to build. Poster, web-safe MP4, an adaptive HLS ladder, and a cheap proxy meant for a model rather than a person.
Yes, put your logos and hero videos here
Section titled “Yes, put your logos and hero videos here”Static assets belong here too, and that is the point rather than an exception.
A logo in your bundle is a logo you cannot swap without a deploy. Here it is:
- addressed — one URL, every size, every format, from the same SHA;
- countable — it shows up in your tenant’s storage and file count;
- swappable — bind it to a slot and an admin changes the hero without touching code;
- optional to optimise — ask for
["original"]and it is stored verbatim, byte-for-byte, no re-encode. The superpowers are opt-in, not mandatory.
The last point is worth stating plainly: you can use this as plain storage.
A file uploaded with presets: ["original"] comes back exactly as you sent it.
Everything else — ladders, transforms, palettes, transcodes — is something you
ask for, per asset.
What you can measure
Section titled “What you can measure”const s = await nt.usage.snapshot();s.storage.totalBytes // how much this tenant is holdings.storage.assetCount // how many filess.today, s.last30Days // what movedawait nt.usage.keys(); // which key did itPer-tenant storage and file count are first-class, which is what makes this usable as the asset store rather than one of several.
Grouping today: slots and metadata
Section titled “Grouping today: slots and metadata”Being honest about the current state, because “collections” means different things to different people:
- Slots are named bindings —
storefront.cr.hero— and they list by prefix, soslots.list({ prefix: "storefront.cr." })is a namespace. That is the grouping primitive that exists and it is the one most products need. patchMetadata(id, {...})attaches arbitrary JSON to an asset.- A first-class Collection entity does not exist yet. If you need album-like grouping with its own listing and permissions, you build it in your database today, keyed by SHA.
When it is genuinely the wrong tool
Section titled “When it is genuinely the wrong tool”Short list, and none of these are about storage:
- Destructive editing. Crops, filters and layers that a user manipulates and re-saves. This transforms on read and keeps one canonical original — it is deliberately not an editor.
- Document intelligence. PDFs and spreadsheets are stored and served intact, but there is no page rendering, no text extraction, no OCR. Store them here; if you need to read inside them, that is a second system.
- Sub-100 ms first byte on a brand-new derivative. The first request for a shape nobody has asked for yet pays for its own generation. Every request after that is a CDN hit — so this only matters for a cold, one-off shape.
What it costs you to adopt
Section titled “What it costs you to adopt”- A server-side seam. The upload key cannot live in the browser, so you need one route of your own. About fifteen lines.
- Deciding presets at first ingest. A variant you do not ask for on the first upload cannot be added later — see Variants and presets.
- Onboarding is a conversation. No self-serve signup yet; you get an endpoint, a tenant id and keys from us. See Getting your credentials.
The mental model in one line
Section titled “The mental model in one line”The SHA is the asset. Everything else — every size, every format, every URL — is a function of it.
Once that lands, the API stops having surprises: upload() gives you a SHA, and
every builder takes a SHA and returns a URL.