curl vs Python requests — Full Comparison with Side-by-Side Examples

curl and Python requests are both HTTP clients — but built for very different contexts. curl is a command-line tool perfect for testing, scripting, and one-off API calls. Python requests is a library for production code, automation, and complex HTTP workflows. This guide shows every common pattern side by side with complete working examples.

curl

perfect for testing and one-off API calls

requests

perfect for production Python code

1:1

mapping exists for almost every curl flag

Session

requests.Session() handles cookies like curl -c

1

When to Use Each

ItemUse curl When...Use Python requests When...
Primary useTesting an API endpoint quickly from terminalMaking HTTP calls in production Python code
SharingSharing a reproducible API call with a teammateHandling responses, parsing JSON, proper error handling
AutomationShell scripts and CI/CD pipelines (bash)Complex multi-step API workflows with Python logic
DebuggingSeeing exactly what is being sent with -vRetry logic, session management, connection pooling
Quick useOne-liner from the command lineAny Python program that needs HTTP calls
PerformanceHigh-throughput download/upload scriptsAsync HTTP with httpx for concurrent requests
2

Basic GET Request

bashGET Request — curl vs Python
# curl — simplest form:
curl https://api.example.com/users

# curl with verbose output (shows headers):
curl -v https://api.example.com/users

# Python requests — simple GET:
import requests
response = requests.get('https://api.example.com/users')
print(response.status_code)    # 200
print(response.json())          # parsed JSON dict/list
print(response.headers)         # response headers
print(response.elapsed)         # request timing

# curl with query params:
curl "https://api.example.com/users?page=2&limit=10"

# Python (auto-encodes to URL):
response = requests.get(
    'https://api.example.com/users',
    params={'page': 2, 'limit': 10}
    # → GET /users?page=2&limit=10
)

# Always check for errors:
response.raise_for_status()  # raises HTTPError for 4xx/5xx
data = response.json()
3

POST with JSON Body

data= sends form encoding (wrong for JSON APIs)

❌ Bad
# curl sends JSON correctly:
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"alice@example.com"}'

# ❌ WRONG Python equivalent — sends form data, not JSON!
requests.post(url, data={"name": "Alice", "email": "alice@example.com"})
# Content-Type: application/x-www-form-urlencoded  ← wrong!

json= matches curl -d with JSON Content-Type

✅ Good
# ✅ CORRECT — use json= parameter (matches curl -d with JSON)
response = requests.post(
    'https://api.example.com/users',
    json={"name": "Alice", "email": "alice@example.com"}
)
# requests automatically sets: Content-Type: application/json
# requests automatically serializes the dict to JSON string
print(response.status_code)  # 201
print(response.json())        # {"id": 42, "name": "Alice", ...}
4

Headers and Authentication

bashHeaders and Auth — curl vs Python
# Custom headers:
# curl:
curl -H "Authorization: Bearer mytoken" \
     -H "X-Custom-Header: value" \
     -H "Accept: application/json" \
     https://api.example.com/protected

# Python requests:
response = requests.get(
    'https://api.example.com/protected',
    headers={
        'Authorization': 'Bearer mytoken',
        'X-Custom-Header': 'value',
        'Accept': 'application/json',
    }
)

# Basic auth:
# curl:
curl -u username:password https://api.example.com

# Python:
response = requests.get(url, auth=('username', 'password'))

# Digest auth:
from requests.auth import HTTPDigestAuth
response = requests.get(url, auth=HTTPDigestAuth('user', 'pass'))

# API key in header:
# curl -H "X-API-Key: your_key" https://api.example.com
response = requests.get(url, headers={'X-API-Key': 'your_key'})
5

File Upload

bashFile Upload — curl vs Python
# curl single file upload:
curl -X POST -F "file=@photo.jpg" -F "user_id=123" \
  https://api.example.com/upload

# Python equivalent:
with open('photo.jpg', 'rb') as f:
    response = requests.post(
        'https://api.example.com/upload',
        files={'file': f},
        data={'user_id': '123'}
    )

# With custom filename and content type:
response = requests.post(
    url,
    files={'file': ('custom_name.jpg', open('photo.jpg','rb'), 'image/jpeg')}
)

# Multiple files:
response = requests.post(url, files=[
    ('images', ('photo1.jpg', open('photo1.jpg', 'rb'), 'image/jpeg')),
    ('images', ('photo2.jpg', open('photo2.jpg', 'rb'), 'image/jpeg')),
])
6

Session / Cookie Handling

bashSessions and Cookies — curl vs Python
# curl maintains cookies with -c (write) and -b (read):
curl -c cookies.txt https://api.example.com/login \
  -X POST -d "user=alice&pass=secret"

# Use saved cookies in subsequent requests:
curl -b cookies.txt https://api.example.com/dashboard

# Python requests.Session() handles cookies automatically:
import requests
session = requests.Session()

# Set default headers for ALL session requests:
session.headers.update({
    'Authorization': 'Bearer mytoken',
    'User-Agent': 'MyApp/1.0',
})

# Login (Set-Cookie headers stored automatically):
session.post('https://api.example.com/login',
             json={'user': 'alice', 'pass': 'secret'})

# Cookies and headers automatically included in subsequent requests:
dashboard = session.get('https://api.example.com/dashboard')
profile = session.get('https://api.example.com/me')

# Session also reuses TCP connections (connection pooling) → 30-50% faster
7

Timeout and SSL

bashTimeout and SSL — curl vs Python
# curl timeouts and SSL:
curl --connect-timeout 5 --max-time 30 https://api.example.com
curl -k https://api.example.com  # skip SSL verification
curl --cacert /path/to/ca.pem https://api.example.com

# Python requests equivalents:
# Separate connect timeout and read timeout:
response = requests.get(url, timeout=(5, 30))  # (connect_sec, read_sec)
response = requests.get(url, timeout=30)        # applies to both

# Skip SSL (dev only — never in production):
import urllib3
urllib3.disable_warnings()
response = requests.get(url, verify=False)

# Custom CA bundle:
response = requests.get(url, verify='/path/to/ca.pem')

# Client certificate:
# curl --cert client.crt --key client.key https://api.example.com
response = requests.get(url, cert=('client.crt', 'client.key'))
8

Full Flag Reference

Itemcurl flagPython requests equivalent
-X POSTHTTP methodrequests.post() / requests.request("POST", url)
-H "K: V"Headerheaders={"K": "V"}
-d "json"JSON bodyjson={...} (use json=, not data=!)
-F "key=val"Form datadata={"key": "val"}
-F "f=@file"File uploadfiles={"f": open("file","rb")}
-u user:passBasic authauth=("user", "pass")
-b "k=v"Cookiecookies={"k": "v"}
-kSkip SSL verificationverify=False
--max-time 30Total timeout (seconds)timeout=30
--connect-timeout 5Connect timeout onlytimeout=(5, 30) — (connect, read)
-LFollow redirectsallow_redirects=True (True by default)
-o file.jsonSave to filewith open('f.json','wb') as f: f.write(resp.content)
--proxy http://p:8080HTTP proxyproxies={"http": "http://p:8080"}
-IHEAD requestrequests.head(url)

Debug with httpbin.org

Use httpbin.org to test your requests — it echoes back exactly what it received including all headers, cookies, and body: requests.post("https://httpbin.org/post", json=your_data). Compare curl and Python responses against the same endpoint to verify they match.
1

Start with curl for discovery

Use curl -v to explore an API endpoint. It shows exactly what headers and body the server expects and returns. Much faster to iterate than Python code.

2

Copy curl to Python

Once you have a working curl command, use our curl-to-Python converter (or curlconverter.com) to get the Python requests equivalent automatically.

3

Add error handling

Call response.raise_for_status() before response.json(). Wrap in try/except for requests.exceptions.RequestException to handle network errors.

4

Use Session for multiple calls

If making 2+ calls to the same API, use requests.Session() for connection pooling, shared headers, and automatic cookie management. 30-50% faster.

5

Add timeout to all calls

Always set timeout=(5, 30) or similar. Never make a production API call without a timeout — a slow server will hang your program indefinitely.

6

Consider httpx for async

If you're using FastAPI, asyncio, or making many concurrent requests, switch to httpx. The API is nearly identical to requests but supports async/await.

Frequently Asked Questions