cURL to Python requests Converter

Paste a curl command and get idiomatic Python using the requests library, ready to run or drop into a script. The conversion is done in your browser, so the credentials in the command stay with you.

cURL → Python requests

Input

Settings

History

Load from URL

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, .head or .options; other verbs use requests.request("PURGE", ...).
  • params: a simple query string is split into a params dict. If any part needs percent-encoding or a name repeats, the URL is kept as written.
  • headers: a dict of your -H values; duplicates are merged.
  • cookies: a -b 'session=abc; theme=dark' cookie string becomes a cookies dict 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 as json=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: -F fields become a files mapping, with open(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:pass becomes auth=("user", "pass").
  • timeout: -m 30 gives timeout=30; adding --connect-timeout 5 gives timeout=(5, 30).
  • verify=False for -k, and proxies for -x, with -U credentials 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.

Input
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}'
Output
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)
Open this example in the tool

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.

Input
curl https://files.example.com/upload \
  -u reports:Tr0ub4dor \
  -F 'file=@q3-report.pdf;type=application/pdf' \
  -F 'folder=finance'
Output
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)
Open this example in the tool

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.

Input
curl -L https://shop.example.com/cart -d 'sku=KB-104' -d 'qty=2' -d 'note=gift+wrap'
Output
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)
Open this example in the tool

Common errors and how to fix them

ErrorCauseFix
--digest authentication is not converted; HTTP Basic authentication is used insteadThe 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 placeholderA 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 usedThe command has -u user without :password.Add the password to the auth tuple, ideally read from an environment variable.
Unknown option --foo is ignoredThe 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.

Related tools