API keys and secrets
Why this matters
An API key for an LLM provider is a direct line to your credit card. Anyone who has it can run up charges in your name. Leaked keys are found and abused within minutes of being pushed to a public repository. Handling keys properly is a small habit every AI tutorial assumes you already have. It's also the one beginners most often get wrong.
- Send an API key in the header an API expects
- Read a key from an environment variable instead of writing it in code
- Keep keys out of git with a .env file and .gitignore
- Respond correctly when a key leaks
What an API key is
An API key is a long random string that identifies your account to an API, something like sk-ant-api03-Xk2.... Every request you send carries it. That way the provider knows whose request it is, what that account may do, and who to bill.
Think of a hotel key card. Whoever holds it gets into the room, and nobody checks their face. An API key is a password for programs, usually with no second check. Whoever holds the key is you, as far as the API is concerned. They can spend your credits, read your data, and get your account suspended for what they do with it.
The practice API has a key too: gk_demo_7Hq2_not_a_real_secret. It's printed here because it's fake. A real key never appears in a lesson, a screenshot, a support ticket or a chat with an AI assistant.
Sending a key
APIs expect the key in a header. The two common patterns:
X-Api-Key: gk_demo_7Hq2_not_a_real_secret Authorization: Bearer sk-...
- The first is a custom header. Anthropic's API uses
x-api-key(header names ignore capitals). - The second is the standard
Authorizationheader, with the wordBearerand a space before the key. OpenAI and many others use it.
The API's documentation tells you which one it wants.
Never put a key in the URL's query string. URLs end up in server logs, browser history and analytics tools.
import requests url = "https://api.gradient.test/v1/report" no_key = requests.get(url) print(no_key.status_code, no_key.json()["error"]["message"]) wrong_key = requests.get(url, headers={"X-Api-Key": "guess-123"}) print(wrong_key.status_code, wrong_key.json()["error"]["message"]) right_key = requests.get(url, headers={"X-Api-Key": "gk_demo_7Hq2_not_a_real_secret"}) print(right_key.status_code, right_key.json())
401 means no valid key, and 403 means the key was rejected. Both are your cue to check how the key is being sent, before anything else.