vibeledger
v0.4.0
Published
CLI tool for registering applications with Ledger LLM Gateway
Readme
vibeledger CLI
Register your application with the Ledger API Gateway in seconds.
Quick Start
npx vibeledgerThat's it. The CLI will walk you through:
- Project info — auto-detects name, tech stack, and git repo
- Browser auth — opens your browser to approve the registration
- Config — writes
LEDGER_URL,LEDGER_API_KEY, andLEDGER_APP_IDto your.env
What It Does
- Detects your project name from
package.json - Detects your tech stack (Node.js, Python, Ruby, Go, Rust, Java, PHP, etc.)
- Detects your git repo URL from
.git/config - Uses a secure device authorization flow (no passwords in the terminal)
- Writes credentials to
.envand ensures.envis in.gitignore
Usage
# Register a new app
npx vibeledger
# If you already have a key, it will ask before overwritingInteractive Prompts
| Prompt | Default | Required |
|--------------------|----------------------|----------|
| Project name | From package.json | Yes |
| Notification email | — | No |
| App URL | — | No |
After Registration
Your app is approved for self-service configuration. You can set up routing, budgets, provider keys, and features programmatically — no dashboard required.
Option 1: SDK (recommended)
import { LedgerClient } from "@ledger/sdk";
const ledger = new LedgerClient({
apiKey: process.env.LEDGER_API_KEY,
baseUrl: process.env.LEDGER_URL,
});
// Discover available providers and models
const config = await ledger.setup.getConfig();
// Add your provider key (encrypted immediately, never returned)
await ledger.setup.addProviderKey({
providerSlug: "openai",
apiKey: process.env.OPENAI_API_KEY,
label: "Production",
});
// Register a feature with a primary provider/model + fallbacks
await ledger.setup.createFeature({
slug: "video-generation",
name: "Video Generation",
providerSlug: "openai",
modelId: "gpt-4o",
fallbackChain: [
{ provider: "anthropic", model: "claude-sonnet-4-20250514" },
{ provider: "google", model: "gemini-2.0-flash" },
],
keySource: "auto",
});
// Set a spending limit
await ledger.setup.createBudget({
scopeType: "app",
limitCents: 50000,
period: "monthly",
actionOnExceed: "block",
});
// Now proxy requests through Ledger
const response = await ledger.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }],
});Option 2: OpenAI-compatible drop-in
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.LEDGER_API_KEY,
baseURL: process.env.LEDGER_URL + "/v1",
defaultHeaders: {
"X-Ledger-Feature": "my-feature",
},
});
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }],
});Option 3: Python
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ["LEDGER_API_KEY"],
base_url=os.environ["LEDGER_URL"] + "/v1",
default_headers={
"X-Ledger-Feature": "my-feature",
},
)Self-Service API
After approval, your app can manage its own configuration via the self-service API.
Auth: include X-Ledger-Key: {LEDGER_API_KEY} on all requests.
| Endpoint | Method | Description |
|----------|--------|-------------|
| /api/self-service/config | GET | Current config + available providers/models |
| /api/self-service/routing-rules | POST | Create routing rule |
| /api/self-service/routing-rules/:id | PUT/DELETE | Update or delete rule |
| /api/self-service/budgets | POST | Create budget |
| /api/self-service/budgets/:id | PUT/DELETE | Update or delete budget |
| /api/self-service/provider-keys | POST | Add provider key (write-only) |
| /api/self-service/provider-keys/:id | DELETE | Remove provider key |
| /api/self-service/features | POST | Register feature with optional routing config |
| /api/self-service/features/:id | PUT/DELETE | Update routing/model/fallbacks or delete feature |
| /api/self-service/logs | GET | Recent request logs (provider, model, cost, latency) |
| /api/self-service/logs/summary | GET | Routing summary grouped by feature/provider/model |
AI-Assisted Setup (Claude, Cursor, etc.)
If your project uses an AI coding assistant, add the Ledger instructions to your project's
CLAUDE.md (or equivalent) so the assistant knows how to configure and use the gateway.
The CLI outputs a template after registration. Key rules for AI assistants:
- Always read provider keys from environment variables — never hardcode or log them
- Call
GET /api/self-service/configfirst to see what's already configured - Ask the user before adding provider keys — the assistant should not guess or invent keys
- Provider keys are write-only — once submitted, they cannot be read back (encrypted at rest)
Environment Variables
After registration, these are written to .env:
| Variable | Description |
|------------------|------------------------------------------|
| LEDGER_URL | Gateway URL (e.g. https://vibeledger.replit.app) |
| LEDGER_API_KEY | Your app's API key (starts with ldgr_) |
| LEDGER_APP_ID | Your app's unique ID |
Security
.envis never committed — the CLI ensures.envis in.gitignoreLEDGER_API_KEY(ldgr_*) grants access only to your app's own resources. It cannot read or modify other apps, org-level settings, or platform keys.- Provider API keys submitted via the self-service API are encrypted immediately using AES-256-GCM envelope encryption. They are never returned in API responses, never logged, and are only decrypted in memory at proxy time.
- If a key is compromised, an admin can immediately suspend the app via the dashboard (
POST /api/apps/:appId/suspend), which revokes all self-service access. - Do not put
LEDGER_API_KEYor provider keys inCLAUDE.mdor any committed file. Always reference environment variables.
Feature Routing
Each feature can be configured with a primary provider/model and an ordered fallback chain. When a request includes the X-Ledger-Feature header, the gateway checks the feature's routing config first.
Routing priority: Feature config > App routing rules > Default model pass-through
Configure via SDK
// Create a feature with routing
await ledger.setup.createFeature({
slug: "image-generation",
name: "Image Generation",
providerSlug: "openai",
modelId: "gpt-4o",
fallbackChain: [
{ provider: "anthropic", model: "claude-sonnet-4-20250514" },
],
keySource: "auto", // "auto" | "byok" | "platform" | "profile:{slug}"
});
// Update routing for an existing feature
await ledger.setup.updateFeature("feature-id", {
providerSlug: "anthropic",
modelId: "claude-sonnet-4-20250514",
fallbackChain: [
{ provider: "openai", model: "gpt-4o" },
{ provider: "google", model: "gemini-2.0-flash" },
],
});
// Clear routing (fall back to routing rules)
await ledger.setup.updateFeature("feature-id", {
providerSlug: null,
modelId: null,
fallbackChain: [],
});Configure via API
PUT /api/self-service/features/:id
Content-Type: application/json
X-Ledger-Key: $LEDGER_API_KEY
{
"providerSlug": "openai",
"modelId": "gpt-4o",
"fallbackChain": [
{ "provider": "anthropic", "model": "claude-sonnet-4-20250514" }
],
"keySource": "auto"
}How fallback works
- Gateway receives request with
X-Ledger-Feature: image-generation - Looks up the feature's
providerSlug+modelId(primary) - If primary provider is unhealthy (circuit breaker open) or has no valid key, tries each entry in
fallbackChainin order - If all feature-level options are exhausted, falls through to app-level routing rules
- If no routing rules match, uses default model-based routing
Observability: Tracking Routing Decisions
Every request through Ledger is logged with full routing metadata. Apps can query their own logs via the self-service API to see which provider, model, and key source handled each request — making it easy to detect when fallbacks are triggered.
Self-Service Logs API
GET /api/self-service/logs?feature=image-generation&limit=50
X-Ledger-Key: $LEDGER_API_KEYReturns paginated request logs scoped to your app. Each log entry includes:
provider— which provider actually handled the requestmodel— which model was usedkeySource— how the key was resolved (byok,platform,auto, etc.)feature— the feature tag fromX-Ledger-FeaturestatusCode— success/failurelatencyMs— response timecostCents— cost of the requestcacheHit— whether a cached response was returned
Optional query params: feature, provider, model, statusCode, limit (max 200), offset, dateFrom, dateTo.
Routing Summary API
GET /api/self-service/logs/summary?feature=image-generation
X-Ledger-Key: $LEDGER_API_KEYReturns aggregated stats grouped by feature / provider / model / keySource:
requestCount,totalCostCents,totalTokens,avgLatencyMs
This is the fastest way to see primary vs fallback distribution without scanning individual logs.
Building a Simple Admin Page
Using the SDK:
// Get routing summary for a feature
const { summary } = await ledger.setup.getLogsSummary({
feature: "image-generation",
});
for (const row of summary) {
console.log(
`${row.provider}/${row.model}: ${row.requestCount} requests, ` +
`$${(row.totalCostCents / 100).toFixed(2)}, avg ${row.avgLatencyMs}ms`
);
}
// Get recent individual logs
const { logs, total } = await ledger.setup.getLogs({
feature: "image-generation",
limit: 100,
});
const primaryHits = logs.filter(l => l.provider === "openai").length;
const fallbackHits = logs.filter(l => l.provider !== "openai").length;
console.log(`Primary: ${primaryHits}, Fallback: ${fallbackHits}, Total: ${total}`);With these endpoints you can build a table or chart showing:
- Primary vs fallback usage — compare
provideragainst the feature's configuredproviderSlug - Fallback frequency — how often the primary provider was unavailable
- Cost by provider — see if fallbacks are more/less expensive
- Latency comparison — detect performance differences between providers
Custom Headers
Request Headers
Send these headers with your LLM requests for routing and tracking:
| Header | Description |
|---------------------|------------------------------------|
| X-Ledger-Feature | Tag requests by feature/workflow — also triggers feature-level routing |
| X-Ledger-User-Id | Associate requests with a user |
| X-Ledger-Tier | Override tier for this request (economy, standard, premium) |
| X-Ledger-Tag | Custom routing tag for matching routing rules |
Response Headers
Every gateway response includes these headers so your app knows what happened:
| Header | Description |
|-----------------------|------------------------------------|
| X-Ledger-Provider | Which provider handled the request (e.g. openai, anthropic) |
| X-Ledger-Model | Which model was used (e.g. gpt-4o, claude-sonnet-4-20250514) |
| X-Ledger-Key-Source | How the API key was resolved (auto, byok, platform) |
| X-Ledger-Cache | HIT or MISS — whether a cached response was returned |
Use X-Ledger-Provider to detect fallbacks in real time — if it differs from your feature's configured providerSlug, a fallback was used.
Troubleshooting
"Could not connect to server"
- Check your internet connection
- Verify the Ledger server is running
- Set
LEDGER_SERVER_URLenv var if using a custom server
"Registration was denied"
- Make sure you approved the registration in your browser
- Try running
npx vibeledgeragain for a fresh code
"Authorization code expired"
- The code expires after a few minutes
- Run
npx vibeledgeragain to get a new code
"App is not approved for self-service"
- The app's approval status may have been suspended by an admin
- Contact your organization admin to reinstate access
Browser didn't open automatically
- Copy the URL shown in the terminal and open it manually
Server Configuration
To point at a different Ledger server:
LEDGER_SERVER_URL=https://your-ledger.example.com npx vibeledger