Package Exports
- caplyr
Readme
Caplyr
AI Cost Control Plane — Stop runaway API bills automatically.
Caplyr wraps your AI client and enforces cost controls based on your project settings in the Caplyr dashboard. Budget guardrails, auto-downgrade, and kill switch — in 2 lines of code.
Install
npm install caplyrQuick Start
import Anthropic from "@anthropic-ai/sdk";
import { protect } from "caplyr";
// Wrap your client — everything else stays the same
const client = protect(new Anthropic(), {
apiKey: "caplyr_...", // Get yours at https://app.caplyr.com
mode: "cost_protect", // Enforce budget limits
budget: { monthly: 500, daily: 50 }, // Budget caps in dollars
fallback: "claude-haiku-4-5-20251001", // Auto-downgrade target
});
// Use exactly as before — Caplyr is invisible
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});Works with OpenAI too:
import OpenAI from "openai";
import { protect } from "caplyr";
const client = protect(new OpenAI(), {
apiKey: "caplyr_...",
mode: "cost_protect",
budget: { monthly: 500 },
fallback: "gpt-4o-mini",
});
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello" }],
});What It Does
| Feature | Description |
|---|---|
| Budget Guardrails | Daily and monthly caps. Hit the limit → requests are blocked. Budget limits are configured in the Caplyr dashboard and enforced via the SDK. |
| Auto Downgrade | When budget threshold is reached, automatically route to a cheaper model. Your app keeps working. |
| Kill Switch | One-click emergency stop from the dashboard. Halts all AI API calls instantly. |
How Enforcement Works
Budget limits are managed server-side in your Caplyr project settings. The SDK sends your configured budget values to the server via heartbeats, and the server returns the current budget status (usage, limits, kill switch state). The SDK enforces limits locally based on that server response — the server is the source of truth, not the local config.
If you set budget in the SDK but have different limits in your dashboard, the dashboard settings take precedence.
Modes
// Alert-only (default): observe and log, don't enforce
protect(client, { apiKey: "..." });
// Cost protect: enforce budget caps and auto-downgrade
protect(client, { apiKey: "...", mode: "cost_protect", budget: { monthly: 500 } });Tip: Setting
budgetwithout specifyingmodewill automatically enablecost_protect.
Currently Wrapped Endpoints
- Anthropic:
client.messages.create() - OpenAI:
client.chat.completions.create()
Other endpoints (embeddings, images, Responses API) are not yet wrapped. Streaming requests (stream: true) are passed through but usage tracking may be incomplete.
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
required | Your Caplyr project API key |
budget |
{ monthly?: number, daily?: number } |
— | Budget caps in dollars |
fallback |
string |
auto | Fallback model for auto-downgrade |
mode |
"alert_only" | "cost_protect" |
"alert_only" |
Enforcement mode |
downgradeThreshold |
number |
0.8 |
Budget % at which downgrade activates |
endpoint_tag |
string |
— | Custom tag for cost attribution |
dashboardUrl |
string |
https://app.caplyr.com |
Dashboard URL for error messages |
endpoint |
string |
https://api.caplyr.com |
API endpoint for heartbeat/ingestion |
Handling Blocked Requests
When a request is blocked in cost_protect mode, Caplyr throws a structured error:
try {
const response = await client.messages.create({ ... });
} catch (err) {
if (err.caplyr) {
console.log(err.caplyr.code); // "BUDGET_EXCEEDED" | "KILL_SWITCH_ACTIVE"
console.log(err.caplyr.retry_after); // ISO timestamp for next reset
console.log(err.caplyr.budget_used); // Current spend
}
}In alert_only mode, the request proceeds and the onEnforcement callback is fired instead.
Shutdown
Caplyr does not register SIGINT/SIGTERM handlers — your app owns its process lifecycle. Call shutdown() in your own signal handler to flush pending logs before exit:
import { shutdown } from "caplyr";
async function gracefulShutdown() {
await shutdown();
process.exit(0);
}
process.on("SIGTERM", gracefulShutdown);
process.on("SIGINT", gracefulShutdown);Links
- Dashboard: app.caplyr.com
- Docs: caplyr.com/docs
- Website: caplyr.com
License
MIT