API Client Examples
Reference implementations for calling the Kibot API from eight environments, curl, Python, Rust, Go, TypeScript (Node.js), R, C++, and .NET (C# and VB.NET), covering the same minimal flow: log in as the guest account, request ten days of MSFT daily data, ensure the gzip response is decompressed, and write the result to a local CSV file. Kibot publishes official source for the .NET versions; the others are idiomatic adaptations using the libraries each language community treats as the current default for HTTP work in 2024–2025. Where it helps, each example also notes how to switch to the skip-login pattern so a single GET handles authentication and data retrieval together.
What every example does
Each snippet performs the same four steps:
- Logs in to
api.kibot.comas the guest account (user=guest&password=guest). - Requests the last ten days of daily bars for MSFT.
- Decompresses the gzip response transparently.
- Writes the decompressed CSV to a local file.
The login response is always 200 OK followed by the authorization list; the data response is plain CSV (Date,Open,High,Low,Close,Volume). Either step can fail with the codes documented in Server responses, production code should branch on the HTTP status, not assume success.
Which sample to start with
The snippets below all do the same thing, but each is tuned to a different starting point. Use this map to pick the right one for the job at hand.
| Sample | Best for | What it demonstrates beyond the basic flow |
|---|---|---|
| curl | Spot-checking the API, ops scripts, ad-hoc downloads | Cookie jar via -c/-b; the one-line skip-login form |
requests (Python) |
Batch downloaders, research notebooks, ETL pipelines | Session cookie reuse, streamed writes for tick-sized bodies |
reqwest (Rust) |
Async services, latency-sensitive backends | Feature flags for cookies/gzip/stream; error_for_status |
net/http (Go) |
Long-running daemons, container workloads | Standard-library cookiejar; the implicit-gzip gotcha |
undici (Node.js TypeScript) |
Web backends already on Node | tough-cookie integration; pipeline for backpressure |
httr2 (R) |
Academic and buy-side quants pulling into RStudio | req_perform(path = ...) streams straight to disk |
| libcurl (C++) | HFT and other latency-bounded shops | Handle reuse across calls, minimal allocations |
| VB.NET / C# (.NET) | Windows-hosted pipelines, GUI downloaders | The official Kibot reference; AutomaticDecompression + Kibot API Client scaffold |
If you have no preference, start with curl to verify your account works, then move to the Python or .NET sample depending on the surrounding codebase.
curl (one-liner for testing)
The simplest possible client. Useful for spot-checking API behaviour without writing any code.
# Login (cookie jar holds the session)
curl -c kibot.cookies "http://api.kibot.com/?action=login&user=guest&password=guest"
# Fetch, --compressed handles gzip automatically
curl -b kibot.cookies --compressed \
"http://api.kibot.com/?action=history&symbol=MSFT&interval=daily&period=10" \
-o MSFT.csv
-c writes Kibot's session cookie to a file, -b reads it back on the next call, and --compressed advertises Accept-Encoding: gzip,deflate and decompresses the response in place. To skip session management entirely, collapse to one call:
curl --compressed \
"http://api.kibot.com/?action=history&symbol=MSFT&interval=daily&period=10&user=guest&password=guest" \
-o MSFT.csv
Python (requests)
requests remains the default HTTP library for Python despite httpx being newer; its Session object handles cookies and connection reuse automatically and decompresses gzip transparently when the server advertises it (per the official requests docs). For tick-sized downloads use stream=True plus iter_content so the body never sits in memory all at once.
import requests
API = "http://api.kibot.com/"
with requests.Session() as s:
# Login, Session() carries the cookie forward
r = s.get(API, params={"action": "login", "user": "guest", "password": "guest"})
r.raise_for_status()
if not r.text.startswith("200"):
raise RuntimeError(f"Login failed: {r.text}")
# Fetch, stream=True keeps memory flat for big tick responses
with s.get(API, params={"action": "history", "symbol": "MSFT",
"interval": "daily", "period": 10}, stream=True) as r:
r.raise_for_status()
with open("MSFT.csv", "wb") as f:
for chunk in r.iter_content(chunk_size=64 * 1024):
f.write(chunk)
For tick downloads, raise the chunk size to 256 KB or 1 MB. The response body is already CSV, no parsing library is needed unless the consumer wants pandas.read_csv("MSFT.csv", header=None) to land bars as a DataFrame.
To skip session management entirely, drop the login call and add user/password to the data params:
r = requests.get(API, params={"action": "history", "symbol": "MSFT",
"interval": "daily", "period": 10,
"user": "guest", "password": "guest"})
Rust (reqwest)
reqwest is the de facto async HTTP client for Rust (per the reqwest README); enable the cookies, gzip, and stream features for the full Kibot surface.
Cargo.toml:
[dependencies]
reqwest = { version = "0.12", features = ["cookies", "gzip", "stream"] }
tokio = { version = "1", features = ["full"] }
futures-util = "0.3"
use futures_util::StreamExt;
use std::error::Error;
use tokio::{fs::File, io::AsyncWriteExt};
const API: &str = "http://api.kibot.com/";
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = reqwest::Client::builder()
.cookie_store(true)
.gzip(true)
.build()?;
// Login
let body = client.get(API)
.query(&[("action", "login"), ("user", "guest"), ("password", "guest")])
.send().await?.text().await?;
if !body.starts_with("200") {
return Err(format!("Login failed: {body}").into());
}
// Fetch, stream chunks straight to disk
let resp = client.get(API)
.query(&[("action", "history"), ("symbol", "MSFT"),
("interval", "daily"), ("period", "10")])
.send().await?
.error_for_status()?;
let mut file = File::create("MSFT.csv").await?;
let mut stream = resp.bytes_stream();
while let Some(chunk) = stream.next().await {
file.write_all(&chunk?).await?;
}
Ok(())
}
The cookie_store(true) flag tells reqwest to capture and replay Kibot's session cookie automatically. gzip(true) asks the server for compression and decompresses transparently. bytes_stream() plus tokio::io::AsyncWriteExt keeps memory flat for tick downloads.
Go (net/http standard library)
The standard library is the right choice for Kibot calls, net/http ships with a cookie jar in net/http/cookiejar and decompresses gzip automatically as long as the request does not set Accept-Encoding explicitly. (When the caller sets the header by hand, Go assumes the caller wants raw bytes and turns transparent decompression off, a documented gotcha in the net/http docs.)
package main
import (
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"strings"
)
const API = "http://api.kibot.com/"
func main() {
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
// Login
q := url.Values{"action": {"login"}, "user": {"guest"}, "password": {"guest"}}
resp, err := client.Get(API + "?" + q.Encode())
must(err)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if !strings.HasPrefix(string(body), "200") {
panic("login failed: " + string(body))
}
// Fetch, stream straight to disk, gzip handled by the transport
q = url.Values{"action": {"history"}, "symbol": {"MSFT"},
"interval": {"daily"}, "period": {"10"}}
resp, err = client.Get(API + "?" + q.Encode())
must(err)
defer resp.Body.Close()
f, err := os.Create("MSFT.csv")
must(err)
defer f.Close()
_, err = io.Copy(f, resp.Body)
must(err)
fmt.Println("done")
}
func must(err error) { if err != nil { panic(err) } }
If for some reason the caller needs to set Accept-Encoding explicitly (rare, Go does it for you), wrap resp.Body in a gzip.NewReader from compress/gzip before reading.
TypeScript (Node.js with undici)
undici is Node's modern fetch-first HTTP client and outperforms node-fetch, axios, and the legacy http module on large responses (per the undici benchmarks). For session cookies, pair it with the tough-cookie library that Node's standard cookie tooling builds on.
package.json:
{
"type": "module",
"dependencies": { "undici": "^7.0.0", "tough-cookie": "^5.0.0" }
}
import { request } from "undici";
import { CookieJar } from "tough-cookie";
import { createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
const API = "http://api.kibot.com/";
const jar = new CookieJar();
async function call(qs: Record<string, string>) {
const url = new URL(API);
Object.entries(qs).forEach(([k, v]) => url.searchParams.set(k, v));
const cookie = await jar.getCookieString(url.href);
const r = await request(url, {
headers: { cookie, "accept-encoding": "gzip,deflate" },
});
for (const c of [].concat(r.headers["set-cookie"] || [])) {
await jar.setCookie(c, url.href);
}
return r;
}
// Login
const login = await call({ action: "login", user: "guest", password: "guest" });
const text = await login.body.text();
if (!text.startsWith("200")) throw new Error(`Login failed: ${text}`);
// Fetch, pipeline streams the body directly to disk
const data = await call({ action: "history", symbol: "MSFT",
interval: "daily", period: "10" });
await pipeline(data.body, createWriteStream("MSFT.csv"));
undici automatically decompresses gzip when the response carries Content-Encoding: gzip, no extra flag needed. The pipeline helper backpressures the stream to disk so even multi-hundred-MB tick responses stay bounded in memory.
R (httr2)
httr2 is the modern successor to httr and is what the R community recommends for new code; it pipes cleanly with |>, persists cookies via its own request object chain, and decompresses gzip automatically (per the httr2 reference). R is the lingua franca of academic and buy-side quant research, so a working example matters even though the codebase is smaller than Python's.
library(httr2)
API <- "http://api.kibot.com/"
# Login
login_resp <- request(API) |>
req_url_query(action = "login", user = "guest", password = "guest") |>
req_perform()
stopifnot(startsWith(resp_body_string(login_resp), "200"))
# Fetch, write_disk streams to a file; httr2 reuses the session via cookies
request(API) |>
req_url_query(action = "history", symbol = "MSFT",
interval = "daily", period = 10L) |>
req_perform(path = "MSFT.csv")
bars <- read.csv("MSFT.csv", header = FALSE,
col.names = c("date", "open", "high", "low", "close", "volume"))
head(bars)
The key flag worth knowing: req_perform(path = ...) writes the response straight to disk without buffering, the right choice for tick data. httr2 handles the gzip Accept-Encoding/Content-Encoding round trip transparently. To skip the explicit login, append user/password to the data query and drop the first call.
C++ (libcurl)
libcurl is the universal HTTP library for C++ and is standard inside HFT shops where minimal-allocation, latency-bounded HTTP matters. It handles gzip transparently when CURLOPT_ACCEPT_ENCODING is set and persists cookies through CURLOPT_COOKIEFILE / CURLOPT_COOKIEJAR.
#include <curl/curl.h>
#include <cstdio>
#include <stdexcept>
#include <string>
constexpr auto API = "http://api.kibot.com/";
static size_t to_file(void *ptr, size_t size, size_t nmemb, void *fp) {
return std::fwrite(ptr, size, nmemb, static_cast<FILE *>(fp));
}
static size_t to_string(void *ptr, size_t size, size_t nmemb, void *s) {
static_cast<std::string *>(s)->append(static_cast<char *>(ptr), size * nmemb);
return size * nmemb;
}
int main() {
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL *c = curl_easy_init();
if (!c) throw std::runtime_error("curl init failed");
// Cookie persistence: same file is read on each call and written on cleanup
curl_easy_setopt(c, CURLOPT_COOKIEFILE, "kibot.cookies");
curl_easy_setopt(c, CURLOPT_COOKIEJAR, "kibot.cookies");
curl_easy_setopt(c, CURLOPT_ACCEPT_ENCODING, ""); // empty = "all supported" (gzip+deflate)
// Login
std::string login_body;
curl_easy_setopt(c, CURLOPT_URL,
(std::string(API) + "?action=login&user=guest&password=guest").c_str());
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, to_string);
curl_easy_setopt(c, CURLOPT_WRITEDATA, &login_body);
if (curl_easy_perform(c) != CURLE_OK || login_body.rfind("200", 0) != 0)
throw std::runtime_error("Kibot login failed: " + login_body);
// Fetch, write straight to disk
FILE *out = std::fopen("MSFT.csv", "wb");
curl_easy_setopt(c, CURLOPT_URL,
(std::string(API) + "?action=history&symbol=MSFT&interval=daily&period=10").c_str());
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, to_file);
curl_easy_setopt(c, CURLOPT_WRITEDATA, out);
if (curl_easy_perform(c) != CURLE_OK)
throw std::runtime_error("Kibot fetch failed");
std::fclose(out);
curl_easy_cleanup(c);
curl_global_cleanup();
}
Two libcurl idioms worth highlighting. Setting CURLOPT_ACCEPT_ENCODING to the empty string "" advertises every encoding libcurl knows how to decompress, almost always the right choice. The same handle is reused for both the login and the fetch so connection state, TLS session, and cookie jar all carry over with zero extra setup.
.NET, Kibot's official samples
Kibot publishes the canonical reference samples for .NET in both VB.NET and C#. They are the highest-fidelity reference because they ship from Kibot itself and underpin the open-source Kibot API Client, the .NET application Kibot offers as a complete starting point for building custom downloaders (full breakdown of source, binaries, and Visual Studio project layout on that page).
The flow is the same as every other example on this page: login, check the body starts with 200, fetch with Accept-Encoding advertising gzip+deflate, copy the response stream to a local file. The C# version:
private void Download()
{
string urlLogin = "http://api.kibot.com?action=login&user=guest&password=guest";
string urlData = "http://api.kibot.com?action=history&symbol=msft&interval=daily&period=10";
string fileName = "c:\\MSFT.txt";
string userAgent = "Your application name or id";
// Login
var req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(urlLogin);
req.UserAgent = userAgent;
using (var resp = req.GetResponse())
using (var sr = new System.IO.StreamReader(resp.GetResponseStream(), System.Text.Encoding.ASCII))
{
var text = sr.ReadToEnd();
if (!text.StartsWith("200"))
throw new Exception("Cannot login to the server. " + text);
}
// Fetch, AutomaticDecompression handles gzip transparently
if (System.IO.File.Exists(fileName)) System.IO.File.Delete(fileName);
req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(urlData);
req.AutomaticDecompression =
System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip;
req.Headers.Add("Accept-Encoding", "gzip,deflate");
req.UserAgent = userAgent;
using (var fs = System.IO.File.Create(fileName))
using (var resp = req.GetResponse())
using (var stream = resp.GetResponseStream())
{
var buffer = new byte[4 * 1024];
int n;
while ((n = stream.Read(buffer, 0, buffer.Length)) > 0)
fs.Write(buffer, 0, n);
}
}
The corresponding VB.NET version is line-for-line equivalent and is included in the official source-code-examples page; both are intended to be pasted directly into a .NET project as a starting point. The two crucial flags are AutomaticDecompression plus the explicit Accept-Encoding header, without both, the response arrives as opaque gzip bytes that the consumer would have to decompress by hand.
For modern .NET 6+ codebases, HttpWebRequest has been superseded by HttpClient; the equivalent two lines are var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, UseCookies = true }; and var client = new HttpClient(handler);. Functionally the call sequence is identical to the C# example above and to every other example on this page.
Open-source .NET reference client
Beyond the snippets, Kibot ships a complete open-source .NET application called Kibot API Client. The project page exposes both the source code (VB.NET and C# Visual Studio projects in 2008 and 2010 formats) and a pre-built binary, with a separate portable standalone .exe for users without admin rights. The application "demonstrates the communication between the client application and our servers" and "contains all the source code required for authentication and download of historical data". For anyone building a Windows GUI downloader, it is the right starting point, the included source covers session management, gzip handling, and the per-symbol authorization flow with no third-party dependencies beyond the .NET base class libraries. See Kibot API Client (.NET) for the full project breakdown, runtime compatibility (Framework 2.0 forward) and common extension paths.
Common patterns across all examples
A few notes apply equally to every language above:
Always advertise gzip. The savings are 5–10× on tick and intraday bodies. Most modern HTTP clients do this by default, verify yours does, and explicitly set Accept-Encoding: gzip,deflate if not. Server-side compression details live in Server responses.
Login once, not per request. The cost of an extra round trip per symbol is roughly the cost of a small data fetch, for batch jobs over hundreds of symbols, login-per-symbol roughly doubles wall-clock time. The patterns and the 401 Not Logged In retry logic are documented in API authentication.
Stream large responses to disk. Tick data can exceed 100 MB uncompressed for a single symbol-week. Every example above streams chunks rather than buffering the whole body, iter_content in Python, bytes_stream in Rust, io.Copy in Go, pipeline in Node, req_perform(path=...) in R, WRITEFUNCTION in libcurl, and Stream.Read in .NET.
Branch on HTTP status. A non-200 code is information, not an exception. The full code list lives in Server responses; the most actionable codes are 401 Not Logged In (re-login and retry), 402 Unauthorized (no entitlement to this symbol), 403 Login Failed (bad credentials or rate limit, disambiguate by request rate), 404 Symbol Not Found (typo or wrong &type=), and 405 Data Not Found (no data in the requested window).
Set a UserAgent. Kibot's own sample code sets a UserAgent string explicitly; doing the same makes it easier for support to identify your traffic in logs if you ever need to file a ticket.
Production patterns from support tickets
The minimal samples above get a single MSFT fetch working. Three patterns surface again and again in Kibot support tickets and are worth folding into any client that will run unattended.
Retry on 401 Not Logged In, not on every call
A login establishes a session that stays valid for 20 minutes of inactivity. Calling login before every history request roughly doubles wall-clock time on batch jobs because every symbol now costs two HTTP round trips. The canonical pattern, used by Kibot's own Agent software, is: log in once at start of run; on each data fetch, check for 401 Not Logged In; if seen, re-login and retry the fetch once; otherwise proceed. A real exchange from support captures this well: "Once you login, the server waits for 20 minutes of inactivity before it logs you out. In practice, you only need to login once before you start downloading data and as long as you do not pause or stop the download process you will be able to access API without any problems."
Python sketch:
def fetch(session, params, max_retries=2):
for attempt in range(max_retries):
r = session.get(API, params=params)
if r.status_code == 200 and not r.text.startswith("401"):
return r
# 401 Not Logged In, re-login and retry once
login(session)
raise RuntimeError(f"Fetch failed after {max_retries} tries")
Note the body-prefix check: Kibot's status codes are emitted both as the HTTP status and as a leading token in the response body (200 OK ..., 401 Not Logged In ...). Some intermediaries normalise the status code, so parsing the body is the more robust path.
Retry on transient pool errors
api.kibot.com resolves to a fleet of physical servers behind one address; a single request can land on a back-end that is momentarily unhealthy while the rest of the fleet is fine. Support's standing advice: "instruct your code to watch for errors and repeat the request if you detect one. This will make your download process more robust. We are using the same logic in our Kibot Agent software." A sleep of ~10 seconds between retries is the figure that Kibot's own tooling uses; shorter delays risk pinning the retry to the same misbehaving back-end. Two retries is normally enough, past that, raise the failure to the caller and log the symbol for a later pass.
Cap parallelism at five connections
The hard server-side limit is higher (historically 20 new connections per second or 40 concurrent), but the practical sweet spot is much lower. Support's recommendation, repeated across many tickets: "My recommendation is to work with maximum 5 connections. Going above that will decrease hard drive performance and result in slower processing times." The reasoning combines server- and client-side bottlenecks, beyond five concurrent fetches, the local SSD/NVME stack and the per-back-end rate guards each start adding latency that more parallelism cannot overcome. Use a bounded thread pool or async semaphore sized to 5; do not unleash one task per CPU core.
Gzip stripping by proxies and antivirus
If a previously-working integration suddenly returns uncompressed responses (or returns bytes that look like garbled CSV), the cause is almost always a middlebox on the caller's side. From a 2018 ticket: "It looks like the antivirus protection or a proxy server on your side may be causing this. Gzipping is probably applied by the web server but antivirus trying to protect http connections unzips server response, checks it and after that rewrites response headers on the fly." The check is straightforward: capture the response headers with the same client you use in production. If Content-Encoding: gzip is missing while Vary: Accept-Encoding is present, a middlebox has decompressed and re-emitted the response. The fix is on the caller's network, not in the client code.
The skip-login shortcut as a fallback
For stateless callers, serverless functions, and cron jobs where session management is overhead rather than optimisation, the skip-login pattern collapses every example on this page to a single GET, append &user=<email>&password=<pwd> to the data URL and drop the explicit login call. Support has pointed users at this exact pattern when stale-session errors appeared sporadically: "In order to simplify login/logout procedure and prevent the 'not logged in' error, just add your user name and password to the end of your URLs: &user=[username]&password=[password]." Each request is independent, so a transient 401 simply fails one fetch instead of poisoning the whole batch. The trade-off is one extra round-trip's worth of credential parsing per request; for chatty clients that is real overhead, for batch jobs it is negligible.
Streaming and timeouts for tick data
A single symbol-week of tick data can exceed 100 MB uncompressed; year-scale fetches can take minutes per symbol over a slow link. Set generous read timeouts (3600 s is not unreasonable for tick) and always stream chunks to disk rather than buffering the full body. A Python user shipping a working bid/ask downloader in 2025 ran the equivalent of requests.get(url, timeout=(10, 3600)), a 10-second connect timeout, a 1-hour read timeout, and that combination handled the full range of intervals without spurious failures.
Key Takeaways
- Eight idiomatic clients, curl, Python, Rust, Go, TypeScript, R, C++, .NET, all execute the same login-then-fetch flow with their language community's current default HTTP library.
- The two non-negotiable flags in every implementation are (a) gzip advertising/decompression and (b) cookie or session persistence so the login carries forward to data calls.
- For the four user-requested languages, the current defaults are
requests(Python),reqwestwithcookies+gzip+streamfeatures (Rust),net/httpwithcookiejar(Go), andundiciwithtough-cookie(Node.js TypeScript). - The two extra languages worth knowing are R (
httr2) for academic/buy-side quants and C++ (libcurl) for HFT and latency-bounded shops; both have minimal idiomatic call shapes shown above. - Kibot's official .NET samples (VB.NET and C#) are the canonical reference and underpin the open-source Kibot API Client application; modern .NET 6+ should prefer
HttpClientwithHttpClientHandler { AutomaticDecompression = ... }. - The "skip login" pattern (
&user=...&password=...on every data URL) reduces every example above to a single GET, useful for serverless and ad-hoc use cases.
Related
- API authentication, Login flow, session timeout, and the skip-login shortcut.
- History request, The
?action=historyparameters the data fetch in every example uses. - Server responses, HTTP status codes the examples should branch on, plus gzip negotiation details.
- API overview, Where these clients fit in Kibot's broader API model.
- Kibot agent, The free GUI built on top of these same endpoints.