client.attest()

The headline call. Runs a payload through the orchestrator's nine stages and returns a sealed receipt containing the manifest hash.

Signature

client.attest(
    payload: str | bytes,
    program_id: str,
    route_id: str,
    registry_record_hash: str,
    attested_context: AttestedContext | None = None,
    reproducibility_context: Any | None = None,
    ai_output_text: str | None = None,
) -> AttestationReceipt
ArgumentTypeHTTP wire formatNotes
payloadstr or bytespayload_text (string) or payload (string)Strings sent as payload_text; UTF-8-decodable bytes are decoded and sent as payload_text; other bytes are base64-encoded and sent as payload. The wire format is always a string.
program_idstrprogram_id64-char lowercase hex
route_idstrroute_idNon-empty
registry_record_hashstrregistry_record_hash64-char lowercase hex

Payload encoding

The HTTP request body sends payload as a JSON string, not as bytes. The SDK's handling:

  • String input: Sent as payload_text in the JSON request.
  • Bytes input (UTF-8 decodable): Decoded to string, then sent as payload_text (same as string case).
  • Bytes input (non-UTF-8): Encoded as base64, then sent as the payload field (string, not bytes).

The orchestrator's DOG stage hashes the string it receives. If that string parses as a single JSON value, it is canonicalized under RFC 8785 (JCS) and hashed in the dog.v1.jcs mode; otherwise its raw UTF-8 bytes are hashed in the dog.v1.raw mode. A base64 payload is not decoded: the base64 string itself is what gets hashed.

This means the bytes that get hashed depend on your input type. To ensure determinism across language implementations, always pass strings if your content is text.

Return value

AttestationReceipt. See AttestationReceipt for full field reference. Most callers just want receipt.manifest_hash.

Raises

ExceptionWhen
ValidationErrorClient-side validation failed (bad hex, empty route, wrong type)
StageErrorA specific pipeline stage rejected the run; check .stage
APIErrorAPI returned a non-2xx response that wasn't a stage error
ConnectionErrorCouldn't reach the API

All four inherit from VerdifaxError for single-except handling.

Example

from verdifax import VerdifaxClient

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

Canonical request body

Identical inputs produce an identical canonical request body. The SDK enforces this by canonicalizing the request body, payload_text always wins over payload when the input is a UTF-8 string.

Each call still seals its own run. The orchestrator mints a fresh admission clock and per-run nonce for every /execute call, so two calls with identical inputs return different manifest_hash values; see What the Manifest Hash proves.

Module-level shortcut

For one-shot scripts, verdifax.attest(...) is a convenience that constructs a client from env vars, runs the call, and closes the client:

import verdifax
receipt = verdifax.attest(
    payload="hi", program_id="a"*64, route_id="r", registry_record_hash="b"*64,
)

For high-throughput use, instantiate VerdifaxClient yourself and reuse.

Continue