OpenAI API key example: using a key in curl, Python and Node.js
Ready examples of passing an API key in a header, environment variables and an SDK, the typical format mistakes, and a key check with a /v1/balance request.
5 min · · API keys · development
Looking for an OpenAI API key example: how exactly does the key get into a request? Below are short working examples for curl, Python and Node.js, ways to keep the key in environment variables and a connection check with a /v1/models request. The examples are written for the OpenAI-compatible format: a Zukko fetch_... key from the dashboard and the address https://api.zukko.pro/v1. The same code with a different key and address works with other OpenAI-compatible servers.
What a key looks like and where it is used
A key is a string that tells the server whose request this is and which balance to charge. In the OpenAI format it is passed in the Authorization header with the word Bearer and a space before the key:
Authorization: Bearer fetch_xxxxxxxxxxxxxxxx
A Zukko key starts with fetch_. It is created in the dashboard and shown in full only once at creation, so save it right away in a password manager or an environment variable. If you lose it, create a new one. Authorization details are in the authentication reference.
Remember one rule: a key works only on the server that issued it. An official OpenAI key will not work with api.zukko.pro, and a fetch_... key will not work with OpenAI's servers. If you use the wrong key, you get a 401.
The key in an environment variable
Do not paste the key straight into code: it ends up in the repository. Put it into an environment variable.
macOS and Linux:
export OPENAI_API_KEY="fetch_your_key"
export OPENAI_BASE_URL="https://api.zukko.pro/v1"PowerShell:
$env:OPENAI_API_KEY = "fetch_your_key"
$env:OPENAI_BASE_URL = "https://api.zukko.pro/v1"The official OpenAI SDKs and many tools read OPENAI_API_KEY and OPENAI_BASE_URL, so after this you do not need to specify the key in code. The variable lives only in the current terminal session; to keep it, add the lines to your shell profile or use a .env file, and remember to add it to .gitignore.
OpenAI API key curl example
The first request is a key check. GET /v1/balance generates no text and returns the balance. The list GET /v1/models is not suitable for this: it is open and does not validate the key:
curl https://api.zukko.pro/v1/balance \
-H "Authorization: Bearer $OPENAI_API_KEY"If you get JSON with the balance, the key and address are correct. Now a generation request:
curl https://api.zukko.pro/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6.1-sol",
"messages": [{"role": "user", "content": "Say hello in one word"}]
}'Pay attention to the quotes: the shell expands $OPENAI_API_KEY only inside double quotes. In single quotes the literal text $OPENAI_API_KEY goes to the server. The gpt-6.1-sol model is taken from the catalog as of October 18, 2026; /v1/models always returns the current names.
A Python example
pip install openaiimport os
import httpx
from openai import OpenAI
client = OpenAI(
base_url="https://api.zukko.pro/v1",
api_key=os.environ["OPENAI_API_KEY"],
)
## Key check: balance request (the SDK has no separate method for it)
check = httpx.get(
"https://api.zukko.pro/v1/balance",
headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"},
)
print(check.status_code, check.json())
response = client.chat.completions.create(
model="gpt-6.1-sol",
messages=[{"role": "user", "content": "Say hello in one word"}],
)
print(response.choices[0].message.content)If OPENAI_API_KEY and OPENAI_BASE_URL are already set, you can call the constructor without arguments: client = OpenAI().
A Node.js example
npm install openaiFile check.mjs (the .mjs extension allows import and top-level await):
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.zukko.pro/v1",
apiKey: process.env.OPENAI_API_KEY,
});
const response = await client.chat.completions.create({
model: "gpt-6.1-sol",
messages: [{ role: "user", content: "Say hello in one word" }],
});
console.log(response.choices[0].message.content);Run it with node check.mjs. If the key is in a .env file, in Node.js 20.6 and newer you can load it with the flag node --env-file=.env check.mjs. The same logic without an SDK is available through the built-in fetch:
const res = await fetch("https://api.zukko.pro/v1/balance", {
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
});
console.log(res.status, await res.json());Common mistakes with the key
Most problems come from one set:
- No
Bearerword or no space. A headerAuthorization: fetch_...withoutBearergives a 401. - Extra characters. A space, a newline or quotes inside the value often remain after copying or reading from a file. Trim the value (
.strip()in Python,.trim()in JavaScript). - Wrong key. A key from another service. Check that the string starts with
fetch_. - Address without
/v1. For the OpenAI format the base URL must end with/v1; without it you get a 404. See the article base URL with or without /v1. - The variable did not pass through. In Python
os.environ["OPENAI_API_KEY"]raisesKeyErrorif the variable is not set in this terminal session. Open a new terminal after changing the profile. - Key in single quotes in curl. The shell will not substitute the value (see above).
Response codes: 401 is an invalid key, 402 is insufficient balance, 403 means the model is not allowed for this key (model_not_allowed_for_key), 404 is an unknown model or a wrong address, 429 means the key limit or a provider restriction was reached. The most common is 401, and the cause is almost always one of the items in the list above.
How to store a key safely
A few rules that save nerves:
- Do not commit the key to git and do not put it in frontend code: any site visitor will see it in the browser. Make API requests from the server.
- Create separate keys per project and give each a daily and monthly dollar limit and a list of allowed models. If one key leaks, damage is capped by the limit and only that key needs revoking.
- If a key lands in a public repository, treat it as compromised: create a new one and delete the old one. Step by step instructions are in the article an API key leaked.
More practices are in the material on API key security.
FAQ
Where do I get an API key for an OpenAI-compatible API?
For Zukko: sign in to the dashboard, open the keys section and create a key. It is shown once. The minimum balance top-up is $1. For OpenAI's server the key is issued in that service's own account, and it will not work with our address.
Can I pass the key in the x-api-key header?
The Anthropic SDK sends the key that way and it works. In the OpenAI format use Authorization: Bearer.
Why does the balance request work but generation does not?
The balance request checks only the key. Generation additionally requires a balance (otherwise 402), a model allowed for the key (otherwise 403) and a correct model name (otherwise 404). Take current names from the /v1/models response.
Do I need to change anything in code when moving to another server?
Only base_url and the key. The rest of the OpenAI SDK code stays the same. The link for creating a key and making a first request is the dashboard.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Your Own Neural Network Proxy on Node.js: Keep the API Key Off the FrontendA small Express server between your frontend and the model API: the key stays on the server, and models, request size and rate are capped.
- DeepSeek API in Python: Requests, Streaming, JSON and Error HandlingReady code for DeepSeek V4.1 Flash and V4 Pro on the OpenAI SDK: a request, streaming, JSON parsing and careful error handling.
- Gemini API in Python: requests via the OpenAI SDK and OpenAI formatA working Gemini API example in Python with no extra library: change base_url in the OpenAI SDK, add streaming and JSON, and compare with google-genai.
- Anthropic SDK for Python: base_url, key and a first request via a gatewayThe official anthropic package for Python just needs a different address. We cover base_url, the key, streaming, token counting, caching and errors.