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