Overview
What is apiexec?
A vendor-agnostic streaming execution engine that turns unreliable API calls into a
deterministic, streaming data retrieval model. It handles retry, backoff, adaptive chunking,
pagination, and cost budgets internally - exposing a clean Stream<T> interface upward and
delegating API-specific logic to swappable VendorAdapter<T> implementations downward.
Common use cases:
- Paginating any REST API - point
generic_restat a cursor-based endpoint; apiexec drives the pagination loop, retries, and prefetch automatically - Streaming Datadog metrics -
datadog_metricshandles time-windowing, adaptive chunk sizing, and rate-limit backoff - AI inference with cost budgets -
openaiandanthropicadapters track token spend viaresponse_cost(); aCostAwarePolicyhalts the stream when the budget is reached
Adapters
Four adapters ship with the library. All are registered at process startup via static initialisers - no explicit registration call required.
| Adapter | Description |
|---|---|
generic_rest | Cursor-paginated REST APIs - works with any JSON endpoint that returns a next-page token |
datadog_metrics | Datadog Metrics API v1 - time-window queries with adaptive window sizing |
openai | OpenAI Chat Completions - streaming batch inference with token cost tracking |
anthropic | Anthropic Messages API - streaming batch inference with token cost tracking |
See Adapters for the full config key reference and example JSON configs.
Language Bindings
The C++ core is hidden behind an ABI-stable C header (c_api/c_api.h). Every language reaches
the engine through its native FFI mechanism:
| Language | Binding | Notes |
|---|---|---|
| C | Direct call | No overhead |
| C++ | Direct call | Can also include C++ headers directly |
| Go | cgo | import "github.com/voseghale/apiexec/bindings/go/apiexec" |
| Rust | extern "C" block | Safe wrapper crate at bindings/rust |
| Python | ctypes | Ships as a wheel with bundled .so |
| Java | JNA | com.apiexec.Stream try-with-resources |
| JavaScript | ffi-napi | Node.js; requires native addon build |
All bindings share the same semantics: one stream_create, one stream_destroy,
caller-owned buffers, and int32_t error codes. See Quick Start
for per-language setup instructions.
Design Goals
- ABI stability first. The C API header is the freeze point. Adding functionality means adding new versioned symbols - never mutating existing ones.
- Null-safe by contract. Every pointer parameter is null-checked before dereference. Callers get a well-defined error code, not a crash.
- Caller-owned memory. The C API never allocates buffers visible to the caller. All scratch space is passed in, so bindings control their own lifetimes.
- One-way ownership. Handles are created by the library and destroyed by a single dedicated function. No silent frees. No shared mutable state.
ABI Freeze Point
The C API header (c_api/c_api.h) is frozen after the initial release.
Any change to function signatures, struct layouts, or error code values
requires a new versioned symbol (e.g. stream_next_batch_v2). Callers
must not call a function whose ABI version they did not compile against.
Ownership Model at a Glance
#include "c_api.h"
const char* config = "{\"base_url\": \"https://api.example.com/v1/data\"}";
StreamHandle* h = stream_create("generic_rest", config, NULL);
if (!h) { /* out of memory or bad config */ }
char buf[1048576];
int32_t count = 0;
while (stream_has_next(h) == 1) {
int32_t rc = stream_next_batch_v1(h, buf, sizeof buf, &count);
if (rc != STREAM_OK) break;
printf("Got %d records: %s\n", count, buf);
}
stream_destroy(h); // the only valid deallocation pathA few things to notice:
stream_createreturns a heap-allocated handle. The caller owns it.stream_destroyis the only valid way to release a handle. Callingfree()directly is undefined behaviour.bufis caller-allocated. apiexec never allocates caller-visible memory.stream_destroy(NULL)andstream_cancel(NULL)are safe no-ops.
Metrics
apiexec tracks 11 runtime counters per stream (request count, retry count, error breakdown, window size, cumulative cost). The engine exposes them via a callback and a Prometheus text formatter - no metrics library dependency required. See Metrics.
Where To Go Next
- Quick Start - build, link, and write your first stream in any language
- Architecture - understand the
VendorAdapter/Stream<T>/ C ABI model - Adapters - config keys and examples for all four built-in adapters
- Stream API - full function table, error codes, and null-safety contract
- Metrics - runtime observability, Prometheus export, and language binding support
- Examples - real-world patterns across all languages
