Spots

Why your cURL command works in terminal but fails in Python (4 gotchas that…

We've all been there: you open Chrome DevTools, click Copy as cURL, paste it into your terminal, and it works like a charm. Then you rewrite it into Python requests or httpx, hit run, and suddenly: Or worse, a silent HTTP 403 Forbidden.

I spent an hour yesterday debugging an internal

I spent an hour yesterday debugging an internal webhook call that worked in bash but kept failing in our automated runner. Here are the 4 subtle edge-cases where translating cURL to Python breaks—and how to fix them. 1. The Trailing Slash & Automated Redirect Trap When you run this in terminal:

If the endpoint expects https://api.example.com/v1/auth/ (with a trailing

If the endpoint expects https://api.example.com/v1/auth/ (with a trailing slash), modern curl silently follows or handles it depending on server config.

By default, standard requests.post() will follow a 301/302

By default, standard requests.post() will follow a 301/302 redirect by downgrading the method to GET and dropping the payload entirely!

Fix: Always verify whether the server enforces a

Fix: Always verify whether the server enforces a trailing slash. If you need automatic redirect preservation, use a Session or inspect r.history. 2. Double-Encoded JSON String vs. Raw Dict In curl, people often write: When porting to Python, junior devs often do this:

Notice the mistake? Passing json=json.dumps(...) serializes the string

Notice the mistake? Passing json=json.dumps(...) serializes the string twice. The server receives a string literal instead of a JSON object. Use json=my_dict (requests handles serialization and headers for you). OR use data=json.dumps(my_dict) with explicit headers={'Content-Type': 'application/json'}. Never mix both. 3. The Pseudo-Headers Copied from Chrome When you click "Copy as cURL" from browser DevTools, Chrome copies everything, including HTTP/2 pseudo-headers: If you blindly paste all those headers into Python:

Some WAFs (like Cloudflare or Akamai) detect that

Some WAFs (like Cloudflare or Akamai) detect that the TLS fingerprint does NOT match a real browser Chrome TLS handshake, and they flag the request as a spoofed bot.

If Accept-Encoding: br (Brotli) or zstd is sent

If Accept-Encoding: br (Brotli) or zstd is sent, Python's requests library cannot decode it natively unless you have brotli installed, leaving you with raw binary gibberish in r.text.

Fix: Strip out browser-specific sec-ch-* headers and let

Fix: Strip out browser-specific sec-ch-* headers and let Python handle encoding headers naturally. Keep only Authorization, Content-Type, and your custom headers. 4. Escaped Quotes in Bash Shells If your curl payload contains shell variables or nested quotes:

Bash string escaping rules differ wildly from Python

Bash string escaping rules differ wildly from Python string escaping. Unescaping backslashes by hand on a 50-line payload is a recipe for syntax errors. After hitting these gotchas one too many times during staging tests, I stopped doing manual string surgery.

News

Why your cURL command works in terminal but fails in Python (4 gotchas that wasted my afternoon)

We've all been there: you open Chrome DevTools, click Copy as cURL, paste it into your terminal, and it works like a charm.

@spots #dev
Source: Dev.to
See more like this