FiGuard Python SDK — pre-flight spend authorization for AI agents
Project description
FiGuard Python SDK
Pre-flight spend authorization for AI agents. Stop your agent from overspending before it happens.
Install
# Core (sync client only)
pip install figuard
# With framework integrations
pip install figuard[langchain] # LangChain + LangGraph
pip install figuard[crewai] # CrewAI
pip install figuard[openai] # OpenAI function calling
pip install figuard[openai-agents] # OpenAI Agents SDK
pip install figuard[anthropic] # Anthropic tool_use
pip install figuard[async] # AsyncFiGuardClient (aiohttp)
pip install figuard[all] # everything above
Requires Python 3.9+.
Zero-config demo (no account needed)
from figuard import FiGuardClient
# No arguments — connects to the shared public sandbox automatically
client = FiGuardClient()
Or with framework integrations — one line wires up the entire budget + callback:
# LangChain (also available as: pip install figuard-langchain)
from figuard import auto_guard_langchain
# Set FIGUARD_API_KEY + FIGUARD_BASE_URL for production — picked up automatically.
# Without them, connects to the shared sandbox (demo only, data wiped periodically).
executor = auto_guard_langchain(executor, budget=500, velocity_max_per_minute=10)
# CrewAI
from figuard import auto_guard_crewai
auto_guard_crewai(book_flight_tool, budget=500, velocity_max_per_minute=10)
Note: The shared sandbox is for demos only. Data is wiped periodically. For production, self-host FiGuard and set
FIGUARD_API_KEY/FIGUARD_BASE_URL— the wrappers pick them up automatically.
Quickstart
from figuard import FiGuardClient, FiGuardDeniedException
client = FiGuardClient(api_key="fg_live_...")
# 1. Create a budget for your user's session
budget = client.create_budget(
user_id="user_123",
total_limit=500.00,
expires_in="24h",
)
# 2. Pre-authorize every spend before it happens
try:
result = client.authorize(
session_token=budget.primary_token.session_token,
agent_id="agent_flight_booker",
action_type="PURCHASE",
description="NYC to LAX flight",
requested_quantity=299.00,
idempotency_key="txn-abc-001", # required — use a stable unique key
).raise_if_denied()
# 3. Execute the real transaction, then confirm
external_tx_id = payment_processor.charge(299.00)
client.confirm_event(result.event_id, confirmed_quantity=299.00,
external_transaction_id=external_tx_id)
except FiGuardDeniedException as e:
print(f"Spend denied: {e.denial_reason}")
# e.g. INSUFFICIENT_FUNDS, BUDGET_PAUSED, ANOMALY_DETECTED
Async (LangChain / CrewAI / OpenAI Agents)
import asyncio
from figuard import AsyncFiGuardClient
async def run_agent():
async with AsyncFiGuardClient(api_key="fg_live_...") as client:
budget = await client.create_budget(
user_id="user_123",
total_limit=500.00,
expires_in="24h",
)
result = await client.authorize(
session_token=budget.primary_token.session_token,
agent_id="langchain_agent",
action_type="PURCHASE",
description="Hotel booking",
requested_quantity=189.00,
idempotency_key="hotel-booking-001",
)
if result.is_authorized:
await client.confirm_event(result.event_id, confirmed_quantity=189.00)
Allocation-based budgets
Allocations let you ring-fence spend by category and enforce item-type rules:
budget = client.create_budget(
user_id="user_123",
total_limit=500.00,
expires_in="24h",
allocations=[
{
"category": "flights",
"allowedCategories": ["flight", "airline"],
"limit": 300.00,
"enforcementMode": "STRICT",
"forbiddenItemTypes": ["gift_card", "upgrade"],
},
{
"category": "hotels",
"allowedCategories": ["hotel", "accommodation"],
"limit": 200.00,
"enforcementMode": "CATEGORY_CONSTRAINED",
},
],
)
# claimedCategory must match one of allowedCategories
result = client.authorize(
session_token=budget.primary_token.session_token,
agent_id="travel_agent",
action_type="PURCHASE",
description="Flight to NYC",
requested_quantity=250.00,
idempotency_key="flight-nyc-001",
claimed_category="flight",
claimed_item_type="economy_ticket",
)
Payment lifecycle
# Authorize reserves funds — money has not moved yet
result = client.authorize(...).raise_if_denied()
# Confirm when payment succeeds — finalizes the spend
client.confirm_event(result.event_id, confirmed_quantity=249.00)
# Fail when the payment processor declines — releases the reservation
client.fail_event(result.event_id, reason="PAYMENT_DECLINED")
# Void if the action is cancelled before payment
client.void_event(result.event_id, reason="USER_CANCELLED")
Anomaly detection
Enable per-budget anomaly detection to auto-pause budgets when a single request is statistically unusual:
budget = client.create_budget(
user_id="user_123",
total_limit=2000.00,
expires_in="24h",
anomaly_detection_enabled=True,
# optional: dedicated URL for anomaly alerts
# anomaly_alert_webhook_url="https://your-service.com/alerts",
)
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:
budget = client.resume_budget(
budget_id,
override_reason="Reviewed — legitimate bulk purchase",
override_by="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 session_token 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
fleet_budget = client.create_budget(
user_id="user_123",
total_limit=2000.00,
expires_in="8h",
allocations=[
{"category": "flights", "limit": 1000.00},
{"category": "hotels", "limit": 1000.00},
],
)
# Issue a scoped token for a sub-agent (e.g. a flight-booking specialist)
token = client.create_delegation_token(
budget_id=fleet_budget.id,
label="flight-agent",
caps=[{"category": "flights", "limit": 300.00}],
)
# Hand token.session_token to the sub-agent
flight_agent_token = token.session_token
# Sub-agent authorizes against its cap — cannot exceed $300 flights
result = sub_client.authorize(
session_token=flight_agent_token,
agent_id="flight-specialist",
action_type="PURCHASE",
description="NYC flight",
requested_quantity=250.00,
idempotency_key="flight-nyc-001",
claimed_category="flights",
).raise_if_denied()
# Revoke at any time
client.revoke_delegation_token(token.id)
Error handling
from figuard import (
FiGuardDeniedException, # decision == DENIED (not an HTTP error)
FiGuardApiError, # 4xx / 5xx from the API
FiGuardConnectionError, # network failure after all retries
)
try:
result = client.authorize(...).raise_if_denied()
except FiGuardDeniedException as e:
print(e.denial_reason) # e.g. "INSUFFICIENT_FUNDS"
print(e.denial_message) # human-readable explanation
# if denial_reason == "ENTITY_ALREADY_AUTHORIZED":
# e.original_event_id # UUID of the existing event
except FiGuardApiError as e:
print(e.status_code, e.message)
except FiGuardConnectionError as e:
print("Network failure:", e)
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
page = client.get_ledger(budget_id, page=0, size=20, decision="CONFIRMED")
for event in page.events:
print(event.id, event.decision, event.confirmed_quantity)
# Causal spend tree (which agent triggered which spend)
tree = client.get_spend_tree(budget_id)
for root in tree.roots:
print(root.event.agent_id, len(root.children), "child events")
Configuration
client = FiGuardClient(
api_key="fg_live_...",
base_url="https://your-figuard.example.com", # your self-hosted instance
timeout=30, # per-request timeout in seconds
)
Security notes
- The raw
session_tokenis returned once oncreate_budget()and never again. Store it securely — treat it like a password. - The SDK logs only the first 8 characters of the session token. The full token never appears in logs.
idempotency_keyis 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.
Framework integrations
Each integration is an optional extra. Install only what you need.
LangChain / LangGraph
pip install figuard[langchain]
from figuard.integrations.langchain import FiGuardCallbackHandler, FiGuardToolGuard
# Option A — callback handler: guards every tool in an AgentExecutor
executor = AgentExecutor(
agent=agent,
tools=tools,
handle_tool_error=True, # required — sends denial to the LLM
callbacks=[FiGuardCallbackHandler(
client=client,
session_token=budget.primary_token.session_token,
tool_category_map={"book_flight": "flight", "book_hotel": "hotel"},
ignore_tools={"search_web"}, # skip authorization for read-only tools
)],
)
# Option B — tool guard: wraps a single tool in-place, hard enforcement
FiGuardToolGuard(
tool=book_flight_tool,
client=client,
session_token=budget.primary_token.session_token,
category="flight",
amount_key="price",
)
FiGuardCallbackHandler raises ToolException on denial — the LLM receives the denial reason and can try an alternative. FiGuardToolGuard patches tool._run directly so the tool never runs regardless of AgentExecutor configuration.
CrewAI
pip install figuard[crewai]
from figuard.integrations.crewai import FiGuardCrewGuard
FiGuardCrewGuard(
tool=book_flight_tool,
client=client,
session_token=budget.primary_token.session_token,
category="flight",
amount_key="price",
)
travel_agent = Agent(role="Travel Coordinator", tools=[book_flight_tool])
OpenAI Agents SDK
pip install figuard[openai-agents]
from agents import function_tool
from figuard.integrations.openai_agents import guarded_function_tool
@function_tool
@guarded_function_tool(
client=client,
session_token=budget.primary_token.session_token,
category="flight",
amount_key="price",
)
def book_flight(destination: str, price: float) -> str:
"""Book a flight to the specified destination."""
...
Apply @guarded_function_tool as the inner decorator (before @function_tool) so FiGuard wraps the raw function and has access to all kwargs.
OpenAI Function Calling
pip install figuard[openai]
import json
from figuard.integrations.openai import guarded_openai_function
@guarded_openai_function(
client=client,
session_token=budget.primary_token.session_token,
category="flight",
)
def book_flight(destination: str, amount: float) -> str:
...
# Dispatch in your tool call loop:
for tool_call in response.choices[0].message.tool_calls:
if tool_call.function.name == "book_flight":
result = book_flight(**json.loads(tool_call.function.arguments))
Anthropic Tool Use
pip install figuard[anthropic]
from figuard.integrations.anthropic import guarded_anthropic_tool
@guarded_anthropic_tool(
client=client,
session_token=budget.primary_token.session_token,
category="flight",
)
def book_flight(destination: str, amount: float) -> str:
...
# Dispatch in your tool use loop (Anthropic passes block.input as a dict):
for block in response.content:
if block.type == "tool_use" and block.name == "book_flight":
result = book_flight(**block.input)
Denial handling across all integrations
When a tool call is denied, each integration handles it differently:
| Integration | Denial behavior |
|---|---|
FiGuardCallbackHandler |
Raises ToolException — LLM receives denial reason |
FiGuardToolGuard |
Returns denial string — LLM receives denial reason |
FiGuardCrewGuard |
Returns denial string — LLM receives denial reason |
guarded_function_tool |
Returns denial string — LLM receives denial reason |
guarded_openai_function |
Returns denial string — return as tool result to the model |
guarded_anthropic_tool |
Returns denial string — return in tool_result block to Claude |
The denial string format: "FiGuard DENIED: <code> — <message>", e.g.:
FiGuard DENIED: INSUFFICIENT_FUNDS — flight allocation has $0.00 remaining
License
Apache 2.0 — see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file figuard-1.1.2.tar.gz.
File metadata
- Download URL: figuard-1.1.2.tar.gz
- Upload date:
- Size: 103.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
951b672266fd5d577c5afcbd48f538d848a782eedd563f8e9c28920a293229f5
|
|
| MD5 |
aab7dbcd9702b9420c9e10880cf4d687
|
|
| BLAKE2b-256 |
0a0c844bd30b90eaa2fee746e0de343c04ebb1df773baedb194d968f738b73c0
|
Provenance
The following attestation bundles were made for figuard-1.1.2.tar.gz:
Publisher:
publish-python.yml on figuard/figuard-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
figuard-1.1.2.tar.gz -
Subject digest:
951b672266fd5d577c5afcbd48f538d848a782eedd563f8e9c28920a293229f5 - Sigstore transparency entry: 1776456579
- Sigstore integration time:
-
Permalink:
figuard/figuard-core@b620a5a6f757c09c0e0d42fd0a6bef988dfc3b9f -
Branch / Tag:
refs/tags/v1.1.2 - Owner: https://github.com/figuard
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@b620a5a6f757c09c0e0d42fd0a6bef988dfc3b9f -
Trigger Event:
push
-
Statement type:
File details
Details for the file figuard-1.1.2-py3-none-any.whl.
File metadata
- Download URL: figuard-1.1.2-py3-none-any.whl
- Upload date:
- Size: 78.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
801991ccbdf6c1bec03a861b6a11cb0a8889a0655b4114153a44f6ed1c8b5289
|
|
| MD5 |
e5449105e5427047f281364868beb5ee
|
|
| BLAKE2b-256 |
f39b9643ea2142d2dec8f399dc91aa2d3428ad6a38d7995b5dacf88c42d33a29
|
Provenance
The following attestation bundles were made for figuard-1.1.2-py3-none-any.whl:
Publisher:
publish-python.yml on figuard/figuard-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
figuard-1.1.2-py3-none-any.whl -
Subject digest:
801991ccbdf6c1bec03a861b6a11cb0a8889a0655b4114153a44f6ed1c8b5289 - Sigstore transparency entry: 1776456682
- Sigstore integration time:
-
Permalink:
figuard/figuard-core@b620a5a6f757c09c0e0d42fd0a6bef988dfc3b9f -
Branch / Tag:
refs/tags/v1.1.2 - Owner: https://github.com/figuard
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@b620a5a6f757c09c0e0d42fd0a6bef988dfc3b9f -
Trigger Event:
push
-
Statement type: