You copied it as cURL. Now you need it in Python.

It starts in the Network tab. The endpoint is misbehaving, you right-click, 'Copy as cURL,' paste it into a terminal, and it works — status 200, body intact, mystery 500 nowhere in sight. Now you know the request is fine and the bug is in your code. The natural next move is to turn that working request into your language, and the natural way to do that is to start typing headers one at a time like it's 2013.

The endpoint you copied has eleven headers, a bearer token, a JSON body, and two cookies. Transcribing it by hand takes five minutes and introduces an average of one typo — a missing comma, a header name that lost its capitalization, a body that got string-mangled. Then the bug you're hunting hides inside your transcription and you spend the afternoon debugging the transcription instead.

The curl Converter above exists for exactly this moment. Paste the command, get working code — JavaScript fetch, Python requests, axios, Go, PHP — with headers, bodies, auth, and cookies mapped to each language's idiom. It runs in your browser, which matters here more than on almost any other tool on this site: copied curl commands contain live credentials, and the architecture guarantees yours go nowhere. The rest of this page covers how the parsing works, a full command converted three ways, and the parts of curl that don't survive translation.

From pasted command to working code

  1. Paste the curl command

    Straight from DevTools 'Copy as cURL,' from documentation, or from a teammate's message. Multi-line commands with backslash continuations are handled — the parser rejoins them before reading flags.

  2. Watch it parse live

    The tool shows what it understood: method, URL, every header, the body, auth, cookies. This intermediate view is your chance to catch shell variables and placeholders before they fossilize into generated code.

  3. Pick a target language

    Tabs for fetch, Python requests, axios, Go, and PHP. The parse is shared — switching languages re-emits, it doesn't re-parse, so comparing dialects of the same request is instant.

  4. Review the generated code

    Bodies convert to language-native forms: -d JSON becomes a JSON.stringify() body in fetch and a json= parameter in requests, which sets its own Content-Type. Auth becomes auth= or headers entries; cookies become cookie jars or headers as each language prefers.

  5. Copy and adapt

    Copy the snippet into your project. The converter's job is a correct starting point — parameterize the URL and pull secrets into environment variables before this code reaches a repository.

What the parser reads, flag by flag

Converting curl correctly means implementing the parts of its command language that appear in real usage. The parser treats a curl command as a URL plus a stream of flags, and each flag has a defined translation:

curl flagMeaningBecomes in generated code
-X POSTExplicit methodmethod: 'POST' / requests.post(...)
-H 'K: V'Header (repeatable)headers object / dict entries
-d '{...}'Request body (implies POST)body / json= parameter
--data-urlencodeURL-encoded body fielddata= with encoded params
-u user:passBasic authauth= tuple / Authorization header
-b 'a=1; b=2'Cookiescookies dict or Cookie header
-kSkip TLS verificationverify=False / NODE_TLS_REJECT_UNAUTHORIZED (flagged)
-LFollow redirectsallow_redirects=True / default browser behavior
-GMove -d data to query stringparams= dictionary
-A 'UA'User-AgentUser-Agent header entry
--compressedRequest compressed encodingsAccept-Encoding header (handled natively by libraries)
The twelve flags that cover the overwhelming majority of copied commands.

Two parsing details do most of the quiet work. First, shell quoting: single-quoted strings pass through literally, double-quoted strings may have contained shell expansions before you copied them, and the parser handles both forms the way a POSIX shell would. That's also why commands copied from docs with $PLACEHOLDER variables don't fully convert — the browser never knew what the shell substituted, and the placeholder survives as literal text. Second, flag implications: curl switches its default method to POST whenever a body is supplied, and the converter encodes those implicit behaviors explicitly, because generated code should never rely on defaults the target library doesn't share. Headers arriving as an array of strings become a JSON object — the one-click 'headers to JSON' mapping — with duplicate headers preserved where the language allows, because Set-Cookie and repeated headers are a real thing the naive converters drop.

One command, three languages

Here is the full pipeline on a realistic command — the shape of request that API debugging actually produces. Same command, three of the tool's five targets:

curl 'https://api.example.com/v1/orders' \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' \
  -H 'Idempotency-Key: 8f14e45f-ea36-4f5b-b0d1-ccf7f4b9e6b0' \
  -d '{"sku": "TS-42", "qty": 3, "note": "gift wrap"}'
// JavaScript — fetch (Node 18+ or browser)
const res = await fetch("https://api.example.com/v1/orders", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
    "Idempotency-Key": "8f14e45f-ea36-4f5b-b0d1-ccf7f4b9e6b0"
  },
  body: JSON.stringify({ sku: "TS-42", qty: 3, note: "gift wrap" })
});
const data = await res.json();
# Python — requests
import requests

resp = requests.post(
    "https://api.example.com/v1/orders",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
        "Idempotency-Key": "8f14e45f-ea36-4f5b-b0d1-ccf7f4b9e6b0",
    },
    json={"sku": "TS-42", "qty": 3, "note": "gift wrap"},
)
print(resp.json())

Note the structural choices, because they are where converters earn their keep. The raw JSON string in -d became a parsed object passed through json= in Python — requests sets Content-Type itself and handles encoding, so the header entry becomes redundant (the converter keeps your explicit header, since your command asserted it). The same string became JSON.stringify() over an object literal in fetch, because the body must be a string on the wire but nobody should hand-write escaped JSON. Go and PHP follow their own idioms — a req, _ := http.NewRequest with header Set calls in Go, curl_setopt calls in PHP — all from the same parse. The axios tab emits axios.post(url, data, { headers }) with the config object laid out identically to fetch, which makes the two JavaScript dialects directly comparable.

// Go — net/http
package main

import (
    "bytes"
    "encoding/json"
    "net/http"
)

func main() {
    body, _ := json.Marshal(map[string]interface{}{
        "sku":  "TS-42",
        "qty":  3,
        "note": "gift wrap",
    })
    req, _ := http.NewRequest("POST", "https://api.example.com/v1/orders",
        bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
    req.Header.Set("Idempotency-Key", "8f14e45f-ea36-4f5b-b0d1-ccf7f4b9e6b0")
    resp, err := http.DefaultClient.Do(req)
    _ = resp
    _ = err
}

The Go emitter surfaces one idiom the others hide: errors are values, not exceptions. The Python and JavaScript versions raise on network failure; the Go version hands you the error and expects a decision, which is why generated Go reads longer for the same request. Its header API carries its own rule — Set replaces, Add appends — so duplicate headers from the parse view need Add to survive intact.

A 500, chased from Network tab to regression test

The scenario every backend developer eventually lives: the staging orders endpoint returns 500, but only from the production client. The server log shows a JSON decode error and nothing else. The debugging sequence that resolves it in ten minutes runs straight through this tool, and it is worth walking in full.

Step one is the Network tab — reproduce the failure from the browser, right-click the request, Copy as cURL, paste into a terminal, run. It returns 200. That single run halves the problem: the endpoint, the auth, and the payload are all fine, and the difference must live in what the client actually puts on the wire.

curl 'https://api.staging.example.com/v1/orders' \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' \
  -d '{"sku": "TS-42", "qty": 3}'

Step two is the conversion — paste the command, open the Python tab, and set it beside the client code. The difference turns out to be one parameter long:

# The failing client — 500 in staging
import requests

resp = requests.post(
    "https://api.staging.example.com/v1/orders",
    headers={"Authorization": "Bearer eyJ...",
             "Content-Type": "application/json"},
    data={"sku": "TS-42", "qty": 3},   # form-encoded, mislabeled
)
print(resp.status_code)  # 500
# The converted, working version
import requests

resp = requests.post(
    "https://api.staging.example.com/v1/orders",
    headers={"Authorization": "Bearer eyJ..."},
    json={"sku": "TS-42", "qty": 3},
)
print(resp.status_code)  # 200

The failing client hands requests a dict through data=, which form-encodes it — sku=TS-42&qty=3 on the wire — while the manually set header insists on application/json. The server believes the header, tries to decode the form string as JSON, and dies without a useful trace. The converted code uses json=, which serializes properly and sets the correct Content-Type itself; the 200 returns, and the manual header turns out to be deletable rather than load-bearing.

Step three freezes the fix so it cannot regress — the converted call wrapped in the test the ticket deserved from the start:

import requests

def test_orders_accepts_json_body(staging_base, token):
    resp = requests.post(
        f"{staging_base}/v1/orders",
        headers={"Authorization": f"Bearer {token}"},
        json={"sku": "TS-42", "qty": 3},
    )
    assert resp.status_code == 200
    assert "id" in resp.json()

The whole loop — copy, run, convert, diff, test — takes about ten minutes, and the conversion step is what turns a hunch into a diff. Without it the investigation is 'maybe the body is wrong somewhere'; with it, the failing line and the working line sit next to each other, one parameter apart.

What doesn't survive translation

A converter that claims total fidelity is lying, so here is the honest list of what stays behind:

  • Local file paths in -F uploads. Multipart fields convert; the files themselves don't travel. Expect to reattach open() calls or File objects by hand.
  • Non-HTTP protocols. curl speaks FTP, SMTP, TELNET, and more; fetch and requests largely don't. Those commands are out of scope by nature.
  • Retry, timing, and tracing flags. --retry, --connect-timeout, --trace have per-library equivalents that differ enough that auto-emitting them would mislead more than help. Add them deliberately in the target language.
  • Proxy and interface flags. --proxy, --interface are infrastructure decisions, not request properties; they belong in the target's session or environment configuration.
  • Shell context. Anything a shell did before curl ran — variable expansion, command substitution inside the URL, pipes — never existed as far as the copied command's text is concerned. Convert the resolved command, not the template.
  • Browser-only constraints, positively stated. Converted fetch code is faithful but the browser may refuse to send it: CORS preflights, credentials-mode rules, and forbidden headers (User-Agent is settable in curl, not in browser fetch). Node runs the same code without those rules. Knowing which environment you're targeting is part of the conversion.

Header folklore the parser quietly respects

HTTP details that trip up experienced developers anyway, and how each one is handled on the way from command to code:

  • Header case is defined as insensitive and most servers honor it — but HTTP/2 lowercases every header on the wire, and the rare backend that matches case-sensitively works over HTTP/1.1 and breaks over h2. Generated code normalizes header casing to each language's convention, which is correct and occasionally diagnostic.
  • Duplicate headers are legal, and Set-Cookie cannot be safely merged into one comma-joined line. The converter preserves repeats instead of collapsing them — the naive object mapping that loses duplicates is why converted code sometimes quietly drops a cookie.
  • -u user:pass and an Authorization: Basic header are the same bytes on the wire — base64 of user:pass — but the flag version leaks the password into shell history and the process list. Landing in generated code as a header is a small hygiene upgrade that happens silently.
  • User-Agent is trivially set by curl and requests, settable in Node, and forbidden in browser fetch — one more reason identical converted code behaves differently across runtimes, and a frequent answer to 'works in Node, fails in the browser.'
  • Whitespace at the edges of a copied JSON body is invisible in a terminal and hostile to strict parsers downstream. Bodies get trimmed before emission; when converted code suddenly works where the original curl failed, a trailing newline is the usual suspect.

The headers Chrome adds when you copy, and which ones your code needs

A 'Copy as cURL' from DevTools arrives carrying the browser's whole negotiation stack: accept headers, client hints, fetch metadata, cookies, origin context. Most of it is atmosphere; a little of it is load-bearing. The census, with a verdict for each row:

Header in the copied commandWhat it tells the serverKeep it in converted code?
Authorization / CookieYour actual identityAlways — moved into environment variables, never into source
Content-TypeThe body's formatAlways, or let the library's json= mode set it
AcceptWhich response representation you understandKeep when the API varies output by Accept; drop otherwise
Accept-EncodingWhich compression you supportDrop — every HTTP library negotiates this itself
Accept-LanguagePreferred response localeKeep only when testing localized behavior
Origin / RefererWhere the request appears to come fromKeep when the endpoint validates them — some APIs and CSRF defenses do
Sec-Fetch-Site / Mode / Dest / UserFetch metadata from the browser contextDrop for scripts; keep when replaying requests a gated endpoint expects
Sec-CH-UA familyClient hints describing the browserDrop — bot defenses read them, application servers ignore them
User-AgentThe client's self-descriptionKeep — and see below, because the default library UA gets blocked
One request, sixteen headers, three that usually matter. The parse view shows all of them; this table decides what survives.

The User-Agent verdict deserves its own paragraph, because it is the most common reason converted code fails where curl succeeded. curl identifies itself as curl/8.x, Python's requests identifies as python-requests/2.x, and a meaningful number of APIs, CDNs, and bot defenses treat the library's default string as grounds for a 403 on sight. If the command you copied worked in a terminal, paste its exact User-Agent into the converted headers and the failure usually vanishes. Some bot defenses fingerprint deeper than headers — right down to the TLS handshake — at which point no header fixes the script and the curl command itself is the reliable client, a fact worth knowing before an afternoon is spent blaming the converter.

A stripping discipline that scales: keep the headers the server would refuse you without (auth, cookies, content type), keep the headers the server changes behavior for (accept variants, origin on gated endpoints), drop the headers that describe a browser context your script is not. Start from the three-row version, add headers back one at a time when a request fails, and the converted snippet stays honest about what it actually needs rather than wearing the browser's entire wardrobe.

Who pastes curl commands all day

The traffic pattern for curl converters is a spike pattern with no season — it's every day a developer anywhere meets an API. The populations are distinct enough to be worth naming.

Backend and API developers are the core: they debug in DevTools, confirm with curl, and then need the working request as Python or Node in a test suite. Data scientists hit public APIs from notebooks and want the endpoint they just tested as a requests snippet without hand-translating headers — the Python tab was built with them in mind. QA and automation engineers convert captured requests into regression tests, often a dozen at a time. Frontend developers use the fetch and axios tabs to prototype calls against staging, then hit CORS and learn the Node/browser distinction the hard way, once, forever. Integration engineers and SREs round it out — webhook payloads to replay, incident-time requests to reproduce in a script. And students convert documentation examples into their course language, which is quietly the best way to learn what an HTTP request actually consists of: seeing one request as five dialects at once teaches the request/response model faster than any diagram.

Four ways to convert, compared

ApproachBest atCost
Hand transcriptionLearning the request structure onceSlow; typo-prone; every new command pays again
curlconverter.comThorough single-command conversion, many targetsOne command per visit; heavyweight page for quick iterations
Postman importFull API exploration workflowA whole application between you and the code; account-centric
This converterInstant multi-language tabs, header-to-JSON mapping, credential-safe by architectureNot an API workbench — a fast, faithful translator
Different tools for different relationships to the problem.

The niche this tool occupies is the clipboard-shaped one: you have a command, you need code, and everything between those two states should be one paste and one click. Workbench tools are excellent at what they do and the wrong shape for this moment — the way a table saw is wrong for hanging a picture. What we optimized for is the round trip: paste, read the parse, flip tabs, copy, done — with no account, no saved workspace, and no network request carrying your bearer token to anyone.

Your terminal history stays yours

Everything — the flag parsing, the body decoding, all five language emitters — runs in your browser. The command you paste, including its authorization headers, cookies, and API keys, is processed on your device and transmitted nowhere. There is no conversion history, no 'recent commands' panel, and no account, because there is no server holding commands at all.

For this tool that architecture is not a nice-to-have; it is the only acceptable design. DevTools 'Copy as cURL' captures live session credentials — that is literally its job. A converter that posts those commands to a backend for processing is a credential relay with a UI. The client-side design also means the tool works when you're offline, air-gapped, or on a locked-down corporate network, which describes a real fraction of the moments people need to convert a request.

Conversion questions, answered

Does -d in curl automatically mean POST?

Yes. Adding -d/--data switches curl's default method to POST even without -X POST, and sets a form-style Content-Type unless you override it. That's why the converter emits an explicit POST with a body object in the generated code — it's encoding curl's implicit behavior into code that has no such defaults.

My copied command has $ variables in it. Why won't it convert?

Because $API_KEY was a shell variable, and your browser has no idea what it held. DevTools 'Copy as cURL' sometimes includes shell-escaped values, and commands copied from documentation often contain placeholders. Paste the command with the real values substituted, or edit the $ placeholders in the input box before converting — the parser treats them as literal text otherwise.

Why does the fetch version work in Node but fail in my browser?

CORS. The fetch call is faithful to your curl command, but browsers enforce cross-origin rules that curl ignores — no preflight, no credential restrictions, nothing. A server that happily talks to curl from your terminal can still refuse a browser request from a different origin. Test converted fetch code in Node first; if it works there and fails in the browser, the difference is CORS policy, not the conversion.

Does the converter handle multipart file uploads (-F)?

Partially. -F fields become multipart form entries in the generated code, but local file paths don't translate — your Python or JavaScript has no access to a file that existed on the machine where the curl command ran. Expect to replace file fields with open('path','rb') or a File object yourself. Everything else about the multipart request converts normally.

Are my cookies and auth headers sent to your server?

No. Parsing and code generation happen entirely in your browser. Commands containing bearer tokens, session cookies, and API keys never leave the page — there is no upload, no history, no logging. This matters more than usual for this tool, because pasted curl commands frequently contain live credentials.

Why does the generated Python include a warning about verify=False?

Because that's what curl's -k flag becomes in requests — disabling TLS certificate verification. The converter keeps the flag's behavior faithful, and flags it loudly because verify=False makes your code vulnerable to interception in transit. -k is fine for debugging a broken cert on a test box and indefensible anywhere else.

Which target languages does the converter support?

JavaScript fetch (browser and Node), Python with the requests library, Node with axios, Go with net/http, and PHP with curl — selectable as tabs over the same parsed command. The parse is identical; only the emitter changes, so switching languages to compare dialects is instant.

Why do my curl commands use single quotes around the JSON body?

Because of the shell. Double quotes let the shell expand $variables and backticks inside your JSON, which corrupts bodies containing dollar signs. Single quotes pass the string through untouched. When converting, paste the command as you would actually run it — the parser understands both quoting styles.

Is it safe to paste curl commands containing live tokens?

Into this tool, yes — parsing and code generation are local, and the command never leaves your browser. The risk sits elsewhere: shell history records every command you run, and -H with a literal token or -u with a password lands there in plain text. Prefer environment variables on the command line (-H "Authorization: Bearer $TOKEN"), and replace literals with env lookups in converted code before anything reaches a repository.

What is the difference between -d and --data-urlencode?

-d sends the string as-is — the bytes you type are the bytes on the wire, so fields containing spaces or ampersands arrive malformed unless you encode them yourself. --data-urlencode encodes the field properly before sending. The converter maps -d bodies straight through and --data-urlencode fields to encoded params, which is why form posts captured from DevTools convert cleanly when the browser used the encoded form.

Should I strip the browser-added headers from a copied command?

Mostly. DevTools copies everything the browser sent, including sec-fetch-*, sec-ch-ua, and Accept-Language noise that servers rarely inspect. Stripping those makes the generated code cleaner without changing behavior for typical APIs. Keep authorization, content-type, accept, and any custom headers the API documents — and keep User-Agent if the endpoint validates it, since some APIs reject unknown clients.

Can the converter generate PowerShell instead?

Not currently — the targets are fetch, Python requests, axios, Go, and PHP. The manual mapping is short enough to do by eye: Invoke-RestMethod -Uri with -Method, a -Headers hashtable for the header entries, -Body with the JSON string and -ContentType 'application/json', and curl's -u flag becoming -Authentication Basic with -Credential. PowerShell's curl alias for Invoke-WebRequest is also worth remembering — on Windows, the command named curl is frequently not curl at all.

Why does my converted code return 403 when the curl command worked?

Almost always the User-Agent. Scripts and libraries announce themselves with strings like python-requests/2.x, and plenty of APIs, CDNs, and bot defenses block those on sight while admitting curl/8.x. Copy the exact User-Agent from the working command into the converted headers first. If the 403 survives a real browser UA, the defense is fingerprinting the TLS handshake itself — at that point the curl command is the only client that gets through, and the converted code needs a different route or an approved token.

Pair this with

Converted requests live inside a small toolkit, and the neighbors here cover the rest of the loop. When the response comes back as JSON and you need to pull one field out of a deep payload, the JSON Path Tester develops and checks the path expression against your actual data. When the request carries a JWT you want to inspect rather than just replay, the JWT Decoder opens the token's header and payload locally. And when the converted URL itself is the mystery — query params, encoded fragments, the difference between what you sent and what the server logged — the URL Parser breaks it into labeled components.

Convert the request, decode the token, dissect the URL, extract the response field. Four tools, one complete debugging loop, all of it local.

Docs worth keeping open

The flags this parser implements are documented at the source, and the target libraries have canonical references of their own:

Sources and further reading

We cite primary sources (standards bodies, government agencies, official documentation) wherever they exist so you can verify every number on this page yourself.