Skip to content
Bugwise
Menu
Web Testing

API Testing with Postman

What an API is, how REST requests and responses work, and how to test an API with Postman, from your first GET request to environments, collections and automated checks.

35 min beginner Web

A food delivery app had a neat little quantity stepper: the – button stopped at 1, so you could never order fewer than one pizza. The UI was tested carefully and worked perfectly.

Then someone sent the request to the server directly, with "quantity": -3. The server accepted it. The order total went negative, and the “refund” landed on a real card.

The bug was never in the screen. It was in the APIAPIThe way different software parts talk to each other, like an app asking a server for data.Open in glossary → behind it, and the UI was simply hiding it. This lesson teaches you to test that layer directly, with Postman, the most popular tool for the job.

Why it matters

Almost every modern app is two parts: a front end (the website or the mobile app you see) and a back end (servers and databases). They talk to each other through APIs. The same API often serves the website, the iOS app, the Android app and partner companies.

That means:

What is an API?

An API (Application Programming Interface) is a set of rules that lets one program ask another program for data or actions.

The classic analogy is a restaurant:

You (the client)

The app or website. It knows what it wants, but can’t go into the kitchen.

The waiter (the API)

Takes your order in an agreed format (the menu), carries it to the kitchen and brings back the result.

The kitchen (the server)

Does the real work: checks stock, saves orders, reads the database.

The menu is the API documentation: it lists what you can order and how. As an API tester you skip the dining room (the UI) and talk to the waiter directly: you order normal dishes, strange dishes, dishes that aren’t on the menu, and you check that the waiter always responds correctly.

REST basics

Most web and mobile APIs today are REST APIs. They use plain HTTP, the same protocol your browser uses, and usually send data as JSONJSONA simple text format for data, used by most APIs, like {"name": "Anna", "age": 31}.Open in glossary →. Every call is a request from the client and a response from the server.

1 The request (you → server)

Request line

what and where

POSTMethodhttps://api.shop.test/v1Base URL/cart/itemsPath?source=appQuery

Headers

extra info, e.g. who you are

Authorization: Bearer test-anna-123
Content-Type: application/json

Body

the data you send (JSON)

{ "productId": 1, "quantity": 2 }

2 The response (server → you)

Status

did it work?

201 Created· 143 ms

Headers

info about the response

Content-Type: application/json
Location: /v1/cart/items/1

Body

the data you get back

{ "items": [ { "productId": 1, "quantity": 2 } ], "total": 39.98 }

Endpoints and URLs

An endpointEndpointA specific API address the app calls, like GET /api/products or POST /api/login.Open in glossary → is one address of the API that does one job. A URL has several parts:

PartExampleMeaning
Base URLhttps://api.shop.test/v1Where the API lives (often different per environment)
Path/productsWhich resource you want
Path parameter/products/42Which one item (here product 42)
Query parametersQuery parameterData in a URL after the ?, like ?q=mug&sort=price, often used to keep search and filter state.Open in glossary →?category=shoes&limit=10Filters and options, after a ?, joined with &

Methods

The HTTP methodHTTP methodThe type of request: GET reads data, POST creates, PUT/PATCH updates, DELETE removes.Open in glossary → says what you want to do with the resource:

MethodDoesExampleHas a body?Idempotent?
GETReadGET /products/42NoYes
POSTCreate / trigger an actionPOST /cart/itemsYesNo
PUTReplace / set completelyPUT /cart/items/42 with {"quantity": 3}YesYes
PATCHChange some fieldsPATCH /profile with {"name": "Ana"}YesNot necessarily
DELETERemoveDELETE /cart/items/42Usually noYes

Status codes

Every response starts with an HTTP status codeHTTP status codeA three-digit number in every server response: 200 means OK, 404 not found, 500 server error.Open in glossary →. The first digit tells you the family:

CodeNameWhen you’ll see it
2xxSuccess
200OKA read or update worked
201CreatedSomething new was created (POST)
204No ContentWorked, nothing to send back (often DELETE)
3xxRedirection
301 / 302Moved Permanently / FoundGo to another URL
304Not ModifiedUse your cached copy
4xxClient error (the request was wrong)
400Bad RequestInvalid data, missing field, broken JSON
401UnauthorizedWho are you? No token, or an invalid / expired one
403ForbiddenI know who you are, but you’re not allowed
404Not FoundThe resource doesn’t exist
405Method Not AllowedE.g. DELETE on an endpoint that only supports GET
409ConflictClashes with current state, e.g. creating a duplicate
422Unprocessable ContentThe JSON is valid but the values break a rule (some APIs use 400 for this)
429Too Many RequestsRate limit hit
5xxServer error (the server failed)
500Internal Server ErrorUnhandled crash on the server: almost always a bug
502 / 503 / 504Bad Gateway / Service Unavailable / Gateway TimeoutServer down, overloaded or too slow

Headers

Headers are key–value pairs with extra information about the request or response:

  • Content-Type: application/json — “the body I’m sending is JSON”.
  • Authorization: Bearer eyJhbGci... — “here’s my token, this is who I am”.
  • Accept: application/json — “please answer in JSON”.
  • Response headers like Cache-Control, Location (where a new resource lives) or X-Request-Id (great to paste into bug reports so developers can find the server log).

JSON bodies

POST, PUT and PATCH usually send a body in JSON: keys in double quotes, values as strings, numbers, true/false, null, arrays [ ] or objects { }.

{
  "productId": 1,
  "quantity": 2,
  "giftWrap": false,
  "note": "Leave at the door"
}

Notice that 1 (a number) and "1" (a string) are different types. Many API bugs are type bugs.

Authentication: tokens and API keys

Most APIs need to know who is calling:

  • Bearer token. You log in once (e.g. POST /login), the server returns a token, and you send it on every request in the header Authorization: Bearer <token>. Tokens expire. A common format is a JWT (a long string starting with eyJ).
  • API key. A fixed secret string for an app or partner, sent in a header like x-api-key: ... or sometimes as a query parameter.

The two error codes to remember: 401 = missing or invalid credentials; 403 = valid credentials, but not allowed (for example a customer trying an admin endpoint).

Why test the API directly?

ReasonExample
Faster feedbackCheck 15 invalid inputs in two minutes, no clicking through screens
Finds bugs the UI hidesThe UI greys out ”–” at 1, but the API accepts -5
Validation bypassFront-end checks can be skipped by anyone. The server must validate everything again.
Earlier testingThe API is often ready days before the screens are
Clear blameWrong data in the API response → back-end bug. Correct response but wrong screen → front-end bug.
Security checksCall another user’s resources with your token to test access rules (see Security Basics)

Try it: a mini API client

Here’s a simplified Postman-like client connected to a simulated Shop API that runs in your browser. Nothing is sent over the internet. Try these:

  1. Send the first request. “List products” is preloaded. Click Send and look at the status, time and JSON body.
  2. Change a query parameter. In Params, change limit to 2, then to 0. Which status do you get for 0, and does the message help?
  3. Use the environment. Click the eye icon to see {{baseUrl}} and {{token}}. Switch the environment to No environment and send again. Then switch back.
  4. Check 401 vs 403. Load View cart, untick the Authorization header and send (401). Then load Delete product (admin) and change {{adminToken}} to {{token}} (403).
  5. Break the body. Load Add to cart, change "quantity": 2 to "quantity": "two", then remove the closing }.
  6. Run tests. Open Tests, tick a few checks, send, and open Test Results. Then click curl to see the same request as a terminal command.

GET https://api.shop.test/v1/products?category=accessories&limit=10

Query parameters are added after a ? in the URL. Edit here or in the URL bar.

Response

Click Send to get a response.

History

Requests you send appear here.

Postman essentials

Postman is an app (desktop and web, with a free plan) for building, sending, saving and automating API requests. Here’s what you’ll use every day.

  1. Build a request. Click New › HTTP (or the + tab). Choose the method from the dropdown, paste the URL and click Send. The response appears below with status, time, size, body (Pretty / Raw / Preview) and headers.
  2. Params. Query parameters typed in the URL appear in the Params tab, and vice versa. Path variables written as :id in the URL (e.g. /products/:id) also get a value field there.
  3. Headers. Add headers in the Headers tab. For tokens, the Authorization tab is easier: choose Bearer Token and paste {{token}}. Postman then adds the header for you.
  4. Body. In Body, choose raw and JSON. Postman highlights invalid JSON and sets Content-Type: application/json automatically.
  5. Environments and variables. Create an environment (e.g. Staging) with variables like baseUrl and token, select it in the top-right dropdown, and write {{baseUrl}}/products. Switching to a Dev environment then retargets every request at once. Unresolved variables show in red.
  6. Collections. Save requests into a collection (a folder for one API, e.g. Shop API) with sub-folders like Products and Cart. Collections can be shared with your team and run all at once with the Collection Runner.
  7. Save examples. After a response, click Save Response › Save as example. Examples document what a correct (or error) response looks like and can power mock servers.
  8. Scripts and tests. In the Scripts tab (called Tests in older versions), the Post-response script runs after every response. Add checks with pm.test and pm.expect. The Snippets list on the side writes common ones for you.

A few tests worth knowing, in Postman’s JavaScript syntax:

// Status code
pm.test("Status code is 200", () => {
  pm.response.to.have.status(200);
});

// Response time
pm.test("Response time is below 500 ms", () => {
  pm.expect(pm.response.responseTime).to.be.below(500);
});

// Body content and types
pm.test("Product has the right shape", () => {
  const json = pm.response.json();
  pm.expect(json.id).to.eql(1);
  pm.expect(json.name).to.be.a("string");
  pm.expect(json.price).to.be.a("number");
});

// Header
pm.test("Content-Type is JSON", () => {
  pm.expect(pm.response.headers.get("Content-Type")).to.include("application/json");
});

// Save a value for the next request (e.g. after POST /login)
pm.environment.set("token", pm.response.json().token);

What to test

Positive (happy path): each endpoint with valid data returns the documented status (200, 201, 204) and body · created items can be read back with GET · updates really change the data · lists support their filters, sorting and paginationPaginationSplitting a long list into pages that load one at a time.Open in glossary →.

Negative: input validation. input validationInput validationChecking that what the user typed is acceptable before the app uses it.Open in glossary → on the server is the heart of API testing:

  • Missing required fields → 400 with a message naming the field.
  • Wrong types: "quantity": "two", "price": "19.99", null, an array instead of an object.
  • Boundary values (boundary value analysis): if quantity is 1–10, try 0, 1, 10, 11, and also -1 and 1.5.
  • Invalid JSON (a missing bracket) → 400, not 500.
  • Extra or unknown fields like "isAdmin": true are ignored or rejected, never applied.

Negative: auth and existence.

  • No token or an invalid / expired token → 401.
  • Valid token but no permission → 403. Another user’s resource → 403 or 404.
  • An ID that doesn’t exist → 404, not 200 with an empty body.
  • Creating a duplicate (same email, same coupon code) → 409, not 500.
  • Wrong method on an endpoint → 405.

Behavior:

Common API bugs

#BugWhat you seeTypical severity
1Missing server validationquantity: -5 or 0 is accepted, totals go negativeHigh
2Wrong status code200 with null or {} instead of 404; 200 with {"error": ...}Medium
3500 instead of 4xxA duplicate, missing field or bad JSON crashes the serverMedium–High
4Missing auth checkAn endpoint works without a token, or for the wrong userCritical
5Type / schema mismatch"price": "19.99" (string) where the docs say number; renamed fieldsMedium
6Not idempotentRepeating PUT adds the item again; a retried payment charges twiceHigh
7Over-sharingThe response includes password hashes, internal IDs or other users’ dataHigh–Critical
8Leaky errorsStack traces, SQL queries or server paths in error messagesMedium
9Inconsistent errorsEach endpoint returns errors in a different formatLow
10Slow endpointsA list call takes 4 seconds once there are 1,000 itemsMedium

Find the bug

Here’s the same client connected to a buggy build of the Shop API. The API docs at the bottom say what the API promises. Compare the real responses with the docs, and use the Tests tab and History to help.

🐞 Bug hunt: Shop API test build

This demo has 6 hidden bugs. Explore it like a tester, then check your findings.

Found0/6

GET https://api.shop.test/v1/products?category=accessories&limit=10

Query parameters are added after a ? in the URL. Edit here or in the URL bar.

Response

Click Send to get a response.

History

Requests you send appear here.

  • GET/productsPublic

    List products. Query: category (string), limit (integer 1–50). Returns { data: Product[], total }. Product = { id: integer, name: string, price: number, category: string, inStock: boolean }.

    200 · 400 bad limit

  • GET/products/{id}Public

    One product.

    200 Product · 404 if it does not exist

  • DELETE/products/{id}Admin token

    Delete a product.

    204 · 401 no/invalid token · 403 not an admin · 404

  • GET/cartToken

    The current user’s cart: { items, total: number }.

    200 · 401

  • POST/cart/itemsToken

    Add a product. Body { productId: integer, quantity: integer 1–10 }. Adds to any quantity already in the cart.

    201 · 400 invalid body · 401 · 404 unknown product · 409 out of stock

  • PUT/cart/items/{productId}Token

    Set the quantity of a cart item. Body { quantity: integer 1–10 }. Idempotent: sending it twice gives the same result.

    200 · 400 · 401 · 404

  • DELETE/cart/items/{productId}Token

    Remove an item from the cart.

    204 · 401 · 404 not in cart

  • POST/couponsAdmin token

    Create a coupon. Body { code: string, percent: integer 1–90 }. Codes are unique.

    201 · 400 · 401 · 403 · 409 code already exists

  1. 1

    Hint: The docs say quantity must be 1–10. Try values outside that range in Add to cart.

  2. 2

    Hint: Ask for a product ID that is not in the list.

  3. 3

    Hint: Add an item, then untick the Authorization header and remove it.

  4. 4

    Hint: Send Create coupon (admin) twice with the same code.

  5. 5

    Hint: Compare the type of price with the docs, or run the price test.

  6. 6

    Hint: Send Update cart item several times and look at the quantity.

Reading API docs: Swagger and OpenAPI

Before testing, read the docs. Most teams describe their API in an OpenAPI file (formerly called Swagger), a YAML or JSON file that lists every endpoint, parameter, request body, response and status code. Tools like Swagger UI or Redoc turn it into a readable web page, often at a URL like /docs or /swagger.

As a tester, the docs are your expected results:

  • Each documented field, type and status code is something to verify.
  • Anything missing from the docs (what happens on a duplicate? what is the max length?) is a question for the team, and often a bug waiting to happen.
  • Postman can import an OpenAPI file (Import › file or URL) and generate a whole collection for you.

Alternatives to Postman

ToolWhat it isGood for
InsomniaDesktop API client, similar to PostmanA cleaner, lighter UI; REST, GraphQL and gRPC
BrunoOpen-source desktop client that stores collections as plain filesTeams that keep API tests in Git next to the code
curlCommand-line tool available on macOS, Linux and WindowsQuick checks, sharing an exact request with a developer
Browser DevToolsNetwork tab › right-click a request › Copy as cURL / fetchGrabbing the real request the app sent, then replaying it in Postman (Import › Raw text)
curl -X POST 'https://api.shop.test/v1/cart/items' \
  -H 'Authorization: Bearer <your-test-token>' \
  -H 'Content-Type: application/json' \
  -d '{"productId": 1, "quantity": 2}'

Example test cases

TC-API-001Get an existing product

Steps

  1. Send GET {{baseUrl}}/products/1

Expected result

200 OK. Body has id 1, name (string), price (number), category (string), inStock (boolean). Response time under 500 ms.

TC-API-002Get a product that does not exist

Steps

  1. Send GET {{baseUrl}}/products/999

Expected result

404 Not Found with an error message. No 200, no null body.

TC-API-003Add to cart rejects quantities outside 1–10

Preconditions

  • Valid customer token in {{token}}

Steps

  1. Send POST {{baseUrl}}/cart/items with each quantity from the test data
  2. Check status and body of each response

Test data

quantity: 0, -1, 11, 1.5, "two", missing

Expected result

Each request returns 400 with a message naming the quantity rule. The cart does not change.

TC-API-004Cart endpoints require a valid token

Steps

  1. Send GET /cart without the Authorization header
  2. Send it with "Bearer invalid-token"
  3. Repeat for POST /cart/items, PUT and DELETE /cart/items/1

Expected result

Every request returns 401 and makes no change.

TC-API-005Customer token cannot use admin endpoints

Steps

  1. Send DELETE {{baseUrl}}/products/5 with the customer token

Expected result

403 Forbidden. The product still exists (GET /products/5 returns 200).

TC-API-006Duplicate coupon code returns 409

Preconditions

  • Admin token in {{adminToken}}

Steps

  1. Send POST /coupons with code SPRING10
  2. Send the same request again

Expected result

First: 201 Created. Second: 409 Conflict with a clear message. Never 500.

TC-API-007PUT on a cart item is idempotent

Steps

  1. Send PUT /cart/items/1 with {"quantity": 3}
  2. Send the exact same request two more times
  3. Send GET /cart

Expected result

Every PUT returns 200 with quantity 3. GET /cart shows quantity 3 for product 1.

Tip: click a test case to expand it.

Checklist

API testing checklist

0 of 14 checked

Check your understanding

Quick quiz

0/6 answered · 0 correct

  1. 1.You send a request with a valid customer token to an admin-only endpoint. What status code should you get?

  2. 2.Which method should be idempotent?

  3. 3.Creating a user with an email that already exists returns 500. Is that a bug?

  4. 4.Which Postman script checks that the response status is 200?

  5. 5.Why use {{baseUrl}} instead of typing https://api.staging.example.com in every request?

  6. 6.The UI blocks quantities below 1. Why test quantity -5 on the API anyway?

Key takeaways

  • An API is how apps talk to servers. Testing it directly is fast, finds bugs the UI hides, and shows whether a bug is front end or back end.
  • Know the basics: methods (GET, POST, PUT, PATCH, DELETE), status codes (2xx success, 4xx client error, 5xx server error), headers, JSON bodies and tokens.
  • Remember 401 vs 403, 404 for missing items, 409 for duplicates, and that a 500 caused by user input is a bug.
  • In Postman, use environments and variables, organize requests in collections, and automate checks with pm.test and pm.expect.
  • Always test validation, auth, idempotency, the response schema against the docs, response time and error messages.

Go deeper: API Testing in Depth covers JWTs and expired tokens, authorization tests, negative requests, response checks beyond the status code, and a FoodDash API sandbox with 8 bugs to find.

Finished this page?Nice work, this page is complete!

Mark it as done to track your progress. It's saved in this browser, no account needed.

Its review cards are now in Bites ⚡
Esc

Type to search the whole site.