API reference

Connect to LedgerCap

A real-time, strongly-consistent spend cap for AI & agentic workloads. Meter spend against a prepaid balance with a hard limit that holds under concurrency — debit directly with charge, or reserve-then-settle with authorize / capture. All endpoints are bearer-authenticated over HTTPS and return JSON.

Base URLhttps://api.ledgercap.dev/api/v1
Examples in

Get started

Quickstart

  1. 1

    Mint an API token

    Go to Settings → API tokens. The secret is shown once; only its hash is stored. Use a charge token for agents and an admin token (which can top up) for your billing backend.

  2. 2

    Set your caps

    Load credit and (optionally) a per-UTC-day spend limit on Dashboard and Settings. The balance caps total exposure; the daily limit caps burn rate.

  3. 3

    Make your first charge

    export LEDGERCAP_TOKEN="<your-token>"
    
    curl -X POST https://ledgercap.dev/api/v1/charge \
      -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"amountNanos":1500000,"description":"haiku call"}'
    # → 200 { "allowed": true, "balanceNanos": 9998500000, ... }

Security

Authentication

The data plane (/api/v1/*) is authenticated with your own bearer tokens — independent of the human sign-in, so a dashboard outage can never take your spend API down. Send the token on every request:

Authorization: Bearer $LEDGERCAP_TOKEN

A missing or invalid token returns 401. Tokens carry a scope: charge (spend only) or admin (spend + provision credit). A spend token deliberately cannot raise its own cap.

Units

Money: nanodollars or cents

Every money field accepts either integer nanodollars (amountNanos) or decimal cents (amountCents) — send whichever is natural, never both. Internally everything is nanodollars (1 nano = 1e-9 USD), so integer math keeps per-token AI costs exact with no floating-point drift.

$1.00amountNanos: 1000000000amountCents: 100
amountNanos: 10000000amountCents: 1
$0.0015amountNanos: 1500000amountCents: 0.15
$0.000000001amountNanos: 1(sub-cent — nanos only)
# $0.0015 two ways — pick whichever unit your model pricing uses:
curl -sX POST .../charge -d '{"amountNanos":1500000}'
curl -sX POST .../charge -d '{"amountCents":0.15}'
# The same choice applies on topup, authorize, capture, wallet rules, and wallet defaults

Reference

Endpoints

Base URL https://ledgercap.dev. On contention the write endpoints answer 429 with { "retryable": true } — back off and retry.

POST/api/v1/charge

Debit prepaid credit. This is the hard cap: it refuses to overspend and never lets the balance go negative. Enforces both the balance and the daily burn-rate limit atomically.

Request body

amountNanosintegerPositive integer nanodollars. Send this OR amountCents, not both.
amountCentsnumber?Decimal cents (e.g. 0.15 = $0.0015). Alternative to amountNanos.
idempotencyKeystring?Replays the prior result instead of double-charging.
descriptionstring?Free-text label stored on the ledger entry.

Responses

200allowed{ allowed: true, balanceNanos, ledgerId, idempotent, spentTodayNanos, dailyLimitNanos }
402refused{ allowed: false, reason: 'insufficient_funds' | 'daily_limit_exceeded', ... }
# $0.0015 — use the unit toggle above to switch amountNanos ⇄ amountCents
curl -X POST https://ledgercap.dev/api/v1/charge \
  -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amountNanos":1500000,"idempotencyKey":"req_abc123","description":"haiku call"}'
POST/api/v1/meter

Price an LLM call off the built-in rate card, apply your markup, and charge the marked-up amount — all in one call. The cost is exact integer nanodollars; the same hard cap enforces the result. Buy tokens at cost, bill at your price. See the live rate card on Rates.

Request body

Send token counts one of two ways (mutually exclusive): explicit fields, or a raw usageblob from your LLM SDK that we normalize for you. The blob handles the provider quirks — OpenAI's prompt_tokens (Chat Completions) and input_tokens(Responses API) both include cached tokens (we subtract them); Anthropic's input_tokens already excludes them — so the billed lines come out right either way.

modelstringRate-card model id, e.g. claude-opus-4-8, gpt-4o.
usageobject?Raw provider usage blob — OpenAI Chat Completions (completion.usage) or Responses (response.usage), or Anthropic message.usage. Mutually exclusive with the explicit token fields below.
inputTokensinteger?Uncached prompt tokens (≥ 0). Required unless usage is sent.
outputTokensinteger?Completion tokens (≥ 0). Required unless usage is sent.
cacheReadTokensinteger?Prompt-cache hits (read line). Requires a cache_read rate on the model.
cacheWriteTokensinteger?Prompt-cache writes (Anthropic cache_creation). Requires a cache_write rate on the model.
markupBpsinteger?Markup in basis points (10000 = +100% = 2×). Default 0.
idempotencyKeystring?Safe-retry key (bound to the full token breakdown).
descriptionstring?Label stored on the ledger entry.

Responses

200allowed{ allowed: true, model, modelName, inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?, costNanos, markupBps, marginNanos, amountNanos, ledgerId, idempotent, balanceNanos, spentTodayNanos, dailyLimitNanos }
402refused{ allowed: false, reason, model, modelName, inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?, costNanos, markupBps, marginNanos, amountNanos, balanceNanos, spentTodayNanos, dailyLimitNanos } (cap held; full breakdown still returned)
400bad inputUnknown model, zero amount, an unmappable usage blob, or cache tokens for a model with no cache rate configured.
# Explicit tokens
curl -X POST https://ledgercap.dev/api/v1/meter \
  -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-opus-4-8","inputTokens":1000,"outputTokens":500,"markupBps":2000}'
# cost $0.0175 + 20% → charges $0.021

# Or forward the raw usage blob — OpenAI (cached_tokens are billed at the cache_read rate):
curl -X POST https://ledgercap.dev/api/v1/meter \
  -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","usage":{"prompt_tokens":1000,"completion_tokens":500,"prompt_tokens_details":{"cached_tokens":800}}}'
# billed as 200 uncached input + 800 cache read + 500 output

# Anthropic (cache_creation = write, cache_read = read):
curl -X POST https://ledgercap.dev/api/v1/meter \
  -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet-4-6","usage":{"input_tokens":200,"output_tokens":500,"cache_creation_input_tokens":1000,"cache_read_input_tokens":800}}'
POST/api/v1/authorize

Reserve credit for a pending operation without spending it — like a card authorization. Returns a holdId you later capture or void. Reservations count against available but are exempt from the daily cap (only the capture spends).

Request body

amountNanosintegerPositive nanodollars to reserve. Send this OR amountCents.
amountCentsnumber?Decimal cents (alternative to amountNanos).
expiresInSecondsinteger?TTL before the hold auto-releases (default 7 days).
idempotencyKeystring?Safe-retry key.
descriptionstring?Label stored on the hold.

Responses

200authorized{ authorized: true, holdId, amountNanos, availableNanos, reservedNanos, balanceNanos }
402refused{ authorized: false, reason: 'insufficient_funds', ... }
curl -X POST https://ledgercap.dev/api/v1/authorize \
  -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amountNanos":500000000,"expiresInSeconds":900}'
POST/api/v1/capture

Settle an open hold: debit the captured amount (defaults to the full hold) and release the reservation. Any uncaptured remainder returns to available. The capture is the actual spend, so it is subject to the daily cap.

Request body

holdIdstringThe hold to settle.
captureNanosinteger?Amount to capture (≤ hold). Defaults to the full hold.
captureCentsnumber?Decimal cents alternative to captureNanos.
descriptionstring?Label stored on the capture ledger entry.

Responses

200captured{ ok: true, holdId, capturedNanos, releasedNanos, ledgerId, ... }
402daily_limit_exceededThe capture would exceed today’s spend limit.
404 / 409 / 400errorsnot_found · already_voided / expired · capture_exceeds_hold
curl -X POST https://ledgercap.dev/api/v1/capture \
  -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"holdId":"<id>","captureNanos":300000000}'
POST/api/v1/void

Release an open hold without spending. The reserved credit returns to available.

Responses

200released{ ok: true, holdId, releasedNanos, availableNanos, reservedNanos, balanceNanos }
404 / 409errorsnot_found · already_captured
curl -X POST https://ledgercap.dev/api/v1/void \
  -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"holdId":"<id>"}'
POST/api/v1/topup

Provision credit. Admin-scoped tokens only — a charge token gets 403, so a spending agent can never raise its own cap.

Request body

amountNanosintegerPositive nanodollars to add. Send this OR amountCents.
amountCentsnumber?Decimal cents (alternative to amountNanos).
idempotencyKeystring?Safe-retry key, bound to amount and description.
descriptionstring?Ledger entry label.
curl -X POST https://ledgercap.dev/api/v1/topup \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amountNanos":5000000000}'   # = $5.00
GET/api/v1/balance

Returns the caller's current spend pool. Accepts any token scope (charge or admin). Useful for agents that want to check credit before acting.

curl https://ledgercap.dev/api/v1/balance -H "Authorization: Bearer $LEDGERCAP_TOKEN"
# → { "balanceNanos": 5000000000, "reservedNanos": 0, "availableNanos": 5000000000,
#     "spentTodayNanos": 1000000000, "dailyLimitNanos": 10000000000 }
GET/api/v1/me

Identity check: the user the token belongs to, plus current settings.

curl https://ledgercap.dev/api/v1/me -H "Authorization: Bearer $LEDGERCAP_TOKEN"
# → { "userId": "user_…", "email": null, "settings": { "spendLimitNanos": 100000000000 } }

Handling

Errors & status codes

200OKRequest succeeded (check allowed / authorized / ok in the body).
400Bad requestMalformed JSON or failed validation (see issues).
401UnauthorizedMissing or invalid bearer token.
402Cappedinsufficient_funds or daily_limit_exceeded — the cap held.
403ForbiddenToken scope too low (e.g. charge token calling topup).
404Not foundNo such hold.
409ConflictHold state conflict, or an idempotency key was reused with a different request.
429RetryableWrite contention outlived the retry budget — back off and retry.

Idempotency. Pass an idempotencyKey on charges and authorizations so a network retry replays the original result instead of charging twice. A key is bound to its normalized amount and other operation fields; reusing it with a different payload returns 409. 429s are expected under heavy concurrency on one account and are safe to retry with a short backoff.

Lifecycle

Holds: reserve, then settle

Use holds when you know an upper bound but not the final cost — reserve up front, then capture the real amount once the work finishes.

available = balanceNanos − reservedNanos

authorize(amountNanos)        → reserves credit, returns holdId   (exempt from daily cap)
   ├── capture(holdId, n?)     → spends n (≤ hold), releases the rest   (subject to daily cap)
   └── void(holdId)            → releases the whole hold, spends nothing
expires_at reached             → auto-released by the daily sweep

A direct charge debits against available, so it can never eat funds locked by an open hold.

Performance

Inference latency: authorize + capture

Calling chargesynchronously before an LLM call puts the billing round-trip on the critical path — adding ~300 ms to every user's perceived latency. The fix is to call authorize in parallel with other setup work (fetching context, constructing the prompt, loading history), check the result before inference starts, then capture the real token cost afterwards. The user sees the model, not the billing API.

import { LedgerCap } from './ledgercap'
const lc = new LedgerCap({ token: process.env.LEDGERCAP_TOKEN! })

// ❌ Slow: charge blocks inference — adds ~300 ms to every call
const result = await lc.charge({ amountCents: 1.00, externalId: userId })
if (!result.allowed) throw new Error('cap hit')
const response = await llm.generate(prompt)

// ✅ Fast: authorize races with setup work — billing is off the critical path
const [hold, context] = await Promise.all([
  lc.authorize({ amountCents: 1.00, externalId: userId }),  // max cost estimate
  fetchUserContext(userId),                                   // work you'd do anyway
])
if (!hold.authorized) throw new Error(`cap hit: ${hold.reason}`)

const response = await llm.generate({ prompt, context })

// Capture the actual cost (≤ reserved amount) — any remainder returns to available
const actualCents = tokensToUSD(response.usage)
await lc.capture({ holdId: hold.holdId, captureCents: actualCents })

If there is no parallelisable setup work, fire authorizeand await it before generating — you still get the correct-cost capture, and you can add setup work later without restructuring the billing calls. Either way, capture is always fire-and-forget from the user's perspective (inference is already done).

Reserve conservatively. Set the authorized amount to your maximum plausible cost for the call (e.g. max output tokens × price). The capture releases the unused portion — only the real cost is spent. A hold that is never captured auto-expires at expiresInSeconds (default 7 days), so set a shorter TTL for streaming calls where you know the model will respond quickly.

Per-user wallets. Add externalId to both authorize and captureto debit the right user's wallet. The hold is always tied to a specific wallet — the capture does not need to repeat the wallet lookup.

SaaS multi-tenancy

Wallets: per-user sub-accounts

A wallet is a first-class balance owner for one end-user of your SaaS — its own balance, spend cap, ledger, and renewal rules, all keyed by an opaque UUID. The consuming SaaS provisions wallets via the admin API and then charges against them using an externalId(the SaaS's own user id) with createIfMissing: true — so a new user goes from signup to first charge in a single API call, with no pre-provisioning step.

All wallet management endpoints require an admin token. Spend endpoints (charge, meter, authorize) accept an optional externalId / walletId field and route the debit to that wallet instead of the account — a charge token is sufficient.

POST/api/v1/wallets

Provision a wallet for one end-user. Returns the wallet with its generated UUID. Requires admin scope.

Request body

labelstring?Human-readable name, e.g. the end-user email.
externalIdstring?Your own user id. Unique per account; used in charge/meter instead of our UUID.
initialBalanceNanosinteger?Credit to load at creation (default 0).
capNanosinteger?Per-UTC-day spend cap for this wallet (0 = unlimited).
allowOverrunboolean?Let the wallet dip below zero (overrun). Default false.
overrunLimitNanosinteger?Max negative balance when allowOverrun is true.
metadatastring?Free-form string stored on the wallet (plan tier, region, etc.).

Response

201created{ wallet: { id, externalId, label, status, capNanos, balanceNanos, spentTodayNanos, ... } }
409conflictexternalId already exists for this account.
curl -X POST https://ledgercap.dev/api/v1/wallets \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "user_42",
    "label": "Jane Doe",
    "initialBalanceNanos": 5000000000,
    "capNanos": 1000000000
  }'
# → 201 { "wallet": { "id": "wallet_…", "status": "active", "balanceNanos": 5000000000, ... } }
GET/api/v1/wallets

List all wallets for the account. Optionally filter by externalId or status.

curl "https://ledgercap.dev/api/v1/wallets?status=active" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN"
GET/api/v1/wallets/:id

Fetch one wallet including its live balance and spentTodayNanos.

curl "https://ledgercap.dev/api/v1/wallets/$WALLET_ID" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN"
PATCH/api/v1/wallets/:id

Update a wallet's label, cap, status, or overrun settings. Pass only the fields you want to change.

labelstring?New human-readable name.
capNanosinteger?New per-UTC-day spend cap (0 = unlimited).
statusstring?'active' | 'suspended' | 'closed'
allowOverrunboolean?Enable / disable overrun.
overrunLimitNanosinteger?New overrun ceiling.
curl -X PATCH "https://ledgercap.dev/api/v1/wallets/$WALLET_ID" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status":"suspended"}'
POST/api/v1/wallets/:id/topup

Add credit to a wallet's balance. Same idempotency semantics as the account-level topup. Requires admin scope.

amountNanosintegerPositive nanodollars. Send this OR amountCents.
amountCentsnumber?Decimal cents (alternative to amountNanos).
idempotencyKeystring?Deduplicates a retry.
descriptionstring?Ledger entry label.
curl -X POST "https://ledgercap.dev/api/v1/wallets/$WALLET_ID/topup" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amountNanos":2000000000,"description":"monthly renewal"}'
GET/api/v1/wallets/:id/usage

Full usage view for one wallet: balance, ledger history, and open holds. Embed this in your end-user's "usage page" so they can see remaining credit and what they've spent.

curl "https://ledgercap.dev/api/v1/wallets/$WALLET_ID/usage" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN"
# → { wallet: { balanceNanos, spentTodayNanos, capNanos, … }, ledger: […], holds: […] }

Inline wallet targeting

Add externalId (or walletId) to any charge, meter, or authorize request to debit a wallet instead of the account. Add createIfMissing: true to provision the wallet on first use — the SaaS signup→first-generation flow in one call:

curl -X POST https://ledgercap.dev/api/v1/meter \
  -H "Authorization: Bearer $LEDGERCAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-4-8",
    "inputTokens": 1200,
    "outputTokens": 400,
    "markupBps": 3000,
    "externalId": "user_42",
    "createIfMissing": true,
    "walletDefaults": {
      "capNanos": 1000000000
    }
  }'
# → 200 { "allowed": true, "walletId": "wallet_…", "balanceNanos": … }

Automation

Wallet rules: renewals & lifecycle

Rules automate wallet lifecycle events. Attach them to a wallet once; they fire when you call the renew endpoint (for billing cycles) or automatically after each charge (for auto-suspend). Three rule types ship today:

renewal_topupRenewalTop up or set the balance on a renewal event. Idempotent per eventId.
auto_suspendLifecycleSuspend the wallet automatically when balance falls to/below a threshold.
notifyWebhookPOST a JSON payload to your URL when a lifecycle event fires (e.g. suspended).
POST/api/v1/wallets/:id/rules

Attach a rule to a wallet. The request body is a discriminated union on ruleType.

renewal_topup — top up on a billing renewal

ruleTypestring"renewal_topup"
modestring"add" (add credit) | "set" (set balance to amount)
amountNanosintegerNanodollars to add/set. Send this OR amountCents.
amountCentsnumber?Decimal cents (alternative to amountNanos).
eventstring?Only fire on this event name (omit to fire on every renewal).
curl -X POST "https://ledgercap.dev/api/v1/wallets/$WALLET_ID/rules" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ruleType": "renewal_topup",
    "mode": "set",
    "amountNanos": 5000000000,
    "event": "monthly_renewal"
  }'

auto_suspend — suspend below a balance threshold

ruleTypestring"auto_suspend"
thresholdNanosinteger?Suspend when balance ≤ this (nanodollars). Default 0 = when exhausted.
thresholdCentsnumber?Decimal cents alternative to thresholdNanos.
# Suspend the wallet the moment it hits zero
  -d '{"ruleType":"auto_suspend","thresholdNanos":0}'

notify — webhook on lifecycle events

ruleTypestring"notify"
urlstringHTTPS endpoint. Private IPs / loopback are blocked.
onstring[]Events to POST: ["suspended"] (more events planned).
  -d '{"ruleType":"notify","url":"https://yourapp.com/hooks/fusebox","on":["suspended"]}'
GET/api/v1/wallets/:id/rules

List all rules attached to a wallet.

curl "https://ledgercap.dev/api/v1/wallets/$WALLET_ID/rules" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN"
PATCH/api/v1/wallets/:id/rules/:ruleId

Enable or disable a rule without deleting it.

curl -X PATCH "https://ledgercap.dev/api/v1/wallets/$WALLET_ID/rules/$RULE_ID" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'
DELETE/api/v1/wallets/:id/rules/:ruleId

Permanently delete a rule. If the deleted rule was an auto_suspendrule, the wallet's suspend threshold is recomputed immediately. Returns 204 on success, 404 if the rule or wallet is not found.

curl -X DELETE "https://ledgercap.dev/api/v1/wallets/$WALLET_ID/rules/$RULE_ID" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN"
# → 204 No Content
POST/api/v1/wallets/renew

Trigger renewal rules for one wallet — call this from your billing webhook when a subscription renews. All enabled renewal_topup rules matching the event fire in order. Each rule is idempotent per eventId: a redelivered webhook tops up exactly once.

walletIdstring?Our wallet UUID (provide this or externalId).
externalIdstring?Your user id (alternative to walletId).
eventIdstring?Idempotency key for this renewal event (e.g. Stripe invoice id).
eventstring?Event name to match against rule filters.
curl -X POST https://ledgercap.dev/api/v1/wallets/renew \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "user_42",
    "eventId": "inv_stripe_abc123",
    "event": "monthly_renewal"
  }'
# → 200 { "ok": true, "walletId": "wallet_…", "toppedUp": true, "evaluated": 1, "skipped": 0 }
GET/api/v1/wallets/summary

Portfolio-level aggregate across all wallets for the account. Useful for a SaaS operator dashboard. Admin-scoped token required.

curl "https://ledgercap.dev/api/v1/wallets/summary" \
  -H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN"
# → {
#     "totalWallets": 142, "activeWallets": 138,
#     "suspendedWallets": 3, "closedWallets": 1,
#     "totalBalanceNanos": "48200000000",
#     "totalReservedNanos": "500000000",
#     "totalAvailableNanos": "47700000000"
#   }

Security

Webhook signature verification

Every notifyrule webhook is signed with an HMAC-SHA256 of the raw JSON body, keyed by your account's webhook secret. The signature is sent in the X-LedgerCap-Signature header as sha256=<hex>.

Your webhook secret is available in the dashboard under Settings. Verify payloads using a timing-safe comparison:

import { createHmac, timingSafeEqual } from 'crypto'

function verifyLedgerCapWebhook(
  rawBody: string,
  signature: string,
  secret: string,
): boolean {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
  const received = signature.replace('sha256=', '')
  if (expected.length !== received.length) return false
  return timingSafeEqual(Buffer.from(expected), Buffer.from(received))
}

// In your webhook handler:
const rawBody = await req.text()                     // read body BEFORE parsing
const sig = req.headers.get('X-LedgerCap-Signature') ?? ''
if (!verifyLedgerCapWebhook(rawBody, sig, process.env.LEDGERCAP_WEBHOOK_SECRET!)) {
  return new Response('Unauthorized', { status: 401 })
}
const event = JSON.parse(rawBody)

Always verify against the raw body — JSON re-serialization can change key order and break the signature match.

Client libraries

TypeScript SDK

A single-file, zero-dependency TypeScript client with full type coverage, dual-unit money support, and automatic retry on OCC contention (HTTP 429).

Download

# paste into your project
curl -s https://api.ledgercap.dev/api/v1/sdk > ledgercap.ts

Usage

import { LedgerCap, LedgerCapError } from './ledgercap'

const lc = new LedgerCap({ token: process.env.LEDGERCAP_TOKEN! })

// charge (use cents OR nanodollars, not both)
const result = await lc.charge({ amountCents: 0.15, description: 'gpt-4o call' })
if (!result.allowed) throw new Error(`cap hit: ${result.reason}`)

// authorize → capture pattern
const hold = await lc.authorize({ amountCents: 1.00 })
if (hold.authorized) {
  const actual = computeActualCost()  // dollars, e.g. 0.73 = $0.73
  await lc.capture({ holdId: hold.holdId, captureCents: actual })
}

// per-user wallet
const r = await lc.charge({
  amountCents: 0.15,
  externalId: 'user_abc',
  createIfMissing: true,
  walletDefaults: { capNanos: 5_000_000_000 },  // $5 cap
})
MethodDescription
charge(params)Debit credit; returns { allowed }
meter(params)Price an LLM call from the rate card and charge it
authorize(params)Reserve credit without spending
capture(params)Settle an open hold
void(params)Release an open hold
topup(params)Add credit (admin token required)
balance()Current balance, reserved credit, and daily limit
me()Identity + settings for the token owner

Constructor options: token (required), baseUrl (default: https://api.ledgercap.dev), maxAttempts (default: 3). On HTTP 429 the client waits and retries automatically with exponential back-off up to maxAttempts.