Skip to content

Usage, cost & logs

Pricing a run in one call

Instead of looking up every request's log record, ask the cost API:

import datetime as dt

today = dt.date.today()
spend = faig.cost_summary(today, today, ws_id=ws_id)
print(spend["totals"]["chargebackSpend"], spend["volume"])

# Who or what spent it
for row in rank_items(faig.cost_rankings(today, today, by="key", limit=10, ws_id=ws_id)):
    print(row["name"], row["chargebackSpend"])

Dates are date objects or "YYYY-MM-DD"; omitted, the window is the last 30 days. by is key, user, workspace or model; usage_* calls report requests and tokens, cost_* calls report spend. Use rank_items() to read ranking rows: the API answers items, top/bottom lists (end="both"), or only meta when nothing was used in the window.

Drill into one ranked row with usage_breakdown(by, id) / cost_breakdown(by, id) and read it with breakdown_items(): a key, user or workspace splits by model, while a model splits by who used it (the API names that list consumers for usage and spenders for cost). Rankings still list deleted keys (deleted: true), and their breakdown is a 404.

Usage and cost figures are aggregated, so a request can take a little while to appear in them. The traffic log is immediate.

Traffic logs

for row in faig.traffic_logs(range="24h", filters={"virtual_key": "student04-key"}):
    detail = faig.traffic_log(row["uuid"])
    print(detail["model"], detail["verdict"], detail["cost"], detail["detector"])

traffic_logs() is an iterator that follows the API's cursor; stop early with max_items=. Rows are summaries; traffic_log(uuid) returns the full record (prompt, cost, latency, detector, guardrail, policy, http status).

Time is range= 24h (default), 7d, month or all, or a custom window with start=/end= (datetime or epoch milliseconds).

Filters map a field to a value, a list of values, or an operator:

filters = {
    "model": ["openai-gpt-4.1-mini", "openai-gpt-4o"],  # is any of
    "verdict": {"operator": "is not", "value": "Log"},
    "virtual_key": {"operator": "contains", "value": "student"},
}

The API ignores filters it does not understand

A misspelled filter name or operator is silently dropped and every row comes back. The client checks both against the known lists and raises ValueError instead. Valid values per field: traffic_log_filter_options().

ICAP logs (FortiGate-inspected traffic) work the same way through icap_logs(), with their own filter fields. An appliance with ICAP turned off answers 404.

Waiting for a request's log row

Traffic log rows land a few seconds after the request. Note the time BEFORE sending, then wait for the row:

import datetime as dt

sent = dt.datetime.now(dt.timezone.utc)
faig.chat_completion(secret, model, messages)
rows = faig.wait_for_traffic(sent, filters={"virtual_key": "student04"}, timeout=60)
print(rows[0]["verdictLabel"], faig.traffic_log(rows[0]["uuid"])["detector"])

The virtual_key filter takes the key's NAME (its keyAlias in the rows).

Audit log

for row in faig.iter_audit_logs(range="7d", filters={"action": "Approve"}):
    print(row["username"], row["message"])

Same time and filter rules as the traffic log; filters are user, role, action, message and workspace.

Which models can a key call?

A request for a model the key is not granted fails with 404 "model not available to this API key". Check first:

faig.available_models(secret)  # names the key may call
faig.can_use_model(secret, "openai-gpt-4.1-mini")

To see why a model is missing: workspace_models(ws_id) (what the workspace allows), grantable_providers(ws_id) (what a key there can be granted) and attachable_policies(ws_id).

Natural Language detectors and the Playground

body = ai_detector_body(
    "dark-side",
    [("sith-recruiting", ["Urging someone to join the dark side"], ["Film plot discussion"])],
)
faig.playground("Join the dark side, apprentice.", body, ws_id)  # test it unsaved
det = faig.create_detector(ws_id, body)
faig.submit(ws_id, "detectors", det["uuid"])  # or approve_new() as org admin

Testing without an LLM call:

What Call
An unsaved detector body playground(text, body, ws_id)
A saved detector test_detector(ws_id, uuid, text)
A guardrail (saved, or a list of detector ids) test_guardrail(text, guardrail_id=..., ws_id=...)

Each rule is (name, violate_if, exempt_if); the builder fills both the editor's aiRules and the flat rules list the engine evaluates. withdraw(ws_id, kind, id) cancels a pending submission.