Zig SDK
UnpublishedAllocator-first; blocking; explicit error unions. Requires Zig 0.14.
Not yet published
Initialize
init takes an allocator first and your API key; it returns an error union (an empty key gives error.Validation). Tear down with close() then deinit().
const std = @import("std");
const counters = @import("counters");
var client = try counters.CountersClient.init(
allocator,
std.posix.getenv("COUNTERS_API_KEY").?,
.{},
);
defer client.deinit();
defer client.close() catch {};Count
add and subtract are buffered and coalesced per counter into one POST /batch; addNow/subtractNow apply immediately and return a std.json.Parsed(Counter) you must deinit(). Amounts are a tagged union: .{ .int = 5 }, .{ .str = ... }, or .{ .big = ... }.
var reg = try client.counter("registrations");
try reg.add(.{ .int = 1 }); // buffered
try reg.add(.{ .int = 5 }); // coalesced
try client.flush(); // one POST /batch
const parsed = try reg.addNow(.{ .int = 1 }); // immediate
defer parsed.deinit();
std.debug.print("{s}", .{parsed.value.value}); // "42"Read
Reads return a std.json.Parsed(T) — deinit it when done. SeriesParams takes RFC 3339 strings. Read a value via parsed.value.value.
const v = try reg.value();
defer v.deinit();
// v.value.value is the current value string
const series = try reg.series(.{
.from = "2026-01-01T00:00:00Z",
.to = "2026-01-08T00:00:00Z",
.bucket = "1d",
});
defer series.deinit();Manage
clear resets to zero (new epoch, history retained); delete tombstones the counter; list pages the counters in the organization. Parsed results must be deinit'd.
const cleared = try reg.clear();
defer cleared.deinit();
try reg.delete();
const page = try client.list(null, 50); // cursor, limit
defer page.deinit();Lifecycle
Two-step teardown: close() flushes and stops the background worker thread, then deinit() frees memory. With LIFO defers, defer deinit() before defer close() runs close first.
defer client.deinit();
defer client.close() catch {};Errors
Functions return a Zig error set: error.Validation, error.Api, error.Transport (plus error.OutOfMemory, error.InvalidResponse). Zig errors carry no payload — read status and title from client.lastError() immediately after an error.Api, before the next call.
const parsed = reg.addNow(.{ .int = 1 }) catch |err| switch (err) {
error.Api => {
const info = client.lastError().?;
std.debug.print("api error {d}", .{info.status});
return err;
},
else => return err,
};
defer parsed.deinit();