Module 1 · Technical Foundations · 40 min
API Keys, Secrets, Rate Limits, and Cost Controls
Is lesson ke baad tum
Treat credentials, capacity, and spend as enforceable boundaries: keep secrets off clients and out of Git, classify errors, retry only transient failures with bounded jitter, and stop requests when measured limits are reached.
In this lesson
Run a simulated request controller and prove that its input and attempt limits stop work. Use Node from lesson 1.1 and dummy configuration only; this lab makes no provider calls and needs no key. The result is a small program plus observed success and refusal cases.
Separate Public Configuration From Secrets
A model identifier or maximum input size is configuration. An API key grants access and is a secret. Keep real keys outside source files, Git, browser/mobile bundles, logs, screenshots, and lesson submissions. A minified browser bundle still exposes a shipped key.
Commit only safe variable names/placeholders in .env.example; ignore the actual .env before committing. Environment variables separate secrets from code but are not automatically protected from logs, processes, or dashboards. Use client-owned, least-privilege credentials and separate environments where supported. If a key is exposed, revoke or rotate it and investigate every copy; deleting the latest line does not remove Git history.
Understand Limits as More Than Requests Per Minute
Providers may limit requests, tokens, concurrent work, or media. A 429 can be temporary throttling or a spending/quota condition, so inspect the error code before retrying. Follow a valid Retry-After delay; otherwise use bounded exponential backoff with jitter, which spreads retries across time. Stop when the attempt or elapsed-time budget is exhausted.
The mock below deliberately returns two temporary 429 responses and then success. It models one process and one in-memory counter. A real shared daily budget needs durable atomic reservations, a date/timezone rule, and reconciliation of uncertain costs. Restarting this lab resets its counter.
Worked Example: A Guarded, Simulated Request Runner
Create .env.example:
AI_PROVIDER_KEY=replace_in_your_local_environment
AI_MODEL=choose_from_your_provider_dashboard
MAX_INPUT_CHARS=2000
MAX_ATTEMPTS=4
DAILY_REQUEST_BUDGET=20Create .gitignore:
.env
*.log
usage-ledger.json
node_modules/The following JavaScript makes no external request. It lets you test limits without spending money or exposing a credential:
// guard-lab.mjs
const maxInputChars = Number(process.env.MAX_INPUT_CHARS ?? 2000);
const maxAttempts = Number(process.env.MAX_ATTEMPTS ?? 4);
const dailyRequestBudget = Number(process.env.DAILY_REQUEST_BUDGET ?? 20);
function assertPositiveInteger(name, value) {
if (!Number.isSafeInteger(value) || value < 1) {
throw new Error(`${name} must be a positive integer`);
}
}
assertPositiveInteger("MAX_INPUT_CHARS", maxInputChars);
assertPositiveInteger("MAX_ATTEMPTS", maxAttempts);
assertPositiveInteger("DAILY_REQUEST_BUDGET", dailyRequestBudget);
if (maxAttempts > 5) throw new Error("Lab attempts must be at most 5");
const maxElapsedMs = 5_000;
const startedMs = performance.now();
const input = "Classify this synthetic support message by urgency.";
if (input.length > maxInputChars) throw new Error("Input exceeds local size cap");
// In-memory lab counter: restarting resets it. A real daily cap needs durable,
// atomic per-account storage and an explicit date/timezone rollover.
let callsToday = 0;
async function simulatedRemoteCall(attempt) {
if (callsToday >= dailyRequestBudget) throw new Error("Local run budget reached");
callsToday += 1;
if (attempt < 3) return { ok: false, status: 429 };
return { ok: true, status: 200, data: { urgency: "normal" } };
}
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
if (performance.now() - startedMs >= maxElapsedMs) throw new Error("Elapsed budget reached");
const result = await simulatedRemoteCall(attempt);
console.log({ attempt, status: result.status }); // never log a key
if (result.ok) {
console.log(result.data);
break;
}
if (result.status !== 429 || attempt === maxAttempts) {
throw new Error(`Stopped after HTTP ${result.status}`);
}
const baseMs = 100 * 2 ** (attempt - 1);
const jitterMs = Math.floor(Math.random() * 100);
const delayMs = baseMs + jitterMs;
if (performance.now() - startedMs + delayMs >= maxElapsedMs) {
throw new Error("Retry would exceed elapsed budget");
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
}Run it with safe, non-secret configuration. In PowerShell:
$env:MAX_INPUT_CHARS="2000"
$env:MAX_ATTEMPTS="4"
$env:DAILY_REQUEST_BUDGET="20"
node guard-lab.mjsOn macOS/Linux with bash or zsh, set the same values for this command:
MAX_INPUT_CHARS=2000 MAX_ATTEMPTS=4 DAILY_REQUEST_BUDGET=20 node guard-lab.mjsThe first two simulated calls return 429; the third succeeds. Change MAX_ATTEMPTS to 2 and confirm that the runner stops. Change MAX_INPUT_CHARS to 10 and confirm that it refuses the input before any call. These are testable gates, unlike a note saying “be careful with costs.”
On macOS/Linux, run the two refusal cases separately:
MAX_INPUT_CHARS=2000 MAX_ATTEMPTS=2 DAILY_REQUEST_BUDGET=20 node guard-lab.mjs
MAX_INPUT_CHARS=10 MAX_ATTEMPTS=4 DAILY_REQUEST_BUDGET=20 node guard-lab.mjsEach command supplies its own configuration; the small-input case restores the attempt limit to 4.
Build Cost Controls in Layers
Request count is not a money amount: input, output, media, and tools can cost differently. A real application should cap work before dispatch, record provider-reported usage, and reconcile it against the provider dashboard. Control concurrency as well as retries.
Current OpenAI controls distinguish spend alerts from organization/project hard spend limits. Alerts leave traffic running; a reached hard limit returns 429 with a spend-limit error code. Enforcement can lag slightly, so it is not a cent-exact per-request reservation. Keep the application budget and bounded retry policy even when provider controls are configured.
Failure Cases and Correct Responses
Key committed to Git
revoke it first, then clean history where appropriate and review access logs. A later
.gitignoreentry does not undo exposure.Key printed by debugging
remove or redact the log, rotate the key, and check every log sink and screenshot destination.
Key used in browser code
move the request behind an authenticated server and issue a new key.
Every error retried
retry only transient categories. Authentication, malformed input, and permission errors require correction, not backoff.
Retry storm
add jitter, concurrency limits, maximum attempts, and a queue.
Duplicate side effect
a retry that sends a message, creates an invoice, or records an order may run twice. Use an idempotency key or application record and require confirmation for high-impact actions.
Budget based only on request count
input size, output size, media, and tools can make requests different. Reconcile provider-reported usage.
One shared production key
attribution and revocation become difficult. Separate projects and environments where available.
Hands-On Exercise: Pass a Secret-and-Budget Review
Save and run guard-lab.mjs. Predict the three status lines before looking at output. Then change MAX_ATTEMPTS to 2 and run again; restore it to 4 before changing MAX_INPUT_CHARS to 10.
Check: the default run reports 429, 429, then 200; the two-attempt run stops without reaching success; the small-input run stops before any simulated call. Record those actual outcomes and explain why restarting the program does not preserve a daily account budget.
Finally diagnose this supplied incident: a developer adds a real key to browser JavaScript, then minifies the bundle. Write the first two repairs. Expected: revoke/rotate the exposed key and move provider access behind an authenticated server; minification cannot keep the old key secret.
Completion Rubric
Sources
Practice quiz
Check yourself
4 questions · practise what you just learned
1 / 4 · diagnose
A real API key was shipped in browser JavaScript and then minified. Which response addresses the exposure?
Apna kaam check karo.
Practice complete ki? Result ko example aur lesson ke checks se compare karo.
This is your own completion record, not a graded assessment.