import httpclient, json
# Build the JSON request body
let data = %*{
"token": {
"user": user,
"password": passwd,
"lifetime": "fixed"
}
}
# --- BROKEN: curly library does not send body correctly ---
# curl from terminal works, httpclient works too, but curly does not.
# curly likely ignores/drops the body on POST requests.
#
# How to reproduce:
# var headers = emptyHttpHeaders()
# headers["Accept"] = "application/json"
# headers["Content-Type"] = "application/json"
# let resp = curl.post("https://api.vpsfree.cz/_auth/token/tokens", headers, body = $data)
# -> returns 422/400, server never receives the body
#
# Equivalent curl command that works:
# curl -X POST https://api.vpsfree.cz/_auth/token/tokens \
# -H 'Content-Type: application/json' \
# -d '<data>'
# --- WORKS: standard httpclient ---
let client = newHttpClient()
client.headers = newHttpHeaders({
"Content-Type": "application/json",
"Accept": "application/json"
})
let resp = client.post("https://api.vpsfree.cz/_auth/token/tokens", body = $data)
echo resp.bodyWhat? Curly drops body on POST requests? I use that all the time. That can't be true!
I wrote a little CI test over here and it passes on all the three platforms curly supports. I would need more details to debug this further. What is your operating system version? Your curly version and your libcurl version?
cat /etc/os-release PRETTY_NAME="SparkyLinux 9 (Tiamat)" NAME="SparkyLinux" VERSION_ID="9" VERSION="9 (Tiamat)" ID=sparky ID_LIKE=debian HOME_URL="https://sparkylinux.org/" SUPPORT_URL="https://sparkylinux.org/forum/" BUG_REPORT_URL="https://sourceforge.net/p/sparkylinux/tickets" VERSION_CODENAME=tiamat DEBIAN_CODENAME=forky
curl --version curl 8.14.1 (x86_64-pc-linux-gnu) libcurl/8.14.1 OpenSSL/3.5.6 zlib/1.3.1 brotli/1.1.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.2 libssh2/1.11.1 nghttp2/1.64.0 nghttp3/1.8.0 librtmp/2.3 OpenLDAP/2.6.10 Release-Date: 2025-06-04, security patched: 8.14.1-2+deb13u3 Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp ws wss Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTP3 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd
import pkg/curly
import std/json
import std/httpclient
import std/strformat
import std/osproc
const url = "https://api.vpsfree.cz/_auth/token/tokens"
when defined(c):
import std/asyncdispatch
when defined(js):
import std/asyncjs
import std/jsfetch
when isMainModule:
let data = %*{
"token": {
"user": user,
"password": passwd,
"lifetime": "fixed"
}
}
block:
var headers = emptyHttpHeaders()
headers["Accept"] = "application/json"
headers["Content-Type"] = "application/json"
let curl = newCurly()
let resp = curl.post(url, headers, body = $data)
# -> returns 422/400, server never receives the body
echo resp.code
echo resp.body
# 500
# {"status":false,"response":null,"message":"Server error occurred","errors":{}}
block:
var headers = newHttpHeaders()
headers["Accept"] = "application/json"
headers["Content-Type"] = "application/json"
let client = newHttpClient()
let resp = client.request(url,
headers = headers,
body = $data,
httpMethod = HttpPost)
echo resp.code
echo resp.body
# 200 OK
# {"status":true,"response":{"token":{"token":"6fbc1a7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe10","valid_to":"2026-05-25T20:23:24Z","complete":true,"next_action":null},"_meta":{}},"message":null,"errors":null}
block:
let cmd = fmt"curl -X POST {url} -H 'Content-Type: application/json' -d '{data}'"
echo "Running command: ", cmd
let result = execCmd(cmd)
echo "Command output: ", result
#{"status":true,"response":{"token":#{"token":"dac149708261482xxxxxxxxxxxxxxxxxxx20ac242d3","valid_to":"2026-05-25T20:28:51Z","complete":true,"next_action":null},"_meta":{}},"message":null,"errors":null}
#Command output: 0I tried to reproduce this in a Linux Docker container with the same libcurl version/build line you posted. I do not have an official SparkyLinux Docker base image, so this is Debian trixie amd64 with the Sparky repo/key added and curl/libcurl pinned to 8.14.1-2+deb13u3.
I cannot reproduce the issue there. Curly sends the POST body and gets the same response shape as std/httpclient and the curl CLI.
Printout:
cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
VERSION="13 (trixie)"
VERSION_CODENAME=trixie
DEBIAN_VERSION_FULL=13.5
ID=debian
curl --version
curl 8.14.1 (x86_64-pc-linux-gnu) libcurl/8.14.1 OpenSSL/3.5.6 zlib/1.3.1 brotli/1.1.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.2 libssh2/1.11.1 nghttp2/1.64.0 nghttp3/1.8.0 librtmp/2.3 OpenLDAP/2.6.10
Release-Date: 2025-06-04, security patched: 8.14.1-2+deb13u3
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTP3 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd
Nim Compiler Version 2.2.10 [Linux: amd64]
libcurl loaded by Nim:
libcurl/8.14.1 OpenSSL/3.5.6 zlib/1.3.1 brotli/1.1.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.2 libssh2/1.11.1 nghttp2/1.64.0 nghttp3/1.8.0 librtmp/2.3 OpenLDAP/2.6.10
Curly:
200
{"status":false,"response":null,"message":"invalid user or password","errors":{}}
std/httpclient:
200 OK
{"status":false,"response":null,"message":"invalid user or password","errors":{}}
curl CLI:
{"status":false,"response":null,"message":"invalid user or password","errors":{}}
Command output: Make sure you have up to date version of curly and it has no changes.
One thing I would now check is proxy environment variables. libcurl honors http_proxy, https_proxy, HTTPS_PROXY, ALL_PROXY, and NO_PROXY by default, while Nim std/httpclient does not use those unless explicitly configured with newProxy.
Could you try:
env | sort | grep -iE '^(http|https|all|no)_proxy='
and also rerun with them cleared?
env -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY \
-u all_proxy -u ALL_PROXY -u no_proxy -u NO_PROXY \
nim r culrypostbug.nimSparky Linux uses Debian repositories directly. Debian is the base, SparkyLinux is a user-friendly overlay. The differences are negligible, and there are none in the libraries.
I found out that if I send an incorrect password to the vpsfree.org service via POST, both curl and the httpclient return the same response:
************** curly response ************** 200 {"status":false,"response":null,"message":"invalid user or password","errors":{}}
************** std/httpclient response ************** 200 OK {"status":false,"response":null,"message":"invalid user or password","errors":{}}
The problem only occurs when sending correct credentials. It's strange — the HTTP protocol is the same, I'm sending the same JSON and the same HTTP headers.
What's strange is that curly fails, while curl from the command line works. I don't know which version of libcurl curl is compiled with — curly is compiled with araq/libcurl, and that uses libcurl4.so, whereas curl is a pre-downloaded Debian binary. But that shouldn't be a problem, and most likely the library versions used are the same.
The error could be on the server I'm calling. But that would mean that curly must be sending a slightly different request than curl. Same headers, same JSON body. Or not?
$ env | sort | grep -iE '^(http|https|all|no)_proxy='
=> empty
env -u http_proxy -u https_proxy -u HTTP_PROXY -u HTTPS_PROXY \
-u all_proxy -u ALL_PROXY -u no_proxy -u NO_PROXY \
nim r culrypostbug.nim
=> teh some
************** curly response **************
500
{"status":false,"response":null,"message":"Server error occurred","errors":{}}
**********************************************
************** std/httpclient response **************
200 OK
{"status":true,"response":{"token":{"token":"c56a142b306219bdc7284","valid_to":"2026-05-27T03:24:16Z","complete":true,"next_action":null},"_meta":{}},"message":null,"errors":null}
*******************************************************
It's strange. I can work around the problem by using httpclient or by invoking curl. I use curly often and have never had an issue before. In this case, the server correctly processes a request with an incorrect password but does not accept a correct login. So most likely the server is reacting to some setting that curly adds. Recently, I was tinkering with libcurl and curly in Nim. I added a small fix to libcurl — added support for XferInfoCallback: https://github.com/Araq/libcurl/pull/9/changes/d2cb2430bcf1b6dc097519517165e4423edd5f5b.
But I don't know what to do about this, and...
Both httpclient and command-line curl send a User-Agent, while libcurl does not add a default User-Agent. Maybe try adding a User-Agent header?
headers["User-Agent"] = "curl/8.14.1" Solved. Yes, the missing headers["User-Agent"] header causes that error. The server is probably checking this header and fails when it's missing. Any non-empty string works: headers["User-Agent"] = "x".
So it's an error on the service's server side (possibly intentional, but not very well implemented).
Thank you for your help, and I'm sorry for causing you trouble and taking your time. I'm glad it's resolved — it was a mystery that kept me awake.
Thanks for your help and for all the packages you've created :-)
I am glad we could resolve this!
Could you change the name of the thread to not "error in curly". thanks!
Yes, I'd like to change the thread title, but I haven't found a way to edit it – I can only edit individual comments.
Could the entire thread be deleted? The only important takeaway is that curly does not add a default HTTP header by itself. That's correct behavior – you just need to be aware of it and not forget about it.