Convert Curl to Python Requests

A curl command and a call to the requests library describe the same HTTP request in different vocabularies. Translating is mechanical once you know the mapping, and the traps are all in the defaults the two do not share.

Last updated . How this page is checked.

The command

curl -X POST https://httpbin.org/post \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Ada","role":"admin"}'

Curl flag to requests argument

curlPython requests
-X METHODrequests.request("METHOD", url) or requests.post(url) and friends
-H "K: v"headers={"K": "v"}
-d "a=1&b=2"data={"a": "1", "b": "2"}
-d '{"a":1}' + JSON headerjson={"a": 1} (sets the header itself)
-F "file=@x.png"files={"file": open("x.png", "rb")}
-u user:pwauth=("user", "pw")
-b "k=v"cookies={"k": "v"}
-x proxyproxies={"https": "http://proxy:8080"}
-m 20timeout=20
-kverify=False
--cacert ca.pemverify="ca.pem"
-Ldefault; turn off with allow_redirects=False

The same request in Python

The command above becomes a single call. Passing a dictionary to json= serialises it and sets Content-Type: application/json, so that header disappears from your code. The token comes from the environment, as $TOKEN did in the shell.

import os
import requests

response = requests.post(
    "https://httpbin.org/post",
    headers={"Authorization": f"Bearer {os.environ['TOKEN']}"},
    json={"name": "Ada", "role": "admin"},
    timeout=20,
)
response.raise_for_status()
print(response.json())

Three defaults that differ

Redirects. Requests follows them automatically; curl does not unless given -L. A curl command without -L that returns a 302 will, once translated, quietly return the page behind it.

Timeouts. Requests has none by default and will wait forever. Curl is the same, but a hung terminal is obvious and a hung worker process is not. Always pass timeout=.

Errors. Like curl without -f, requests does not treat a 4xx or 5xx as a failure. raise_for_status() is the equivalent of -f.

Data: string or dictionary

data= with a dictionary form-encodes it, the same as several --data-urlencode flags. data= with a string or bytes sends it untouched, like --data-binary, and then you set Content-Type yourself. Do not pass an already-serialised JSON string to json=; it would be encoded a second time and arrive as a quoted string.

requests.post(url, data={"q": "rate limit & retries"})          # form-encoded
requests.post(url, data=raw_xml.encode("utf-8"),
              headers={"Content-Type": "application/xml"})       # raw body

Common mistakes

  • Do not set Content-Type when using files=. As with curl -F, the library must generate the multipart boundary.
  • Use a Session for several calls. It reuses the connection and keeps cookies, like one curl process with a cookie jar.
  • Compressed responses are decoded automatically, so --compressed has no counterpart to write.

Related curl examples

References

These links leave api-tester.com, which is independent of the projects they point to.

FAQ

How do I convert a curl command to Python?

Map each flag to a requests argument: -H to headers, -d to data or json, -u to auth, -F to files, and the method to requests.get, post, put and so on. A converter tool can do this automatically.

What is the Python equivalent of curl -d?

requests.post(url, data=...) for form data or a raw string, and requests.post(url, json=...) when the body is JSON.

Does Python requests follow redirects like curl -L?

Yes, and by default. Pass allow_redirects=False to get the behaviour of curl without -L.