HomeSkillsEasy API Doppler

Easy API Doppler

Skillv1.110 files

Stop pasting API keys into your AI session. Put secrets in Doppler once, then hand Claude the name, not the value. The set command refuses an empty clipboard, a variable name pasted as a value, and a clipboard that changed mid-flow. Plus a transcript scanner, .env importer and macOS button.

dopplersecretsapi-keysenvsecurityvercelclaude-codedevops
CategoriesDeveloper ToolsProductivity

Documentation

# Easy API Doppler You paste an API key into the chat so your assistant can wire it up, and the first thing it says back is that you should rotate that key. The advice is correct, which is the annoying part. Everything you type into a session gets written to a transcript file on your own disk, in plain text, and it stays there. Whatever you pasted is now sitting in a file you will never open again. So the fix is not a better prompt or a stricter permission rule. It is to keep the value out of the conversation in the first place. By the end of this you will add a key like so: 1. Copy it from the vendor's site. 2. Run one command. 3. Tell Claude the name. The value goes clipboard, pipe, vault. It never touches the model, so it never touches the transcript, so there is nothing to rotate. ### Where the bundled commands live Installing put five helper scripts in one place. Set this once per terminal and every command below works from whatever directory you happen to be in: ```bash SKILL=~/.claude/skills/easy-api-doppler ``` If you moved the skill, point `SKILL` at wherever it went. The plain `doppler` commands need none of this. --- ## Step 0. Prerequisites (blocking) Check these before anything else. If any are missing, **STOP** and get the missing piece first. Do not carry on with a half-configured vault, and do not write placeholder commands to paper over a gap. | # | Requirement | Check | Where to get it | |---|---|---|---| | 1 | A Doppler account, free tier is enough | You can sign in at dashboard.doppler.com | https://dashboard.doppler.com/register | | 2 | The `doppler` CLI | `doppler --version` prints a version | Installing this skill tries to install it for you. If it did not land, follow https://docs.doppler.com/docs/install-cli | | 3 | You are logged in | `doppler me` prints your email | `doppler login`, **in a real terminal window**. See the warning below. | | 4 | A project with a `.env` file you want to move | `ls -a` shows it | No `.env` yet? Skip Step 3 and add secrets one at a time from Step 6. | Only Step 7 needs anything else, and Step 7 is optional. If you plan to do it, you also need the `vercel` CLI (`vercel whoami` prints your username) and a project already linked with `vercel link`. If anything in the table above is missing, STOP. **The one that catches everybody.** `doppler login` opens a browser and waits on a `(Y/n)` prompt. It needs a real terminal. Run it through your AI session's shell, including Claude Code's `!` prefix, and it dies instantly with: ``` Doppler Error: EOF ``` That is a closed stdin, not a broken install. Open Terminal, run `doppler login` there, come back. You do this once per machine. --- ## Step 1. Find out how big your own problem is Before changing anything, look at what is already on disk. ```bash bash "$SKILL/scripts/scan-transcripts.sh" ``` It counts strings shaped like credentials across your saved sessions and prints file names and a total. It never prints a match, because printing one would put the key straight back into the session you are reading the report in. On a large history it takes a minute or two, and it modifies nothing. For scale, the machine this skill was built on reported 1,360 transcripts, 4.2 GB on disk, and 208 files holding a key-shaped string. A match is not proof that a live credential is in there. Test keys, examples and long-dead keys all match too. But any key you actually remember pasting should be treated as exposed and rotated now, on its own merits. Nothing below undoes what is already written. --- ## Step 2. Point Doppler at a project One Doppler project per repo. Names are lowercase and hyphenated. ```bash doppler projects create my-app ``` Every project gets environments. Two is plenty: - `dev` holds what your `.env.local` holds. Your laptop reads this one. - `prd` holds the real production values. Doppler creates `dev`, `stg` and `prd` for you. Ignore `stg` until you need it. --- ## Step 3. Import the `.env` you already have ```bash bash "$SKILL/scripts/doppler-import.sh" .env.local my-app dev ``` That wrapper exists because the bare CLI has three sharp edges: **It prints your secrets.** `doppler secrets upload` without `--silent` dumps the whole config to stdout when it finishes. In an AI session that output is a tool result, and a tool result is transcript. Six live keys reached a transcript this way on the day this skill was written, from a single missing flag. **It accepts values it should not.** A `.env` pulled down from a deploy platform carries injected names like `VERCEL` and `VERCEL_TARGET_ENV` that the platform then refuses to take back. The wrapper drops them. **Empty values are ambiguous and dangerous.** A blank value in a pulled file means either the variable is genuinely empty or the platform marked it sensitive and would not hand it over. The wrapper leaves those out and names them, which is the safe answer in both cases. Read Step 7, then re-run with `--include-empty` if you have confirmed they really are empty. Afterwards it verifies by reading the names back. Doppler adds three of its own (`DOPPLER_PROJECT`, `DOPPLER_CONFIG`, `DOPPLER_ENVIRONMENT`), so seeing three more than you imported is correct. If you would rather do it by hand, the flag is the whole point: ```bash doppler secrets upload .env.local --project my-app --config dev --silent ``` --- ## Step 4. Keep your `.env.local` file Delete nothing yet. In a Next.js app an injected environment variable wins over `.env.local`, which is verified behaviour in `@next/env`, so once you run through Doppler the file is a harmless fallback rather than a competing source of truth. Leave it for a few weeks. When you have forgotten it exists, delete it. The exception is a `.env` that was committed at some point. Two commands, and the second is the one that matters: ```bash git check-ignore -v .env.local # no output means it is not ignored git ls-files --error-unmatch .env.local # exit 0 means it is committed ``` If the second one succeeds, those values are in your git history and stay there through any number of later deletions. That is a separate emergency, and the only fix is rotating every key in the file. --- ## Step 5. Run your code through Doppler ```bash doppler run -p my-app -c dev -- npm run dev ``` Pin the directory once and the flags become optional: ```bash doppler setup --project my-app --config dev --scope "$(pwd)" doppler run -- npm run dev ``` Two things about pins. They are keyed by **absolute path**, so a git worktree or a CI checkout is a different path and is not pinned. Pass `-p` and `-c` there. And in `package.json`, where the same script runs from several places, spell the flags out: ```json "scripts": { "dev": "doppler run -p my-app -c dev -- next dev" } ``` Each invocation costs roughly 250 to 400 milliseconds against a 40 millisecond baseline. Irrelevant for a dev server. Wrong inside a loop. ### Never wrap `build`, `start` or `test` Wrap the dev server and nothing else. Your deploy platform's build container has no Doppler CLI and no Doppler token. Neither does your CI runner. Wrapping `build` breaks every deploy, and it is the single most common way people break their own site on day one of doing this. The production values get to production through the sync in Step 7, or through the platform's own dashboard. They do not get there through `doppler run`. ### `dev` on your laptop, always Running local work against `-c prd` points your machine at production Stripe, production Redis and the production database. It works, which is why people do it, and then a test row lands in a real table. --- ## Step 6. The command that replaces pasting keys This is the part that removes the friction. ```bash bash "$SKILL/scripts/doppler-set.sh" OPENAI_API_KEY my-app ``` Copy the key from the vendor's site, run that, then tell Claude the name. It writes `dev` and `prd`, always with `--silent`, and refuses four things the bare CLI accepts without comment: - **An empty clipboard.** The bare command stores an empty string, exits 0 and prints nothing. A later sync then writes that blank over the live secret. - **The variable's own name as its value**, or any all-caps underscored token. See the incident below. This is the guard that costs nothing and saves most. - **Replacing a value that already exists**, unless you pass `--overwrite` or answer the prompt. That write is the destructive one. - **Writing to a config that syncs to a deploy platform**, unless you pass `--allow-sync` or answer the prompt. It names the platform when it asks. - **A clipboard holding more than one line.** An API key is one line; several means you copied a block of text. Genuinely multi-line secrets go through the bare CLI. - **A clipboard that changed since the run started.** Set `DOPPLER_SET_EXPECT_SHA` to the fingerprint you showed the user and the write refuses if the clipboard has moved since. The button does this automatically. - **A value under 24 characters**, unless you pass `--force` or answer the prompt. Short is not always wrong, so it asks rather than refusing. With no terminal to ask on, the prompts refuse rather than assume yes. Everything that can be judged from the clipboard alone is available without a project or a variable name: ```bash bash "$SKILL/scripts/doppler-set.sh" --validate ``` It prints the length, flags a short value, and refuses an empty, multi-line, whitespace-bearing or variable-name-shaped clipboard. The button runs it as its first act, so a bad clipboard is caught before you are asked to pick anything. A wrong clipboard is wrong whichever project you were about to choose, and being told so after two dialogs is how you end up clicking through the warning. ### What the guard is for On 2026-08-29 the 25-character string `OPENROUTER_MANAGEMENT_KEY` was written as the *value* of `OPENROUTER_MANAGEMENT_KEY` in `my-app`. Two seconds later Doppler's Vercel sync pushed it to production and overwrote the only surviving copy of the real key. Doppler had no rollback and Vercel keeps no version history for env vars, so it could only be replaced, not recovered. **How the name got onto the clipboard is the whole story.** You copy the key, press the button, and then reach the field asking for the variable name — so you copy the name to paste in. That copy replaces the key. `pbpaste | doppler secrets set` then reads the clipboard as it is *now*, not as it was when you pressed, and stores the name. The button read the clipboard twice with a dialog in between, and its own interface gave you a reason to change it in that gap. Nothing else had to go wrong. Nothing caught it either. The value is longer than twelve characters and holds no whitespace, which was the whole of the checking. And the read-back that reported success compared the stored value against the clipboard it had just come from, so it agreed with itself. Two fixes, and they work together. The clipboard is now fingerprinted before any dialog opens and re-checked before every write, so a value that changed mid-flow is refused rather than stored. And the name is chosen from a list of what the project already holds, so there is nothing to copy in the first place. **A read-back is a transport check, not a correctness check.** It proves the bytes arrived unmangled. It cannot tell you they were the right bytes. The only thing that can is exercising the credential against the real API, which is a separate step and is never optional for a key that reaches production. ### By hand, if you would rather ```bash pbpaste | doppler secrets set OPENAI_API_KEY --silent -p my-app -c dev pbpaste | doppler secrets set OPENAI_API_KEY --silent -p my-app -c prd ``` That is the whole mechanism the wrapper is built on, and it is fine for a `dev` config on a project with no sync. It has none of the four refusals, so check the clipboard yourself before you run it, and read Step 7 before you point it at a `prd` that syncs anywhere. Both lines, because `dev` and `prd` are separate configs and your laptop reads `dev`. Write only `prd` and the key works in production while `npm run dev` cannot see it. The clipboard still holds the key for the second line. Where the two environments genuinely need different values, such as a payment provider's test and live keys, copy each one and write it to its own config, and pass the config names to the wrapper: `doppler-set.sh STRIPE_SECRET_KEY my-app prd`. On Linux, swap `pbpaste` for `xclip -selection clipboard -o` on X11, or `wl-paste` on Wayland. The wrapper picks whichever of the three you have. Then tell Claude the name: > `OPENAI_API_KEY` is in Doppler under `my-app`, in both `dev` and `prd`. Wire > it up and run things with `doppler run`. ### Why `--silent` is not optional Leave it off and a successful write prints the **entire config** to stdout when it finishes. Not the one variable you just set. All of them, values included. Measured on CLI v3.76.5 against a config holding three variables: 494 bytes out without the flag, 0 bytes with it. In a terminal you are looking at, that is a convenience. In an AI session it is a tool result, and a tool result is transcript. Prove it on your own machine rather than taking the number on faith. Write a throwaway value, count the bytes, confirm it landed anyway, delete it: ```bash printf 'probe-value-123' | doppler secrets set SCRATCH_PROBE --silent -p my-app -c dev | wc -c [ "$(doppler secrets get SCRATCH_PROBE --plain -p my-app -c dev | tr -d '\n' | shasum -a 256)" \ = "$(printf 'probe-value-123' | tr -d '\n' | shasum -a 256)" ] && echo "value matches" doppler secrets delete SCRATCH_PROBE --yes -p my-app -c dev ``` The first line prints `0`. The second prints `value matches`. Drop `--silent` from the first line and watch the byte count jump to the size of your whole config. Notice what the second line does not do. `doppler secrets get --plain` writes the value to stdout, so it stays inside a `$(...)` and only its hash comes out. Reach for that shape any time you need to confirm a real secret rather than a throwaway probe. The wrapper and the button do the same thing for the same reason. And notice what it cannot do. Here the expected value is written out twice, so the comparison is real. On a genuine key both sides come from the same clipboard, and a hash always agrees with itself. That check is worth running, because a mangled write is a thing that happens. It is just not the check you think it is, so do not let a green one stand in for using the credential. The one thing forfeited by never seeing the value is that you cannot eyeball it, and a key you have never looked at is a key you can get wrong without noticing. That is the trade, and the answer to it is Step 6's refusals plus a real call against the vendor's API, not a peek. ### Why the value never reaches the model The key moves clipboard, pipe, network. It is never a command-line argument, so it is not in your shell history and not in a process list. Your assistant sees the command you ran, not what flowed through the pipe. Run this yourself in a terminal, or paste the whole line into your session, since either way the only thing in the text is the variable's name. --- ## Step 7. Before you sync to a deploy platform Optional, and worth doing for anything already in production. A sync means adding a key in Doppler and having it appear in your hosting platform without you opening a dashboard. Doppler pushes, the platform receives, and you stop having two places to update. Skip this whole step unless the app is already linked to Vercel and deployed. Nothing later depends on it. **Run the checker first.** From your linked project directory: ```bash bash "$SKILL/scripts/vercel-sync-check.sh" production ``` It pulls your production variables and reports which ones came back empty. Those are the ones that will hurt you. Vercel lets you mark a variable sensitive, which makes it write-only, and a pull then returns it as an empty string with no error and no warning. Build a vault from that pull and you have stored blanks. Sync the blanks back and the live secrets are gone. That is not hypothetical. Forty-five production secrets were captured as empty strings this way while this skill was being written, including a service-role database key and an app secret. They were caught before the sync ran. A sync carries whatever Doppler holds, and a wrong value travels just as fast as a blank. On 2026-08-29 a placeholder written to `my-app/prd` reached Vercel production 2.1 seconds later and replaced a live key. Sync status read `synced`, every check was green, and the running deployment kept working because Vercel bakes env vars in at build time, so the damage was invisible until the next deploy. **Once a sync exists, a write to that config is a production write.** Treat it like one. The safe arrangement: leave every sensitive variable where it is. The platform keeps managing it, Doppler never learns it exists, and a sync cannot overwrite what it does not know about. Rotating one of those still means the dashboard, and that is the price of having marked it sensitive. Full walkthrough, including the reserved names and what to do when the platform rejects a duplicate: [`references/vercel-sync.md`](references/vercel-sync.md). --- ## Step 8. The button, if you are on a Mac Optional. The command in Step 6 is the real path and works everywhere. ```bash bash "$SKILL/scripts/install-button.sh" ``` That compiles an app into `~/Applications` using Apple's own `osacompile`, on your machine. No binary ships with this skill. Bind it to a Stream Deck key or a mouse button, then adding a key is: copy, press, answer two questions. It reads the first twelve characters of your clipboard and its length, guesses the variable name from twenty vendor prefixes, and hands the write to `doppler-set.sh`. Its first act is `--validate`, so a clipboard that cannot be a key is refused before the project picker opens. The name dialog offers **Test only**, which runs the dry run and shows you what would happen without writing anything — use it the first time, and any time you are about to aim at something live. Every refusal in Step 6 applies, so the button cannot be looser than the command. Afterwards it swaps the clipboard, so the key comes off and a line for your session goes on. The ordering there is load bearing. The only safe write is `pbpaste | doppler secrets set`, so putting that command on the clipboard first would destroy the value it needs to read. **The button writes `prd`, and `prd` may be wired to production.** If the name already exists, or the config syncs to a deploy platform, it stops and says which before writing. Answer those dialogs rather than clicking through them: one press on a synced config reaches your live site in about two seconds, and neither Doppler nor the platform keeps a copy of what was there before. The logic deliberately lives in the script rather than in the compiled app. An app carries no version and looks identical however stale it is, and this one sat two hours behind its source for two days without anything noticing. Now a fix to the guards takes effect on the next press with no rebuild. Check the button itself with: ```bash bash "$SKILL/scripts/install-button.sh" --verify ``` It decompiles what is installed and diffs it against the source, because that is the only honest way to ask. --- ## Step 9. Tell your assistant the rules Offer to add this to the project's `CLAUDE.md`, ask before writing, and keep it short. Substitute the real project name. ```markdown ## Secrets Secrets live in Doppler under the `my-app` project, not in `.env`. Ask for a variable by name and never ask for its value. Run local commands with `doppler run -p my-app -c dev -- <cmd>`. Never wrap `build`, `start` or `test` in `doppler run`. The CI runner has no Doppler token and the deploy will fail. ``` --- ## When something goes wrong Every trap worth knowing, with the evidence behind it, is in [`references/gotchas.md`](references/gotchas.md). The short list: - `Doppler Error: EOF` on login means no terminal. Step 0. - `The fallback file does not exist` means you are in a directory with no pin. Pass `-p` and `-c`. - A deploy that suddenly cannot find its variables means something wrapped `build`. Step 5. - `Secret name "VERCEL" is a reserved name` means an injected name got into your config. Delete it from Doppler, do not fight the platform. - A key that 401s on every endpoint although the vault clearly holds it: check whether the stored value is the variable's own name. Step 6 refuses that now; anything written before it did is still sitting there. - The button behaving unlike the source you just edited: it is a stale build. `install-button.sh --verify` says so, and re-running the installer fixes it. ## What the free tier holds Three users, ten projects, four environments per project, and **five config syncs**. The sync cap is the one that binds, and it counts per pairing of a Doppler config with a platform environment, not per project. Six production apps need six syncs. Plan which ones earn it before you start wiring them up.
Jay Feldman
Built by Jay FeldmanFounder, Lead Gen Jay · Inc. 5000 · 84K+ YouTube subs
AI Automation Insiders

The locked half of the library

This skill is free to install. The ones wearing a padlock are not, and they are where the heavier automations live. Membership opens all of them, plus weekly live coaching and the private community.

Total real-world value
$13,479
You pay today
$1,497
Claim Your SpotSee What's Inside

Encrypted checkout 14-day refund