Zig SDK

Unpublished

Allocator-first; blocking; explicit error unions. Requires Zig 0.14.

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().

main.zig
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 = ... }.

count
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.

read
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.

manage
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.

lifecycle
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.

errors
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();
Prefer raw HTTP? See the REST API reference. Create a key in your dashboard.