Free developer tools

curl Converter

Paste a curl command and read it back as Python, Node.js, Go or PHP. The flags are translated properly, including the places where curl's defaults and your HTTP library's defaults disagree. 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)

python
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)

javascript
// 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)

go
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
<?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

FlagWhat it doesWhat it becomes
-X, --requestThe HTTP method to useThe method argument in every language
-H, --headerOne request header; repeatableA headers dictionary, map or array
-d, --data, --data-rawA request body, and POST unless told otherwiseThe body, plus curl's default content type
-u, --userHTTP basic auth credentialsauth=(), SetBasicAuth, CURLOPT_USERPWD
-x, --proxySend the request through a proxyproxies=, ProxyAgent, http.ProxyURL, CURLOPT_PROXY
-k, --insecureSkip certificate verificationverify=False, rejectUnauthorized, InsecureSkipVerify
--compressedAsk for a compressed replyAlready the default in three of the four; set explicitly in PHP
-L, --locationFollow redirectsAn explicit redirect setting, because the defaults differ
-b, --cookieA cookie string to sendA Cookie header
-F, --formA multipart form field or filefiles=, 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 bite constantly. 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.

Quoting is where pasted commands go wrong

The parser here 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 rather than producing plausible but wrong code.

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 different spellings of the same idea, and getting them wrong is the usual reason a scraper that worked in the shell leaks its real address in production. If you need to build the proxy URL itself, including percent-encoding credentials that contain @ or :, the connection string builder does that part, and Node4's datacenter proxies are what we run behind ours.

The infrastructure behind these tools

Need proxies behind your code?

Node4 sells datacenter, residential and rotating proxies with HTTP(S) and SOCKS5 support in 170 countries. Start with free proxies, no card required.