Quickstart, Python

The fastest path to a sealed Verdifax artifact. You'll have a real manifest hash printed in your terminal in under five minutes.

Prerequisites

  • Python 3.11 or newer

  • A Verdifax API key, stored as VERDIFAX_API_KEY in your environment. The SDK talks to the production API at https://api.verdifax.com by default.

  • To use a self-hosted orchestrator instead, which needs access to the private image (see Quickstart, Docker), set VERDIFAX_API_URL=http://localhost:9090 and provision a key. On a deployment where no key has ever been created, mint the first one with admin scope (a key minted without it is customer scope, which gets 403 on the /admin/* calls, and creating it ends open mode):

    curl -X POST http://localhost:9090/admin/keys \
      -H "Content-Type: application/json" \
      -d '{"name": "python-quickstart", "scope": "admin", "confirm_admin_scope": true}'
    

    The server answers HTTP 201, and the response carries the secret once; store it as VERDIFAX_API_KEY in your environment. Once any key has been created, POST /admin/keys requires an admin key in the X-Verdifax-Key header. See Authentication.

1. Install

pip install "verdifax>=0.6.0"

verify_run(), used in step 3, was added in 0.6.0.

2. Three lines

import verdifax

receipt = verdifax.attest(
    payload="hello verdifax",
    program_id="a" * 64,
    route_id="route-test",
    registry_record_hash="b" * 64,
)
print(receipt.manifest_hash)

That's it. receipt.manifest_hash is your sealed proof artifact, and receipt.run_id is the server-side ID of the recorded run.

3. Verify

The manifest hash re-derives from the run's sealed fields. Ask the orchestrator to recompute the seal from the run's stored manifest fields and report whether it matches the hash it recorded:

result = verdifax.verify_run(receipt.run_id)
assert result.verified is True
assert result.recomputed_hash == receipt.manifest_hash

This is a server-side check. For an independent offline check of the run's sealed audit bundle, use the open-source verdifax-verify CLI. See client.verify_run() for exactly what each check proves.

Do not verify by calling attest() again with the same inputs: every run seals its own admission clock, nonce, and public-log entry, so a second run never reproduces the first run's hash. The old verdifax.verify() did exactly that and now raises UnsupportedOperationError. To check that the pipeline replays deterministically, use verdifax.research.verify_determinism().

Configuration

The SDK reads VERDIFAX_API_URL (default https://api.verdifax.com) and VERDIFAX_API_KEY:

export VERDIFAX_API_URL=https://api.verdifax.example.com
export VERDIFAX_API_KEY=vfx_your_key_here

Or pass them explicitly:

from verdifax import VerdifaxClient

client = VerdifaxClient(
    base_url="https://api.verdifax.example.com",
    api_key="vfx_...",
)
receipt = client.attest(
    payload="...",
    program_id="...",
    route_id="...",
    registry_record_hash="...",
)

Async

For high-throughput inference servers, use AsyncVerdifaxClient:

import asyncio
from verdifax import AsyncVerdifaxClient

async def main():
    async with AsyncVerdifaxClient() as client:
        receipt = await client.attest(
            payload="hello verdifax",
            program_id="a" * 64,
            route_id="route-test",
            registry_record_hash="b" * 64,
        )
        print(receipt.manifest_hash)

asyncio.run(main())

Payload canonicalization

When you call verdifax.attest(payload="hello verdifax", ...), the SDK sends the payload as the HTTP request field payload_text, which is the plaintext form. The orchestrator's DOG stage hashes it:

  • A payload that parses as a single JSON value is canonicalized under RFC 8785 (JCS) and hashed in the dog.v1.jcs mode, so key order and whitespace do not change its hash.
  • Any other payload, such as hello verdifax, is hashed over its raw UTF-8 bytes in the dog.v1.raw mode.

The mode is bound into the envelope hash, so the two modes never collide. For byte payloads, the SDK sends UTF-8-decodable bytes as text and base64-encodes the rest into the payload field; the orchestrator hashes that base64 string as it is, without decoding it.

For attesting Anthropic Claude / OpenAI prompt-response pairs in one call, use the provider-specific helpers:

import verdifax

receipt = verdifax.attest_claude_response(
    prompt="What is the boiling point of water?",
    response="100 °C at sea level.",
    program_id="a" * 64,
    route_id="claude-route",
    registry_record_hash="b" * 64,
)

verdifax.attest_openai_response(...) has the same signature. Important: These helpers are helper-specific and canonicalize the prompt + response with a provider-specific prefix. The Claude helper joins these five parts with the ASCII record separator (\x1e), shown here one per line:

verdifax.helper.claude.v1
verdifax.helper.prompt.v1
What is the boiling point of water?
verdifax.helper.response.v1
100 °C at sea level.

This means identical text under different providers (attest_claude_response() vs. attest_openai_response()) produces different manifest hashes, which is intentional. The same prompt + response from Claude and from GPT-4 are meaningfully different evidence.

If you don't want provider-specific canonicalization, build the payload yourself and call verdifax.attest() directly with your own formatting.

Continue