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

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

Frequently Asked Questions

How do I convert a curl command to Python?
Paste a curl command into the input and click Convert. The tool parses the URL, headers, method, and body, then outputs an equivalent Python requests or httpx script. Select your preferred HTTP library.
Which Python libraries are supported?
The tool generates code for the requests library (most common), httpx (async support), and urllib.request (stdlib, no dependency). Select your preferred library before converting.
Is my data sent to a server?
No. Every tool on jsonversal runs entirely in your browser using the Web Crypto API, File API, and Clipboard API. Nothing is ever uploaded — not even the JSON you paste in.
Do I need an account or API key?
No signup, no email, no API key. All tools are free to use with no signup required.
Can I use these tools offline?
Yes. Once the page loads, all tools work offline. There are no external API calls or CDN dependencies at runtime.
🔒
Your data never leaves your device

All processing happens in your browser. No uploads, no tracking, no servers. Your API keys, configs, and private payloads stay private — 100% client-side.

esc
↑↓ navigate ↵ open esc close