Skip to main content

06 · HTTP Clients

TL;DR

Before you write a single line of Python to talk to an API, test it with an HTTP client. curl is in every Linux/macOS/Windows (since 2018) install. httpie is curl for humans. Postman is for saving and sharing collections of requests.

Why Learn Multiple Clients?

Each has its sweet spot:

ClientBest for
curlScripts, CI, server-side automation, docs (every API doc uses it)
httpieExploring APIs interactively with clean output
Postman / Requestly API ClientSaving request collections, teams, environments, pre-request scripts
VS Code REST Client extensionRequests checked into Git alongside your code

curl — The Universal Client

curl is preinstalled on macOS, most Linux distros, and Windows 10+.

The 80% you need

bash
# GET
curl https://api.github.com/users/octocat

# GET with headers
curl -H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
https://api.github.com/user

# POST JSON
curl -X POST https://httpbin.org/post \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "role": "admin"}'

# POST from a file
curl -X POST https://httpbin.org/post \
-H "Content-Type: application/json" \
-d @payload.json

# Follow redirects, show headers, verbose
curl -L -i -v https://example.com

# Download a file
curl -O https://example.com/big.zip # save as big.zip
curl -o out.zip https://example.com/big.zip

Useful flags

FlagPurpose
-X METHODSet HTTP method (GET, POST, PUT, ...)
-H "K: V"Add a header
-d "data"Request body
-d @fileBody from file
-F "file=@path"Multipart upload
-u user:passBasic auth
-o file / -OWrite response body to file
-iInclude response headers in output
-IHEAD request (headers only)
-LFollow redirects
-vVerbose (see full request/response)
-sSilent (for scripting)
-w "%{http_code}"Print HTTP status code
--fail-with-bodyExit non-zero on 4xx/5xx, still show body

Real-world curl patterns

bash
# Piping to jq for pretty JSON
curl -s https://api.github.com/users/octocat | jq .

# Save cookies and reuse
curl -c cookies.txt -b cookies.txt https://example.com/login

# Upload a file via multipart
curl -X POST https://httpbin.org/post \
-F "file=@report.pdf" \
-F "description=Q3 report"

# Test a GraphQL endpoint
curl -X POST https://api.example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ viewer { login name } }"}'

# Download everything (resume + retries)
curl -L -C - --retry 5 --retry-delay 2 -O https://example.com/data.zip

httpie — curl for humans

httpie makes output colorful, pretty-prints JSON, and uses a cleaner command syntax.

bash
# Install
brew install httpie
# or
sudo apt install httpie
# or
uv tool install httpie

The syntax

bash
# GET
http GET https://api.github.com/users/octocat
# Or shorter:
http api.github.com/users/octocat

# POST JSON (httpie assumes JSON by default)
http POST httpbin.org/post name=Alice role=admin
# Equivalent to:
# curl -X POST ... -d '{"name": "Alice", "role": "admin"}'

# Headers with `Name:Value`
http httpbin.org/headers Authorization:"Bearer abc123" User-Agent:TDS

# Query params with `name==value`
http api.github.com/search/repositories q==tensorflow per_page==5

# File body (@)
http POST httpbin.org/post @payload.json

Output features

bash
# Just the headers
http --headers GET httpbin.org/get

# Just the body, suitable for piping
http --body httpbin.org/get | jq .

# Follow redirects; show all the intermediate responses
http --follow --all GET bit.ly/somelink

# Print request too (-v)
http -v POST httpbin.org/post name=alice

Sessions and auth

bash
# Basic auth
http -a user:pass GET httpbin.org/basic-auth/user/pass

# Bearer token (stored in session)
http --session=github api.github.com Authorization:"Bearer $GH_TOKEN"
http --session=github api.github.com/user # reuses token

Postman / Requestly API Client — GUI Collections

A GUI is indispensable when you're dealing with:

  • Collections — dozens of related requests saved and shared.
  • Environments — swap baseUrl, token across dev/staging/prod with one click.
  • Pre-request scripts — compute signatures, refresh tokens.
  • Tests — assertions on responses (pm.test("status 200", ...))
  • Documentation — auto-generated API docs from your collection.

Postman (the original) is great but increasingly requires login and pushes cloud features. Requestly API Client (covered next page) is a privacy-first alternative that stores collections as plain JSON in a folder — so you can commit them to Git. We'll use Requestly throughout this course.

Postman quick tour

code
Workspaces → Collections → Folders → Requests
└── Environments (variables)
  1. Create a request: GET https://api.github.com/users/{{username}}
  2. Create an environment with username = octocat.
  3. Switch environments to test as different users.
  4. Write tests in the "Tests" tab:
    js
    pm.test("Status is 200", () => pm.response.to.have.status(200));
    const data = pm.response.json();
    pm.expect(data.login).to.eql("octocat");

VS Code REST Client — Requests in Git

The REST Client extension lets you write .http files with your requests. Save, commit, share.

http
api-tests.http
### Get user (click "Send Request" above the line)
GET https://api.github.com/users/octocat
Accept: application/json

### Variables
@host = https://api.github.com
@token = {{$processEnv GITHUB_TOKEN}}

### Authenticated request
GET {{host}}/user
Authorization: Bearer {{token}}

### POST example
POST {{host}}/repos
Authorization: Bearer {{token}}
Content-Type: application/json

{
"name": "test-repo",
"private": false
}

No separate app, no cloud account, fully auditable via Git.

REST vs GraphQL — The Quick Comparison

RESTGraphQL
EndpointsMany (/users/1, /users/1/repos)One (/graphql)
MethodsGET, POST, PUT, DELETEAlways POST
Over/under-fetchingCommonClient picks exact fields
CachingHTTP caches work nativelyHarder — needs client cache
Learning curveLowMedium
Docs/DiscoveryOpenAPI / SwaggerIntrospection built-in

GraphQL request with curl:

bash
curl -X POST https://api.github.com/graphql \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ viewer { login repositories(first: 5) { nodes { name } } } }"}' \
| jq .

HTTP Status Codes — The Quick Map

RangeMeaningCommon codes
2xxSuccess200 OK, 201 Created, 204 No Content
3xxRedirect301 Moved, 302 Found, 304 Not Modified
4xxYour fault400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests
5xxServer fault500 Internal, 502 Bad Gateway, 503 Unavailable, 504 Timeout

Rule of thumb: if you get 401, check your token; 403, check permissions; 404, check the URL; 429, back off and retry; 5xx, retry with exponential backoff.

A Playground for Testing: httpbin.org

Use httpbin.org to experiment without touching a real API:

bash
http GET httpbin.org/get # echo back your request
http POST httpbin.org/post name=test # see what the server received
http GET httpbin.org/status/429 # simulate rate-limit
http GET httpbin.org/delay/3 # 3-second response
http GET httpbin.org/basic-auth/u/p -a u:p

5-Minute Exercise

  1. Get a GitHub personal access token: github.com → Settings → Developer settings → Personal access tokens → generate classic, no scopes needed.
  2. Run:
    bash
    export GH=ghp_your_token
    curl -s -H "Authorization: Bearer $GH" https://api.github.com/user | jq .login
  3. Same with httpie:
    bash
    http api.github.com/user "Authorization:Bearer $GH"
  4. Create a .http file in VS Code with the same request. Install the REST Client extension. Click "Send Request".
  5. Compare output ergonomics.

Further Reading