From Copy as cURL to Production Code, Without the Copied Secrets
The workflow is universal. A request works in the browser. You need it to work in your code. So you open DevTools, right-click the request, choose Copy as cURL, and now you have a 1,400-character command with nineteen headers in it.
Translating that by hand is dull and error-prone, and the thing that usually goes wrong is not the translation — it is what comes along with it.
Read the command before you convert it
A browser’s Copy as cURL is a faithful reproduction of what the browser sent.
That includes every header it attached automatically: the full Cookie jar for
that origin, Authorization, CSRF tokens, sec-ch-ua client hints, Referer,
and a User-Agent string identifying your exact browser build.
Most of that is noise your code does not need. Some of it is a live credential.
The flags that actually carry meaning:
| Flag | Means |
|---|---|
-X, --request | HTTP method |
-H, --header | A header; repeats |
-d, --data, --data-raw | Request body; repeated flags join with & |
-u, --user | Basic auth as user:password |
-G, --get | Send the data as a query string instead of a body |
-b, --cookie | Cookie header |
-L, --location | Follow redirects |
-k, --insecure | Skip TLS verification |
-F, --form | Multipart form field; @ prefixes a file |
Two behaviours catch people out. First, -d implies POST: a command with a
body and no -X is a POST, so a “GET” you wrote with -d was never a GET.
Second, -G moves the data into the query string, which is how you send
parameters with -d syntax on an actual GET.
Mapping onto each language
The translation is mostly mechanical, but each ecosystem has one thing it wants done its own way.
JavaScript fetch. Headers go in a headers object, the body goes in
body as a string — fetch will not serialise an object for you, so a JSON
body needs JSON.stringify. And fetch does not reject on a 4xx or 5xx; it
only rejects on a network failure. Checking response.ok is not optional:
const response = await fetch(url, { method: 'POST', headers, body });
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
Axios. Takes data rather than body, serialises a plain object to JSON
on its own, sets Content-Type for you, throws on non-2xx by default, and
follows redirects without being asked. Passing a JSON.stringifyed string here
is a common and harmless-looking mistake that produces a double-encoded body.
Python requests. The distinction that matters is json= versus data=.
json= serialises the object and sets the content type; data= sends a form
encoding. Booleans and nulls become True and None, which is why a body
copied verbatim from a cURL command will not parse as Python.
Python httpx. Near-identical to requests in spirit, with async support
and one behavioural difference worth knowing: it does not follow redirects
by default, so -L maps to an explicit follow_redirects=True.
Go net/http. The most verbose. The body must be an io.Reader, so a
string body gets wrapped in strings.NewReader. Headers are set individually
with req.Header.Set, basic auth has a dedicated req.SetBasicAuth, and the
response body must be closed — defer resp.Body.Close() — or you leak
connections.
The part that ends up in a commit
This is the failure mode worth designing against. The copied command contains a bearer token. The generated snippet contains it too. The snippet gets pasted into a file, the file gets committed, and now a credential is in the repository history — where deleting it in a later commit does not remove it.
Three defences, in order of how much they help:
- Strip what you do not need. Client hints,
RefererandUser-Agentare almost never required. Fewer headers means fewer places for a secret to hide. - Mask before sharing. If the snippet is going into a ticket, a pull
request or a message, replace the credential first. Keeping the scheme
visible —
Bearer …— preserves the diagnostic information without the secret. - Read secrets from the environment.
os.environ["API_TOKEN"],process.env.API_TOKEN,os.Getenv("API_TOKEN"). A generated snippet is a starting point, not the final shape of the code.
If the token in your command is a JWT and you want to know what it grants before deciding how carefully to handle it, decoding it locally with the JWT Decoder will tell you its scopes and expiry — covered in more detail in Debugging JWTs Client-Side.
Doing it without a server
Most online cURL converters post your command to a backend. Given that the
command routinely contains a session cookie and an Authorization header, that
is the one category of tool where the convenience is least worth it.
The cURL to Code converter parses and generates entirely in the browser, with a toggle that masks tokens and passwords in the output before you copy it. Same result, no upload, and nothing to rotate afterwards.