Sign litellm calls with Asqav

If you route model calls through litellm, whether the Python SDK or the proxy gateway, you can wrap each call so it leaves a signed compliance receipt. Two Asqav calls do the work: preflight gates the call in the decision phase, and sign records it in the execution phase. Every governed call lands a verifiable ML-DSA-65 receipt on the Asqav cloud, and anyone holding the receipt id can check it with no account.

Free vs paid

The SDK local mode (local_sign) writes a hash-chained record to disk with no account and no network, free and generic. The Asqav cloud API, which the quickstart below uses, adds server-side ML-DSA-65 signing, a public verify endpoint, and policy enforcement across the action lifecycle, free up to 25,000 signatures a month.

Install

bash
pip install litellm asqav

Quickstart

Set ASQAV_API_KEY to a test key while you experiment, then wrap a completion. The example uses litellm's mock_response so it runs without a provider key. Swap the mock for a real model and the receipt still lands on your dashboard.

python
import os
import asqav
import litellm

ACTION_TYPE = "api:llm:chat"

agent = asqav.govern(
    api_key=os.environ["ASQAV_API_KEY"],
    agent_name="litellm-bridge",
)


def governed_completion(model, messages, **kwargs):
    # Decision phase: preflight checks agent status and org policy, fail closed.
    check = agent.preflight(ACTION_TYPE)
    if not check.cleared:
        raise PermissionError(check.explanation)

    response = litellm.completion(model=model, messages=messages, **kwargs)

    # Execution phase: sign the completed call, binding the provider response id.
    receipt = agent.sign(
        ACTION_TYPE,
        {"model": model, "provider_response_id": response.id},
        model_name=model,
        tool_name="litellm",
    )
    return response, receipt


response, receipt = governed_completion(
    "gpt-4o-mini",
    [{"role": "user", "content": "Summarise this quarter's churn drivers."}],
    mock_response="Churn rose on onboarding friction and a pricing-tier gap.",
)
print("receipt id:", receipt.signature_id)
print("verify at:", receipt.verification_url)

The full script is served alongside this page as litellm-bridge-example.py. Running it prints a receipt id and a public verify URL:

text
receipt id: sig_qMkGicNs1vEoJRv-4Kz5Zw
verify at: https://asqav.com/verify/sig_qMkGicNs1vEoJRv-4Kz5Zw

Confirm the receipt

Anyone holding the signature_id can call the public verify endpoint. No auth, no account.

bash
curl https://api.asqav.com/api/v1/verify/sig_qMkGicNs1vEoJRv-4Kz5Zw
json
{
  "verified": true,
  "algorithm": "ML-DSA-65",
  "action_type": "api:llm:chat",
  "receipt_type": "protectmcp:decision",
  "verification_detail": {
    "signature_valid": true,
    "signer_key_match": true,
    "chain_valid": true,
    "validation_label": "valid"
  }
}

litellm proxy

Proxy users wire the same two calls into a CustomLogger. The pre-call hook runs the decision-phase preflight and can reject the request, and the post-call success hook signs the completed call. Save this as asqav_bridge.py next to your proxy config.

python
import os
import asqav
from litellm.integrations.custom_logger import CustomLogger

ACTION_TYPE = "api:llm:chat"

agent = asqav.govern(
    api_key=os.environ["ASQAV_API_KEY"],
    agent_name="litellm-proxy",
)


class AsqavBridge(CustomLogger):
    async def async_pre_call_hook(self, user_api_key_dict, cache, data, call_type):
        # Decision phase: gate before the model call.
        check = agent.preflight(ACTION_TYPE)
        if not check.cleared:
            raise ValueError(check.explanation)
        return data

    async def async_post_call_success_hook(self, data, user_api_key_dict, response):
        # Execution phase: sign the completed call.
        agent.sign(
            ACTION_TYPE,
            {"model": data.get("model")},
            model_name=data.get("model"),
            tool_name="litellm-proxy",
        )


proxy_handler_instance = AsqavBridge()

Point the proxy at the instance from your config.yaml:

yaml
litellm_settings:
  callbacks: asqav_bridge.proxy_handler_instance

Every request through the proxy now preflights before the model call and signs after it, so the gateway produces a receipt per call. For a high-throughput proxy, use the async client (asqav.AsyncAgent) so the hooks never block the event loop. The preflight call reads org policy, so the key needs the policies:read scope alongside the default agent scopes.

Next

See Agent Receipts for what a receipt contains, and Policies for the rules preflight checks.