curl Converter
Paste a curl command and read it back as Python, Node.js, Go or PHP. It reads the method, URL, headers, body, basic auth, proxy, cookies, form fields, redirect and certificate flags, and sets redirect behavior and content type explicitly where curl's defaults and the library's differ. Nothing you paste is sent anywhere and no request is made.
What that command asks for
- Method
- POST
- URL
- https://api.example.com/v1/products
- Headers
- Content-Type: application/jsonAuthorization: Bearer YOUR_TOKEN
- Body
- {"sku":"A-1","qty":2}
- Basic auth
- None
- Proxy
- http://user:pass@proxy.example.com:8080
- Follows redirects
- No (curl does not without -L)
- Verifies certificates
- Yes
Python (requests)
import requests
url = "https://api.example.com/v1/products"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_TOKEN",
}
data = "{\"sku\":\"A-1\",\"qty\":2}"
proxies = {
"http": "http://user:pass@proxy.example.com:8080",
"https": "http://user:pass@proxy.example.com:8080",
}
response = requests.request(
"POST",
url,
headers=headers,
data=data,
proxies=proxies,
allow_redirects=False,
timeout=30,
)
print(response.status_code)
print(response.text)requests follows redirects by default and curl does not, so allow_redirects is set explicitly. requests already sends Accept-Encoding and decompresses the reply, which is what --compressed asks for.
Node.js (fetch)
// fetch is built into Node 18+. undici supplies the proxy and TLS dispatchers.
import { ProxyAgent } from "undici";
const url = "https://api.example.com/v1/products";
const dispatcher = new ProxyAgent("http://user:pass@proxy.example.com:8080");
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_TOKEN",
},
body: "{\"sku\":\"A-1\",\"qty\":2}",
redirect: "manual",
dispatcher,
});
console.log(response.status);
console.log(await response.text());redirect: "manual" matches curl without -L: the 3xx comes back to you instead of being followed. fetch already requests and decompresses gzip and brotli, which is what --compressed asks for.
Go (net/http)
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
req, err := http.NewRequest("POST", "https://api.example.com/v1/products", strings.NewReader("{\"sku\":\"A-1\",\"qty\":2}"))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
proxyURL, err := url.Parse("http://user:pass@proxy.example.com:8080")
if err != nil {
panic(err)
}
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
client := &http.Client{Transport: transport}
// Go's client follows redirects by default; curl without -L does not.
client.CheckRedirect = func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode)
fmt.Println(string(out))
}net/http already asks for gzip and decompresses it transparently.
PHP (cURL)
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/v1/products',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_TOKEN',
],
CURLOPT_POSTFIELDS => '{"sku":"A-1","qty":2}',
CURLOPT_PROXY => 'http://user:pass@proxy.example.com:8080',
CURLOPT_ENCODING => '',
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $status . "\n";
echo $response;The flags this converter reads
| Flag | What it does | What it becomes |
|---|---|---|
| -X, --request | The HTTP method to use | The method argument in every language |
| -H, --header | One request header; repeatable | A headers dictionary, map or array |
| -d, --data, --data-raw | A request body, and POST unless told otherwise | The body, plus curl's default content type |
| -u, --user | HTTP basic auth credentials | auth=(), SetBasicAuth, CURLOPT_USERPWD |
| -x, --proxy | Send the request through a proxy | proxies=, ProxyAgent, http.ProxyURL, CURLOPT_PROXY |
| -k, --insecure | Skip certificate verification | verify=False, rejectUnauthorized, InsecureSkipVerify |
| --compressed | Ask for a compressed reply | Already the default in three of the four; set explicitly in PHP |
| -L, --location | Follow redirects | An explicit redirect setting, because the defaults differ |
| -b, --cookie | A cookie string to send | A Cookie header |
| -F, --form | A multipart form field or file | files=, FormData, multipart.Writer, CURLFile |
Flags that only change what curl prints in your terminal (-s, -v, -o) are dropped, and listed as dropped. Anything that would change what goes on the wire and cannot be translated is reported above the code rather than quietly ignored: reading a file with -d @file, moving a body onto the query string with -G, or loading a cookie jar from disk.
Where curl and your HTTP library disagree
A converted command that inherits the library's defaults is not the same request. Three differences matter most. curl does not follow redirects unless you pass -L, while Python requests and Go's http.Client both follow them without being asked, so the generated code always states the redirect behavior explicitly. curl labels a -d body as application/x-www-form-urlencoded when you have not set a content type, which is why the snippets add that header rather than letting the library guess. And --compressed is already the default behavior in three of the four languages here, so it changes only the PHP output.
How quoting is read
The parser works the way a shell does: single quotes protect everything inside them, double quotes let \" and \\ through, a trailing backslash or caret joins the next line, and adjacent runs concatenate into one argument. That matters because a JSON body is full of double quotes, and a command copied from a browser devtools panel arrives wrapped in whichever style that browser prefers. If a command will not parse, the usual cause is an unbalanced quote, and the tool says which kind instead of producing code from a partial read.
Sending the converted request through a proxy
If your command carries -x, the generated code carries the proxy too: a proxies dictionary in Python, an undici ProxyAgent in Node, an http.Transport in Go and CURLOPT_PROXY in PHP. Those are four spellings of the same setting, and if it is dropped in translation the request goes out from your own address. If you need to build the proxy URL itself, including percent-encoding credentials that contain @ or :, the connection string builder does that part. If you need proxies for the request itself, Node4's datacenter proxies work with all four clients.
More free tools
Request one URL from a datacenter IP and residential IPs at once, and see which of them gets blocked.
What your HTTP client gives away: header order, missing client hints, and contradictions with your user-agent.
Five questions and a straight answer, including when the answer is that you do not need proxies at all.
Your public IP address, the country it resolves to, reverse DNS and the headers your browser sends.
Derive how many proxies a job needs from volume, deadline, per-IP rate limit, concurrency and retries.
Check a path against a pasted robots.txt and see the exact rule that allowed or disallowed it.
Turn a proxy host, port and credentials into working curl, Python, Node.js and Scrapy snippets.
Network address, broadcast, usable range and host counts for any CIDR prefix length.
Decode any user-agent string into browser, engine, OS and device, including bots and HTTP libraries.
Need proxies behind your code?
Node4 sells datacenter, rotating and residential proxies over HTTP(S), with SOCKS5 on paid plans. Datacenter and rotating exits are in the US, Italy and Spain; residential exits cover 170+ countries. Start with free proxies, no card required.