JSPM

@spendkill/sdk

0.1.4
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 12
  • Score
    100M100P100Q67408F
  • License FSL-1.1-MIT

Per-user spend-capped keys for AI APIs to prevent surprise AI bills

Package Exports

  • @spendkill/sdk

Readme

Spend Kill

A Per-user spend-capped keys for AI APIs to prevent surprise AI bills. Give every user or server their own AI API key with a hard dollar limit. When that limit is hit, Spend Kill automatically stops requests so you never get surprise AI bills.

Installation

npm install @spendkill/sdk

Quick Start

import SpendKill from "@spendkill/sdk";

const sk = new SpendKill({
  base: "https://your-spend-kill-server.com",
  adminKey: process.env.SK_ADMIN_HMAC,
});

// Issue a $10 capped key for a user
const { api_key } = await sk.issue("user-123", { limitUsd: 10 });

// Use the key with Claude
const claude = await sk.client(api_key).anthropic({
  model: "claude-sonnet-4-20250514",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello!" }],
});

// Use the key with OpenAI
const openai = await sk.client(api_key).openai({
  model: "gpt-4o",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello!" }],
});

// Use the key with Grok
const grok = await sk.client(api_key).grok({
  model: "grok-beta",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello!" }],
});

Features

  • Spend Limits - Hard dollar caps per user/key
  • Multiple Providers - Claude, OpenAI, and Grok support
  • Usage Tracking - Real-time spending in response headers
  • Auto-Block - Automatically stops requests when cap is reached
  • Webhooks - 80% budget alerts

Response Headers

Every response includes spending information:

response.headers.get("x-sk-spent-usd");      // "2.45"
response.headers.get("x-sk-cap-usd");        // "10.00"
response.headers.get("x-sk-remaining-usd");  // "7.55"
response.headers.get("x-sk-request-id");     // Unique request ID

API Methods

issue(userId: string, options)

Create a spend-capped key for a user.

const { api_key } = await sk.issue("user-123", { 
  limitUsd: 10,
  notes: "Premium tier user"
});

revoke(userId: string)

Revoke a user's key.

await sk.revoke("user-123");

listKeys()

List all keys with usage stats.

const keys = await sk.listKeys();
keys.forEach(key => {
  console.log(`${key.user_id}: $${key.spent}/$${key.cap}`);
});

report(userId: string)

Get detailed usage report for a user.

const report = await sk.report("user-123");
console.log(report.usage_by_model); // Usage by model

topup(userId: string, usd: number)

Increase a user's spending cap.

// Add $5 to user's cap (e.g., $10 → $15)
await sk.topup("user-123", 5);

reset(userId: string)

Reset spending to $0 while keeping the cap.

// Reset monthly spending (cap stays the same)
await sk.reset("user-123");

setStatus(userId: string, status: 'active' | 'revoked')

Change a key's status.

// Reactivate a revoked key
await sk.setStatus("user-123", "active");

// Or revoke (same as sk.revoke())
await sk.setStatus("user-123", "revoked");

client(apiKey).anthropic(body)

Call Anthropic Claude API.

const response = await sk.client(api_key).anthropic({
  model: "claude-sonnet-4-20250514",
  max_tokens: 1024,
  messages: [
    { role: "user", content: "Write a haiku" }
  ],
});

client(apiKey).openai(body)

Call OpenAI API.

const response = await sk.client(api_key).openai({
  model: "gpt-4o",
  max_tokens: 1024,
  messages: [
    { role: "user", content: "Write a haiku" }
  ],
});

client(apiKey).grok(body)

Call xAI Grok API.

const response = await sk.client(api_key).grok({
  model: "grok-beta",
  max_tokens: 1024,
  messages: [
    { role: "user", content: "Write a haiku" }
  ],
});

Handling Limits

When a user hits their spending cap:

const response = await sk.client(api_key).anthropic({...});

if (response.status === 429) {
  console.log("Budget limit reached!");
  // Handle gracefully - upgrade prompt, notify user, etc.
}

Programmatic Usage

Perfect for Cloudflare Workers, AWS Lambda, Durable Objects, or any multi-tenant architecture where you need to dynamically issue capped API keys.

Example: Cloudflare Durable Objects

Each Durable Object gets its own capped API key. Perfect for game servers, chatbots, or any isolated tenant:

export class MinecraftServerDO {
  state: DurableObjectState;
  apiKey?: string;

  async fetch(req: Request): Promise<Response> {
    const sk = new SpendKill({ 
      base: env.SK_BASE, 
      adminKey: env.SK_ADMIN_HMAC 
    });

    // Lazy-initialize: issue key on first request
    if (!this.apiKey) {
      this.apiKey = await this.state.storage.get<string>("apiKey");
      if (!this.apiKey) {
        const issued = await sk.issue(`server-${this.state.id}`, { 
          limitUsd: 10, 
          notes: "Minecraft server" 
        });
        this.apiKey = issued.api_key;
        await this.state.storage.put("apiKey", this.apiKey);
      }
    }

    // Use the key for AI calls
    const response = await sk.client(this.apiKey).openai({
      model: 'gpt-4o',
      messages: [{ role: 'user', content: 'Hello!' }]
    });

    return new Response(JSON.stringify(response.json));
  }
}

Admin Monitoring

Build dashboards or automated billing systems:

const sk = new SpendKill({
  base: "https://your-spendkill.com",
  adminKey: process.env.SK_ADMIN_HMAC
});

// List all users and their spending
const keys = await sk.listKeys();
keys.forEach(k => {
  console.log(`${k.user_id}: $${k.spent_usd}/$${k.cap_usd} (${k.percent_used}%)`);
});

// Find users near their cap
const nearCap = keys.filter(k => parseFloat(k.percent_used) >= 80);

// Top up users programmatically
for (const user of nearCap) {
  await sk.topup(user.user_id, 10); // Add $10
}

License

This project is licensed under the FSL-1.1-MIT License. See the LICENSE file for details.