# Reading API Docs & Using Postman

> How to go from an unfamiliar API docs page to a working request: read the reference, fire the call in Postman or curl, and read the response - without guessing.


---

# Reading API Docs & Using Postman

You've been handed an API and a link to its docs, and the page is a wall of endpoints, headers, and
words like "bearer token" and "query parameter." You scroll, you copy something that looks right, you
paste it somewhere, and you get back a number you don't understand. The frustrating part isn't that
APIs are hard - it's that nobody showed you how a docs page is *organized*, so you don't know where to
look for the five things you actually need.

That's all this guide fixes. By the end you'll open any well-structured API reference, find the exact
request you need, fire it from a graphical tool (Postman) or the command line (curl), and read what
comes back. Same skill, two tools, one calm process.

## How to read this
- **Need to send one request right now?** Skim [Phase 1](01-how-to-read-api-docs.md) to find the five
  things, then jump to [Phase 2](02-making-the-request.md) for the Postman and curl steps.
- **Want it to finally make sense?** Read in order - each phase builds on the last, from reading the
  docs to firing the call to understanding the answer.

## The phases
1. **[How to Read API Docs](01-how-to-read-api-docs.md)** - the five things every reference is telling
   you (base URL, endpoint + method, parameters, auth, example), and how to skim for the one you need.
2. **[Making the Request (Postman & curl)](02-making-the-request.md)** - two equivalent ways to fire
   the same request: Postman the GUI, and curl on the command line, with an annotated transcript.
3. **[Reading the Response & Iterating](03-reading-the-response.md)** - status code first, then the
   body; tweaking and re-sending; saving requests into collections with variables - and the
   secret-leak trap to avoid.

> This guide assumes you already know roughly what an HTTP request is (a method, a URL, headers, maybe
> a body) and what JSON looks like. If those are fuzzy, read
> [HTTP & JSON API Basics](/guides/http-and-json-api-basics) first, then come back. Designing your own
> API, pagination, rate limits, and OAuth flows are deliberately left to follow-up guides - this one is
> about *using* an API someone else built.


---

# How to Read API Docs

A docs page looks like a wall of text because you're reading it like prose - top to bottom, hoping the
answer appears. It isn't prose. It's a *reference*, and every reference is laid out to answer the same
handful of questions. Once you know the five things you're looking for, the wall turns into a form you
fill in, and you can scan straight to the box you need.

Here's the whole mental model, and the rest of this phase is just the five fields explained one at a
time:

```text
   To make ONE request, the docs always tell you five things:

   1. BASE URL        where the API lives          https://api.example.com/v1
   2. ENDPOINT+METHOD which thing, what action     GET /users/{id}
   3. PARAMETERS      the details you fill in       id (required), fields (optional)
   4. AUTH            how you prove who you are     Authorization: Bearer <token>
   5. EXAMPLE         a sample call + response      copy it, adapt it, send it

   Put 1+2+3 together and you have the URL. Add 4 and it's allowed.
   Check 5 to confirm you assembled it right.
```

We'll use a made-up but typical service - a "Bookshelf API" - as the running example, so the shapes are
realistic without leaning on any one vendor's website.

## 1. The base URL - where the API lives

The base URL is the front door of the whole API: the part of the address that's the same for *every*
request. Everything else you read in the docs gets tacked onto the end of it. Find this once and you've
found the foundation for every call.

Look for a section near the top called "Base URL," "Getting Started," or "Introduction." It almost always
shows a version number in the path.

```text
   https://api.bookshelf.dev/v1
   └──────┬───────────────┘ └┬┘
      the host (the server)  the version
```

📝 **Terminology.** The `/v1` is the **API version**. APIs change over time, so providers freeze old
behavior under `/v1` and ship new behavior under `/v2`, letting your code keep working. Use the version
the docs tell you to - usually the newest one shown in their examples.

⚠️ **Gotcha.** Some docs print the base URL in one place and then show *only the endpoint* (like `/books`)
everywhere else, assuming you'll remember to glue them together. If a request 404s with "not found," the
first thing to check is whether you dropped the base URL or the version.

## 2. The endpoint and method - which thing, what action

An **endpoint** is the path to a specific resource - the books, one book, the reviews on a book. The
**method** is the verb that says what you want to *do* to it. Together they read almost like a sentence:
"`GET /books`" means "fetch the list of books."

📝 **Terminology.** The four you'll meet constantly:

| Method | What it does | Sentence |
|---|---|---|
| `GET` | read, change nothing | "show me the books" |
| `POST` | create something new | "add a new book" |
| `PUT` / `PATCH` | update something existing | "edit this book" |
| `DELETE` | remove something | "delete this book" |

Reference pages list one endpoint per entry, almost always with the method in bold or color right next to
the path:

```text
   GET    /books            list all books
   GET    /books/{id}       get one book by its id
   POST   /books            create a book
   DELETE /books/{id}       delete a book
```

That `{id}` in curly braces is a **path parameter** - a blank you fill in. `GET /books/42` asks for the
book whose id is `42`. The braces are the docs' way of saying "put a real value here"; you never send
the braces themselves.

When a teammate says "the API returns a 405," knowing methods tells you instantly what happened: you sent
the wrong verb to a real path - like a `POST` to an endpoint that only accepts `GET`. The path was fine;
the action wasn't allowed.

## 3. The parameters - the details you fill in

Parameters are the inputs to a request - the specifics that turn "get some books" into "get me 10
science-fiction books, newest first." The docs list every parameter an endpoint accepts, and crucially,
marks which are **required** and which are **optional**.

There are three places a parameter can ride along, and the docs will tell you which:

- **Path** - baked into the URL, like the `{id}` above. Always required.
- **Query** - tacked onto the end of the URL after a `?`, for filtering and options:
  `/books?genre=scifi&limit=10`. The `?` starts the list; `&` separates each one.
- **Body** - a chunk of JSON you send along with `POST`/`PUT`, describing the thing you're creating or
  changing.

Usually a table right under the endpoint. The "Required" column is the one to read first - it tells you
the minimum you must supply for the call to work at all.

```text
   GET /books - query parameters

   Name    Required  Type     Description
   genre   no        string   filter to one genre, e.g. "scifi"
   limit   no        integer  how many to return (default 20, max 100)
   sort    no        string   "newest" or "title"
```

Nothing is required here, so `GET /books` alone works and gives you the default 20. Want fewer, filtered,
sorted? That's what the optional query parameters are for. Note the docs even tell you the **default**
(`20`) and the **limit** (`max 100`) - real numbers from the reference, not ones to guess at.

⚠️ **Gotcha.** "Optional" doesn't mean "ignored." If you send a query parameter the API doesn't
recognize (a typo like `limt=10`), most APIs quietly ignore it rather than erroring - so you'll get the
default behavior and wonder why your limit "didn't work." When a request behaves unexpectedly, re-check
your parameter *names* against the table, character for character.

## 4. The auth requirement - how you prove who you are

Most useful APIs won't talk to a stranger. **Authentication** is how you prove you're an allowed caller,
usually by attaching a secret - an **API key** or a **token** - to your request. The docs have a section,
normally called "Authentication," that tells you exactly two things: *what* secret to send and *where* to
put it.

📝 **Terminology.** A **bearer token** is the most common pattern: a long secret string you place in a
header, and whoever "bears" (carries) it is treated as you. The header looks like this:

```text
   Authorization: Bearer sk_live_8Kd... (your secret token)
   └──────┬──────┘ └─┬──┘ └──────┬──────┘
     header name    scheme      the actual secret
```

The "Authentication" section spells out the exact header. It will say something like: *"Authenticate by
sending your API key as a bearer token in the `Authorization` header."* That one sentence tells you the
header name (`Authorization`), the scheme (`Bearer`), and that the value is your key.

⚠️ **Gotcha.** A request with the secret missing or malformed comes back as **401 Unauthorized**; a
request where the secret is valid but isn't *allowed* to do that thing comes back as **403 Forbidden**.
People conflate them and chase the wrong fix. 401 means "I don't know who you are" (check the token is
present and spelled right). 403 means "I know who you are, and no" (your key lacks permission). You'll
meet both again in [Phase 3](03-reading-the-response.md).

⚠️ **Gotcha - and this is the big one.** That token *is* your account. Anyone who has it can act as you.
We'll return to keeping it out of your code and your shared collections in
[Phase 3](03-reading-the-response.md), but plant the flag now: treat it like a password, because it is
one.

## 5. The example - a request and response you can copy

Good docs give you a worked example for each endpoint: a sample request and the response it produces.
This is the most valuable thing on the page, because it shows all four pieces above *already assembled
correctly*. Your job becomes "copy this and change the values to mine," not "build it from scratch."

Typically a request snippet (often already written as curl - which you'll recognize after
[Phase 2](02-making-the-request.md)) and a JSON response:

```text
   Example response - GET /books/42

   {
     "id": 42,
     "title": "The Left Hand of Darkness",
     "author": "Ursula K. Le Guin",
     "genre": "scifi",
     "year": 1969
   }
```

This is your map of what the answer *will look like* before you ever send the call. If your code needs
the publication year, you now know it'll arrive under the key `year`. Reading the example response is how
you know what fields to expect - no guessing. When your real response doesn't match - a field is missing,
or the shape is different - that mismatch is the bug, and now you can see it.

## Putting the five together

Here's the whole skim, start to finish. Say the task is *"get the details of book 42."* You go to the
docs and answer five questions in order:

```text
   1. Base URL?   https://api.bookshelf.dev/v1   (from "Getting Started")
   2. Endpoint?   GET /books/{id}                (from the reference list)
   3. Params?     id = 42 (required, path)       (from the parameter table)
   4. Auth?       Authorization: Bearer <token>  (from "Authentication")
   5. Example?    returns {id, title, author...} (from the sample response)

   Assembled request:
     GET https://api.bookshelf.dev/v1/books/42
     Authorization: Bearer sk_live_...
```

That's it. You've read a docs page the way it's meant to be read - not front to back, but as a form with
five fields. Next, let's actually send this request, two different ways.

## Recap

1. A docs page is a **reference, not prose** - skim for five things, don't read top to bottom.
2. **Base URL** is the unchanging front door (with a version like `/v1`).
3. **Endpoint + method** is which resource and what action (`GET /books/{id}`).
4. **Parameters** are your inputs - note which are *required*, and whether they ride in the path, the
   query (`?key=value`), or the body.
5. **Auth** is the secret you attach (usually `Authorization: Bearer <token>`) - treat it like a
   password.
6. The **example** shows it all assembled correctly and tells you the response shape in advance.


---

# Making the Request (Postman & curl)

You found the five things in [Phase 1](01-how-to-read-api-docs.md): the base URL, the endpoint and
method, the parameters, the auth header, and the example. Now you fire the request. There are two tools
you'll reach for, and here's the thing that makes both feel calm - **they are the same tool wearing
different clothes.**

## The one idea: both tools build the same request

Whether you click around in Postman or type a command in curl, you are assembling the exact same four
things and handing them to the same server:

```text
   An HTTP request = four parts, every time:

   ┌─────────────────────────────────────────────┐
   │ METHOD   GET                                 │  the verb
   │ URL      https://api.bookshelf.dev/v1/books  │  where + which thing
   │ HEADERS  Authorization: Bearer sk_live_...   │  the "envelope" info, incl. auth
   │ BODY     (none for GET)                       │  the payload, for POST/PUT
   └─────────────────────────────────────────────┘
                          │
              Postman ────┤──── curl
            (you click    │   (you type
             the parts)   │    the parts)
                          ▼
                    the same server
```

The moment you see that Postman fields and curl flags are *the same four parts*, you stop memorizing
either tool. A header is a header. A method is a method. You learn the request once and translate between
tools freely.

📝 **Terminology.**
- **Postman** is a graphical app (GUI) for building and sending HTTP requests. You fill in fields and
  click **Send**; it shows you the response in a nice panel. Great for exploring an API and saving
  requests to reuse.
- **curl** ("see-URL") is a command-line tool that does the same thing in one typed line. It's
  everywhere - preinstalled on macOS and Linux and on modern Windows - which is why docs and Stack
  Overflow answers are written in curl. Great for scripts, quick checks, and pasting into a ticket.

Neither is "better." Postman is friendlier to poke at an API by hand; curl is friendlier to automate
and to share. Knowing both means you're never stuck.

## The same request in Postman

Let's send the request we assembled in Phase 1: `GET https://api.bookshelf.dev/v1/books/42` with an auth
header. In Postman, you're filling in the four parts of that diagram, each in its own spot:

**1. Method and URL.** At the top of a new request there's a dropdown (it defaults to `GET`) and a long
URL bar. Set the dropdown to `GET` and paste the full URL - base URL, endpoint, and the `42` filled in
for `{id}`:

```text
   [ GET ▼ ]  https://api.bookshelf.dev/v1/books/42        [ Send ]
```

**2. Headers - where auth goes.** Below the URL is a row of tabs: **Params**, **Authorization**,
**Headers**, **Body**. Two equivalent ways to attach your token:

- The **Headers** tab: add a row with key `Authorization` and value `Bearer sk_live_...`. This is the
  literal header from the docs - what's actually sent over the wire.
- The **Authorization** tab: choose type "Bearer Token" and paste just the token. Postman *builds the
  same `Authorization` header for you* behind the scenes. Same result, fewer chances to fat-finger the
  word "Bearer."

💡 **Key point.** The **Params** tab and the **Body** tab map straight onto Phase 1's parameters. Query
parameters go in **Params** (Postman appends them to the URL as `?key=value` for you); a JSON body for a
`POST` goes in **Body**. You're filling in the same fields the docs' parameter table listed.

**3. Send.** Click **Send**. The response - status code, time, and body - appears in the panel below.
We read that panel in [Phase 3](03-reading-the-response.md).

⚠️ **Gotcha.** A request that works in Postman but fails when your *code* runs it is almost always a
header your code forgot - most often `Authorization`, or a `Content-Type: application/json` on a `POST`.
Postman can be configured to add some headers automatically, so what you see in the GUI isn't always
exactly what a bare script sends. When debugging, compare the *actual* headers, not the convenient ones.

## The same request in curl

Now the identical request, typed. curl's flags are just the four parts again. Here it is, annotated
line by line (the `\` at the end of each line lets one command span several lines for readability):

```console
$ curl https://api.bookshelf.dev/v1/books/42 \
    --request GET \
    --header "Authorization: Bearer sk_live_8Kd2x9..."
```

Reading the flags against the diagram:

```text
   curl  https://.../books/42         the URL  (curl's first argument)
   --request GET                      the METHOD   (short form: -X GET)
   --header "Authorization: Bearer …" a HEADER     (short form: -H; repeat for more)
```

📝 **Terminology.** Those `--request` / `--header` are **flags** (options). curl has a short form for
each: `-X` for `--request`, `-H` for `--header`. You'll see both in the wild; they're identical. For
`GET`, you can even drop `--request` entirely, because curl sends `GET` by default - so the *minimal*
version of this exact call is:

```console
$ curl https://api.bookshelf.dev/v1/books/42 \
    -H "Authorization: Bearer sk_live_8Kd2x9..."
```

Now the full transcript - command, then realistic output:

```console
$ curl https://api.bookshelf.dev/v1/books/42 \
    -H "Authorization: Bearer sk_live_8Kd2x9..."
{"id":42,"title":"The Left Hand of Darkness","author":"Ursula K. Le Guin","genre":"scifi","year":1969}
```

curl built an HTTP `GET` request to that URL, attached your `Authorization` header, sent it, and printed
the server's response body straight to your terminal. That body is JSON - the same shape the docs'
example promised in Phase 1 - but crammed onto one line, because curl prints exactly what the server
sent, with no prettifying. (You'll see how to read it, including the status code curl hid by default, in
[Phase 3](03-reading-the-response.md).)

⚠️ **Gotcha - the quotes matter.** Wrap the header value in double quotes: `-H "Authorization: Bearer
..."`. Without quotes, your shell sees the space after `Bearer` and treats the rest as separate
arguments, and curl gets a broken, half-a-header. If a curl call fails in a confusing way, missing or
mismatched quotes are the first suspect.

## Translating between the two

Because they're the same four parts, copying a request from one tool to the other is mechanical - and
both tools help you. This is a genuinely useful daily move:

- **Docs give you curl, you prefer Postman?** Postman has an **Import** that pastes a raw curl command
  and fills in the method, URL, headers, and body for you.
- **Built it in Postman, need it in a ticket or a script?** Postman's **Code** button exports the
  request as curl (and many languages). Paste that into a bug report and a teammate can reproduce your
  exact call.

🪖 **War story.** The fastest way I've seen a "the API is broken!" panic get resolved: someone exported
their failing Postman request as curl, pasted it into the chat, and a teammate spotted in two seconds
that the token had an extra space in it. The curl line made the *exact* request visible - no "well, what
did you click?" The lesson: when a request misbehaves, get it into curl, because curl is the request
with nothing hidden.

## Recap

1. Postman and curl build the **same HTTP request** - method, URL, headers, body - just clicked vs.
   typed.
2. In **Postman**: method dropdown + URL bar, the **Authorization**/**Headers** tab for your token, and
   **Params**/**Body** for parameters; then **Send**.
3. In **curl**: the URL is the first argument, `-X`/`--request` is the method (skippable for `GET`), and
   `-H`/`--header` adds a header - **quote the header value**.
4. The auth header is the same `Authorization: Bearer <token>` from the docs in both tools.
5. You can **import curl into Postman** and **export Postman as curl** - translate freely, and reach for
   curl when you need to show someone the exact request.


---

# Reading the Response & Iterating

You sent the request. Something came back. The instinct is to dive into the body looking for your data - 
but the first thing to read is the small number that tells you whether the request even *worked*. Read
it in the right order and the response stops being a mystery: status code first, then body. This phase
is that habit, plus how to tweak-and-resend efficiently and save your work safely.

## In a panic? The status-code cheat-card

You got a number you don't recognize and something's on fire. Find it here, breathe, then read the
section underneath for the fix.

| Code | Family | What it means | Calm first move |
|---|---|---|---|
| **200** | success | It worked, body has your data | Read the body |
| **201** | success | Created (after a `POST`) | Read the body for the new thing's id |
| **204** | success | Worked, no body to send back (common after `DELETE`) | Nothing - it's fine |
| **400** | your fault | Bad request - malformed input | Check your body/params against the docs |
| **401** | your fault | Unauthorized - who are you? | Token missing/wrong - check the auth header |
| **403** | your fault | Forbidden - known, but not allowed | Your key lacks permission for this |
| **404** | your fault | Not found - wrong URL or id | Check base URL, endpoint, and the id |
| **429** | your fault | Too many requests - slow down | Wait, then retry; you hit a rate limit |
| **500** | their fault | Server error on their end | Not you - retry; if it persists, report it |

💡 **Key point - the first digit is the whole story.** You don't memorize the table; you read the first
digit: **2xx** = it worked, **4xx** = *you* sent something wrong (fixable by you), **5xx** = *their*
server broke (not your fault). That single digit tells you which direction to look before you read a word
of the body.

## Status code first, then the body

Every HTTP response leads with a **status code** - a three-digit number that's the server's one-word
verdict on your request. The body is the detail; the status is the headline. Read the headline first,
because a `401` body and a `200` body need completely different reactions, and the code tells you which
you're holding.

Where to see it:

- **Postman** shows it in bold right above the response panel: `200 OK`, with the response time next to
  it.
- **curl** *hides it by default* - it prints only the body. To see the status, ask for the response
  headers with `-i`:

```console
$ curl -i https://api.bookshelf.dev/v1/books/42 \
    -H "Authorization: Bearer sk_live_8Kd2x9..."
HTTP/2 200
content-type: application/json
content-length: 102

{"id":42,"title":"The Left Hand of Darkness","author":"Ursula K. Le Guin","genre":"scifi","year":1969}
```

`-i` ("include") told curl to print the **response headers** along with the body. The very first line,
`HTTP/2 200`, is the status - `200` means success - followed by the server's own headers (it's sending
back JSON, 102 bytes of it), a blank line, and then the body. Now you can read the verdict before the
data.

**A failure looks like this:**

```console
$ curl -i https://api.bookshelf.dev/v1/books/42 \
    -H "Authorization: Bearer wrong-token"
HTTP/2 401
content-type: application/json

{"error":"invalid_token","message":"The API key provided is not valid."}
```

The status line says `401` before you read anything else - so this is an auth problem, *your* side. The
body confirms it in plain words: the token isn't valid. Good APIs put a human-readable `message` in the
error body - read it, it usually names the fix. Here: check the token (see
[Phase 1, §4](01-how-to-read-api-docs.md)).

⚠️ **Gotcha.** A `200` doesn't always mean you got what you wanted - it means the *request* succeeded.
`GET /books?genre=banana` might return `200` with an empty list `[]` because "no banana books" is a
perfectly successful answer to a valid question. When the body is empty or surprising but the code is
`2xx`, the problem is in your *parameters*, not your auth or URL. Re-read the parameter table from
Phase 1.

## Tweak a parameter and re-send

The real rhythm of working with an API is rarely one perfect request. It's: send, read, adjust, send
again. This is where having the request already built pays off - you change *one* thing and fire again.

Say `GET /books` returned the default 20 books and you want 5 science-fiction ones, newest first. You
don't rebuild anything - you add the query parameters from Phase 1's table:

- **Postman:** in the **Params** tab, add rows `genre = scifi`, `limit = 5`, `sort = newest`. Watch
  Postman build the URL for you as you type, then **Send**.
- **curl:** add them to the URL after a `?`, joined by `&`:

```console
$ curl "https://api.bookshelf.dev/v1/books?genre=scifi&limit=5&sort=newest" \
    -H "Authorization: Bearer sk_live_8Kd2x9..."
```

The `?` begins the query string and `&` separates each parameter, exactly as the docs described. You
changed the *inputs*, not the endpoint or the auth - same call, narrower question.

⚠️ **Gotcha - quote the whole URL in curl.** Notice the URL is in double quotes here. The `&` character
means "run this in the background" to most shells, so an unquoted URL with `&` in it gets chopped in
half and curl receives only the first parameter. Quote any URL that has a `?` and `&` in it, and you'll
save yourself a baffling debugging session.

## Save your work: collections, variables, and environments

Once you've got a working request you'll want it tomorrow too. Postman's job here is to stop you
retyping - and, done right, to stop you leaking secrets.

📝 **Terminology.**
- A **collection** is a saved folder of requests - your "Bookshelf API" set, grouped together so you
  (and your team) can reopen and re-send them.
- A **variable** is a named placeholder you write as `{{name}}` in a request. Instead of pasting the
  base URL into every request, you write `{{baseUrl}}/books` and define `baseUrl` once.
- An **environment** is a set of variable *values* you can switch between - a "Staging" environment
  where `baseUrl` points at the test server, a "Production" one where it points at the real server.
  Flip the environment and every request retargets at once.

**Why this is worth it.** With variables, moving a whole collection from staging to production is one
dropdown change instead of editing every URL by hand. And the token becomes a variable too - 
`{{token}}` in the auth header, its value living in the environment - which sets up the safety move
below.

```text
   Collection: "Bookshelf API"
     ├─ GET  {{baseUrl}}/books
     ├─ GET  {{baseUrl}}/books/{{bookId}}
     └─ POST {{baseUrl}}/books

   Environment: "Staging"          Environment: "Production"
     baseUrl = https://api-staging…  baseUrl = https://api.bookshelf.dev/v1
     token   = sk_test_…             token   = sk_live_…   ← switch with one dropdown
```

## ⚠️ The secret-leak trap - do not ship your API key

This is the one mistake in this whole guide that can genuinely hurt you, so it gets its own section.

Your token is your account (Phase 1, §4). It is dangerously easy to leak it without noticing, in two
specific ways:

1. **Exporting a Postman collection with the token baked in.** If you typed your real token directly
   into a request's auth field and then **Export** the collection to a `.json` file - to share it,
   commit it to the repo, or attach it to a ticket - the secret travels *inside that file*, in plain
   text. Now it's in your git history, in someone's downloads, in a Slack thread.

2. **Pasting a curl command with the token in it.** Every annotated curl in this guide has the token
   right there in the `-H` flag. The instant you paste a *real* one into a bug report, a gist, a chat
   message, or a screenshot, you've published your password.

**The fix - keep the secret out of anything you share:**

- In **Postman**, store the token as an **environment variable** and reference it as `{{token}}` in the
  request. Mark the variable type **secret** so its value is masked, and keep it in your *personal*
  environment. Crucially, environment values are **not** included when you export the collection - so
  the shared file contains `{{token}}`, a harmless placeholder, and your real secret stays on your
  machine.
- In **curl** / scripts, read the token from an **environment variable** instead of typing it inline.
  Set it once in your shell, then reference it - the secret never appears in the command you might paste:

```console
$ export BOOKSHELF_TOKEN="sk_live_8Kd2x9..."
$ curl https://api.bookshelf.dev/v1/books/42 \
    -H "Authorization: Bearer $BOOKSHELF_TOKEN"
```

`export` put the token into a shell variable named `BOOKSHELF_TOKEN`. The curl command then refers to it
as `$BOOKSHELF_TOKEN`, so the line you'd copy-paste contains the *name*, not the secret. Same request, but
nothing sensitive to leak.

🪖 **War story.** Leaked keys get found *fast*. Bots continuously scan public code for things that look
like API keys, and a key committed to a public repo can be abused within minutes of the push. This is
exactly why good providers let you **revoke and rotate** a key from their dashboard - so the recovery
move, if you ever do leak one, is: revoke the old key immediately, issue a new one, and update your
environment variable. Knowing that escape hatch exists is half of staying calm about it.

💡 **Key point.** The rule is short: **the secret lives in a variable, never in the thing you share.**
`{{token}}` in the collection, `$TOKEN` in the script - the real value stays only on your machine.

## Recap

1. **Read the status code first.** The first digit tells you everything: 2xx worked, 4xx is your fault,
   5xx is theirs.
2. In **curl**, use `-i` to see the status and headers (it hides them by default); **Postman** shows the
   status above the response.
3. A **2xx with a surprising body** is a *parameter* problem, not an auth or URL problem.
4. **Iterate** by changing one input and re-sending - add query parameters in Postman's **Params** tab
   or after `?...&...` in curl (and **quote the URL** so `&` doesn't break it).
5. Save requests in **collections**, and use **variables** + **environments** to swap base URL and token
   without editing every request.
6. **Never share your secret.** Keep the token in a (secret) environment variable / `$TOKEN`, reference
   it as `{{token}}` / `$TOKEN`, and if one ever leaks: revoke, rotate, replace.

### Related guides
- [HTTP & JSON API Basics](/guides/http-and-json-api-basics) - what a request, header, and JSON body actually are, if any of that felt shaky.
- [REST APIs Explained](/guides/rest-apis-explained) - why endpoints and methods are shaped the way they are, the next layer down.
