When integrating third-party APIs (such as Stripe, OpenAI, AWS, or GitHub), documentation examples and Chrome DevTools network exports are almost always provided as cURL commands.
While cURL is the undisputed Swiss Army knife for CLI network probing, manually converting complex cURL flags—involving Basic Auth headers, multi-part form payloads, query parameters, and custom cookies—into robust application code is tedious and prone to subtle bugs.
This guide explores the anatomy of cURL flags, provides idiomatic translations across TypeScript/Node.js, Python, and Go, and highlights critical production conversion traps.
1. Common cURL Flags Decoded
curl -X POST "https://api.example.com/v1/orders?priority=high" \
-H "Authorization: Bearer sk_live_98a7sd8f7as" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
--data-raw '{"customerId": "usr_102", "amount": 4900, "currency": "usd"}' \
--compressed \
--max-time 10
| Flag | Long Flag | Purpose | Client Code Equivalent |
|---|---|---|---|
-X |
--request |
Specifies the HTTP method (POST, PUT, DELETE) |
method: 'POST' |
-H |
--header |
Adds an HTTP request header | headers: { 'Authorization': '...' } |
-d |
--data / --data-raw |
Sends raw HTTP body (defaults method to POST) | body: JSON.stringify(...) / data={...} |
-F |
--form |
Sends multipart/form-data for file uploads |
FormData instance / files={...} |
-u |
--user |
Sets HTTP Basic Authentication (user:password) |
Base64-encoded Authorization: Basic ... |
-b |
--cookie |
Passes raw Cookie string or cookie jar file | Cookie header string / session jar |
-L |
--location |
Follows 301/302 HTTP redirects | redirect: 'follow' |
-k |
--insecure |
Bypasses SSL certificate verification | Custom HTTPS Agent with rejectUnauthorized: false |
--max-time |
--max-time |
Hard timeout limit in seconds | AbortController.timeout(10000) / timeout=10 |
Tip: Automatically convert any cURL snippet into 10+ programming languages with the DevFlow cURL Converter.
2. Converting to Modern JavaScript / TypeScript (Native Fetch)
Modern Node.js (v18+) and all browser environments feature native fetch. Notice the inclusion of proper error handling (response.ok) and cancellation timeouts:
interface OrderRequest {
customerId: string;
amount: number;
currency: string;
}
interface OrderResponse {
orderId: string;
status: string;
}
async function createOrder(data: OrderRequest): Promise<OrderResponse> {
const url = new URL('https://api.example.com/v1/orders');
url.searchParams.set('priority', 'high');
// Abort request after 10 seconds timeout
const signal = AbortSignal.timeout(10_000);
const response = await fetch(url.toString(), {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(data),
signal,
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`HTTP error ${response.status}: ${errorBody}`);
}
return (await response.json()) as OrderResponse;
}
3. Converting to Python (Requests & Modern Async HTTPX)
Standard Synchronous Python with requests
import os
import requests
def create_order(customer_id: str, amount: int, currency: str = "usd") -> dict:
url = "https://api.example.com/v1/orders"
params = {"priority": "high"}
headers = {
"Authorization": f"Bearer {os.environ['API_SECRET_KEY']}",
"Accept": "application/json",
}
payload = {
"customerId": customer_id,
"amount": amount,
"currency": currency,
}
# Pass json= to automatically serialize and set Content-Type: application/json
response = requests.post(
url,
params=params,
headers=headers,
json=payload,
timeout=10.0,
)
response.raise_for_status()
return response.json()
Modern Async Python with httpx (FastAPI / Asyncio)
import httpx
async def async_create_order(payload: dict) -> dict:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.post(
"https://api.example.com/v1/orders",
params={"priority": "high"},
headers={"Authorization": f"Bearer {os.environ['API_SECRET_KEY']}"},
json=payload,
)
response.raise_for_status()
return response.json()
4. Converting to Production Idiomatic Go (net/http)
Go's standard library requires explicit context cancellation, buffer encoding, and ensuring the response body is always closed to prevent connection pool leaks:
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type OrderPayload struct {
CustomerID string `json:"customerId"`
Amount int `json:"amount"`
Currency string `json:"currency"`
}
func CreateOrder(ctx context.Context, payload OrderPayload) ([]byte, error) {
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to marshal JSON: %w", err)
}
reqURL := "https://api.example.com/v1/orders?priority=high"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+getApiKey())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // Mandatory to prevent socket leak
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read body: %w", err)
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
}
return body, nil
}
5. Common Conversion Traps & Gotchas
1. Stripping Pseudo-Headers and Browser-Only Headers
When copying cURL from Chrome DevTools, the command includes dozens of browser-specific headers:
sec-ch-ua,sec-fetch-mode,sec-fetch-dest,:authority,:path- Fix: Strip all pseudo-headers (starting with
:) andsec-*headers when converting to server-side code to avoid triggering bot detection filters or payload bloating. Inspect and clean headers using our HTTP Headers Analyzer.
2. Multi-Part Boundary Duplication
When using -F (form data), cURL automatically computes the multipart/form-data; boundary=----... header.
- Trap: Manually writing
headers: { 'Content-Type': 'multipart/form-data' }infetchoraxiosoverrides the automatic boundary generation, resulting in broken payload parsing on the server. - Rule: Omit the
Content-Typeheader when passing aFormDataobject; let the HTTP client set the boundary dynamically.
3. Automatic Gzip / Brotli Decompression
cURL requires --compressed to automatically negotiate and decompress gzip or br responses. Most high-level HTTP libraries (Fetch, Axios, Requests) handle decompression transparently.
Frequently Asked Questions
What is the difference between cURL --data and --data-raw?
--data treats leading @ symbols as a file path to read from disk (-d @payload.json), whereas --data-raw disables @ file interpolation, treating the input strictly as literal string bytes.
Why do some converted API requests fail with 401 Unauthorized?
Many APIs require Basic Authentication using base64 encoding (user:password). While cURL -u user:pass handles base64 encoding behind the scenes, raw fetch requests require manual encoding: 'Authorization': 'Basic ' + btoa('user:pass') or Buffer.from('user:pass').toString('base64').
How can I inspect outgoing converted requests?
You can verify request headers, payloads, and response times in real time using the DevFlow API Request Builder or inspect network streams via HAR Analyzer.