Back to blog

How to get an OpenAI API key, and keep it out of .env

Vlad Vidican 6 min read

You create an OpenAI API key in the dashboard, on the API keys page. OpenAI's quickstart then has you export it as OPENAI_API_KEY, and the official Python and Node.js SDKs read that variable on their own when you don't pass a key. That default is handy, and it is also why the key ends up in your shell profile or a .env file. This post covers getting the key, why a key can stop working, and how to keep it in an encrypted Valt0 vault instead.

Get the key

Sign in to the OpenAI platform, open API keys and create a key. OpenAI's quickstart says to store it "in a safe location, like a .zshrc file or another text file on your computer", and its API reference puts it plainly: your API key is a secret. A text file is the part this post changes.

Make one key per app or project rather than one for everything. OpenAI's help center points out that you can track usage by API key, so separate keys make it easy to see which app made which calls, and to delete one without breaking the others.

Where the SDK looks for it

The quickstart sets the key in your terminal. On macOS or Linux:

Terminal
export OPENAI_API_KEY="your_api_key_here"

On Windows, in PowerShell:

POWERSHELL
setx OPENAI_API_KEY "your_api_key_here"

setx only reaches windows opened after it, so open a new PowerShell window before you run the app.

Then the client finds it without being told. In Python, OpenAI() with no arguments reads OPENAI_API_KEY:

app.py
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY

response = client.responses.create(
    model="gpt-6-astra",
    input="Say hello in five words.",
)
print(response.output_text)

And in Node.js, new OpenAI() does the same:

app.mjs
import OpenAI from 'openai';

const client = new OpenAI(); // reads OPENAI_API_KEY

If you'd rather not export it in every terminal, the Python SDK's README recommends python-dotenv and a line in .env, so the key stays out of source control.

A key that worked yesterday

OpenAI's help center says that when it detects an API key on the public internet, it disables that key immediately. If you pushed a .env to a public repository, or pasted the key somewhere public, that is the likely reason. Deleting the line in a later commit doesn't bring it back: create a new key and keep the new one out of the repository.

If a request fails with a key you are sure is fine, check which one the SDK actually picked up. These print the variable names and the files that mention the key, not the key itself, so it doesn't land on your screen:

Terminal
env | grep '^OPENAI_' | cut -d= -f1
grep -l 'OPENAI_API_KEY' ~/.zshrc ~/.zprofile ~/.bashrc ~/.bash_profile ~/.profile 2>/dev/null

An old export in a profile you forgot about will win over the .env you just edited, because python-dotenv doesn't overwrite a variable that is already set unless you pass override=True.

Where the key ends up

Followed to the letter, the setup leaves the key in plain text in a file:

.env · before
LOG_LEVEL=info
OPENAI_API_KEY=the-key-itself

Or in ~/.zshrc as an export line. On Windows, setx saves it in your user environment, and every terminal you open afterwards, and every program it starts, gets it. Anything that can read the file can use the key: a backup, a copy sent to a teammate, a tool or coding agent working in the project folder. And a variable in the environment is handed down to every process the app or the shell starts. OpenAI's help center warns that a leaked key lets someone use the API on your account, with the charges landing on you.

Before: the app's environment holds LOG_LEVEL and OPENAI_API_KEY in plain text, and OpenAI() reads the key from it. After: it holds LOG_LEVEL and APP_VAULT_ID, the key is in dev.valt0, encrypted and committed with the code, and the Valt0 service hands it to the app when it asks

Keep the key in a vault

You need Valt0 installed; valt0 status checks that its service is running. Create a vault for development. Valt0 prints its key once, so put it in your password manager now: without it nobody can open the vault, you included.

Terminal
valt0 init dev

Store the OpenAI key under the same name. Valt0 asks for the vault key, then for the value: paste the OpenAI key.

Terminal
valt0 store dev:OPENAI_API_KEY

The app never sees the vault key. It opens the vault by its id, which is public, and asks the local Valt0 service for the secret by name. Print the id:

Terminal
valt0 info dev

Put that id in .env where the key was. It is safe there:

.env · after
LOG_LEVEL=info
APP_VAULT_ID=your-vault-id

Commit dev.valt0 with the code. Without the vault key it can't be read.

Then remove the old copies. Take the export OPENAI_API_KEY line out of your shell profile and open a new terminal. On Windows, delete OPENAI_API_KEY in the Environment Variables window (search the Start menu for "Edit environment variables for your account") and open a new PowerShell window. Otherwise the key is still in plain text there, and still in the environment of everything you run.

Python

Install the clients. Valt0's needs Python 3.9 or later; the current openai and python-dotenv need 3.10, so use 3.10 or later. python-dotenv keeps reading the settings in .env:

Terminal
python -m pip install valt0-client openai python-dotenv

Then pass the key to OpenAI yourself, from the vault:

app.py · after
import os

import valt0
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

vault = valt0.open_vault(id=os.environ["APP_VAULT_ID"], name="app")

with vault.secret("OPENAI_API_KEY") as secret:
    client = OpenAI(api_key=secret.reveal())

response = client.responses.create(
    model="gpt-6-astra",
    input="Say hello in five words.",
)
print(response.output_text)

reveal() hands the SDK the value, and the with block wipes Valt0's copy when it ends. The SDK keeps the string it was given, because a Python string can't be wiped, but the key is no longer in the environment or in a file next to your code. Anywhere else, the secret prints as [REDACTED]: in print(), an f-string or a log line. If the service can't be reached, open_vault raises an error and the app stops at startup. Get your Python secrets out of .env walks through the same steps for a database URL and more, and fixes the "No module named 'dotenv'" error on the way.

Node.js

Terminal
npm install @valt0/client openai
app.mjs · after
import OpenAI from 'openai';
import { openVault } from '@valt0/client';

const vault = await openVault({ id: process.env.APP_VAULT_ID, name: 'app' });

const apiKey = await vault.secret('OPENAI_API_KEY');
export const client = new OpenAI({ apiKey: apiKey.reveal() });
apiKey.destroy();

destroy() wipes Valt0's copy straight after. The SDK keeps the string it was given, because a JavaScript string can't be wiped, but the key is no longer in process.env or in any file next to your code. Valt0's client needs Node.js 20 or later; the current openai package needs Node.js 22, so use 22 or later. To get APP_VAULT_ID from .env, start the app with node --env-file=.env app.mjs.

From here

  • If the key may have leaked, create a new key on the API keys page, store it with valt0 store dev:OPENAI_API_KEY --force and restart the app, then delete the old key. Check usage for that key for calls you didn't make.
  • Teammates get dev.valt0 when they clone the repo and the vault key from you. They run valt0 import dev once before the app can read it.
  • Keep it out of browser code. OpenAI's API reference says never to expose the key in client-side code such as browsers or apps, and the Node.js SDK refuses to run in a browser unless you set dangerouslyAllowBrowser. Call OpenAI from your server and have the browser talk to that.
  • Production gets its own vault and its own OpenAI key, with the same secret name, so a leaked dev key leaves it alone. Deployment covers servers.

Other secrets in the same .env go the same way: Turn an .env file into a vault converts a whole file in one step, Get your Node.js secrets out of .env covers a database and Stripe, and the Gemini version of this post does the same for a Google key.

Sources: OpenAI quickstart, OpenAI API reference: authentication, OpenAI help center: keeping your account secure, openai for Python, openai for Node.js, python-dotenv, Microsoft: setx, Valt0 Python client docs, Valt0 Node.js client docs.

Create your first vault.

$ valt0 init prod

Install