API documentation, bug reports, and the network panel of every browser give you a request as a curl command. To use that request from a program you convert it, and the code that a converter writes usually looks right. Often it is not quite the same request. curl does not follow redirects, but Python's requests does. curl adds a Content-Type header when you send data with -d, and requests does not. curl removes the line breaks from a file given to -d, and it sends accented text as UTF-8. These differences are easy to miss, and they turn into a 401, a missing field, or a redirect loop that only happens in the program.
This page converts a curl command to Python or JavaScript and tells you where the result and curl part ways. While it was built, the code it writes was run against the same local server as real curl, for 160 different commands, and the requests that arrived were compared byte by byte. Where a library cannot send what curl sends, the page says so beside the code. Everything is done in your browser, and the code behind the page makes no network requests, so a token in your command is not uploaded.
How to use the cURL to Python Converter: requests, httpx, urllib, fetch
- Paste the commandPaste a curl command, on one line or several. A command copied from the browser's developer tools works, in the bash form and in the Windows cmd form, and so does one from documentation.
- Choose the languagePick Python requests, httpx, or urllib, or JavaScript fetch. The number beside each name is how many differences from curl that version has.
- Read the differencesBelow the code, the page lists what Python or JavaScript cannot do exactly as curl does, such as a header sent twice or a lower-case method. If the list is empty, the code sends the request shown under it.
- Check what was understoodThe list of options shows each one in your command and whether it was converted, not needed (like -s), or not converted (like --retry), so nothing is dropped without a word.
- Copy the codeCopy it or download it. If the command holds a token or a password, switch on the setting that reads it from an environment variable first.
Where a converted command goes wrong
A curl command is more than a method, a URL, and some headers. curl has defaults that Python and JavaScript do not share, and a conversion that ignores them changes the request. These are the ones this page handles, and each was found by comparing real curl output with the code.
- Redirects. According to the curl manual, curl does not follow redirects unless you add -L. The Requests documentation says it follows them for every method except HEAD. A command without -L therefore needs allow_redirects=False in requests. httpx, on the other hand, does not follow redirects by default.
- The Content-Type of data. curl sends application/x-www-form-urlencoded with -d unless you set another type. Passing a string as data to requests sets no Content-Type at all, so the code writes the header out.
- Accented text. A body with an é in it has to be sent as UTF-8 bytes. A Python string passed as the body is encoded as Latin-1 by the standard library and fails on characters outside it, so the code encodes it explicitly.
- Files in a body. curl -d @file strips the carriage returns and newlines of the file, while --data-binary @file sends it untouched. The code reads the file the same way each time.
- The type of an uploaded file. curl -F guesses the type of a file part from its extension: .html, .png, .jpg, .gif, .svg, .pdf, .xml, and .txt have names, and everything else is application/octet-stream. requests sends no type unless you give one, so the code writes it.
- Basic login with accents. requests encodes the login of a Basic authentication as Latin-1, and curl uses UTF-8. For a login with accented letters the code writes the Authorization header itself.
- Default headers. curl -H 'Accept:' tells curl not to send its own Accept header. In requests, a header set to None removes it. The page writes that, and says when a library cannot remove one.
JSON bodies: json= or data=
The json= argument of requests is the tidy way to send JSON, and the Requests documentation says it sets the Content-Type for you. It also writes the JSON itself: with a space after each comma and colon, escaping every non-ASCII character, and turning 1.50 into 1.5. If your command sent {"a":1} and the code sends {"a": 1}, the JSON is the same but the bytes are not, and a server that checks a signature of the body will refuse it.
So by default the page writes json= only when Python would write back exactly the text in your command, which it checks character by character. Otherwise it sends the text as it is with data= and the Content-Type header. A body with the same key twice, a number such as 1e3 or 12345678901234567890.5, or a lone surrogate cannot be written as a Python value without changing it, so those are always sent as text. You can choose to always use json= or always send text in the settings.
The commands your browser gives you
The browser's “Copy as cURL” gives different text depending on the system. On Linux and macOS it is bash text, with single quotes, a backslash at the end of each line, and $'...' for text with special characters. On Windows there is a “cmd” form, in which every special character is escaped with a caret, the quotes of the body are written as ^\^" and a line ends with ^. A converter that reads only the bash form turns the cmd form into nonsense.
This page reads both. For the cmd form it removes the carets the way cmd does, keeping a caret that sits inside double quotes, and then splits the line with the rules Windows uses for backslashes and quotes. A PowerShell command with backticks at the line ends is read too. If the text turns out to be an Invoke-WebRequest command, the page says that it is not curl.
Choosing between requests, httpx, urllib, and fetch
requests is the most widely used and the one most people mean by curl to Python. httpx has almost the same interface, supports HTTP/2 when installed with its extra, and has an async client. According to its documentation it does not follow redirects by default, unlike requests, and it has timeouts by default where requests has none.
urllib is part of Python, so it needs no installation, which suits a script that has to run anywhere. It is lower level: it raises an error for 4xx and 5xx answers, follows redirects unless you give it a handler, and has no help for uploading a form with files, so the page writes no urllib code for -F. fetch is built into Node.js and browsers. A browser does not let a page set some headers, such as Cookie and Host. MDN lists them, and the page tells you when your command sets one.
Keeping secrets out of the code
A curl command copied from documentation or from a network panel often holds a token, a cookie, or a password, and code with a secret in it ends up in version control. The setting to keep secrets out of the code replaces an Authorization header, headers with names such as X-API-Key and Token, a Cookie header, a Basic login password, and query parameters named key, token, or password with a read from an environment variable. The page lists the names to set.
A command that already uses a shell variable, such as -H "Authorization: Bearer $API_TOKEN", gets the same treatment automatically, because the shell would have filled in that variable and the code should read it the same way.
How the code was checked
The code was tested against real curl 8.22.0, not against what the manual says. A local server recorded every request it received. The 160 commands, which cover methods, headers, every kind of body, uploads, cookies, redirects, odd addresses, quoting styles, and commands from the browser's cmd form, were run through curl first. The code written for each was then run against the same server, and the method, the address, the headers, and the body of every request were compared.
For the requests output, 152 of the 160 commands produced exactly what curl produced. The other 8 involve something requests cannot do or that curl itself refuses, such as a lower-case method or the same header twice, and the page announces each of them. Every difference found in the tests is announced in the same way, and the tests also check that none of them is silent. The results for httpx, urllib, and fetch were similar and are announced the same way. This is a statement about those 160 commands and curl 8.22.0, not a promise about every possible command.
Limits and accuracy
- The command is not run, and nothing is requested. The page only reads the text of the command and writes code, so it cannot tell what a server will answer.
- Only the first request of a command is converted. A command with --next, several addresses, or curl's URL ranges such as [1-5] and {a,b} is converted for one request, and the page says so.
- Options that read from places this page cannot see are not converted: -K config files, -H @file, cookie files, and data read from standard input.
- A few things cannot be matched in a library: a header sent twice (requests, urllib), a method in lower case (requests, httpx), a path left as written with --path-as-is, and a body with a GET in fetch. The page lists each as a difference.
- Timeouts differ in meaning. curl -m limits the whole transfer, and the timeout of a Python library limits each wait, so a slow download can take longer than curl would allow.
- Retries (--retry), client certificate formats other than a certificate and a key file, NTLM, Negotiate, and proxy logins are not converted.
- Results were checked with curl 8.22.0, requests 2.34, and httpx 0.28 with Python 3.14, and Node.js 24. Other versions may behave differently.
Frequently asked questions
How do I convert a curl command to Python requests?
Paste the command into the box and choose Python requests. The code appears below it. The page also lists any difference between what the code sends and what curl sends, so you can check it before you run it.
Why does my Python code behave differently from the curl command?
The usual reasons are redirects, which curl does not follow without -L and requests does, a missing Content-Type header for data, accented text sent in the wrong encoding, and default headers such as User-Agent that differ. The converter handles these and lists what it cannot match.
What is the difference between -d, --data-raw, and --data-binary?
All three send their text as the body. With -d, a value that starts with @ is read from a file and its line breaks are removed. --data-binary reads the file but keeps it exactly. --data-raw never treats @ as a file. The page follows each of them.
How do I convert Chrome's Copy as cURL (cmd) on Windows?
Paste it as it is. The cmd form escapes special characters with carets and writes quotes in the body as ^\^". The page removes the carets the way cmd does and then reads the arguments with the Windows rules, so the body comes out as the JSON you sent.
Does a Basic login with -u become auth in requests?
Yes, -u user:password becomes auth=("user", "password"), which requests turns into a Basic Authorization header. If the login has accented letters, the page writes the header itself, because requests would encode the login differently from curl.
Can I keep my API token out of the code?
Yes. Switch on the setting that reads secrets from environment variables. The token, key, cookie, or password is replaced with a read of a variable, and the page tells you which variables to set. A command that already uses a shell variable such as $API_TOKEN gets the same treatment.
Is my curl command uploaded anywhere?
No. The command is read and converted in your browser, and the code behind the page makes no network requests, so a token or a password in it stays on your device. You can check this in the Network panel of your browser's developer tools.
Research and references
This page was written and checked against the sources below.

