Skip to content

Behaviour

Settings are syshttp's variables, each with a full name (SYSHTTP_PROXY, HTTPS_PROXY, NO_PROXY) read under every prefix of SYSHTTP_PREFIX, a :-separated list of literal prefixes (none by default: the names as written). SYSHTTP_PREFIX=AGENTS_ reads AGENTS_SYSHTTP_PROXY, AGENTS_HTTPS_PROXY and AGENTS_NO_PROXY and nothing unprefixed; AGENTS_: falls back to the plain names. curl's own variables (CURL_CA_BUNDLE) are read as curl names them, whatever the prefix. CURLIKE_PYLIB names where to find duho when it is not installed.

pycurl https://example.com
pycurl -s -o out.json -H 'Accept: application/json' https://api.example.com/x
pycurl -X POST -d '{"k":1}' -H 'Content-Type: application/json' https://h/api
  • The proxy is syshttp's: SYSHTTP_PROXY (credential and type as syshttp documents them), then the request scheme's *_PROXY variable, then the system's configuration for each URL on the command line (WinHTTP, CFNetwork, libproxy, PAC, WPAD; SYSHTTP_SYSTEM_PROXY=0 turns it off; with HTTP_PROXY_AUTH as its header). Of a list the first proxy that accepts a connection is used; when none does and the list holds no DIRECT, exit 7 (curl: (7) no proxy of the list is reachable: …); a lone proxy is not probed. Every redirect hop is decided again (proxied, or direct without the credential for a NO_PROXY host); loopback is proxied unless listed, as in curl. A prefix gateway is spoken per hop. -x names a proxy of your own (always connect, never given the configured credential); --noproxy (* or a host list) replaces NO_PROXY, as in curl; -U replaces the credential. A malformed SYSHTTP_PROXY/SYSHTTP_PROXY_TYPE that would be used is exit 2, its message naming the variable found (under a prefix, AGENTS_SYSHTTP_PROXY_TYPE), never a credential.
  • Supported (curl -h lists them): request -X -G --url --url-query -H -A -e -r --compressed --request-target --proto --proto-redir --proto-default --path-as-is; auth -u --basic --digest --anyauth --oauth2-bearer -n --netrc-file --netrc-optional and user:pw@ in the URL; body -d --data-ascii --data-raw --data-binary --data-urlencode --json -F --form-string -T; output -o -O --remote-name-all --output-dir --create-dirs -D -i --show-headers -I -s -S -v -f --fail-with-body -N -w --stderr; downloads -C -J -R -z --etag-save --etag-compare --no-clobber --skip-existing --remove-on-error --max-filesize; TLS -k --cacert --capath -E/--cert --cert-type --key --key-type --pass -1/--tlsv1 --tlsv1.0 --tlsv1.1 --tlsv1.2 --tlsv1.3 --tls-max --ciphers --crlfile --pinnedpubkey; connection -x -U -p -0/--http1.0 --proxy-header --proxy-basic --socks4 --socks4a --socks5 --socks5-hostname --socks5-basic --preproxy --proxy-insecure --proxy-cacert --proxy-capath --proxy-cert --proxy-cert-type --proxy-key --proxy-key-type --proxy-pass --proxy-ciphers --proxy-crlfile --proxy-pinnedpubkey --proxy-tlsv1 --proxy-digest --proxy-anyauth --noproxy -L --max-redirs --location-trusted --post301 --post302 --post303 -m --connect-timeout --timeout -4 -6 --resolve --connect-to --unix-socket --interface --local-port --limit-rate -Y/--speed-limit -y/--speed-time --ignore-content-length --dns-servers --dns-ipv4-addr --dns-ipv6-addr --dns-interface --doh-url --doh-insecure --retry --retry-delay --retry-max-time --retry-connrefused --retry-all-errors; cookies -b -c -j; and the no-ops -q -g --fail-early --expect100-timeout --tcp-fastopen --false-start --happy-eyeballs-timeout-ms --random-file --egd-file --http1.1 -# --no-progress-meter --styled-output --no-keepalive --keepalive-time --tcp-nodelay --ssl-no-revoke --ssl-revoke-best-effort --ca-native --proxy-ca-native --no-alpn --no-npn --no-sessionid, each true of this client (no config file read, no progress shown, no keepalive, session reuse or ALPN, and no revocation check but --crlfile's). -g sends a URL's {} / [] as typed; without it a glob pattern (one transfer per expansion in curl) is refused, exit 2, and a broken one is curl's exit 3. A URL with no scheme is http://, as in curl; an option value may start with - (-z -DATE, -d -1), as in curl. With curl's meaning where it matters:
  • -L follows as curl does, in its own loop: at most 50 redirects (--max-redirs N; -1 unlimited, 0 refuses the first), then exit 47; credentials (Authorization from -u, --oauth2-bearer or -H; Cookie from -H or a -b string) go to a hop only while its scheme, host and port are the first request's, unless --location-trusted (which also implies -L), and a -H 'Host:' only to the first request's host; the cookie engine (-b FILE, -c) keeps what each hop sets and sends it to the next hop it applies to, and -c files a cookie under the host of the hop that set it; -D and -i show every hop's headers; -e 'URL;auto' sets each later hop's Referer to the page it came from; 301/302 turn a POST into a GET (--post301/--post302 keep it), 303 anything but HEAD, 307/308 keep method and body, and -X stays forced. A redirect to a scheme this command cannot fetch (ftp://, ftps://) is exit 1 (Protocol "ftp" not supported or disabled in libcurl); one that --proto-redir forbids (file://, gopher:// by default) is exit 1 too.
  • Credentials: -u wins (-u USER asks for the password, on the terminal, as curl does), then user:pw@ in the URL, then the netrc entry with -n/--netrc-optional/--netrc-file: the first machine (any case) or default entry in file order, or, for a user@ URL, that login's entry. The netrc reads "quoted words" with backslash escapes, ends a line at a word starting with # and skips macdef bodies. --digest (MD5, SHA-256) and --anyauth (Basic when that is all the server offers) answer a 401 once, over a connection of the same kind as the first request (an https URL through a proxy: the CONNECT, then the origin's TLS again; -p: a CONNECT to the origin): a rejected credential is not sent again, and a challenge curlike cannot answer is the response. -n without a netrc is exit 26.
  • Downloads: -C - resumes from the file's size (a server ignoring the range is exit 33, a 416 means already complete); -J takes the Content-Disposition filename= (at a parameter's start) without its directories, as a Windows name curl would make it (:<>"|?* become _, a device name like CON gets a _), and never overwrites (exit 23); -O names the file after the URL's last non-empty path segment as written (a backslash separates, . and .. resolve, nothing is percent-decoded: %2F stays three characters), so it never leaves the current or --output-dir directory, else curl_response; on Windows : and the like become _; --etag-save is created before the transfer (26, "no transfer performed", when it cannot be); -R sets the mtime from Last-Modified; -z DATE|FILE (-z -DATE: unmodified since) and --etag-compare make the request conditional, and an unmet condition or a 304 writes no file; --no-clobber writes name.1..name.100; --max-filesize over the limit is exit 63 (by Content-Length before anything is written, else when the body passes it); a 206 answering -C must start at the offset (Content-Range: bytes OFFSET-) or it is exit 33; --remove-on-error removes the file a failed transfer opened for writing, one it overwrote included (curl refuses it with -C: exit 2).
  • Proxies: -x takes http:// and socks4:// / socks4a:// / socks5:// / socks5h:// (socks:// is SOCKS4), as does SYSHTTP_PROXY; the --socks* flags name one too and override -x. A proxy URL's path, query and fragment are ignored, as in curl. socks5 and socks4 resolve the name here, socks5h and socks4a let the proxy do it; SOCKS5 credentials come from -U or the URL. A SOCKS handshake that fails is exit 97 (refused, unreachable, rejected credentials, no acceptable method, dropped). --preproxy socks5h://... reaches the -x proxy through SOCKS. --proxy-header goes to the proxy only: the request to it, or the CONNECT -- never through a tunnel, never to a bypassed host. --proxy-basic is a no-op (-U is Basic). An https:// proxy is spoken to over TLS, verified by its own options -- --proxy-cacert, --proxy-capath, --proxy-insecure, --proxy-cert / --proxy-key / --proxy-pass, --proxy-crlfile, --proxy-pinnedpubkey, --proxy-ciphers, --proxy-tlsv1 -- never by --cacert or -k, which stay the origin's; an https target goes through it with the origin's TLS inside the proxy's. --proxy-digest sends no credential up front and answers the proxy's 407 once with Digest (MD5 / SHA-256), on the plain request or on the CONNECT -- never an origin's 407, reached directly or through a tunnel, which is the response; --proxy-anyauth takes Digest when offered, else Basic. NTLM, Negotiate and SOCKS5 GSS-API are not available (no standard- library implementation): those flags stay refused.
  • URLs as curl reads them: /a/../b is sent as /b and /a/./b/. as /a/b/ (the query untouched), unless --path-as-is; a redirect's target too. --proto (+name, -name, =name, all) limits every request and hop, --proto-redir (default http, https, ftp, ftps) the redirects: a disallowed one is exit 1. --proto-default https is the scheme of a URL that names none. -b <file> sends its cookies in curl's order (longest path, domain, name first; the later line first); -j leaves out its session cookies. -i / -D show a proxy's CONNECT reply before the response, as curl does, unless --suppress-connect-headers.
  • Shaping a connection: --interface takes an IP address, a host (host!NAME) or an interface (if!NAME, or a bare name; by name only with netimps installed), --local-port N[-M] the first port of the range that binds (none: exit 45). -p tunnels an http:// URL through the proxy with CONNECT too. --limit-rate RATE[k|m|g] paces the body both ways; --speed-limit/--speed-time (30 s, 1 B/s when only one is given) end a slower transfer with exit 28. --anyauth sends no credentials until the server's 401 says which (Digest, else Basic). --request-target replaces the first request's target (* for OPTIONS). --crlfile checks the server's chain against the CRLs in a PEM file (revoked: 60; a file with no CRL is exit 82) and trusts nothing else in it: a certificate there is not an anchor. --pinnedpubkey sha256//BASE64[;...] or a public-key file requires the server's key (exit 90), checked even with -k, on a connection reopened after a proxy's 407 and over --unix-socket; a key file is read at the handshake, so an http:// transfer never reads it. The contexts curlike builds do not apply the strict X.509 checks Python 3.13 added, as curl does not.
  • Name resolution by netimps (pip install 'netimps>=0.4.1'; without it, or with an older one, these flags are refused, exit 2): --dns-servers IP[:PORT],... asks those nameservers (UDP, TCP when truncated), --dns-ipv4-addr / --dns-ipv6-addr / --dns-interface send the queries from that address, --doh-url https://... asks a DNS-over-HTTPS endpoint, verified as the transfer is (--cacert) unless --doh-insecure, and never through a proxy. A name none of them resolves is exit 6; an address needs no lookup. --doh-url is checked against real curl; the --dns-servers family needs a curl built with c-ares, which most are not (they refuse it, exit 2).
  • Connections: -4/-6, --resolve HOST:PORT:ADDR and --connect-to HOST1:PORT1:HOST2:PORT2 steer only name resolution, so the Host header, TLS name and certificate check stay the URL's; --unix-socket PATH needs a Python with AF_UNIX (not Windows CPython), else it is refused. A CONNECT carries one Host line. A SOCKS5 user name or password over 255 bytes, or a host name over 255 bytes the proxy is to resolve, is exit 97.
  • Exit codes are curl's, checked against real curl by the project's parity tests (same command, same fake servers; skipped without a curl). An HTTP status is a response, printed and exit 0 (a 404, a 302 without -L, a plain-http proxy's 407); -f makes a 4xx/5xx exit 22 with no body, --fail-with-body the same exit with the body. Before anything is sent: 1 a scheme curl does not speak, 3 a URL it cannot parse; 2 a --cacert / --netrc-file naming no file, 26 a -d @ / -H @ / -w @ / -T file that cannot be read (these print curl: message with no (N) and no -w, as curl does). Connecting: 5 the proxy's name or URL is unusable, 6 the host's name, 7 could not connect, 28 timed out, 35 a failed TLS handshake, 60 a certificate that does not verify, 58 a client certificate and 77 a CA bundle/path that cannot be loaded (at the handshake, so an http:// transfer never fails on them), 59 an unusable --ciphers list. A proxy refusing the https CONNECT is 7 (22 under -f), dropping it 56. The reply: 52 nothing came back, 1 not HTTP at all, 56 the connection reset, 18 a body shorter than its Content-Length or chunks cut short (what arrived is written first), 56 a malformed chunk; 23 when -o/-D/-O cannot be written, 26 when a -F file cannot be read. An unsupported flag, a conflicting pair (-d with -F, -f with --fail-with-body) or a bad proxy configuration is exit 2 (curl: (2) …, one line, never a traceback). -o /dev/null is the null device on Windows too.
  • --retry N retries what curl calls transient — a timeout, a name that does not resolve and HTTP 408/429/500/502/503/504 (syshttp.retry.TRANSIENT_STATUSES, the list syshttp's client retries too) — after 1s, doubling (--retry-delay fixes it, a non-zero Retry-After wins; one that would pass --retry-max-time ends the retries with a warning); --retry-connrefused adds a refused connection, --retry-all-errors any error (a body cut short, and with -f an HTTP error status, too). Every failed attempt prints its error line, and each retry warns Problem : …. Retrying in N seconds. M retries left. on stderr unless -s.
  • Time and size: there is no time limit unless one is given. --connect-timeout bounds the connect and TLS handshake only; -m is a deadline for the whole attempt, redirect hops and a body that trickles in included (a retry starts it again); --timeout (not a curl option) ends a transfer that receives nothing for that long. A response whose headers pass 300 KB is exit 56, one with over 5000 headers or a header line over 100 KB exit 100, a Content-Length that is not one number (or negative) or a NUL in a header exit 8. The body is delivered as it arrives, not held: -N writes each piece at once, --compressed decodes piece by piece (at most 5 content encodings, as in curl: exit 61 beyond; each layer passes on pieces of at most 16 KiB, so a small nested gzip body cannot grow in memory), -T FILE is read as it is sent, and nothing process-wide (http.client's limits, socket.getaddrinfo) is changed for a transfer.
  • --json sends Content-Type and Accept: application/json (repeats concatenate); --data-urlencode takes curl's content, =content, name=content, @file, name@file; -G moves the data into the query; --url-query adds to it (+ as it stands). -F name=value, name=@file (an upload: filename, type by extension), name=<file (its content), with ;type= / ;filename=. -T file PUTs it (a URL ending in / gets its name). -r is Range: bytes=…, --oauth2-bearer a Bearer Authorization.
  • -w / --write-out (@file, @- read the format) writes curl's output for http_code response_code http_version method scheme url url_effective redirect_url num_redirects content_type num_headers header_json size_download size_upload time_total time_starttransfer exitcode errormsg filename_effective urlnum, the timings time_namelookup time_connect time_appconnect time_pretransfer time_posttransfer time_redirect time_queue, the addresses remote_ip remote_port local_ip local_port, num_connects num_retries speed_download speed_upload size_header size_request size_delivered http_connect proxy_used referer conn_id xfer_id, the parts url.* / urle.* (scheme, user, password, host, port, path, query, fragment), and %{json} with curl's keys, plus %header{name}, %{stdout} / %{stderr} / %{onerror}, %% and \n \r \t — after failures too (000 and exit 7 when nothing answered, the status after -f's 22). Timings are measured on the connection (lookup, connect, handshake, request sent, first byte); urllib opens a new connection per redirect, so num_connects counts hops curl may reuse one for. In %{json}, ssl_verify_result is 0 (verified, or not checked under -k); certs / num_certs are empty. Any other variable (certs, ssl_verify_result, …) is refused before the request is sent. -q is a no-op (no .curlrc is read).
  • -d repeats and joins with &; @file reads a file (CR, LF and NUL stripped, as curl does; one it cannot read is exit 26 in curl's three lines), @- stdin; --json pieces follow the previous piece with no &; --data-urlencode and --url-query write a space as +; -F takes @"file;name" and ;type=text/plain;charset=utf-8, and a .json file is application/octet-stream; -T FILE sends no Content-Type and adds the file name only to a URL with no query; --data-binary keeps bytes as they are; a body without a Content-Type gets application/x-www-form-urlencoded.
  • Request headers: a request carries Host, User-Agent: curl/8.22.0 (-A replaces it) and Accept: */*, and Connection: close — the one difference from curl, which keeps the connection: a transfer is one request on one connection here. -H 'Name: v' sets a header (replacing only a default of that name); given again it sends another line; -H 'Name:' removes it, Host, Connection, Accept-Encoding and Content-Type included; -H 'Name;' sends it empty; -H @file is one header per line. Header values (-H, -A, -e, -b) go out as the UTF-8 bytes of what was typed. -u user:pass is Basic, -e the Referer, --compressed asks for gzip/deflate and decodes the body. A response's header bytes are written back as they came by -i, -D and %header{}, with the status line's HTTP version (HTTP/1.0 for a 1.0 reply); -w @file is read and written as bytes. A URL with a space or a control character is exit 3 before anything is sent; non-ASCII characters in it are sent as their percent-encoded UTF-8 (a host name as IDNA).
  • -b is a cookie file when it holds no = (- is stdin; a file that is not there holds nothing), else the cookie string; -c - writes the jar to stdout after the body, and a jar that cannot be written does not fail the transfer. -b <file> sends only the rows that apply to the URL — domain (the leading dot or the TRUE flag covers subdomains), path, Secure only over https, not expired, #HttpOnly_ rows included — so a jar with several hosts never leaks one host's cookies to another. -c writes the origin's cookies with their expiry and HttpOnly, on top of what -b read.
  • -I prints the headers by itself (-I -X POST sends POST); -i adds them to a body. Two URLs, an unknown option or a missing option value is exit 2 in curl's two lines (the message and the --help hint), not argparse's usage text.
  • An output that cannot be written is exit 23 in curl's words, which depend on where the write failed, and %{errormsg} follows them. A stdout whose reader went away (curl … | head -c 16): Failure writing output to destination, passed N returned M when the write overflowed the 4096 bytes the C library holds for a pipe, Failed writing body (no code on that line) when it failed only at the final flush. -o naming what cannot be opened: client returned ERROR on write of N bytes. -D likewise: Failed to open <path>; -D - to a dead stdout: Failed writing headers to -. Lines without a code are wrapped at the terminal's width (79 with none), as curl wraps them. What -w would then have written to stdout is dropped; when only -w had stdout (the body went to -o), the exit code stays the transfer's.
  • Unsupported flags fail loud (curl: (2) Unsupported options: --http2, exit 2) rather than silently do the wrong thing — that guard is deliberate. If you hit one, call real curl.
  • Revocation: curlike checks no certificate revocation (as an OpenSSL curl) unless --crlfile.
  • One curl — curlike answers as curl 8.22, the latest stable release it was measured against, and follows new releases; it does not imitate older ones, whose exit codes, messages and -w variables differ in places.
  • TLS verifies against the OS trust store — syshttp.tls, the same store syshttp's client uses — unless --cacert / --capath or $CURL_CA_BUNDLE (curl's own variable, read as written: syshttp's CA_BUNDLE is not it) name one, which replaces it as in curl; -k/--insecure disables verification. --cert file[:password] (the file may hold the key), --key, --pass present a client certificate, PEM only.
  • -v prints the request as given, so an Authorization or Cookie the caller set appears in it, as in curl -v; only the configured proxy's credential is redacted.