Convert curl to Python requests and JavaScript fetch

🔁 12K+ searches/mo💰 CPC: $1.5–4⏱️ 7 min read

API docs speak curl. Your code speaks Python or JavaScript. Between them sits a translation layer that's mostly mechanical — until quoting, implied methods, and auth headers get involved. Here's the complete mapping, what breaks naive converters, and how to check a converter before you feed it a real token.

Advertisement

The mapping, flag by flag

Almost everything in curl has a direct equivalent. The trick is knowing which curl flags are load-bearing:

curlPython requestsJavaScript fetchNode axios
-X POSTrequests.post()method: "POST"axios.post()
-H 'Key: value'headers={"Key": "value"}headers: {"Key": "value"}headers: {"Key": "value"}
-d '{"a":1}'json={"a": 1}body: JSON.stringify({a: 1}){a: 1} as 2nd arg
-d 'a=1&b=2'data={"a": "1", "b": "2"}new URLSearchParams()new URLSearchParams()
-F '[email protected]'files={"f": open(...)}FormData.append()FormData
-u user:passauth=("user", "pass")Authorization: Basic btoa(...)auth: {username, password}
-G -d 'q=x'params={"q": "x"}?q=x in the URLparams: {"q": "x"}
-m 5timeout=5AbortSignal.timeout(5000)timeout: 5000
-kverify=False— (browsers refuse)custom https.Agent
-b 'k=v'becomes a Cookie header everywhere
-A 'Bot/1.0'becomes a User-Agent header everywhere

Skip the manual translation

Paste a curl command and get all three targets at once — parsed with real shell grammar (tree-sitter), entirely in your browser.

Open the curl Converter →

A full conversion, step by step

Start with a command that has the three parts worth converting — method, headers, JSON body:

curl -X POST https://api.example.com/v1/users \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer tok_123' \
  -d '{"name":"Ada","role":"admin"}'

The Python version:

import requests

url = "https://api.example.com/v1/users"
headers = {
    "Authorization": "Bearer tok_123",
}
json_data = {
    "name": "Ada",
    "role": "admin",
}

response = requests.post(url, headers=headers, json=json_data)
print(response.status_code)
print(response.text)

Notice the Content-Type header is gone. When you pass json=, requests serializes the body and sets application/json itself, so carrying the header over would be redundant. A converter that keeps it isn't wrong — just noisy. The fetch version does the same dance with body: JSON.stringify(...), and axios passes the object as the second argument and handles serialization internally.

Three curl behaviors every converter must copy

1. -d implies POST

Most commands you'll paste have no -X at all. That's fine: curl turns any -d, --data-raw, --data-binary, or -F into a POST automatically. A converter that emits a GET because "no method was specified" produces code that looks right and hits the wrong endpoint — or the same endpoint with a 405.

2. -G moves data to the query string

curl -G -d 'q=hi' https://x.io/s is a GET with ?q=hi. With multiple -d flags, they join with &. Converters that treat -G as decoration send the data as a body on a GET, which most servers silently drop.

3. Shell quoting is the actual hard part

A curl command isn't a list of flags — it's a line of shell. The JSON body '{"name":"Ada L"}' works in single quotes; swap to double quotes and $ and backticks suddenly interpolate. Copy a command out of a doc that used "It's" inside double quotes, or adjacent segments like 'Authorization: Bearer '"$TOKEN", and regex-based converters mis-tokenize. That's why serious tools (including the MIT-licensed curlconverter project) parse the command with a real bash grammar — tree-sitter — instead of splitting on spaces.

Handling auth without leaking it

Bearer headers convert cleanly: they're just headers. Basic auth (-u user:pass) is worth converting to the library's native form — auth= in requests, auth: in axios — because the base64 dance in fetch ("Basic " + btoa("user:pass")) is easy to get subtly wrong.

Before pasting anything with a real key anywhere: check whether the converter runs client-side (our converter parses in your browser with WebAssembly — verify it in your network tab), or scrub the token to CHANGEME and re-add it locally. A doc command with a production bearer token is a credential leak waiting for a paste.

The output nobody talks about: status codes and errors

curl prints the body and exits 0 unless the connection fails — HTTP 4xx and 5xx look like success on the terminal. Your converted code should do better: check response.status_code in requests (response.raise_for_status() is the one-liner), res.ok in fetch (which, like curl, doesn't throw on 4xx/5xx by default), or axios's built-in error throw. The JSON formatter next door is handy for eyeballing the error payloads you get back while debugging.

Frequently Asked Questions

How do I convert a curl command to Python automatically?

Paste the full command into a converter that parses the shell syntax — our curl converter runs in your browser and emits a complete requests script. Watch three things in the output: that -H headers landed in a headers dict, that a JSON -d body became json= (not data= with a string), and that -u auth became auth=() instead of a header you have to maintain by hand.

What's the difference between data= and json= in requests?

data= sends whatever you give it — pass a dict and it's form-encoded, pass a string and it goes raw. json= serializes the object to JSON and sets the Content-Type header for you. The curl equivalent of json= is -d with a JSON body plus an explicit 'Content-Type: application/json' header, which is why good converters drop that header from the dict when they use json=.

Why does my converted code have a literal $TOKEN in it?

Because a static converter can't know what your shell variables contain — it never runs your shell. The safe move is to keep the reference and flag it. In Python, read it back with os.environ['TOKEN']; in JavaScript, process.env.TOKEN (Node) or inject it at build time in the browser. Never paste a production token into an online converter you don't trust.

How do I convert curl -F file uploads?

Each -F 'field=@file' becomes a multipart part. In Python requests, use files={'field': open('file', 'rb')}; in browser JavaScript, build FormData and append a File (usually from a file input); in Node axios, pass FormData the same way. Ordinary -F 'key=value' fields ride along as form fields, not files.

Is it safe to paste curl commands with API keys into an online converter?

Only into converters that run entirely client-side — and verify the claim: open dev tools, watch the network tab, and confirm nothing is sent while you paste. Server-side converters see your tokens. Scrubbing the command first (replace the real key with CHANGEME) costs five seconds and removes the risk entirely.

Related Tools