Free developer tools

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)

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 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.

The infrastructure behind these tools

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.