npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

levelfour

v1.8.0

Published

The official TypeScript SDK for the LevelFour cloud cost optimization API

Downloads

1,883

Readme

LevelFour Node SDK

npm Downloads

The official TypeScript SDK for the LevelFour API.

Documentation

Full API reference at docs.levelfour.ai.

Requirements

Node.js >=22. TypeScript >=5.0 for type-correct usage.

This SDK targets Node.js only. Deno, Bun, Cloudflare Workers, and browser environments are not supported.

Installation

npm install levelfour

Usage

Set LEVELFOUR_API_KEY in your environment, or pass the key directly:

import LevelFour from "levelfour";

const client = new LevelFour({ apiKey: "l4_live_..." });
// Or use LEVELFOUR_API_KEY env var:
const client = new LevelFour();

const summary = await client.recommendations.getSavingsByProvider();
console.log(summary);

Named import also works:

import { LevelFourClient } from "levelfour";
const client = new LevelFourClient();

Importing Types

Request and response types are available through the LevelFour namespace:

import { LevelFour } from "levelfour";
type Item = LevelFour.RecommendationItem;
type Detail = LevelFour.RecommendationDetailResponse;

Or as direct imports:

import type { LevelFour } from "levelfour";
const handle = (item: LevelFour.RecommendationItem) => { /* ... */ };

Cost alerts

client.costAlerts creates, changes, pauses and deletes the alerts that watch an account or a saved cost view. It ships in 0.2.0, and writes need a read-write key.

import LevelFour from "levelfour";

const client = new LevelFour();
const monthly = (budget: number) => ({
    kind: "budget" as const,
    budget,
    reset: "monthly" as const,
    rules: [{ amount: 80, unit: "percent" as const }],
});

const created = await client.costAlerts.createAlert({
    name: "Production budget",
    source_id: "123456789012", // an id from client.costAlerts.listSources()
    params: monthly(40000),
    recipients: [{ name: "Ana", email: "[email protected]", member: true }],
});
const alert_id = created.data.id;

// Only the fields you pass change. params replaces the whole rule, so send it back with the new figure.
await client.costAlerts.updateAlert({ alert_id, params: monthly(50000) });

await client.costAlerts.updateAlert({ alert_id, enabled: false }); // pause
await client.costAlerts.updateAlert({ alert_id, enabled: true }); // resume

// Deleting takes the alert's history with it. Pausing keeps it.
await client.costAlerts.deleteAlert({ alert_id });

Error Handling

import { LevelFourClient, NotFoundError, RateLimitError, LevelFourError } from "levelfour";

const client = new LevelFourClient();
try {
    const rec = await client.recommendations.get({ recommendation_id: "abc" });
} catch (err) {
    if (err instanceof NotFoundError) {
        console.log("Recommendation not found");
    } else if (err instanceof RateLimitError) {
        console.log("Rate limited");
    } else if (err instanceof LevelFourError) {
        console.log(`${err.statusCode}: ${err.message}`);
    }
}

Available errors: BadRequestError (400), AuthenticationError (401), AuthorizationError (403), NotFoundError (404), ConflictError (409), ValidationError (422), RateLimitError (429), InternalServerError (500).

Network-level failures (DNS, TCP, TLS) throw LevelFourConnectionError. Timeouts throw LevelFourTimeoutError. Both extend LevelFourError, so instanceof LevelFourError catches all SDK errors:

import { LevelFourClient, LevelFourError, LevelFourConnectionError, LevelFourTimeoutError } from "levelfour";

try {
    await client.recommendations.list();
} catch (err) {
    if (err instanceof LevelFourTimeoutError) {
        console.error("Request timed out");
    } else if (err instanceof LevelFourConnectionError) {
        console.error("Connection failed:", err.message);
    } else if (err instanceof LevelFourError) {
        console.error(`API error ${err.statusCode}:`, err.message);
    }
}

Configuration

const client = new LevelFourClient({
    apiKey: "l4_live_...",
    baseURL: "https://custom.api.com",
    timeoutInSeconds: 60,
    maxRetries: 5,
    defaultHeaders: { "X-Custom": "value" },
    logging: { level: "debug", silent: false },
});

| Option | Default | Description | |--------|---------|-------------| | apiKey | LEVELFOUR_API_KEY env var | API key | | baseURL | https://api.levelfour.ai | API base URL | | timeoutInSeconds | 30 | Request timeout | | maxRetries | 2 | Retry attempts for 429, 408, 409, 5xx | | fetch | globalThis.fetch | Custom fetch implementation | | defaultHeaders | none | Additional headers on every request | | logging | { silent: true } | Logger configuration |

Custom Fetch

Pass a fetch-compatible function for proxies, interceptors, or custom TLS:

import { LevelFourClient } from "levelfour";
import { ProxyAgent, fetch as undiciFetch } from "undici";

const dispatcher = new ProxyAgent("http://proxy.corp.example.com:3128");

const client = new LevelFourClient({
    fetch: (url, init) => undiciFetch(url, { ...init, dispatcher } as any),
});

Logging

The SDK accepts a logging option to control log output. Logging is silent by default.

const client = new LevelFourClient({
    logging: { level: "debug", silent: false },
});

| Level | Output | |-------|--------| | "debug" | Request URL, method, headers, retry attempts | | "warn" | Warnings only | | "error" | Errors only |

Per-Request Options

Typed resource methods accept per-request options:

const result = await client.recommendations.list({
    timeoutInSeconds: 10,
    maxRetries: 0,
    abortSignal: controller.signal,
});

Raw HTTP methods accept an options object as the second argument:

await client.get("/api/v1/resource", {
    headers: { "X-Idempotency-Key": "unique-key" },
    timeoutInSeconds: 10,
    maxRetries: 0,
});

Retries

All API calls automatically retry failed requests with exponential backoff, jitter, and Retry-After header support. By default, retries up to 2 times on:

  • 429 Too Many Requests (respects Retry-After header)
  • 408 Request Timeout
  • 409 Conflict
  • 5xx Server Errors
const client = new LevelFourClient({ maxRetries: 5 });
// or disable entirely:
const client = new LevelFourClient({ maxRetries: 0 });

Timeouts

The default request timeout is 30 seconds. Override per-client:

const client = new LevelFourClient({ timeoutInSeconds: 60 });

For per-request cancellation, pass an AbortSignal:

const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

const result = await client.recommendations.list({
    abortSignal: controller.signal,
});

Pagination

Paginated endpoints return a Page with an async iterator:

for await (const item of await client.recommendations.list()) {
    console.log(item);
}

Collect all items into an array:

import { collectAll } from "levelfour";

const all = await collectAll(await client.recommendations.list());

Or control pagination manually (note: getNextPage() mutates the page in place):

let page = await client.recommendations.list();
console.log(page.data);

while (page.hasNextPage()) {
    page = await page.getNextPage();
    console.log(page.data);
}

Raw HTTP Requests

For undocumented or beta endpoints:

const data = await client.get<MyType>("/api/v1/custom-endpoint");
const result = await client.post<CreateResponse>("/api/v1/custom", {
    body: { name: "example" },
});

For full response access (headers, status):

const { data, rawResponse } = await client.get("/api/v1/health").withRawResponse();
console.log("Status:", rawResponse.status);

All methods (get, post, put, patch, delete, request) accept optional RequestOptions:

await client.get("/api/v1/resource", {
    headers: { "X-Custom": "value" },
    queryParams: { page: "1" },
    timeoutInSeconds: 10,
    maxRetries: 0,
});

Webhooks

Verify incoming events using HMAC-SHA256:

import { WebhookVerifier } from "levelfour";

const verifier = new WebhookVerifier("whsec_...");
const payload = verifier.verify(request.body, request.headers);

Manage webhook endpoints using the generated client.webhooks sub-client:

const endpoints = await client.webhooks.list();

await client.webhooks.register({
    url: "https://yourapp.com/webhooks",
    event_types: ["recommendation.created"],
});

await client.webhooks.delete({ endpoint_id: endpointId });

Install the optional peer dependency:

npm install @opentelemetry/api

When @opentelemetry/api is installed, the SDK automatically instruments all HTTP requests with OpenTelemetry spans and metrics. Alternatively, pass a custom fetch function with your own tracing:

import { LevelFourClient } from "levelfour";

const client = new LevelFourClient({
    fetch: myCustomTracedFetch,
});

Versioning

This SDK follows Semantic Versioning. After v1.0.0, breaking changes will only ship in major version bumps.

Support

Docs are at docs.levelfour.ai. For help, email [email protected].

Security

To report a vulnerability, email [email protected].

License

Apache-2.0.