Here's a build log that would have been impossible a year ago.
- I told an agent to clone unshutter and get it running.
- It provisioned a Cloudinary Claimable Cloud, a Neon Postgres database and a Stripe sandbox, without me creating a single account.
- It configured the media pipeline, then ran a real test purchase end to end: watermarked preview → Stripe Checkout → signed download of the original.
- It handed me two claim URLs and stopped.
I claimed the environments the way you review a PR: after seeing the working result.
The storefront is just the demo. What I actually want to talk about is the provisioning.
The one step agents couldn't do
Agents don't evaluate a product the way we do. They don't read the landing page or compare the pricing table. They want to try it: spin something up, build against it, run the result, decide.
Until recently every platform had exactly one step that blocked that: creating the account. A signup form, an email confirmation, a dashboard to fish an API key out of. Ten minutes of human clicking standing in front of hours of autonomous work.
That step is gone across this whole stack:
npx @cloudinary/cloud # Claimable Cloud → CLOUDINARY_URL in .env, claim URL, 24h
npx neon-new --yes # Claimable Postgres → connection string, claim URL, 72h
stripe sandbox create # anonymous sandbox → working test keys
Three commands, no signups, and each one returns working credentials plus a claim link.
Cloudinary's version returns something like this:
CLOUD_NAME=a1b2c3d4
API_KEY=123456789012345
API_SECRET=«redacted»
CLOUDINARY_URL=cloudinary://123456789012345:«redacted»@a1b2c3d4
EXPIRES_AT=2026-08-06T08:29:07Z
CLAIM_URL=https://cloudinary.com/users/agent_email_confirmation?token=«redacted»
It writes CLOUDINARY_URL straight into .env and the whole platform is live from that first call: uploads, transformations, AI add-ons, CDN delivery. There's no trial tier with a queue in front of it.
Two properties matter for what follows:
- Unclaimed, it expires in 24 hours. Claiming it (email confirmation on that URL) converts it to a permanent free account, with the same cloud name, the same credentials and every uploaded asset intact.
-
Unclaimed, media delivery is locked to a public IP, by default the one the command ran from. You can name up to three at provisioning time by repeating
--ip, which is enough for your laptop plus a preview host, but not enough for the open internet.
The second one is the interesting bit: the claim is the go-public switch.
The agent's build works from the IPs it was provisioned for. Claiming is the moment it can serve media to anyone, anywhere. That's why the agent stops at a working local site instead of deploying: deploy before claiming and you've published something that looks broken to every visitor but you. Claim, then deploy.
What it built
unshutter (v.: to open the shutters; to let the light in) is a stock-media storefront selling photos, videos and photosets. Next.js 16, Payload CMS, Neon, Stripe, Cloudinary, MIT-licensed. Every mechanic below links to the file that implements it, so you can check my work.
The paywall is enforced by the media layer, not by hiding URLs.
- Originals upload as restricted assets,
type: privatefor images andtype: authenticatedfor video (uploadOriginal.ts). The CDN refuses to deliver them unsigned. - With Strict Transformations on, only allowlisted transformations resolve publicly (
PreviewImage.tsxis the one component allowed to render them). There's no URL you can craft to get the un-watermarked variant, and what the public can see is deliberately degraded: watermark,q_auto:low, size-capped. - Paying mints a ~10-minute signed download URL (
buildDownloadUrl.ts). Stripe's payment record is the entitlement, checked with one live API call per visit (verifySession.ts). No user table, no entitlement table.
Videos lean on e_preview for an AI-picked five-second clip plus six filmstrip stills (videoTransformations.ts). Photosets sell a folder as one product, delivered as a signed, expiring zip that generate_archive assembles on the fly (buildPhotosetZipUrl.ts, ingest script).
![]() |
![]() |
It's worth reading even if you never run it:
-
src/lib/cloudinary/README.md: the honest version of this section. Every named transformation with its exact recipe, whyf_autohas to be chained outside the named transformation, and the trade-offs I accepted rather than solved. Every URL builder sits insrc/lib/cloudinary/with unit tests beside it. -
docs/ARCHITECTURE.md: how Payload, Neon, Stripe and Cloudinary divide the work, plussrc/collections/for the CMS hooks that upload on save and destroy on delete. -
src/site.config.tsandunshutter-tokens.css: rebranding it as your own storefront is those two files and no component edits.
Read the code: github.com/cloudinary-devs/unshutter
Every one of those mechanics is configuration that has to exist inside the media account: named transformations, strict-mode allowlists, delivery types, signed archives. That's the real test of an anonymously provisioned one.
Could it actually do the work?
Provisioning an empty environment is the easy half. The question is whether an agent can stand all that up in one:
It configured the pipeline. Four named transformations created over the Admin API (the watermark chain, OG images, the AI video preview, the filmstrip stills), each flagged allowed_for_strict: true. No console, no clicking.
It verified the security model instead of assuming it. Uploaded test media, then requested a raw original URL unsigned and confirmed the 401. That check matters: if Strict Transformations happens to be off, the watermark is bypassable and everything above is theatre. So the agent proves it and reports what it found.
It ran the money path. Browse → cart → Stripe Checkout with 4242 4242 4242 4242 → webhook → /download → signed URL → un-watermarked original on disk.
Then it stopped and told me what existed and what needed claiming.
All of it ran against live infrastructure rather than mocks. When it needed current facts rather than whatever was in its training data, it read Cloudinary's agent docs and machine-readable llms.txt, the same entry points any agent can load.
Where the human went
| Before | Now | |
|---|---|---|
| Get three live environments | ✋ three signup forms, dashboard key hunts | 🤖 three commands, no accounts |
Write the credentials into .env
|
✋ copy-paste | 🤖 |
| Configure the media pipeline | ✋ console clicking | 🤖 Admin API |
| Verify the paywall holds | ✋ hope | 🤖 tested, reported |
| Own the environments | ✋ first, before a line of code ran | ✋ last, confirm an email, ≤24h / ≤72h |
| Deploy it | ✋ | ✋ your account, your call |
| Decide it's real money time | ✋ | ✋ |
Read that middle row on its own: the email confirmation didn't disappear, it just stopped being step zero. Same click, opposite end of the process. By the time you make it, you're confirming something you've already watched work.
The human touches didn't only shrink, they moved to after the work, where they feel like decisions rather than chores. What's left is ownership, infrastructure and judgment, which is roughly what should need a person anyway.
Things that bit us
None of this was completely frictionless but the agent was able to route around it. Some things worth knowing before you try it:
Claimable Clouds make it easy to provision a working environment, but claiming one means confirming an email address. If that address already belongs to a Cloudinary account, the claim page rejects it. So a claimable cloud you provision for real work can't be folded into the account you already have. If you're already a customer, just hand the agent credentials from your own account.Already have a Cloudinary account to use? You don't have to provision a Claimable Cloud (but you might want to)
An unclaimed cloud delivers media only to the IPs it was provisioned for, by default just the machine that ran the command. Locally everything is perfect; deploy it and the site is fine for you and broken for everyone else. You can allow up to three addresses at provisioning time by repeating The IP lock surprises you at deploy time
--ip (handy for a laptop plus a preview host, and requester_ip is accepted as a literal), but going properly public means claiming, which lifts the restriction entirely. So: claim before you deploy, and if you're scripting this, have your agent report the claim URL loudly rather than burying it in a summary.
Request it in a plain delivery URL and you get an HTTP 423 until the derived asset exists. It has to be eager-generated at upload with e_preview can't generate on the flyeager_async: true, plus a signature-verified webhook to flip a previewReady flag when Cloudinary finishes. Also: the source needs to be longer than the excerpt, so a 3-second clip fails silently.
Which means the "preview ready" callback never lands during local development. Either tunnel (ngrok/cloudflared) or flip the flag manually; the repo does the latter as a documented dev fallback.Cloudinary can't POST a webhook to localhost
Strict Transformations and the AI add-on registrations are account-posture settings you toggle in the console. The add-ons don't block anything: photo ingest retries without them and lands the image untagged. Strict mode does matter, which is why the agent tests for it rather than trusting it.Two settings still have no API
Try it
Point an agent at the repo:
Clone cloudinary-devs/unshutter and get it running locally, using claimable environments. Don't ask me for credentials, don't deploy anywhere. Give me the local URL and the claim URLs when you're done.
Or run the three commands yourself and follow the quick start.
cloudinary-devs
/
unshutter
Photography paywall storefront. An open-source showcase demonstrating Cloudinary in a modern full-stack app - Cloudinary + Next.js 16 + Payload CMS + Neon + Stripe + Vercel
unshutter
unshutter (v.): to open the shutters; to let the light in.
A stock-media storefront an AI agent can take from git clone to a running, verified store — media, database, payments — without a single human signup. Every platform in the stack provisions claim-later environments (npx @cloudinary/cloud, npx neon-new, stripe sandbox create), so the human touches move to where they belong: claiming something that already works. Paste the Agent quick start into your agent and watch.
Built on Next.js 16 + Payload CMS + Neon + Stripe + Vercel, with Cloudinary as the media layer. Sibling to kickoff-cards (Cloudinary + Supabase).
Three product shapes:
- Photos — browse watermarked, heavily compressed Previews; paying unshutters the image with a time-limited signed link to the full-size Original
-
Videos — an AI-selected 5-second watermarked, low-res preview clip (Cloudinary
e_preview) plus six auto-extracted watermarked stills; purchase…
The repo ships the guardrails that made agent-building work on a stack this fiddly: AGENTS.md working agreements, a provisioning walkthrough with every human touch called out, and the verified platform facts the implementation rests on. Sibling project, same idea with Supabase: Kickoff Cards.
Further reading: Claimable Cloud provisioning · Agents, start here
Most of the media plumbing is the agent's job now. What was left for me was deciding whether to keep what it built.
| Cloudinary ❤️ developers |
|---|
| Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today. |
| 👉 Create your free account |





Top comments (0)