Skip to content
Free tool · Runs in your browser

curl converter

Paste a curl command, from Copy as cURL in your browser's DevTools or written by hand, and get the same request in Python requests, curl_cffi, httpx, Node.js fetch or Go, or sent from inside a real browser page with Clearcote.

Bash, cmd and PowerShell copies all work. Converted in your browser: the command, its cookies and tokens are not sent anywhere.

POST https://api.example.com/v1/search?page=2 · 9 headers · 2 cookies · json body

import requests

cookies = {
    "session": "abc123",
    "theme": "dark",
}

headers = {
    "accept": "application/json, text/plain, */*",
    "accept-language": "en-US,en;q=0.9",
    "origin": "https://www.example.com",
    "referer": "https://www.example.com/",
    "sec-ch-ua": "\"Chromium\";v=\"154\", \"Google Chrome\";v=\"154\", \"Not A(Brand\";v=\"99\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36",
}

json_data = {
    "query": "coffee",
    "filters": {
        "open": True,
    },
    "limit": 20,
}

response = requests.post(
    "https://api.example.com/v1/search?page=2",
    cookies=cookies,
    headers=headers,
    json=json_data,
    allow_redirects=False,
)

print(response.status_code)
print(response.text)

How to copy a request as cURL

Chrome and Edge: open DevTools (F12, or Cmd+Option+I on a Mac), go to the Network panel and reload the page if the list is empty. Right-click the request, then Copy, then Copy as cURL. On Windows there are two: Copy as cURL (bash) and Copy as cURL (cmd), for the Command Prompt; the converter reads both.

Firefox: in the Network panel, right-click the request and choose Copy Value, then Copy as cURL (on Windows, the POSIX or the Windows variant).

Safari: enable the Develop menu in Settings, Advanced; then in Web Inspector's Network tab, right-click the request and choose Copy as cURL.

The copy includes every cookie the browser sent and any token in the headers. To find the request that loads the data you want, filter the Network panel by Fetch/XHR and look at the responses: it is often a JSON call rather than the HTML page.

Why the same request is blocked from Python

Copy as cURL copies the request line, the headers and the body. It cannot copy what the browser did underneath: the TLS handshake it opened the connection with, the HTTP/2 settings it sent, the order its headers went out in. A server sees all of those before it reads a single header, and many sites behind a bot-management service check that they belong to the browser the User-Agent names.

ClientJA4 (TLS)HTTP
Google Chrome155.0.8059.39t13d1517h2_8daaf6152771_cb7bf5808d99HTTP/2
curl7.81.0 (OpenSSL 3.0.2)t13d3113h2_e8f1e7e78f70_ce5650b735ceHTTP/2
Python requests2.25.1 (urllib3 1.26.5, Python 3.10.12)t13d4312h1_c7886603b240_b26ce05bbdd6HTTP/1.1
Python httpx0.28.1 (Python 3.10.12)t13d1812h1_85036bcba153_d41ae481755eHTTP/1.1
Node.js fetchNode 20.18.1t13d5911h1_a33745022dd6_1f22a2ca17c4HTTP/1.1
Go net/httpgo1.18.1t13d1910h2_9dc949149365_97f8aa674fd9HTTP/2

Measured for the JA4 fingerprint checker, which has the full table and reads your own browser's. Every HTTP library in it opens with a handshake of its own, and requests and httpx do not offer HTTP/2 at all unless asked (httpx needs http2=True; requests cannot), so a request that says Chrome in its User-Agent and arrives over HTTP/1.1 with a Python handshake contradicts itself.

The headers have their own problems. A library adds headers of its own and decides their order; Python requests, for example, puts its defaults (User-Agent, Accept-Encoding, Accept, Connection) first, whatever order the dict is in. The copied sec-ch-ua Client Hints name the Chrome version you copied from, and go stale as Chrome updates. And a cookie a site set after a bot check may be tied to the client that passed it.

Two of the converter's targets deal with this. curl_cffi with impersonate="chrome" sends Chrome's handshake and HTTP/2 settings from Python, which is enough for sites that only check the connection. Clearcote goes one step further and sends the request with fetch() from a page in a real browser, after opening the site the request came from: the connection, the headers, the cookies and any JavaScript the site runs first all come from Chrome itself. Playwright's page.request is not the same thing; it shares the browser's cookies but sends the request from Node.js, with Node's handshake.

What the generated code does

  • Redirects follow curl: without -L they are not followed, which takes allow_redirects=False in requests and curl_cffi, redirect: "manual" in fetch and a CheckRedirect in Go.
  • Bodies are kept byte for byte where it matters. JSON becomes a dict or object you can edit, except in fetch when it holds an integer too large for JavaScript, where the original text is sent instead. Form bodies become a dict in Python and stay as written elsewhere.
  • Accept-Encoding is left out of the Go code: Go's client only decompresses responses itself when it set that header. In Python, a copied br or zstd needs the brotli or zstandard package to decode, and the code says so.
  • Clearcote drops the headers a page cannot set (Cookie, User-Agent, sec-* and the rest), because the browser sends its own, and sets cookies through the browser context instead.

FAQ

How do I convert a curl command to Python requests?

Paste the command into the converter above and keep the Python requests tab. Cookies from -b or a Cookie header become a cookies dict, the remaining -H lines a headers dict, a JSON body becomes json= and a form body data=. Because curl only follows redirects with -L and requests follows them by default, the code adds allow_redirects=False when the command has no -L.

How do I convert curl to JavaScript fetch?

Use the Node.js fetch tab. The body is sent exactly as curl would send it, cookies go in a cookie header (Node's fetch allows it; a browser page does not), and -m becomes AbortSignal.timeout(). A proxy or -k needs undici's dispatcher, which the code imports when the command uses them.

What does curl_cffi impersonate="chrome" do?

curl_cffi wraps curl-impersonate, a build of curl that uses BoringSSL, the TLS library Chrome uses, and is configured to open connections the way Chrome does: the same TLS ClientHello, the same HTTP/2 settings and pseudo-header order, and Chrome's default headers. impersonate="chrome" picks the newest Chrome profile your installed version ships. It changes the connection, not the client: no JavaScript runs, so a page that has to compute a token in the browser still will not get one.

Why does the request work in my browser but get a 403 from Python?

Often because the server looks at more than the headers. Before any header arrives it sees the TLS handshake, and Python requests opens with a different one from Chrome (see the table above, or the JA4 fingerprint checker). It also sees HTTP/1.1 where Chrome used HTTP/2, a header order Chrome never sends, and cookies that may have been issued to the browser after a challenge. Copying more headers does not change any of those.

Is my curl command sent anywhere?

No. The parser and the code generators run in this page; nothing you paste is uploaded or stored. That matters, because a command copied from DevTools usually carries your session cookies and tokens: treat it like a password, and remove them before you share it.

Does curl itself look like Chrome?

No. curl has its own TLS fingerprint, which depends on the TLS library it was built with; ours (curl 7.81 with OpenSSL 3.0.2) is in the table above. Copy as cURL copies the headers, not the browser's connection, so the copied command can be refused by the same site the browser just loaded.

Which curl options are converted?

The ones that change the request: -X, -H, -b, -d and its variants (--data-raw, --data-binary, --data-urlencode), --json, -F, -u, -A, -e, -L, -k, --compressed, -x, -G, -I, -T, --url, -m, --http2 and --http1.1. Output options such as -s or -o are accepted quietly; anything else is listed under the code as not converted, never dropped without a word.

Clearcote puts this into practice

An open-source Chromium with fingerprint control compiled into the engine. A drop-in for Playwright & Puppeteer.

Free for one browser with GitHub. No card.