figuard
v1.2.0
Published
FiGuard TypeScript SDK — pre-flight spend authorization for AI agents
Maintainers
Readme
FiGuard TypeScript SDK
Pre-flight spend authorization for AI agents. Stop your agent from overspending before it happens.
Install
npm install figuardRequires Node.js 18+.
Zero-config demo (no account needed)
import { FiGuardClient } from "figuard";
// No arguments — connects to the shared public sandbox automatically
const client = new FiGuardClient();Note: The shared sandbox is for demos only. Data is wiped periodically. For production, self-host FiGuard and pass your API key:
new FiGuardClient({ apiKey: "fg_live_...", baseUrl: "https://your-figuard.example.com" })or setFIGUARD_API_KEY/FIGUARD_BASE_URLenvironment variables.
Quickstart
import { FiGuardClient, FiGuardDeniedException } from "figuard";
const client = new FiGuardClient({ apiKey: "fg_live_..." });
// 1. Create a budget for your user's session
const budget = await client.createBudget({
userId: "user_123",
totalLimit: 500,
expiresIn: "24h",
currency: "USD",
});
// 2. Pre-authorize every spend before it happens
try {
const result = (await client.authorize({
sessionToken: budget.primaryToken!.sessionToken!,
agentId: "agent_flight_booker",
actionType: "PURCHASE",
description: "NYC to LAX flight",
requestedQuantity: 299,
idempotencyKey: "txn-abc-001", // required — use a stable unique key
})).raiseIfDenied();
// 3. Execute the real transaction, then confirm
await client.confirmEvent({
eventId: result.eventId,
confirmedQuantity: 299,
externalTransactionId: externalTxId,
});
} catch (e) {
if (e instanceof FiGuardDeniedException) {
console.log("Spend denied:", e.denialReason);
// e.g. INSUFFICIENT_FUNDS, BUDGET_PAUSED, ANOMALY_DETECTED
}
}Allocation-based budgets
Allocations ring-fence spend by category and enforce item-type rules:
const budget = await client.createBudget({
userId: "user_123",
totalLimit: 500,
expiresIn: "24h",
currency: "USD",
allocations: [
{
category: "flights",
allowedCategories: ["flight", "airline"],
limit: 300,
enforcementMode: "STRICT",
forbiddenItemTypes: ["gift_card", "upgrade"],
},
{
category: "hotels",
allowedCategories: ["hotel", "accommodation"],
limit: 200,
enforcementMode: "CATEGORY_CONSTRAINED",
},
],
});
// claimedCategory must match one of allowedCategories
const result = await client.authorize({
sessionToken: budget.primaryToken!.sessionToken!,
agentId: "travel_agent",
actionType: "PURCHASE",
description: "Flight to NYC",
requestedQuantity: 250,
idempotencyKey: "flight-nyc-001",
claimedCategory: "flight",
claimedItemType: "economy_ticket",
});Payment lifecycle
// Authorize reserves funds — money has not moved yet
const result = (await client.authorize({ ... })).raiseIfDenied();
// Confirm when payment succeeds — finalizes the spend
await client.confirmEvent({ eventId: result.eventId, confirmedQuantity: 249 });
// Fail when the payment processor declines — releases the reservation
await client.failEvent({ eventId: result.eventId, reason: "PAYMENT_DECLINED" });
// Void if the action is cancelled before payment
await client.voidEvent({ eventId: result.eventId, reason: "USER_CANCELLED" });Anomaly detection
Enable per-budget anomaly detection to auto-pause budgets when a request is statistically unusual:
const budget = await client.createBudget({
userId: "user_123",
totalLimit: 2000,
expiresIn: "24h",
currency: "USD",
anomalyDetectionEnabled: true,
});When a request exceeds mean × multiplier (default 3×) and at least 5 prior transactions exist, the budget is auto-paused and an ANOMALY_DETECTED webhook fires. Resume after review:
const budget = await client.resumeBudget({
budgetId,
overrideReason: "Reviewed — legitimate bulk purchase",
overrideBy: "ops-team",
});Delegation tokens (multi-agent fleets)
Delegate a capped slice of a fleet budget to a sub-agent. The sub-agent gets its own sessionToken scoped to specific categories and limits — it cannot exceed its caps even if the parent budget still has funds.
// Parent agent creates the fleet budget
const fleetBudget = await client.createBudget({
userId: "user_123",
totalLimit: 2000,
expiresIn: "8h",
currency: "USD",
allocations: [
{ category: "flights", limit: 1000 },
{ category: "hotels", limit: 1000 },
],
});
// Issue a scoped token for a sub-agent (e.g. a flight-booking specialist)
const token = await client.createDelegationToken({
budgetId: fleetBudget.id,
label: "flight-agent",
caps: [{ category: "flights", limit: 300 }],
});
// Hand token.sessionToken to the sub-agent
const flightAgentToken = token.sessionToken!;
// Sub-agent authorizes against its cap — cannot exceed $300 flights
const result = (await subClient.authorize({
sessionToken: flightAgentToken,
agentId: "flight-specialist",
actionType: "PURCHASE",
description: "NYC flight",
requestedQuantity: 250,
idempotencyKey: "flight-nyc-001",
claimedCategory: "flights",
})).raiseIfDenied();
// Revoke at any time
await client.revokeDelegationToken(token.id);Error handling
import {
FiGuardDeniedException, // decision === DENIED (not an HTTP error)
FiGuardApiError, // 4xx / 5xx from the API
FiGuardConnectionError, // network failure after all retries
} from "figuard";
try {
const result = (await client.authorize({ ... })).raiseIfDenied();
} catch (e) {
if (e instanceof FiGuardDeniedException) {
console.log(e.denialReason); // e.g. "INSUFFICIENT_FUNDS"
console.log(e.denialMessage); // human-readable explanation
// if denialReason === "ENTITY_ALREADY_AUTHORIZED":
// e.originalEventId // UUID of the existing event
} else if (e instanceof FiGuardApiError) {
console.log(e.statusCode, e.message);
} else if (e instanceof FiGuardConnectionError) {
console.log("Network failure:", e.message);
}
}The SDK automatically retries 5xx responses up to 3 times with exponential backoff (1s, 2s, 4s). 4xx errors are never retried.
Ledger and reporting
// Paginated spend history
const page = await client.getLedger({
budgetId,
page: 0,
size: 20,
decision: "CONFIRMED",
});
for (const event of page.events) {
console.log(event.id, event.decision, event.confirmedQuantity);
}
// Causal spend tree (which agent triggered which spend)
const tree = await client.getSpendTree(budgetId);
for (const root of tree.roots) {
console.log(root.event.agentId, root.children.length, "child events");
}Multi-resource authorization (CompositeGuard)
Authorize across multiple budgets atomically — if any resource denies, all prior authorizations in the same call are voided automatically:
import { CompositeGuard, GuardedResource } from "figuard";
const guard = new CompositeGuard([
new GuardedResource(client, tokenBudget.sessionToken!, "tokens"),
new GuardedResource(client, usdBudget.sessionToken!, "USD"),
]);
const result = await guard.authorize({
agentId: "travel_agent",
actionType: "LLM_CALL",
description: "search flights",
requested: { tokens: 1500, USD: 0.09 },
idempotencyKey: crypto.randomUUID(),
});
if (result.allAuthorized) {
// ... do the work ...
await guard.confirm(result, { tokens: 1423, USD: 0.085 });
} else {
console.log(`Denied on ${result.firstDenialResource}: ${result.firstDenial?.denialReason}`);
}dry_run mode
Test your integration without writing to the ledger or firing webhooks:
const result = await client.authorize({
sessionToken: budget.primaryToken!.sessionToken!,
agentId: "agent_1",
actionType: "PURCHASE",
description: "Test authorization",
requestedQuantity: 100,
idempotencyKey: "test-key-001",
dryRun: true, // nothing written, no webhooks fired
});
console.log(result.isAuthorized); // true/false based on real enforcementConfiguration
const client = new FiGuardClient({
apiKey: "fg_live_...",
baseUrl: "https://your-figuard.example.com", // your self-hosted instance
timeoutMs: 30_000, // per-request timeout (default: 30s)
});Security notes
- The raw
sessionTokenis returned once oncreateBudget()and never again. Store it securely — treat it like a password. idempotencyKeyis optional — a UUID is auto-generated if omitted. Provide a stable key per logical spend intent so retries collapse to the same event instead of creating duplicates.
Self-hosting
Point baseUrl at your own FiGuard deployment:
const client = new FiGuardClient({
apiKey: "fg_live_...",
baseUrl: "http://localhost:8080",
});Run FiGuard locally: see figuard-core for Docker setup.
MCP Server
Use FiGuard directly from Claude Code, Cursor, or Claude Desktop — no SDK code required:
npx figuard-mcpSee figuard-mcp for setup instructions.
