curl commands into Python scripts
Python is where many API calls end up: automation scripts, data pipelines, notebooks and tests. Starting from a curl command is common — copied from API documentation, from Postman, or from the browser’s network panel with “Copy as cURL”. This converter writes requests code that sends the same bytes as curl, using the library’s keyword arguments wherever that is safe, so the result reads like code a Python developer would write by hand.
Keyword arguments generated
- Method:
requests.get,.post,.put,.patch,.delete,.heador.options; other verbs userequests.request("PURGE", ...). - params: a simple query string is split into a
paramsdict. If any part needs percent-encoding or a name repeats, the URL is kept as written. - headers: a dict of your
-Hvalues; duplicates are merged. - cookies: a
-b 'session=abc; theme=dark'cookie string becomes acookiesdict when every pair is a plain token. - json: a JSON object or array declared as JSON is written as a Python literal (
True,False,None) and passed asjson=json_data; the redundant Content-Type that curl implied is dropped because requests sets it. - data: url-encoded bodies become a dict when requests would encode it back to exactly the same bytes; anything else is passed as the original string. Non-ASCII text is sent as UTF-8 via
.encode(), matching curl. - files:
-Ffields become afilesmapping, withopen(path, "rb")for uploads, a file name and an optional content type; text fields use(None, value). Repeated field names switch to a list of tuples. - auth:
-u user:passbecomesauth=("user", "pass"). - timeout:
-m 30givestimeout=30; adding--connect-timeout 5givestimeout=(5, 30). - verify=False for
-k, and proxies for-x, with-Ucredentials embedded in the proxy URL.
Redirects: a classic gotcha
requests follows redirects automatically for every method except HEAD, but curl only does so with -L. The generated code therefore adds allow_redirects=False when the command lacks -L, and allow_redirects=True for a HEAD request that has it. Delete the argument if you actually want the redirect followed. --compressed needs no code, since requests always accepts gzip and deflate; an info note confirms that.
Output style and what is not converted
The code is laid out the way Black and Ruff format it — double quotes, one argument per line with trailing commas once a call gets long — so running a formatter changes nothing. It prints response.status_code and response.text (the headers for HEAD).
Options that save output or tune curl itself (-o, --retry, -w) are reported as not converted. --digest, --ntlm and --negotiate fall back to Basic auth with a warning; requests needs HTTPDigestAuth or an extra package for those. As with every tool here, nothing you paste is uploaded. To clean up the rest of your script, try the Python formatter.
Examples
JSON order with cookies and timeouts
Produces params, cookies and json_data dicts, Python True and None in the body, and a (5, 30) timeout tuple.
curl -X POST 'https://api.example.com/v1/orders?expand=items' \
-H 'Content-Type: application/json' \
-b 'session=abc123; theme=dark' \
--connect-timeout 5 -m 30 \
-d '{"customer":"cus_42","gift":true,"note":null}'import requests
params = {
"expand": "items",
}
headers = {
"Content-Type": "application/json",
}
cookies = {
"session": "abc123",
"theme": "dark",
}
json_data = {
"customer": "cus_42",
"gift": True,
"note": None,
}
response = requests.post(
"https://api.example.com/v1/orders",
params=params,
headers=headers,
cookies=cookies,
json=json_data,
timeout=(5, 30),
allow_redirects=False,
)
print(response.status_code)
print(response.text)
Multipart upload with basic auth
The file field opens q3-report.pdf in binary mode with its content type, the text field uses (None, “finance”), and -u becomes auth.
curl https://files.example.com/upload \
-u reports:Tr0ub4dor \
-F '[email protected];type=application/pdf' \
-F 'folder=finance'import requests
files = {
"file": ("q3-report.pdf", open("q3-report.pdf", "rb"), "application/pdf"),
"folder": (None, "finance"),
}
response = requests.post(
"https://files.example.com/upload",
auth=("reports", "Tr0ub4dor"),
files=files,
allow_redirects=False,
)
print(response.status_code)
print(response.text)
Form post that follows redirects
The url-encoded body becomes a data dict because requests re-encodes it to the same bytes, and no allow_redirects argument is needed with -L.
curl -L https://shop.example.com/cart -d 'sku=KB-104' -d 'qty=2' -d 'note=gift+wrap'import requests
data = {
"sku": "KB-104",
"qty": "2",
"note": "gift wrap",
}
response = requests.post(
"https://shop.example.com/cart",
data=data,
)
print(response.status_code)
print(response.text)
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
--digest authentication is not converted; HTTP Basic authentication is used instead | The command uses an authentication scheme other than Basic. | Replace auth=(…) with requests.auth.HTTPDigestAuth(user, password) in the generated code. |
The file "report.pdf" for form field "file" is not available here; the code uploads it from disk or contains a placeholder | A warning for -F file uploads: the browser cannot see your files, so the code opens the path at run time. | Make sure the path is correct relative to where the script runs. |
No password given with -u, so curl would prompt for one; an empty password is used | The command has -u user without :password. | Add the password to the auth tuple, ideally read from an environment variable. |
Unknown option --foo is ignored | The command contains an option curl itself does not recognise, or a typo. | Check the option name; unknown flags are not converted. |
Frequently asked questions
Why is json= used instead of data=?
When the body is JSON and the content type says so, json= is the idiomatic requests way and sets the header for you. Bodies that are not clean JSON stay as data=.
Why does the code include allow_redirects=False?
Because your curl command does not follow redirects without -L. Remove it if you want requests’ default behaviour.
Can I use the output with httpx?
Mostly. httpx accepts the same params, headers, cookies, json, data and files arguments, but redirect and proxy arguments are named differently.
Is the command sent to a server for conversion?
No. It is parsed and translated by code running in your browser, so API keys in it stay private.