cURL to Python (requests)
Parse a curl command and generate a runnable Python script with requests or httpx — method, headers, JSON and form bodies, cookies, auth and TLS options.
Paste your curl command
How it works
- Tokenizes the command, respecting single and double quotes with escapes.
- Maps
-X,-H,-d/--data-raw/--json,-u,-b,-A,-F,-L,--compressedand--insecureto requests/httpx arguments. - JSON payloads become
json=dicts, form payloads becomedata=dicts, anything else is passed as a raw string. - Unsupported flags are listed as comments in the output instead of being silently dropped.
About cURL to Python
When you copy a request out of the browser network tab or a README, curl is the format you get — but a Python script, test or automation job wants requests or httpx instead. This converter reproduces the request semantics: repeated headers are merged into a dict, JSON-looking bodies become json= so they are serialized correctly, cookies become a cookies= dict, and basic auth becomes auth=(user, password).
The generated script is complete and runnable: imports, a try/except around the call, a printed status code and response text. Options that have no Python equivalent — like -o file output or exotic curl flags — are kept as comments so you know what was skipped. Defaults differ in one notable way: curl sends Content-Type: application/x-www-form-urlencoded for -d payloads, while requests does not; the converter preserves the headers you actually passed.
Tokenizing the shell, and what the shell already destroyed
The tokenizer honours single quotes as fully literal and double quotes with backslash escapes resolved, discarding standalone continuation backslashes. The information loss starts before the text reaches the page: `$(command)` substitution, `$VAR` expansion, brace expansion like `{1..3}`, tilde expansion and globbing have all been performed by the shell. A pasted command with a variable arrives as whatever the shell substituted, or as a literal `$VAR` if the paste came from documentation.
Flags are mapped by explicit table rather than pattern guessing. `-X/--request` sets the method; `-H/--header` collects entries and merges repeats by joining values with `; `; the six data flags all write one slot, so the last wins; `-u`, `-b`, `-o` and `-A` each take one value, with `-A` folded into the header dict under its canonical name.
How the body is classified, and where requests and httpx diverge
Three signals decide the body form, checked in order: the `--json` flag, a `Content-Type` containing `application/json`, or a payload whose first non-whitespace character is `{` or `[`. When any holds, the raw string goes through a real `JSON.parse`, and only a successful parse produces a `json=payload` argument. A payload that looks like JSON but does not parse falls back to a raw `data=` string — the safe outcome, since the bytes go out verbatim rather than through a re-serialisation that would mangle them. Form encoding applies only when the content type says `application/x-www-form-urlencoded`.
Multipart is the one genuine approximation. `-F name=value` becomes `files={'name': 'value'}` and `-F name=@path` becomes `open('path', 'rb')`. But curl's non-`@` form value is a plain field, while `requests` sends anything in `files=` as a file part with its own `Content-Disposition`, so the request is not byte-identical. `-L` and `--compressed` become comments, which is correct rather than lossy: both libraries follow redirects and advertise gzip by default. `-k` maps to `verify=False`, and that one does change behaviour — it disables certificate verification outright.
The two library targets differ only where the source differs. `requests` and `httpx` take the same arguments in the same call shape, and the generated code is synchronous either way. The real difference is the exception class in the `except` line.
Flags that change meaning, and the ones that get dropped
The dangerous case is not an unknown flag — those are collected and printed as a `# Skipped flags:` comment, which is exactly right. It is a known flag whose semantics change. `-G/--get` tells curl to move the body into the query string rather than send it as a payload; it is unhandled, so it lands in the skipped list while the body is still transmitted. `--data-urlencode` is treated identically to `--data-raw`, so percent-encoding curl would have performed is not reproduced.
Two guards stop the conversion rather than producing something subtly wrong. A URL containing a brace template such as `{id}` is rejected, because the correct Python equivalent depends on which value curl would have substituted. A payload beginning with `@` is rejected, since `@file` reads from disk. The method defaults to `POST` whenever a body is present, matching curl's own rule, and a 30-second timeout is hardcoded into every call.
Next steps
- cURL to Fetch— JS version
Frequently Asked Questions
How do I convert a curl command to Python?
Which Python libraries are supported?
Is my data sent to a server?
Do I need an account or API key?
Can I use these tools offline?
All processing happens in your browser. No uploads, no tracking, no servers. Your API keys, configs, and private payloads stay private — 100% client-side.