AI School Pakistan
← Applied AI Builder0 of 18 complete
LessonCourse overview

Module 1 · Technical Foundations · 40 min

API Keys, Secrets, Rate Limits, and Cost Controls

Lesson 2 of 18

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:

prompt to try5 lines
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=20

Create .gitignore:

prompt to try4 lines
.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:

javascript50 lines
// 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:

powershell4 lines
$env:MAX_INPUT_CHARS="2000"
$env:MAX_ATTEMPTS="4"
$env:DAILY_REQUEST_BUDGET="20"
node guard-lab.mjs

On macOS/Linux with bash or zsh, set the same values for this command:

bash1 line
MAX_INPUT_CHARS=2000 MAX_ATTEMPTS=4 DAILY_REQUEST_BUDGET=20 node guard-lab.mjs

The 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:

bash2 lines
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.mjs

Each 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 .gitignore entry 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

0/3

Sources

Practice quiz

Check yourself

4 questions · practise what you just learned

0/4
  1. 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.

Kuch samajh nahi aaya? Ask a question →