Skip to content

Testing APIs with Postman

Postman is a handy tool for exploring and debugging APIs.

  • URL + method
  • Query parameters
  • Headers (Content-Type, Authorization)
  • JSON body
  • Status codes
  • Response schema
  • Method: POST
  • Body: raw → JSON

Example payload:

json
{ "title": "Buy milk" }
  • Content-Type: application/json

When you add token auth later, you’ll often set:

  • Authorization: Bearer <token>

Postman can store variables and environments for tokens.

  • If Flask returns 415 Unsupported Media Type, your Content-Type might be wrong.
  • If you get 400, inspect validation errors.
  • If you get 401/403, inspect Authorization header.

If you maintain an API, consider documenting endpoints with:

  • OpenAPI (Swagger)

That’s a more advanced step, but worth it for real projects.

Postman is a graphical client for constructing HTTP requests. Everything it sends is ordinary HTTP, so the things worth asserting are the same whether you click them or script them:

diagram Diagram mermaid

Content-Type decides how your body is read

Section titled “Content-Type decides how your body is read”

Measured, sending {"name": "ada"} three ways to the same endpoint:

how it was sentContent-Typerequest.is_jsonrequest.jsonrequest.form
JSON bodyapplication/jsonTrue{'name': 'ada'}{}
form fieldsapplication/x-www-form-urlencodedFalseNone{'name': 'ada'}
raw string, no headerNoneFalseNone{}

The third row is the one that wastes afternoons: the body was sent, the server received 15 bytes of it, and request.json is None purely because no Content-Type was set. In Postman this is the difference between the raw + JSON body type and raw + Text.

curl equivalent
curl -X POST http://localhost:5000/echo \
  -H "Content-Type: application/json" \
  -d '{"name": "ada"}'

Measured against a real endpoint:

what you sendstatus
valid JSON with the right header200
malformed JSON with application/json400
valid JSON with Content-Type: text/plain415

415 Unsupported Media Type is a genuinely useful signal — the body was fine and the header was wrong — and it is easy to misread as a server bug.

tolerant.py
request.get_json()                 # raises -> 400 on a malformed body
request.get_json(silent=True)      # returns None instead, measured 200

A checklist worth running against every endpoint

Section titled “A checklist worth running against every endpoint”
caseexpect
the happy path200 or 201, correct body shape
a required field missing400 or 422, an error naming the field
no credentials401
valid credentials, someone else’s record403, not 404 and not 200
an id that does not exist404
a duplicate of something unique409
the same request repeated quickly429 once limited

The fourth row is the one most often missed. Fetching /orders/999 while logged in as a different user must not return that order — an ownership check is not the same as an authentication check, and only a deliberate test catches it.

auth_header.py
client.post("/echo", json={}, headers={"Authorization": "Bearer abc123"})
# the app sees: 'Bearer abc123'

Postman is excellent for exploring an API and poor as the only place your tests live — a collection on one laptop is not something CI can run. Once a case is understood, move it into the test suite:

test_api.py
def test_create_requires_name(client):
    r = client.post("/items", json={})
    assert r.status_code == 400
    assert "name" in r.get_json()["message"]
 
def test_cannot_read_another_users_order(client, other_users_token):
    r = client.get("/orders/999", headers={"Authorization": other_users_token})
    assert r.status_code == 403
sketch The same body, different Content-Type p5.js
Flask parses the body according to the Content-Type header. A correct body with the wrong header is invisible to request.json.
pch.quizTag pch.quizDefaultTitle
  1. You send a correct JSON body but forget the Content-Type header. What does request.json contain?

    pch.quizShowAnswer

    B — None, because Flask parses the body according to the header — Measured: the server received 15 bytes and request.json was still None. In Postman this is the difference between the raw JSON body type and raw Text.

  2. Valid JSON is sent with Content-Type: text/plain. What status does Flask return?

    pch.quizShowAnswer

    C — 415 — 415 Unsupported Media Type: the body was fine and the header was wrong. A malformed body with the correct header measured 400 instead.

  3. Which test case is most often missed when testing an API by hand?

    pch.quizShowAnswer

    C — an authenticated user requesting someone else's record must get 403 — Authentication and ownership are different checks. Logging in proves who you are, not that a given record is yours, and only a deliberate test catches the gap.

  4. Why should a Postman collection not be where your API tests live permanently?

    pch.quizShowAnswer

    B — a collection on one machine is not something CI can run, so move proven cases into the test suite — Postman is excellent for exploring an API. Once a case is understood it belongs in code that runs on every commit.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading