How to Generate an API Key (With Code Examples) — The README<br>For AI agents: the site index is at /llms.txt and the full site content at /llms-full.txt. For a markdown version of a page, append .md to its URL or request it with Accept: text/markdown. llms.txt<br>Since we entered the agentic AI era, APIs and CLIs have been thrust into the zeitgeist. I've been seeing Wall Street Journal articles mentioning both, which my nerd-self finds very cool.<br>If you own a SaaS, it means it is important for you to consider offering an API/CLI, and if so, you need to make sure it is secure or you risk your entire business.<br>GitGuardian found 28.6 million secrets exposed in public GitHub commits in 2025, a 34% jump year over year. Over 1.2 million of those were AI-service credentials alone, up 81% year over year (GitGuardian State of Secrets Sprawl 2026).<br>Most had no prefix, no hashing, and no revocation path, and the key was just "my-voice-is-my-passport" — just kidding on that last one, Sneakers fans.<br>To generate a secure API key, you use a cryptographically secure random number generator for 128 bits of randomness, add a type-and-environment prefix like sk_live_, and store only the SHA-256 hash.<br>If you keep reading, we'll go over the full lifecycle with working code in Node.js, Python, and Go, using Stripe API keys as a blueprint.<br>Generate 128 bits of randomness from a CSPRNG<br>Add a type-and-environment prefix like sk_live_<br>Store only the SHA-256 hash, and show the key exactly once<br>Verify in four steps: format, hash, lookup, scope<br>Revoke instantly, and rotate with a grace period<br>What Does a Good API Key Look Like?<br>Stripe's API keys is one of the most widely recognized and copied formats around. In my previous API company we used this as a model for our API keys.<br>Every key encodes three pieces of information before the random token even starts:<br>sk_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6<br>│ │ │<br>│ │ └─ Random token (the secret)<br>│ └─────── Environment (live or test)<br>└────────── Type (sk = secret, pk = publishable)<br>This design is intentional, and I personally love it for the expressiveness and simplicity.<br>When GitHub overhauled their own token formats, they added identifiable prefixes (ghp_, gho_, ghs_) for the same reason: the old hex-only tokens were "indistinguishable from other encoded data like SHA hashes" and nearly impossible for scanners to detect (GitHub Engineering).<br>The same convention plays out across every major API:
ProviderFormatExamplesWhat the Prefix Tells You
Stripe{type}_{env}_{token}sk_live_, pk_test_, rk_live_Key type + environment<br>GitHub{co}{type}_{token}ghp_, gho_, ghs_Company + token type<br>Twilio{type}{token}SK + 32 hex charsKey type<br>AWS{scope}{token}AKIA, ASIAPermanent vs. session
The pattern: a human-readable prefix, then cryptographically random bytes. The examples below encode that token as hex, which is what randomBytes returns by default.<br>Stripe's real keys use a longer base62 alphabet; if you copy their format exactly, widen the validation regex from [0-9a-f] to [A-Za-z0-9].<br>Publishable vs. Secret Keys<br>Stripe splits keys into two categories because the trust boundary matters:<br>pk_live_ / pk_test_ (publishable). Safe to embed in frontend JavaScript. These keys can only create tokens (e.g., tokenize a credit card via Stripe.js). They can't read customer data, issue refunds, or make charges.<br>sk_live_ / sk_test_ (secret). Full API access. Server-side only. If this key leaks, an attacker can move money.<br>Now, you may not have this situation and only have a back-end process or only front-end access. When you're designing your own API, ask: does this client need full access, or just enough to submit data?<br>That answer determines whether to issue a pk_ or sk_ key. Whether you are back-end access only or front-end access only, you should still consider this pattern.<br>For more on how APIs work under the hood, see our guide on what an API actually is.<br>Step 1: Generate Cryptographically Random Bytes<br>The entropy source matters more than anything else. Use your platform's CSPRNG (Cryptographically Secure Pseudorandom Number Generator — say that 3 times fast). Don't use Math.random() or uuid.v4(), and definitely not timestamps.<br>Node.js:<br>import { randomBytes } from 'crypto';
const token = randomBytes(16).toString('hex');<br>// → "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" (32 hex chars, 128 bits)
Python:<br>import secrets
token = secrets.token_hex(16)<br># → "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Go:<br>import (<br>"crypto/rand"<br>"encoding/hex"<br>"fmt"
func generateToken() (string, error) {<br>b := make([]byte, 16)<br>if _, err := rand.Read(b); err != nil {<br>return "", fmt.Errorf("CSPRNG failed: %w", err)<br>return hex.EncodeToString(b), nil<br>// → "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Sixteen bytes gives you 128 bits of entropy: 3.4 × 10³⁸ possible values. Brute-forcing that at a billion guesses per second would take 10²² years — unless the newfangled quantum computers become generally available, and then all bets are off.<br>Why not UUIDs? A v4 UUID gives you...