API Testing with Postman
You now understand what an API is and how HTTP works. It is time to actually drive one. Postman is the tool the industry reaches for first — a friendly workbench where you build requests, inspect every byte of the response, organise your work into reusable suites, and eventually write automated checks that run themselves. By the end of this module you will have a real, saved collection hitting a live public API, powered by variables and environments like a professional does on the job.
Learning objectives
By the end of this lesson you will be able to:
- Install Postman and find your way around its interface with confidence.
- Build and send requests of every HTTP method, including headers, query params and JSON bodies.
- Organise requests into collections and folders that your whole team can share.
- Use environments and variables such as
{{base_url}}so nothing is hard-coded. - Write your first automated tests in the Tests tab with
pm.testandpm.expect. - Chain requests, run a whole collection with the Collection Runner, and export your work.
Getting started: installing and understanding Postman
Download Postman from postman.com/downloads — it is free for individuals and runs on Windows, macOS and Linux. There is also a browser version, but the desktop app is recommended because it can reach APIs running on your own machine (localhost). You can sign in to sync your work to the cloud, or skip sign-in and work locally; either is fine for learning.
When Postman opens, orient yourself around four areas you will use constantly:
- The sidebar (left)
- Your Collections, Environments and request History live here. This is where your saved work is organised.
- The request builder (top centre)
- A method dropdown (
GET,POST, …), the URL bar, and the Send button. Below it are tabs: Params, Authorization, Headers, Body, Tests, and more. - The response panel (bottom centre)
- After you hit Send, this shows the body, the status code, the response time, the size, the response Headers, and the Test Results.
- The Console
- Open it with the console icon at the bottom-left. It shows the exact request Postman sent — final URL, headers, body — and anything you log. It is your single best debugging tool.
Building and sending your first request
Let us hit a live public API. We will use reqres.in, which returns realistic fake user data, and JSONPlaceholder.
Heads-up: reqres.in now needs a free API key
Since its 2025 relaunch, reqres.in requires an API key on every request to /api/*; without one you get 401 Unauthorized. Add a request header named x-api-key with the free public value reqres-free-v1 (or sign up at reqres.in for your own key). In Postman, add it under the Headers tab — or, better, set it once at the collection level so every request inherits it. The other practice APIs in this course (JSONPlaceholder and httpbin.org) need no key, so use those if you prefer to skip the header.
- Click New → HTTP Request (or the + tab).
- Leave the method as GET and type
https://reqres.in/api/users/2into the URL bar. - Click Send. In the response panel you should see
200 OK, a response time of a few hundred milliseconds, and a JSON body containing adataobject with user 2.
Read what came back — this is the whole point of the tool:
{
"data": {
"id": 2,
"email": "janet.weaver@reqres.in",
"first_name": "Janet",
"last_name": "Weaver"
}
}
Query parameters the easy way
Query params filter, sort or paginate. You can type them after a ? in the URL, but the Params tab is cleaner: add a key and value and Postman builds the query string for you. Try https://reqres.in/api/users with a param page = 2. Postman turns it into https://reqres.in/api/users?page=2, and you get the second page of users.
Sending a JSON body with POST
To create something, switch the method to POST, go to the Body tab, choose raw, and pick JSON from the format dropdown (this also sets the Content-Type: application/json header automatically). Point the URL at https://reqres.in/api/users and enter:
{
"name": "Test Learner",
"job": "QA Engineer"
}
Hit Send. You should get 201 Created and a body echoing your data plus a server-generated id and createdAt:
{
"name": "Test Learner",
"job": "QA Engineer",
"id": "873",
"createdAt": "2026-01-12T09:30:00.000Z"
}
The classic beginner body mistake
If your POST returns a confusing 400 Bad Request or the server ignores your data, check three things in order: (1) Did you select raw → JSON, not “Text”? (2) Is your JSON valid — double quotes around keys, no trailing comma after the last field? (3) Is the Content-Type: application/json header actually present (open the Console to confirm what was sent)? Nine out of ten “my POST does not work” problems are one of these three.
Collections and folders: organising your work
A single request is useful once; a collection is useful forever. A collection is a saved group of related requests — think “User API” or “Checkout Service.” Inside a collection you add folders to group by feature: an Auth folder, a Users folder, an Orders folder. This structure is the backbone of an automated API regression suite.
- Click New → Collection and name it
Practice API. - Add folders inside it — for example Users and Posts.
- Save each request you build into the right folder with Save (or Ctrl/Cmd+S), giving it a clear name like
Get user by id.
Why this matters: a saved collection can be shared with your team, version-controlled, run as a batch, and documented automatically. A pile of unsaved tabs can do none of those things.
Environments and variables: stop hard-coding
Never paste the same base URL or token into twenty requests. Instead, define a variable once and reference it with double curly braces: {{variable_name}}. The most common is {{base_url}}.
An environment is a named set of variables — typically one per deployment:
- Click the environments icon, then Create Environment, name it
Staging. - Add a variable: name
base_url, valuehttps://reqres.in/api. - In your requests, use
{{base_url}}/users/2instead of the full URL. - Select
Stagingfrom the environment dropdown (top-right) so Postman knows which values to use.
Now create a second environment, Production, with a different base_url. To re-run your entire suite against production, you change one dropdown — every request follows. This is how one collection tests many environments.
Tutor tip: variable scopes
Postman has several variable scopes. The two you need now are environment variables (tied to Staging/Production) and collection variables (shared by every request in a collection, regardless of environment). Put things that change per deployment (URLs, credentials) in the environment; put things that are constant for the whole suite in collection variables. Set secrets as the variable’s current value and mark the type secret so they are masked and not exported — more on secrets in Module 5.
The Tests tab: your first automated checks
Clicking Send and eyeballing the body does not scale. The Tests tab lets you write small JavaScript snippets that run after the response arrives and turn into green passes or red failures in the Test Results panel. The two building blocks are pm.test(name, fn) — which names and runs a check — and pm.expect(value) — which asserts something about a value.
Add these to the Tests tab of your GET {{base_url}}/users/2 request, then hit Send:
// 1) Status code check
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});
// 2) Response time is reasonable
pm.test("Response time is under 1000 ms", function () {
pm.expect(pm.response.responseTime).to.be.below(1000);
});
// 3) Content-Type header is JSON
pm.test("Content-Type is application/json", function () {
pm.expect(pm.response.headers.get("Content-Type"))
.to.include("application/json");
});
// 4) Parse the body once, then assert on its fields
const body = pm.response.json();
pm.test("Returns the user we asked for", function () {
pm.expect(body.data.id).to.eql(2);
pm.expect(body.data.email).to.be.a("string");
pm.expect(body.data.email).to.include("@");
});
The pattern is always the same: parse the body with pm.response.json(), then make precise claims about it. We go much deeper on assertions in Module 4; for now, notice how readable it is — each pm.test reads almost like a sentence describing the contract.
Chaining requests: reuse a value from one response
Real workflows span multiple calls: create a user, then fetch that same user. To do that you save a value from one response into a variable and use it in the next request. In the Tests tab of a POST that creates a user:
const body = pm.response.json();
// Save the new id into the environment for later requests
pm.environment.set("user_id", body.id);
console.log("Saved user_id =", body.id);
Then a later request can use {{base_url}}/users/{{user_id}}. This “capture then reuse” pattern is the foundation of end-to-end API flows and is used heavily in the capstone.
Note: reqres.in is a mock API — the id it returns from a POST is not actually stored, so fetching it back with a GET will not find it. The chaining here is illustrative; against a real API the created resource would be retrievable.
The Collection Runner: run everything at once
Once your requests have tests, you can execute the whole collection in order. Click the collection’s ··· menu → Run collection. The Collection Runner lets you:
- Choose which requests run and in what order.
- Select the environment (Staging vs Production).
- Set an iteration count and attach a data file (CSV/JSON) to run the same requests with many sets of inputs — this is data-driven testing.
- See a summary of total passes and failures at the end.
This is the moment a collection stops being a toy and becomes a regression suite: one click re-verifies the whole API. The same collection can later be run from the command line with Postman’s CLI tool, Newman (newman run practice-api.json), which is how teams wire Postman tests into CI/CD pipelines.
Exporting and sharing
To hand your work to a teammate or save it as a portfolio piece, click the collection’s ··· menu → Export and choose the Collection v2.1 format. You get a single JSON file that anyone can Import to get your exact requests, folders and tests. Export the environment separately (the environments ··· menu) — but strip real secrets out first.
Full worked example: a mini user-API collection
Here is the shape of a small but complete collection against {{base_url}} (set to https://reqres.in/api), the kind you will build in the exercise:
Practice API (collection)
Users (folder)
GET {{base_url}}/users?page=2 -> expect 200, array of users
GET {{base_url}}/users/2 -> expect 200, data.id === 2
POST {{base_url}}/users -> expect 201, save {{user_id}}
PUT {{base_url}}/users/2 -> expect 200, updatedAt present
DELETE {{base_url}}/users/2 -> expect 204, empty body
Test snippet for the DELETE, confirming a true “no content” success:
pm.test("Delete returns 204 No Content", function () {
pm.response.to.have.status(204);
});
pm.test("Delete body is empty", function () {
pm.expect(pm.response.text()).to.eql("");
});
Common Postman mistakes
- No environment selected. Your
{{base_url}}resolves to nothing and the request fails with an invalid URL. Always check the top-right dropdown. - Forgetting to Save. Sending a request does not save it. Unsaved changes vanish — press Ctrl/Cmd+S.
- Testing against the response you see by eye only. If there is no
pm.test, nothing is actually verified when the collection runs. - Hard-coding an id that changes. Capture ids into variables; do not paste yesterday’s id into today’s request.
- Confusing current value and initial value. The initial value is shared/exported; the current value is local to you. Secrets go in the current value only.
Hands-on exercise
Build a real collection against reqres.in (or JSONPlaceholder’s /posts).
- Create a collection
Practice APIwith a Users folder. - Create an environment
Stagingwithbase_url = https://reqres.in/apiand select it. - Add four requests using
{{base_url}}: a GET list, a GET by id, a POST (create), and a DELETE. - On the GET by id, add tests for: status 200, response time under 1000 ms, and
data.idequals the id you requested. - On the POST, capture the returned
idinto a variable{{user_id}}and log it to the Console. - Run the whole collection with the Collection Runner and note the total pass/fail count. Export the collection to a JSON file.
Knowledge check
- What is a Postman collection, and why is it more useful than loose request tabs?
- How do you reference an environment variable inside a URL?
- Where do you put a JSON payload for a POST, and what does choosing “raw → JSON” do for you automatically?
- Which function parses the response body into a usable object?
- What does the Collection Runner let you do that sending one request cannot?
- How would you reuse an id created by a POST in a later GET request?
Show answers
- A collection is a saved, organised group of related requests (with folders and tests). Unlike loose tabs it can be saved, shared, version-controlled, run as a batch and documented.
- With double curly braces, e.g.
{{base_url}}/users/2, after selecting the environment that definesbase_url. - In the Body tab under raw; choosing JSON also sets the
Content-Type: application/jsonheader for you. pm.response.json().- Run all (or selected) requests in order, against a chosen environment, optionally many times with a data file, and report total passes/failures — turning the collection into a regression suite.
- In the POST’s Tests tab,
pm.environment.set("user_id", body.id), then reference{{user_id}}in the later request’s URL.
Key takeaways
- Postman lets you send any request and inspect the full response — status, time, headers, body and test results.
- Collections and folders organise your work; variables and environments keep it reusable and free of hard-coded values.
- Use
{{base_url}}and switch environments to run one suite against staging or production. - The Tests tab turns manual eyeballing into automated
pm.test/pm.expectchecks. - Capture values to chain requests, run everything with the Collection Runner, and export to share or automate via Newman.
Go deeper
The Tests tab runs after a response; its mirror image is the Pre-request Script tab, which runs before the request is sent. Use it to build a fresh timestamp, sign a payload, or fetch a token so every request starts from a clean state. Postman also exposes dynamic variables you can drop straight into a body or URL — {{$guid}} for a unique id, {{$timestamp}} for the current epoch, {{$randomEmail}} for throwaway data — which is how you avoid duplicate-key collisions when you create resources over and over.
For real end-to-end flows you often need one request to set up another. Inside a script you can call pm.sendRequest to, say, log in and stash a token before the main request runs:
pm.sendRequest({
url: pm.environment.get("base_url") + "/login",
method: "POST",
header: { "Content-Type": "application/json" },
body: { mode: "raw", raw: JSON.stringify({ email: "a@b.com", password: "x" }) }
}, function (err, res) {
pm.environment.set("token", res.json().token);
});
- Newman + CI: the same collection runs headless from a pipeline with
newman run suite.json -e staging.json, so your tests gate every deploy. - Mock servers let you build tests against an endpoint that does not exist yet by returning canned examples — test-first, before the backend is ready.
- Monitors schedule a collection to run every few minutes against production, turning your functional tests into lightweight uptime and contract alarms.