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.
https://api.ledgercap.dev/api/v1Get started
Quickstart
- 1
Mint an API token
Go to Settings → API tokens. The secret is shown once; only its hash is stored. Use a
chargetoken for agents and anadmintoken (which can top up) for your billing backend. - 2
- 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_TOKENA 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.00 | amountNanos: 1000000000 | amountCents: 100 |
| 1¢ | amountNanos: 10000000 | amountCents: 1 |
| $0.0015 | amountNanos: 1500000 | amountCents: 0.15 |
| $0.000000001 | amountNanos: 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 defaultsReference
Endpoints
Base URL https://ledgercap.dev. On contention the write endpoints answer 429 with { "retryable": true } — back off and retry.
/api/v1/chargeDebit 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
| amountNanos | integer | Positive integer nanodollars. Send this OR amountCents, not both. |
| amountCents | number? | Decimal cents (e.g. 0.15 = $0.0015). Alternative to amountNanos. |
| idempotencyKey | string? | Replays the prior result instead of double-charging. |
| description | string? | Free-text label stored on the ledger entry. |
Responses
| 200 | allowed | { allowed: true, balanceNanos, ledgerId, idempotent, spentTodayNanos, dailyLimitNanos } |
| 402 | refused | { 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"}'/api/v1/meterPrice 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.
| model | string | Rate-card model id, e.g. claude-opus-4-8, gpt-4o. |
| usage | object? | 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. |
| inputTokens | integer? | Uncached prompt tokens (≥ 0). Required unless usage is sent. |
| outputTokens | integer? | Completion tokens (≥ 0). Required unless usage is sent. |
| cacheReadTokens | integer? | Prompt-cache hits (read line). Requires a cache_read rate on the model. |
| cacheWriteTokens | integer? | Prompt-cache writes (Anthropic cache_creation). Requires a cache_write rate on the model. |
| markupBps | integer? | Markup in basis points (10000 = +100% = 2×). Default 0. |
| idempotencyKey | string? | Safe-retry key (bound to the full token breakdown). |
| description | string? | Label stored on the ledger entry. |
Responses
| 200 | allowed | { allowed: true, model, modelName, inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?, costNanos, markupBps, marginNanos, amountNanos, ledgerId, idempotent, balanceNanos, spentTodayNanos, dailyLimitNanos } |
| 402 | refused | { allowed: false, reason, model, modelName, inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?, costNanos, markupBps, marginNanos, amountNanos, balanceNanos, spentTodayNanos, dailyLimitNanos } (cap held; full breakdown still returned) |
| 400 | bad input | Unknown 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}}'/api/v1/captureSettle 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
| holdId | string | The hold to settle. |
| captureNanos | integer? | Amount to capture (≤ hold). Defaults to the full hold. |
| captureCents | number? | Decimal cents alternative to captureNanos. |
| description | string? | Label stored on the capture ledger entry. |
Responses
| 200 | captured | { ok: true, holdId, capturedNanos, releasedNanos, ledgerId, ... } |
| 402 | daily_limit_exceeded | The capture would exceed today’s spend limit. |
| 404 / 409 / 400 | errors | not_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}'/api/v1/voidRelease an open hold without spending. The reserved credit returns to available.
Responses
| 200 | released | { ok: true, holdId, releasedNanos, availableNanos, reservedNanos, balanceNanos } |
| 404 / 409 | errors | not_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>"}'/api/v1/topupProvision credit. Admin-scoped tokens only — a charge token gets 403, so a spending agent can never raise its own cap.
Request body
| amountNanos | integer | Positive nanodollars to add. Send this OR amountCents. |
| amountCents | number? | Decimal cents (alternative to amountNanos). |
| idempotencyKey | string? | Safe-retry key, bound to amount and description. |
| description | string? | 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/api/v1/balanceReturns 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 }/api/v1/meIdentity 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
| 200 | OK | Request succeeded (check allowed / authorized / ok in the body). |
| 400 | Bad request | Malformed JSON or failed validation (see issues). |
| 401 | Unauthorized | Missing or invalid bearer token. |
| 402 | Capped | insufficient_funds or daily_limit_exceeded — the cap held. |
| 403 | Forbidden | Token scope too low (e.g. charge token calling topup). |
| 404 | Not found | No such hold. |
| 409 | Conflict | Hold state conflict, or an idempotency key was reused with a different request. |
| 429 | Retryable | Write 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 sweepA 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.
/api/v1/walletsProvision a wallet for one end-user. Returns the wallet with its generated UUID. Requires admin scope.
Request body
| label | string? | Human-readable name, e.g. the end-user email. |
| externalId | string? | Your own user id. Unique per account; used in charge/meter instead of our UUID. |
| initialBalanceNanos | integer? | Credit to load at creation (default 0). |
| capNanos | integer? | Per-UTC-day spend cap for this wallet (0 = unlimited). |
| allowOverrun | boolean? | Let the wallet dip below zero (overrun). Default false. |
| overrunLimitNanos | integer? | Max negative balance when allowOverrun is true. |
| metadata | string? | Free-form string stored on the wallet (plan tier, region, etc.). |
Response
| 201 | created | { wallet: { id, externalId, label, status, capNanos, balanceNanos, spentTodayNanos, ... } } |
| 409 | conflict | externalId 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, ... } }/api/v1/walletsList 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"/api/v1/wallets/:idFetch one wallet including its live balance and spentTodayNanos.
curl "https://ledgercap.dev/api/v1/wallets/$WALLET_ID" \
-H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN"/api/v1/wallets/:idUpdate a wallet's label, cap, status, or overrun settings. Pass only the fields you want to change.
| label | string? | New human-readable name. |
| capNanos | integer? | New per-UTC-day spend cap (0 = unlimited). |
| status | string? | 'active' | 'suspended' | 'closed' |
| allowOverrun | boolean? | Enable / disable overrun. |
| overrunLimitNanos | integer? | 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"}'/api/v1/wallets/:id/topupAdd credit to a wallet's balance. Same idempotency semantics as the account-level topup. Requires admin scope.
| amountNanos | integer | Positive nanodollars. Send this OR amountCents. |
| amountCents | number? | Decimal cents (alternative to amountNanos). |
| idempotencyKey | string? | Deduplicates a retry. |
| description | string? | 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"}'/api/v1/wallets/:id/usageFull 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_topup | Renewal | Top up or set the balance on a renewal event. Idempotent per eventId. |
| auto_suspend | Lifecycle | Suspend the wallet automatically when balance falls to/below a threshold. |
| notify | Webhook | POST a JSON payload to your URL when a lifecycle event fires (e.g. suspended). |
/api/v1/wallets/:id/rulesAttach a rule to a wallet. The request body is a discriminated union on ruleType.
renewal_topup — top up on a billing renewal
| ruleType | string | "renewal_topup" |
| mode | string | "add" (add credit) | "set" (set balance to amount) |
| amountNanos | integer | Nanodollars to add/set. Send this OR amountCents. |
| amountCents | number? | Decimal cents (alternative to amountNanos). |
| event | string? | 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
| ruleType | string | "auto_suspend" |
| thresholdNanos | integer? | Suspend when balance ≤ this (nanodollars). Default 0 = when exhausted. |
| thresholdCents | number? | Decimal cents alternative to thresholdNanos. |
# Suspend the wallet the moment it hits zero
-d '{"ruleType":"auto_suspend","thresholdNanos":0}'notify — webhook on lifecycle events
| ruleType | string | "notify" |
| url | string | HTTPS endpoint. Private IPs / loopback are blocked. |
| on | string[] | Events to POST: ["suspended"] (more events planned). |
-d '{"ruleType":"notify","url":"https://yourapp.com/hooks/fusebox","on":["suspended"]}'/api/v1/wallets/:id/rulesList all rules attached to a wallet.
curl "https://ledgercap.dev/api/v1/wallets/$WALLET_ID/rules" \
-H "Authorization: Bearer $LEDGERCAP_ADMIN_TOKEN"/api/v1/wallets/:id/rules/:ruleIdEnable 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}'/api/v1/wallets/:id/rules/:ruleIdPermanently 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/api/v1/wallets/renewTrigger 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.
| walletId | string? | Our wallet UUID (provide this or externalId). |
| externalId | string? | Your user id (alternative to walletId). |
| eventId | string? | Idempotency key for this renewal event (e.g. Stripe invoice id). |
| event | string? | 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 }/api/v1/wallets/summaryPortfolio-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.tsUsage
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
})| Method | Description |
|---|---|
| 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.