Skip to content

SDKs

Venturi ships and maintains first-party, fully typed SDKs for TypeScript and Python. Both are generated from the same OpenAPI 3.1 document that defines the REST API, so the client surface always matches the contract. Each SDK builds in the platform conventions you would otherwise implement by hand: token refresh, cursor-pagination iterators, idempotency-key generation, Retry-After-aware backoff, and typed problem+json errors.

Generated, never hand-written

The SDKs are regenerated from each released OpenAPI version and pinned for reproducible builds. The generated client cannot drift from the published contract — a mismatch fails CI — so the methods, models, and error types you use are exactly the API's.

Install

npm install @venturi/sdk
pip install venturi

Configure the client

The base URL is your Venturi instance, since the platform runs inside your trust boundary. Supply a token (or API key) and the client manages refresh.

import { Venturi } from "@venturi/sdk";

const venturi = new Venturi({
  baseUrl: "https://<your-venturi-instance>",
  clientId: process.env.ARGMIN_CLIENT_ID,
  clientSecret: process.env.ARGMIN_CLIENT_SECRET,
  // or: apiKey: process.env.ARGMIN_API_KEY
});
from venturi import Venturi

venturi = Venturi(
    base_url="https://<your-venturi-instance>",
    client_id=os.environ["ARGMIN_CLIENT_ID"],
    client_secret=os.environ["ARGMIN_CLIENT_SECRET"],
    # or: api_key=os.environ["ARGMIN_API_KEY"]
)

Reading attribution

Every record carries the interpretation evidence card — stage_origin, confidence, confidence_band, evidence_basis, model_version, degradation_state, freshness, and export_eligibility — fully typed.

const record = await venturi.attribution.get("attr_01HF8...");
console.log(record.costUsd, record.interpretation.confidenceBand);

if (record.interpretation.exportEligibility === "eligible") {
  // safe to include in chargeback
}
record = venturi.attribution.get("attr_01HF8...")
print(record.cost_usd, record.interpretation.confidence_band)

if record.interpretation.export_eligibility == "eligible":
    ...  # safe to include in chargeback

Pagination — transparent iterators

Paginated resources expose an async iterator that follows cursors for you. You never construct, sign, or carry a cursor by hand.

for await (const record of venturi.attribution.list({
  filter: { provider: { eq: "openai" } },
  sort: "-costUsd",
})) {
  console.log(record.id, record.costUsd);
}
for record in venturi.attribution.list(
    filter={"provider": {"eq": "openai"}},
    sort="-cost_usd",
):
    print(record.id, record.cost_usd)

The iterator walks next_cursor to exhaustion, returning each record exactly once. See pagination for the underlying contract.

Idempotency — automatic

The SDK attaches a fresh Idempotency-Key to every mutating call by default, so a transient network retry never creates a duplicate. Pass your own key to make a specific operation safe to retry across process restarts.

const job = await venturi.exports.create(
  { format: "focus", period: { from: "2026-05-01", to: "2026-05-31" } },
  { idempotencyKey: "monthly-2026-05" }, // stable across retries
);
job = venturi.exports.create(
    format="focus",
    period={"from": "2026-05-01", "to": "2026-05-31"},
    idempotency_key="monthly-2026-05",  # stable across retries
)

Rate limits and backoff — automatic

On a 429, the SDK honors Retry-After and retries with jittered backoff. After exhausting retries it raises a typed RateLimitError rather than a bare HTTP error, so you can branch deliberately.

import { RateLimitError } from "@venturi/sdk";

try {
  await venturi.exports.create({ format: "focus", period });
} catch (err) {
  if (err instanceof RateLimitError) {
    // backoff exhausted — slow down or queue
  }
}
from venturi import RateLimitError

try:
    venturi.exports.create(format="focus", period=period)
except RateLimitError:
    ...  # backoff exhausted — slow down or queue

Typed errors

Errors are deserialized into a typed union derived from the published error catalog. Branch on the error_code, not on prose.

import { VenturiError } from "@venturi/sdk";

try {
  await venturi.exports.create(input);
} catch (err) {
  if (err instanceof VenturiError && err.errorCode === "INSUFFICIENT_SCOPE") {
    // this credential cannot create exports
  }
}
from venturi import VenturiError

try:
    venturi.exports.create(**input)
except VenturiError as err:
    if err.error_code == "INSUFFICIENT_SCOPE":
        ...  # this credential cannot create exports

Verifying webhooks

Each SDK ships a signature-verification helper so you do not reimplement the HMAC check. See webhooks.

import { verifyWebhook } from "@venturi/sdk";

app.post("/venturi", (req, res) => {
  const event = verifyWebhook(req.rawBody, req.headers["venturi-signature"], secret);
  // throws on bad signature or timestamp skew
  handle(event);
  res.sendStatus(200);
});
from venturi.webhooks import verify_webhook

@app.post("/venturi")
def handle(request):
    event = verify_webhook(request.body, request.headers["Venturi-Signature"], secret)
    # raises on bad signature or timestamp skew
    process(event)
    return "", 200

Sample apps

Each SDK publishes runnable reference apps you can clone and point at the sandbox:

  • Chargeback exporter — generates a FOCUS export and downloads the result.
  • Webhook receiver — verifies signatures and processes events.
  • Anomaly-alert forwarder — relays anomaly.detected events to your channel.

Versioning

SDK major versions track API major versions. A breaking API change ships alongside a coordinated new SDK major version, and the changelog records the mapping. Within a major version, upgrades are additive and safe. See versioning and rate limits.