Intermediate · Lesson 1 of 6 · 16 min

APIs and API keys

Learn how REST APIs work, how authentication and API keys fit in, why keys live in environment variables, and how to behave under rate limits.

REST in one page

A REST API exposes resources at URLs called endpoints. You act on them with HTTP methods: GET reads, POST creates, PUT or PATCH updates, DELETE removes. Requests and responses usually carry JSON.

The status code tells you what happened before you read the body. 2xx means success. 4xx means the request was wrong: bad input, missing auth, not found, or too many requests (429). 5xx means the server failed. Your code should branch on the status, not assume success.

Every API also has its own required headers. HighLevel's v2 API, for example, needs a Version header on each request alongside the bearer token, and the Claude API needs an anthropic-version header. Missing one of these is a common cause of a 400 or 401 on a first call.

Authentication and key types

Most APIs authenticate with a bearer token in the Authorization header. That token is either a static API key you create in a dashboard, or a short-lived access token obtained through OAuth when an app acts on behalf of someone else's account.

Know which one you need. In HighLevel, a Private Integration token is for connecting your own account to a tool you control, while OAuth is for marketplace apps installed by other agencies or sub-accounts. In the Claude Console, Anthropic recommends a personal key for your own development and a service account key for anything shared, such as a production workflow, because personal keys stop working when that person leaves the organisation.

Two more settings matter on the Claude API. You can set an expiration when you create a key, which limits how long a leaked key stays usable. And if your key is not scoped to a single workspace, every request must also send an anthropic-workspace-id header.

Keys live in environment variables

An API key is a password that spends money or reads customer data. It never goes in source code, a Git repository, a front-end bundle or a shared screenshot. Keys committed to a public repo are found and abused quickly, and deleting the line afterwards does not help: you must rotate the key.

Locally, put keys in a .env file that is listed in .gitignore before your first commit. On a host such as Vercel, set them in the project's environment settings. In n8n, Zapier and Make, use the platform's credential or connection store rather than pasting a key into a text field in a step.

The sample below calls the Claude Messages API with plain fetch, reading the key from the environment and failing loudly if it is missing.

javascript
// call-claude.mjs  run with: node --env-file=.env call-claude.mjs
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) throw new Error("ANTHROPIC_API_KEY is not set");

const workspaceId = process.env.ANTHROPIC_WORKSPACE_ID; // only for keys not scoped to one workspace

const response = await fetch("https://api.anthropic.com/v1/messages", {
  method: "POST",
  headers: {
    authorization: "Bearer " + apiKey,
    "anthropic-version": "2026-10-21",
    "content-type": "application/json",
    ...(workspaceId ? { "anthropic-workspace-id": workspaceId } : {}),
  },
  body: JSON.stringify({
    model: "claude-haiku-5-5",
    max_tokens: 300,
    messages: [
      { role: "user", content: "Summarise this lead in one sentence: Hi, our office AC died this morning, can someone come today?" },
    ],
  }),
});

const body = await response.json();
if (!response.ok) {
  throw new Error(response.status + " " + body.error.type + ": " + body.error.message + " (request " + body.request_id + ")");
}
console.log(body.content.find((block) => block.type === "text").text);

Rate limits

Every API caps how fast you can call it. The Claude API measures requests per minute, input tokens per minute and output tokens per minute for each model class, and returns 429 when you exceed any of them. GitHub's REST API caps requests per hour and reports your remaining budget in x-ratelimit headers on every response.

When a 429 includes a retry-after header, wait that long before retrying. Do not treat every 429 the same, though: on the Claude API, a 429 caused by reaching a spend cap has no retry-after header and keeps failing until access is restored, so retrying in a loop just burns time.

Design for limits up front. Batch work where the API supports it, queue bursts instead of firing them all at once, and spread scheduled jobs so a whole agency's workflows do not start at the top of the hour together.

Key points

  • ◆Branch on status codes: 2xx success, 4xx fix the request, 5xx retry later, 429 slow down.
  • ◆Use a static key for your own account and OAuth when an app acts for other accounts.
  • ◆Use service account keys for shared workloads and set key expirations.
  • ◆Keys live in environment variables or a platform credential store, never in code; rotate a leaked key.
  • ◆Honour retry-after on 429s, and recognise 429s that retrying will not fix.

Terms in this lesson

Try it · exercise

Use a free GitHub token to see authentication and rate limits in action, with the token kept in an environment variable.

  1. Run: curl -s -o /dev/null -D - https://api.github.com/users/octocat and note the x-ratelimit-limit and x-ratelimit-remaining headers.
  2. Create a fine-grained personal access token in GitHub settings with no extra permissions, and store it in an environment variable named GITHUB_TOKEN rather than pasting it into the command.
  3. Run: curl -s -o /dev/null -D - -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user and note the same headers.
  4. Run the authenticated call again with one character of the token changed and note the status code.
  5. Delete the token in GitHub settings when you are done.

You're done when: x-ratelimit-limit is much higher on the authenticated call than the unauthenticated one, x-ratelimit-remaining drops by one between calls, and the altered token returns a 401.

Quiz me on this lesson