Foxy Audit
Book a demo
← The manual

API reference

Every name the package exports, with its arguments, what it returns and what it raises. For a gentler start, read the lessons in Part 3 first.

Skip to the chapter
All chapters

What you'll learn

  • every name you can import from foxy_audit;
  • the arguments and defaults of each class and function;
  • every exception, and when you get it;
  • which submodules are safe to use, and which are internal.

The top-level names

Python
import foxy_audit

print(foxy_audit.__all__)

What you should see:

['AuditRequiredError', 'CheckResult', 'ExplainResult', 'FoxyClient', 'FoxyConfig', 'FoxyPolicyBlocked', 'FoxyResponseBlocked', 'InsecureEndpointError', 'SpoolFull', 'audit', 'check', 'explain', '__version__']
Name Kind Defined in
FoxyClient class foxy_audit.client
audit function foxy_audit
check function foxy_audit.introspect
explain function foxy_audit.introspect
CheckResult dataclass foxy_audit.introspect
ExplainResult dataclass foxy_audit.introspect
FoxyConfig dataclass foxy_audit.config
FoxyPolicyBlocked exception foxy_audit.client
FoxyResponseBlocked exception foxy_audit.client
AuditRequiredError exception foxy_audit.client
SpoolFull exception foxy_audit.spool
InsecureEndpointError exception foxy_audit.config
__version__ text "1.18.0"

FoxyClient

Python
class FoxyClient:
    def __init__(self, api_key=None, endpoint=None, udp_host=None, udp_port=None,
                 desktop_ping=True, timeout=5.0, commitment_key=None, commitment_key_path=None,
                 salt_sidecar_path=None, spool_path=None, client_id=None, system_id=None,
                 audit_required=None, mode=None, response_scan=None, on_event=None,
                 injection_classifier=None): ...

Each argument left as None falls back to its environment variable, then to its default. udp_host, udp_port, desktop_ping, timeout and on_event have no environment variable. Configuration lists them all.

Raises:

  • ValueError when system_id is given but is not written exactly as GET /v1/systems returns it;
  • ValueError when client_id is given with a character other than letters, digits and _ . : -, or over 128 characters;
  • ValueError when audit_required=True is given and no API key is set. FOXY_AUDIT_REQUIRED with no key raises nothing: an error is logged, and audit_required is off;
  • TypeError when on_event is not a function, or is an async def function;
  • InsecureEndpointError when endpoint is plain http:// to another machine;
  • an error such as PermissionError when an API key is set and the spool file's folder cannot be written to;
  • OSError when commitment_key_path is given and the key file there can be neither read nor made, or holds something that is not a key. FOXY_COMMITMENT_KEY_FILE raises nothing: a warning is logged, and the API key is used;
  • OSError when audit_required=True is given and the key file can be neither read nor made, so commitments would be made with the API key. FOXY_AUDIT_REQUIRED raises nothing: an error is logged, and the API key is used.

When it is made, with an API key set, it:

  • makes and keeps a client_id in the spool, if you did not give one;
  • reads the commitment key file, or makes it, when no commitment key is set;
  • wakes up delivery of records left in the spool by an earlier run;
  • signs up for the background read of your workspace's setting. That read never happens on your call's path.
Attribute What it is
cfg the resolved settings, a FoxyConfig. It is read-only.
enabled True when an API key is set, so records are sent
on_event the receipt function. Setting it checks it the same way the constructor does.

FoxyClient.audit

Python
def audit(self, policy="default", agent=None, mode=None, system_id=None): ...

Returns a decorator that checks and records calls to the function it wraps.

It finds the prompt among each call's arguments: the first of the keyword arguments prompt, user_prompt, message, messages, contents, text, input and query, or else the first positional argument that is a string. With neither, it checks and hashes an empty prompt.

Argument What it does
policy which checks run. Folded to lower case. An unknown but valid tag runs the basic checks and warns. A malformed one falls back to "default" and warns.
agent the model's name, saved in the record and in the chain. Letters, digits, _ . - : / ( ) + and spaces, up to 128 characters. Any other value raises ValueError when you decorate, because the backend would refuse it.
mode "observe", "block" or "redact", for this function only. An unknown value warns and falls back to "observe".
system_id the AI system for these calls. Raises ValueError when you decorate, if it is malformed.

The mode governs the prompt only. What happens to the answer is set by the client's response_scan, which has no per-function setting.

It works with plain functions, async def functions, async def functions that yield, and plain functions that return a generator. Errors your function raises reach you unchanged, after an exception record is made.

It can raise three errors into your code:

  • FoxyPolicyBlocked: mode="block", a workspace set to block, or a redaction that could not remove a finding. Your function was not called.
  • FoxyResponseBlocked: response_scan="block" and the scan flagged the answer. Your function did run. In a stream, the pieces before the flagged one have already reached your code.
  • AuditRequiredError: audit_required=True, and the record could not be confirmed. A blocked prompt can raise it in place of FoxyPolicyBlocked.

FoxyClient.check

Python
def check(self, prompt, policy="default"): ...

The same as the module-level check. It uses no client state.

FoxyClient.log_interaction

Python
def log_interaction(self, prompt, response, policy, agent=None, metadata=None,
                    event_type="interaction", decision=None, policy_rules=None,
                    signals=None, blocked_reason=None, system_id=None): ...

Records one call without a decorator. It does not check the prompt first.

Argument What it does
prompt, response any value. Written out in a fixed way and hashed. The text is dropped right after.
policy the policy tag, folded here. After folding it must be lower-case letters, digits and _, up to 32, or ValueError is raised.
agent the model's name, optional. The same limits as on the decorator: any other value raises ValueError.
metadata extra keys for event_metadata. Keys the SDK sets itself are dropped, with a warning, and so is a key the backend does not accept or a value over its limits. The wire format lists both.
event_type "interaction", "stream", "blocked", "redacted", "exception" or "response_blocked". Anything that is not lower-case letters, digits and _, up to 32, raises ValueError.
decision, policy_rules, signals, blocked_reason the checks' own findings. signals is merged with the SDK's own personal-data scan of the prompt and answer, sorted, with no repeats, and sent as pii_signals.
system_id the AI system for this call

Returns, when an API key is set, a small dictionary with the keys status ("queued"), event_id and client_seq. With audit_required on, it returns the backend's receipt for the record instead. With no key, it returns None.

Raises ValueError for a malformed system_id, agent, event_type or policy, and AuditRequiredError when audit_required is on and the record could not be confirmed. Any other problem is logged at debug level and swallowed, so recording can never break your program.

Keys the SDK sets itself in event_metadata, so a copy passed in metadata is dropped with a warning: ruleset_version, ruleset_hash, decision, policy_rules, blocked_reason, policy_tag_raw and system_id. Renaming such a key does not keep it: the SDK drops a key the backend does not know as well.

audit

Python
def audit(policy="default", agent=None, mode=None): ...

The module-level decorator. It uses one client, made from the environment the first time you use it, and kept for the life of the program. It has no system_id argument: set FOXY_SYSTEM_ID, or make a FoxyClient.

check

Python
def check(prompt, policy="default"): ...

Would this prompt trip anything, under today's rules? It needs no key, no network and no spool, and it returns only labels. It may warn about an unknown policy tag.

CheckResult

A read-only dataclass, with these fields: policy, triggered, rules, signals, reason, ruleset_version and ruleset_hash. as_dict() gives a plain dictionary with no prompt text in it.

explain

Python
def explain(prompt, event_id, export, commitment_key, salt_sidecar_path=""): ...

Replays one exported record against the ruleset it names, and shows what matched. export is a parsed export, or a path to one. commitment_key is one key, or a list of keys tried in turn. It never raises for a bad export, a missing record, a wrong prompt or an unknown ruleset: each is a status. See Proving a row with foxy explain.

ExplainResult

A read-only dataclass, with these fields: status, message, event_id, policy_tag, ruleset_version, commitment_verified, ruleset_verified and matches.

Member What it is
ok True when the status is "explained" or "no_matches"
as_dict(include_text=False) a plain dictionary. It leaves out the matched text unless you ask for it.
ruleset_verified True (checked and matched), False (checked and different) or None (not checked)
matches a list of Match

Match

Match(rule_id, start, end, text), with as_dict(include_text=False). Import it from foxy_audit.introspect.

introspect.STATUSES

Python
('explained', 'no_matches', 'hash_mismatch', 'row_not_found', 'export_unreadable',
 'salt_unavailable', 'unknown_ruleset', 'ruleset_mismatch', 'ruleset_unrecorded',
 'no_rules_fired', 'provenance_ambiguous')

Test whether a status is in this tuple, rather than typing the names into your code.

FoxyConfig

A read-only dataclass. FoxyClient makes one for you, in foxy.cfg.

Python
from dataclasses import dataclass


@dataclass(frozen=True)
class FoxyConfig:
    api_key: str = ""
    endpoint: str = "http://127.0.0.1:8000"
    udp_host: str = "127.0.0.1"
    udp_port: int = 9999
    desktop_ping: bool = True
    timeout: float = 5.0
    commitment_key: str = ""
    commitment_key_path: str = ""
    salt_sidecar_path: str = ""
    spool_path: str = ""
    client_id: str = ""
    system_id: str = ""
    audit_required: bool = False
    mode: str = "observe"
    mode_is_explicit: bool = False
    response_scan: str = "observe"
    org_policy_enabled: bool = True
    org_policy_ttl: float = 300.0
    spool_max_rows: int = 100000
    spool_receipt_ttl: float = 604800.0
    injection_classifier: bool = True
    allow_insecure: bool = False
    endpoint_refused: bool = False
Member What it is
FoxyConfig.resolve(...) builds one from arguments, then environment variables, then defaults. A bad environment variable never makes it raise. It raises InsecureEndpointError when the endpoint argument is plain http:// to another machine. It also takes org_policy, org_policy_ttl, spool_max_rows and spool_receipt_ttl, which FoxyClient does not pass on.
enabled True when an API key is set
transport_ok True when the SDK may send the key to endpoint
mode_is_explicit True when a person chose the mode. Your workspace's setting fills in the mode where this is False. A workspace set to block blocks whatever this says.
endpoint_refused True when FOXY_BACKEND_URL was refused, so nothing is sent

Exceptions

Python
from foxy_audit import (AuditRequiredError, FoxyPolicyBlocked, FoxyResponseBlocked,
                        InsecureEndpointError, SpoolFull)

FoxyPolicyBlocked

A RuntimeError. A prompt was blocked before your function ran, because of mode="block" or a redaction that could not remove a finding. A blocked record was made first. The message names the policy tag and a short reason, and never the prompt.

FoxyResponseBlocked

A RuntimeError. An answer was blocked after your function ran, because the scan flagged it and response_scan is "block". The answer is not returned. A response_blocked record was made first.

For a stream, the pieces before the flagged one have already reached your code, and the error is raised inside your loop. The record is then a stream with the decision response_truncated, unless the stream was cut at its first piece.

It has one attribute, audit_delivery_failed. It is True when audit_required is on and the record also could not be confirmed. The block still wins.

It is not a kind of FoxyPolicyBlocked. Code that catches FoxyPolicyBlocked may assume the model was never called. Here, it was.

AuditRequiredError

A RuntimeError, raised only when audit_required is on and the record could not be confirmed: the wait ran out, the spool was full, the backend address is refused, or saving the record failed. Its __cause__ names the problem, when there is one.

When the wait ran out, the record was saved in the spool before the wait began, so it may still reach the ledger later. When the spool was full, the record was refused and never saved, and __cause__ is a SpoolFull.

SpoolFull

A RuntimeError. A record was refused, not saved, because the spool is full. Nothing already in it was thrown away.

Your code never catches it from a call. Without audit_required, the SDK logs a warning and your call goes on. With it, you get an AuditRequiredError whose __cause__ is the SpoolFull, with these attributes:

Attribute What it is
path the spool file
rows how many records are waiting
cap the limit that was reached
scope "pair" when one key and address reached its limit, "file" when the whole file reached its ceiling

SpoolFull means "refused, never saved": that call needs recording again.

InsecureEndpointError

A ValueError, raised when a backend address given in code may not carry your key. That is plain http:// to another machine, unless FOXY_ALLOW_INSECURE=1 is set, or an address that two parts of Python read differently. The same address in FOXY_BACKEND_URL does not raise: it is logged, and nothing is sent.

Warnings and log messages

What When
UserWarning an unknown policy tag, once for each tag in each run
a warning in the log a malformed tag or mode on a decorator, a reserved metadata key, a metadata key or value the backend would refuse, an unreadable FOXY_AUDIT_REQUIRED, a refused system, a commitment key that is your API key (once in each run), a new commitment key file (once, with its path), a key file that can be neither read nor made
an error in the log a malformed FOXY_SYSTEM_ID or FOXY_CLIENT_ID, FOXY_AUDIT_REQUIRED with no API key, or a refused FOXY_BACKEND_URL

All SDK logging goes to the "foxy_audit" logger. The workspace setting uses "foxy_audit.org_policy".

Submodules

These are public and stable:

Module Use it for
foxy_audit.policy evaluate, redact, redact_value, surviving_rules, normalise_policy_tag, resolve_policy_tag, KNOWN_POLICY_TAGS, set_classifier_enabled
foxy_audit.response_policy evaluate_response, StreamScanner, coverage_rule, CARRY_CHARS
foxy_audit.pii detect_pii, redact, REDACTION_LABELS, deep_pii_status
foxy_audit.ruleset CURRENT_VERSION, CURRENT_HASH, load, known_versions, hash_of, provenance, describe_live, drift, PROVENANCE_KEYS
foxy_audit.hashing sha256_hex, canonical_json, commitment_hex, estimate_tokens
foxy_audit.introspect check, explain, replay, STATUSES, CheckResult, ExplainResult, Match, UnknownValidator
foxy_audit.issuer_ranges starts_with_assigned_iin, issuer_for, length_allowed, ISSUERS_2, Issuer

These are internal. They are listed so you can read them, not so you can depend on them: foxy_audit.client, config, dispatch, spool, sidecar, org_policy, adapters, normalise, udp and cli.

policy.PolicyDecision, which evaluate returns, is internal too. It is shared by the prompt and answer checks, and it has changed often. Use check() and its CheckResult instead.