
@dropthis/cli
Official CLI for dropthis -- the publish layer between AI and the internet. Built for humans, AI agents, and CI/CD pipelines.
Install
npm install -g @dropthis/cli
Requires Node.js >= 20.
Quick start
dropthis login
dropthis ./index.html
Authentication
Log in with email OTP:
dropthis login
The CLI prompts for your email and a one-time code. For non-interactive environments (agents, CI), use the two-step flow:
dropthis login request --email you@example.com
dropthis login verify --email you@example.com --otp 123456
A failed verify prints Your code has expired (otp_expired) or That code is incorrect (otp_invalid) and exits non-zero.
Scopes
Keys are minted with a capability bundle. The default for dropthis login is publish — enough to publish drops and read your own context, nothing else. To manage a team you need a wider bundle, requested with --scope on login verify:
publish (default) | publish + read your own account/workspaces + mint downscoped keys | publishing, CI |
team | publish + create/rename workspaces, invite/manage members | running a team |
team-admin | team + delete workspaces, remove members, change roles | irreversible team admin |
service | publish without key-minting (workspace-pinned) | automation / CI that can't mint keys |
dropthis login verify --email you@example.com --otp 123456 --scope team
Minting is downscope-only — a minted key can never have more scopes than the key that minted it. Check what your current key carries:
dropthis whoami --json
If a command returns insufficient_scope (exit 1, HTTP 403), your key lacks a scope that route needs. Fix it by re-logging in with the right bundle (dropthis login verify … --scope team) or, via the SDK, apiKeys.create({ scopes: ["team"] }).
Credentials resolve in this order:
--api-key sk_... flag
DROPTHIS_API_KEY environment variable
- Stored credential from
dropthis login
If you run dropthis publish without credentials in an interactive terminal, the CLI prompts you to log in inline — no separate dropthis login step needed. Disable with --no-interactive.
dropthis whoami
dropthis logout
dropthis logout --revoke
Publish
publish is the default command — you can omit it and pass files directly:
dropthis ./report.html
dropthis publish ./report.html
dropthis ./report.html
dropthis ./dist
echo "<h1>Hello</h1>" | dropthis publish - --content-type text/html --path index.html
dropthis index.html styles.css app.js
dropthis ./dist --url
Publish options
dropthis publish ./dist \
--title "Launch page" \
--visibility unlisted \
--entry index.html \
--expires-at 2026-12-31T00:00:00Z
publish always creates a NEW drop with a new URL. To change a drop you already published, use update-content or update-settings with its drop_… id — never publish again.
Canonical vs raw URLs
Every drop has a canonical url — an always-branded human view (badge guaranteed). For a single non-HTML file (.pdf, .png, .json, a URL you asked the server to fetch, etc.), publish also prints a Raw: line: the file's exact bytes at a natural path, no wrapper. Hand the canonical url to people; hand the raw URL to agents or downstream tooling that needs the real bytes.
Publishing a bundle with remote assets (--manifest)
When an AI agent generates an HTML page that references external images or other files, use --manifest to publish everything as one drop without base64-encoding the assets. The server fetches remote files during publish so bytes never pass through your process.
dropthis publish --manifest bundle.json
bundle.json shape:
{
"files": [
{
"path": "index.html",
"content": "<html>...</html>"
},
{
"path": "hero.jpg",
"source_url": "https://cdn.example.com/hero.jpg"
},
{
"path": "data.json",
"content_base64": "eyJrZXkiOiJ2YWx1ZSJ9"
}
]
}
Each file entry must have a path and exactly one content key:
content | UTF-8 text content (HTML, CSS, JSON, markdown, …) |
source_url | Public https:// URL — the server fetches the bytes server-side. Use this for images and other binary assets instead of base64-encoding them. |
content_base64 | Base64-encoded bytes (for binary files you already have in memory) |
Optional per-file keys: content_type (MIME type override).
The --manifest flag also works on update-content:
dropthis update-content drop_abc123 --manifest bundle-v2.json
Cannot be combined with a positional file/folder/URL argument.
All publish flags:
--title <title> | Drop title |
--visibility <public|unlisted> | Drop visibility |
--password <password> | Require a password to view the drop (Pro plan) |
--noindex | Prevent search indexing |
--slug <vanity-slug> | Vanity slug for a path-mode custom domain (only with --domain; Pro) |
--domain <hostname> | Serve the drop under one of your connected custom domains (Pro) |
--shared | Publish to the shared dropthis pool, bypassing your default custom domain |
--workspace <slugOrId> | Target a specific workspace for this publish (delegated keys) |
--entry <path> | Entry file for directories |
--content-type <mime> | MIME type (required for stdin) |
--path <path> | Filename (required for stdin) |
--manifest <file> | Multi-file bundle JSON (see above) |
--expires-at <datetime> | Expiration datetime |
--metadata <json> | Metadata as JSON string |
--metadata-file <path> | Metadata from a JSON file |
--idempotency-key <key> | Idempotency key |
--url | Print only the URL, nothing else |
--json | Output full JSON response |
--dry-run | Validate without publishing |
Global flags
These flags are inherited by all commands:
--api-key <key> | Override API key for this invocation |
--api-url <url> | Override API base URL |
--json | Force JSON output |
-q, --quiet | Suppress status output and imply JSON |
--no-interactive | Disable interactive prompts (inline auth, confirmations) |
Update
Change an existing drop without creating a new URL. Content and settings are separate operations — pass the full drop_… id (not the slug):
dropthis update-content drop_abc123 ./dist-v2
dropthis update-content drop_abc123 --manifest bundle-v2.json
dropthis update-settings drop_abc123 --title "New title"
dropthis update-content drop_abc123 ./dist-v2 --if-revision 1
update-content updates the files at the URL — patch by default: files you don't include are carried forward, so you can ship a single changed file. Use --mode replace (or --replace) for a full content swap, and --delete-path <path> (repeatable) to remove a file. update-settings changes title, visibility, password, noindex, expiry, or metadata — including --no-password to clear a password and --index to re-allow indexing. Creating a new drop is always publish — neither update command makes a new URL.
With --if-revision, a concurrent edit fails with a 409 instead of clobbering. The error shows the drop's current_revision and how to retry:
✗ Revision mismatch
The drop changed since it was last read. Re-read it with GET /v1/drops/{drop_id} and retry with if_revision: 2 (sent as the If-Revision header).
Current revision: 2 — retry with --if-revision 2
In JSON mode the same error carries current_revision and a next_action field.
Pull (read back)
Download what a drop is serving — by drop_… id, drop URL, or slug. URLs and slugs are resolved to your own drops; the local copy is also the rollback path (pull an old state, then update-content it back):
dropthis pull drop_abc123
dropthis pull https://abc123.dropthis.app
dropthis pull drop_abc123 -o ./site
Pull fetches the current deployment's file manifest and writes every file into the output directory. It is owner-side read-back via the API — it works regardless of any viewer password. Custom-domain URLs are not resolvable yet; use the drop_… id instead.
Teams & workspaces
Publishes land in a workspace. Everyone has a personal workspace; teams add shared ones. List the workspaces you belong to and switch the active one (delegated keys):
dropthis workspace list
dropthis workspace use acme
Creating and managing team workspaces needs a team-scoped key (see Scopes):
dropthis workspace create "Acme" --slug acme
dropthis workspace rename ws_team123 --name "Acme Inc"
dropthis workspace delete ws_team123 --yes
Manage who's in a workspace:
dropthis members list ws_team123
dropthis members invite ws_team123 --email teammate@acme.com --role member
dropthis members role ws_team123 acc_123 --role admin
dropthis members remove ws_team123 acc_123 --yes
Accept an invitation you received:
dropthis invitations list
dropthis invitations accept --token inv_tok_abc
dropthis invitations accept-by-id inv_123
To publish into a specific workspace for one call, pass --workspace <slugOrId> on publish (delegated keys only).
Commands
dropthis [input...]
dropthis publish [input...]
dropthis update-content <drop-id> [input]
dropthis update-settings <drop-id>
dropthis pull <id|url|slug> [-o <dir>]
dropthis get <id|url|slug>
dropthis resolve <id|url|slug>
dropthis list
dropthis list --domain reports.example.com
dropthis delete <id|url|slug>
dropthis deployments list <drop-id>
dropthis deployments get <drop-id> <dep-id>
dropthis domains connect <host> --mode <path|dedicated>
dropthis domains list
dropthis domains status <host>
dropthis domains verify <host> [--wait]
dropthis domains update <host> [--default|--drop <id>]
dropthis domains remove <host> --yes
dropthis workspace list
dropthis workspace use <slug|id>
dropthis workspace create <name> --slug <s>
dropthis workspace rename <ws> --name <name>
dropthis workspace delete <ws> --yes
dropthis members list <ws>
dropthis members invite <ws> --email <e> --role <admin|member>
dropthis members role <ws> <account-id> --role <owner|admin|member>
dropthis members remove <ws> <account-id> --yes
dropthis invitations list
dropthis invitations accept --token <token>
dropthis invitations accept-by-id <inv-id>
dropthis login
dropthis login request --email <email>
dropthis login verify --email <email> --otp <code>
dropthis logout
dropthis whoami
dropthis account
dropthis account update --display-name <name>
dropthis account delete --yes
dropthis api-keys create [--service --workspace <ws>]
dropthis api-keys list
dropthis api-keys delete <key-id> --yes
dropthis upgrade
dropthis doctor
dropthis commands
Agent & CI/CD usage
The CLI is designed for non-interactive use. In non-TTY environments (pipes, CI, agents), output defaults to JSON automatically.
Environment variables:
export DROPTHIS_API_KEY=sk_live_...
export DROPTHIS_API_URL=https://...
export DROPTHIS_NON_INTERACTIVE=1
export DROPTHIS_NO_UPDATE_NOTIFIER=1
export NO_COLOR=1
dropthis publish ./dist --url
Agent protocol:
- Use
--url to get only the published URL (cleanest for agents)
- Use
--json for the full structured response
- Use
--yes for destructive commands (delete, api-keys delete, account delete)
- Use
--no-interactive to disable inline auth prompts
- All errors write to stderr as JSON with a
next_action field
Exit codes:
| 0 | Success |
| 1 | API or generic error |
| 2 | Invalid usage |
| 3 | Auth required (no credential, or the API returned 401 — re-authenticate) |
| 4 | Local input error (file or directory not found, too many files) |
| 5 | Network error (could not reach the API) |
| 6 | Domain verification pending (domains verify one-shot, not live yet) |
| 7 | Domain verification timeout (domains verify --wait exceeded --timeout) |
This table — plus the global flags (--api-key, --api-url, --json, --quiet, --no-interactive) — is machine-readable from dropthis commands --json (the exit_codes and global_options fields), so an agent can discover the whole surface without scraping docs.
Some failures share exit 1 but carry a distinct error code in the JSON envelope:
insufficient_scope | 403 | The key lacks a scope this route needs. Re-login with the bundle (dropthis login verify … --scope team), or mint a key with it via the SDK. See Scopes. |
quota_exceeded | 403/413 | A plan limit was hit. Check dropthis account --json (entitlements.limits vs usage) or upgrade. |
JSON output shape:
{"ok":true,"drop":{"url":"https://abc123.dropthis.app/","id":"drop_abc123"}}
Error shape (stderr):
{"ok":false,"error":{"code":"auth_error","message":"No API key found.","next_action":"Set DROPTHIS_API_KEY or run dropthis login."}}
Doctor
Run diagnostics to verify CLI health:
dropthis doctor
Reports CLI version, auth source (env, flag, storage, or missing), and credential storage backend (secure, insecure, or none).
{"ok":true,"version":"0.32.0","auth":{"source":"env"},"storage":{"backend":"secure"}}
Add --online for a network preflight an agent can run before a publish to tell apart the common failures — re-auth vs network vs an unresolved workspace:
dropthis doctor --online --json
It hits GET /v1/account (token valid? which workspace resolves? which plan?) and lists custom-domain verification status as a checks array. It exits 3 on an auth failure and 5 on a network failure, so a script can branch on the exit code.
For a pre-publish quota check, dropthis account --json returns entitlements.limits (e.g. maxStorageBytes, maxCustomHostnames, seatLimit) alongside your current usage — read it before a large publish to avoid a quota_exceeded failure.
Pricing tiers
dropthis has four tiers — Free, Keep, Pro, and Business. Free drops carry the dropthis badge and expire after 30 days; paid tiers add permanence, larger limits, custom domains, passwords, and analytics. Run dropthis account to see your active tier and its exact caps, or visit https://dropthis.app/pricing for the current plans.
Connecting a custom domain (dropthis domains connect) requires the Pro plan (1 hostname). Domains already connected keep working regardless of plan.
dropthis account shows your active tier, its limits, and the workspace your key is bound to. If the workspace kind is team, your publishes land under the team's shared custom domain automatically — no extra flags needed.
Agent skills
For AI coding agents (Cursor, Claude Code, Windsurf, etc.), install the dropthis-skills package:
npx skills add dropthis-dev/dropthis-skills
Links