One command, and your media is live on Cloudinary.
npx @cloudinary/cloudProvisions a disposable Cloudinary cloud account — no signup, no credentials, no human
in the loop. Working CLOUDINARY_URL credentials land in ./.env; a claim URL lets a human
make the account permanent later (same credentials, assets retained).
Built for AI coding agents: unauthenticated, non-interactive, IP-locked delivery, TTL-bounded, rate-limited per IP.
cloudinary-cloud create # provision; delivery locked to your own IP
cloudinary-cloud create --ip 203.0.113.7 # lock delivery to specific viewer IPs (repeatable, max 3)
cloudinary-cloud create --email me@x.com # pre-fill the claim page (never verified at creation)
cloudinary-cloud create --force # replace an existing CLOUDINARY_URL in ./.env
cloudinary-cloud create --no-env # don't touch ./.env — credentials are only printed
cloudinary-cloud create --json # raw provisioning response for programmatic useRunning with no arguments defaults to create.
- TTY — human summary; the claim URL is the headline. Never opens a browser.
- non-TTY — greppable
KEY=valuelines (CLOUD_NAME=…,CLAIM_URL=…, …). --json— the raw API response plusenv_file/env_file_action.
- If
./.envalready containsCLOUDINARY_URL,createexits 1 without provisioning (clouds are rate-limited; don't burn one you won't store).--forcereplaces the line. - If the
.envwrite fails after provisioning (read-only cwd, CI, clouded runtime), the credentials are still printed in full withENV_FILE_ACTION=failedand a warning — a provisioned cloud is never swallowed by a filesystem error. - If
.envisn't covered by.gitignorein a git repo,createwarns after writing. - Cloud media delivery is locked at the CDN edge to
delivery_ips. By default the CLI sends none and the server locks delivery to the address the request came from — the right default when the caller is also the viewer. Explicit--ipvalues are sent verbatim. After creation the CLI checks (best-effort) whether this machine's public IP is in the returned allow-list, and warns with the exact fix if not — the API path and the delivery path can exit from different addresses behind VPNs and NAT pools. - The claim URL and expiry are persisted to
.envtoo (CLOUDINARY_CLOUD_CLAIM_URL,CLOUDINARY_CLOUD_EXPIRES_AT), so the claim path survives lost terminal output. - Cloud lifetime is server-controlled (no TTL parameter in the API).
- Unclaimed clouds are reaped at
expires_at, assets included. Claiming (a human opensclaim_urland verifies an email) makes the account permanent with the same API key.
The provisioning client is exported for other tools:
import { provisionCloud, runCreate, REQUESTER_IP_SENTINEL } from '@cloudinary/cloud';
const account = await provisionCloud({ deliveryIps: [REQUESTER_IP_SENTINEL] });npm install
npm run build # compile TypeScript to dist/
npm test # build + node:test (no network; tests own their HTTP stubs)Zero runtime dependencies. Point the CLI at any environment:
CLOUDINARY_API_HOST=https://staging.example node dist/index.js createSpeaks POST /v1_1/provisioning/clouds (public, unauthenticated, rate limited per IP):
- Request:
delivery_ips(optional; array of 1–3 public IPs and/or"requester_ip"— the server always appends the requester's resolved address),email(optional, unverified pre-fill). No TTL parameter — lifetime is server-set. - Response:
id,email,expires_at,delivery_ips,claim_url,guidance, and credentials inproduct_environments[0].api_access_keys[](key/secret). - Errors:
{ error: { category, code?, message, details? } }with codes such asdelivery_ips_not_public,ip_rate_limit_exceeded,global_rate_limit_exceeded,agent_registration_disabled.
New response fields flow into --json automatically; the command surface treats its
flags as a stable API.