Skip to content

Python SDK reference

Audience: Python engineers integrating Nanorix into a healthcare-AI / claims / legal-AI / agent backend. Package: nanorix (v0.5.0). The 1.0 contract freeze is planned; the current surface is stable and additive. Python: 3.9 – 3.13. Substrate framing. The SDK wraps the HTTP API documented in api-reference.md. Every public method maps 1:1 to one or more API endpoints. The SDK adds: typed exceptions, exponential-backoff retries, automatic environment auth, and helper modules for verification, signing, and compliance metadata.


Install

pip install nanorix

Or from source: github.com/nanorix-io/nanorix-sdk.

Dependencies: httpx>=0.25,<1.0, pydantic>=2.0,<3.0, cryptography>=41.0,<46.0.


Top-level surface

import nanorix

# Construction
client = nanorix.Client(api_key="nrx_live_...")            # explicit
client = nanorix.Client()                                  # auto-loads NANORIX_API_KEY
client = nanorix.Client(base_url="https://nanorix.io",
                        timeout=30.0, max_retries=4)        # defaults

# Async variant — same surface, same method names; coroutines instead of plain returns
async_client = nanorix.AsyncClient()

# Resources
client.capsulefiles      # Capsulefile lifecycle
client.capsules          # capsule lifecycle + .run(), .batch(), .session()
client.cdp               # AuditProof retrieval (alias: client.audit_proofs)
client.audit_proofs      # alias of .cdp
client.egress_profiles   # named egress allowlists
client.usage             # usage summary, line-items, CSV export
client.webhooks          # webhook endpoints + deliveries

# Subpackages
nanorix.helpers          # signer, checkpoint_saver, activity_events
nanorix.verifier         # verify_auditproof()
nanorix.compliance       # framework-typed metadata builders (HIPAA, GDPR, SOC 2)

# Top-level
client.health()          # GET /v1/health
client.close()           # release HTTP transport

Client is also a context manager:

with nanorix.Client() as client:
    result, cdp = client.capsules.run(...)
# transport closed on exit

Exceptions

Full reference: error catalog.

from nanorix import (
    NanorixError,                       # base
    AuthenticationError,                # 401
    NotFoundError,                      # 404
    ConflictError,                      # 409
    BadRequestError,                    # 400
    QuotaExceededError,                 # 402, 403 (quota)
    PayloadTooLargeError,               # 413
    GoneError,                          # 410 (capsule destroyed)
    RateLimitError,                     # 429 (after auto-retry budget exhausted)
    UnprocessableEntityError,           # 422 default
    InputProvenanceRejectedError,       # 422 input_provenance_rejected
    SecretsInjectionRejectedError,      # 422 secrets_injection_rejected
    ExecutionError,                     # non-zero exit; .cdp attached if destroyed
    CeilingError,                       # safety-ceiling destroy; .cdp attached
    TimeoutError,                       # exec timeout; .capsule attached
)

Exceptions are importable from the package root (from nanorix import ...).

Every exception carries status_code: Optional[int] and body: Optional[Dict[str, Any]]. The body["error"]["request_id"] field is the support-ticket correlation id; quote it when filing.


client.capsulefiles

Class: Capsulefiles (sync) / AsyncCapsulefiles (async).

create(...)

def create(
    self,
    *,
    name: str,
    runtime: str,
    entrypoint: str,
    files: Optional[Union[List[str], Dict[str, bytes]]] = None,
    data_classification: Optional[str] = None,
    egress_declaration: Optional[List[str]] = None,
    default_ttl: Optional[int] = None,
    memory_mb: Optional[int] = None,
    env_vars: Optional[Dict[str, str]] = None,
    packages: Optional[Dict[str, List[str]]] = None,
    finalize: bool = True,
) -> Capsulefile

Creates a Capsulefile, uploads source files, and (by default) finalizes it. Maps to POST /v1/capsulefiles + POST /v1/capsulefiles/:id/files + POST /v1/capsulefiles/:id/finalize.

Argument Type Notes
name str Globally unique within your account.
runtime str e.g., python3.12, node20.
entrypoint str Shell command.
files list[str] OR dict[str, bytes] Either local paths to upload or in-memory {filename: bytes}.
data_classification str general, PHI, PII, financial, credentials. Customer declaration.
egress_declaration list[str] Hostnames / IPs / CIDRs allowlisted.
default_ttl int Seconds; capped by tier max_ttl_seconds.
memory_mb int Per-capsule memory ceiling.
env_vars dict Available inside the sealed sandbox.
packages dict e.g., {"python": ["anthropic==0.39.0"]}.
finalize bool Default True; set False to upload more files manually before finalizing.

Returns: Capsulefile model with .id, .status, .content_hash, .name, etc.

Raises: BadRequestError (schema invalid), ConflictError (name in use), QuotaExceededError.

Example:

cf = client.capsulefiles.create(
    name="med-ai-extraction",
    runtime="python3.12",
    entrypoint="python main.py",
    files=["main.py"],
    data_classification="PHI",
    egress_declaration=["api.anthropic.com"],
    default_ttl=1800,
    memory_mb=2048,
    env_vars={"ANTHROPIC_MODEL": "claude-3-5-sonnet-latest"},
    packages={"python": ["anthropic==0.39.0", "pypdf==5.0.1"]},
)
print(cf.id, cf.content_hash)

list(...) / get(id) / delete(id)

Standard list/get/delete. Maps to GET /v1/capsulefiles, GET /v1/capsulefiles/:id, DELETE /v1/capsulefiles/:id.

for cf in client.capsulefiles.list():
    print(cf.id, cf.name, cf.status)

cf = client.capsulefiles.get("cf_018f...")
client.capsulefiles.delete("cf_018f...")

update(id, **kwargs)

Mutates a draft Capsulefile (cannot mutate finalized). Returns the updated Capsulefile.

wait_until_finalized(id, timeout: float = 60.0)

Polls until status transitions from draft to finalized. Useful when finalize is async or running on the server.

upload_dir(id, dir_path)

Convenience: uploads every file under dir_path recursively. Returns the file count.


client.capsules

Class: Capsules (sync) / AsyncCapsules (async).

create(...) → LiveCapsule

def create(
    self,
    *,
    capsulefile: Optional[str] = None,
    max_lifetime_seconds: Optional[int] = None,
    egress_profile: Optional[str] = None,
    metadata: Optional[Dict[str, str]] = None,
) -> LiveCapsule

Creates a capsule. Returns a LiveCapsule with workspace methods (.upload(), .execute(), .list_outputs(), .download(), .destroy(), .refresh()).

metadata binds customer-declared context into the AuditProof's metadata_hash. Use nanorix.compliance to construct typed framework context (HIPAA, GDPR, SOC 2) or pass a plain Dict[str, str]. Server constraints: 16 keys max, 64-char keys, 256-char values, 4 KB total. The nanorix:* prefix is reserved.

Maps to: POST /v1/capsules.

run(...) → (outputs, cdp)

def run(
    self,
    *,
    capsulefile: str,
    inputs: Optional[Dict[str, Union[bytes, BinaryIO]]] = None,
    command: Optional[str] = None,
    timeout: Optional[int] = None,
    max_lifetime_seconds: Optional[int] = None,
    metadata: Optional[Dict[str, str]] = None,
) -> Tuple[Dict[str, bytes], CdpAccess]

One-shot lifecycle: create → upload → execute → download → destroy.

Returns: (outputs, cdp) where:

  • outputsDict[str, bytes] mapping output filename → content.
  • cdpCdpAccess with .full() (the AuditRecord) and .verification() (the AuditProof projection).

Raises: ExecutionError if the customer entrypoint exits non-zero; the AuditProof is attached at .cdp because destruction still happened.

Example (Pattern 3, single-shot extraction):

outputs, cdp = client.capsules.run(
    capsulefile="cf_018f...",
    inputs={"input.pdf": open("test.pdf", "rb").read()},
    metadata={"matter_id": "ACME-001"},
)
print(outputs["extraction.json"][:200])
print("AuditProof id:", cdp.full().id)
print("final_hash:", cdp.full().final_hash)

batch(...) → BatchResult

Anthropic-Batches-shape batch processing. Per-record custom_id discipline + RFC 6962 Merkle-root computed client-side for byte-equivalent verifier replay.

def batch(
    self,
    *,
    capsulefile: str,
    records: Iterable[Dict[str, Any]],
    on_record: Optional[Callable[[BatchRecord], None]] = None,
    record_id_field: str = "custom_id",
    on_record_error: str = "fail_isolated",   # or "abort_batch"
    max_lifetime_seconds: Optional[int] = None,
    metadata: Optional[Dict[str, str]] = None,
    timeout: Optional[int] = None,
) -> BatchResult

Returns: BatchResult with .merkle_root, .audit_proof, .records (per-record outcomes), .success_count, .error_count.

The customer entrypoint reads /data/input/records.jsonl (one JSON per line, each carrying custom_id) and writes /data/output/results.jsonl with {custom_id, status: "succeeded"|"errored", output, error_code, error_message}.

Use for: Pattern 4 / 6 (claims, e-discovery, batch extraction).

session(...) → SessionResult

LangGraph-shape session-mode for stateful multi-capsule reasoning chains. Customer-managed encrypted state continuity via parent_cdp_id chain.

def session(
    self,
    *,
    capsulefile: str,
    session_id: str,
    parent_cdp_id: Optional[str] = None,
    initial_state: Optional[bytes] = None,
    max_lifetime_seconds: Optional[int] = None,
    metadata: Optional[Dict[str, str]] = None,
    timeout: Optional[int] = None,
) -> SessionResult

Returns: SessionResult with .cdp, .state_output_bytes, .parent_cdp_id.

The customer entrypoint reads /data/input/state.bin and writes /data/output/state.bin. Customer is responsible for KMS-encryption of state bytes BEFORE upload and AFTER download. Use for: Pattern 2 (long-running agent), Pattern 4 (multi-step reasoning).

list() / get(id)

list()CapsuleList (paginated). get(id)Capsule model.

LiveCapsule workspace methods

LiveCapsule is the handle returned by create(). Methods:

Method Maps to
.id (property) capsule id
.status (property) current status
.upload(filename, data) POST /v1/capsules/:id/upload?filename=...
.execute(command=None, input_data=None, timeout=None) POST /v1/capsules/:id/exec
.list_outputs() GET /v1/capsules/:id/output
.download(filename) GET /v1/capsules/:id/output/:filename
.destroy() → CdpAccess DELETE /v1/capsules/:id
.refresh() → LiveCapsule GET /v1/capsules/:id

Workspace-mode example (capsule stays alive across multiple exec calls):

capsule = client.capsules.create(capsulefile="cf_018f...")
capsule.upload("input.pdf", pdf_bytes)
result = capsule.execute(command="python main.py", timeout=60)
print(result.stdout)

# Iterate without re-uploading
result2 = capsule.execute(command="python main.py --verbose")

# Destroy and get the AuditProof
cdp = capsule.destroy()
print(cdp.full().final_hash)

client.cdp (alias: client.audit_proofs)

Class: CdpResource / AsyncCdpResource.

def get(self, capsule_id: str) -> CdpAccess
def verify(self, capsule_id: str) -> AuditProof   # the verification proof object

get() retrieves the AuditRecord for a destroyed capsule (GET /v1/capsules/:id/cdp); verify() retrieves the shareable AuditProof projection (GET /v1/capsules/:id/cdp/verify).

CdpAccess exposes:

  • .full() → CdpDocument — the AuditRecord (private complete record).
  • .verification() → AuditProof — the shareable verification proof object.
  • .id, .capsule_id, .final_hash, .created_at — top-level fields.

client.audit_proofs is the brand-aligned alias; both names point at the same resource and will coexist forever for forever-stability.


client.egress_profiles

Class: EgressProfiles / AsyncEgressProfiles. Tier: Team+.

client.egress_profiles.create(name="...", description="...")
client.egress_profiles.list()
client.egress_profiles.get(profile_id)
client.egress_profiles.update(profile_id, name=None, description=None)
client.egress_profiles.delete(profile_id)
client.egress_profiles.add_rule(profile_id, kind="domain", value="api.example.com", port=443)
client.egress_profiles.list_rules(profile_id)
client.egress_profiles.remove_rule(profile_id, rule_id)

Maps to: /v1/egress/profiles/....


client.webhooks

Class: Webhooks / AsyncWebhooks.

client.webhooks.create(url="https://...", events=["cdp.generated"], description="...")
client.webhooks.list()
client.webhooks.get(webhook_id)
client.webhooks.update(webhook_id, url=None, events=None, description=None, active=None)
client.webhooks.delete(webhook_id)
client.webhooks.test(webhook_id)
client.webhooks.deliveries(webhook_id)

The signing-secret returned from create() is shown once. Use nanorix.helpers.Webhook.verify_signature() (see below) in your handler.

from nanorix import Webhook

if not Webhook.verify_signature(
    payload=request.body,
    signature_header=request.headers["Nanorix-Webhook-Signature"],
    signing_secret=os.environ["NANORIX_WEBHOOK_SECRET"],
):
    return 401

Maps to: /v1/webhooks/.... Full event-type catalog at integrations/webhooks.md.


client.usage

Class: Usage / AsyncUsage.

client.usage.summary(billing_cycle: Optional[str] = None) -> UsageSummary
client.usage.estimate() -> Dict[str, Any]
client.usage.list_items(billing_cycle=None, **filters) -> Iterator[UsageItem]
client.usage.export_csv(billing_cycle=None, **filters) -> bytes

list_items() yields UsageItem records (paginated automatically). export_csv() returns the raw CSV bytes — same shape as the API's /v1/usage/items.csv.

Maps to: /v1/usage/....


nanorix.helpers

Module Exports
nanorix.helpers.signer Signer — Ed25519 over capsule input bytes (input-provenance manifest signing)
nanorix.helpers.checkpoint_saver LocalCheckpointSaver — local-disk fallback for client.capsules.session() flows
nanorix.helpers.activity_events ActivityEvent parsers — typed accessors for AuditProof's activity trail
from nanorix.helpers import Signer

signer = Signer.from_env()  # reads NANORIX_SIGNING_KEY (PEM)
manifest_hash, manifest_signature = signer.sign_input(open("input.pdf", "rb").read())

nanorix.verifier

from nanorix.verifier import verify_auditproof, AuditProofVerificationResult, VerifierPolicy

with open("auditproof.json") as f:
    proof = json.load(f)

result = verify_auditproof(
    proof,
    policy=VerifierPolicy(
        reject_diagnostic=True,
        required_region="us-central1",
        required_authority_id=None,
    ),
)

if not result.valid:
    print("failure:", result.failure_reason.type)
    print("stage_reached:", result.stage_reached)

Cross-impl byte-equivalence: verify_auditproof() produces output byte-equivalent to the Rust nanorix-verify and Go auditproof-verifier-go CLIs on the 100-fixture reference corpus. The FailureReason enum is identical across all five paths (Rust, Go, Python, TypeScript, server-side POST /v1/verify).

The failure-reason enum is closed-set and byte-equivalent across all verifier implementations.


nanorix.compliance

Framework-typed metadata builders that produce Dict[str, str] payloads for metadata=... arguments above. Bind customer-declared context that lands in the AuditProof's metadata_hash.

from nanorix.compliance import HIPAAContext, GDPRContext, SOC2Context

metadata = HIPAAContext(
    covered_entity_id="ce-001",
    business_associate_id="ba-002",
    treatment_purpose=True,
).to_metadata()

outputs, cdp = client.capsules.run(
    capsulefile="cf_...",
    inputs={"input.pdf": pdf_bytes},
    metadata=metadata,
)

The contexts cover the framework-typed surface that procurement teams expect to see traced in the AuditProof. The metadata is customer-defined; the compliance helpers are Nanorix-published convenience builders, not Nanorix-imposed schemas.


Async client

nanorix.AsyncClient exposes the same surface as Client with async def coroutines and AsyncIterator for paginated endpoints:

import nanorix

async with nanorix.AsyncClient() as client:
    cf = await client.capsulefiles.create(...)
    outputs, cdp = await client.capsules.run(capsulefile=cf.id, inputs=...)

    async for cap in client.capsules.list():
        print(cap.id, cap.status)

End-to-end mapping to the API surface

Every Python SDK method maps to one or more API endpoints documented in api-reference.md:

Python method API endpoint
Client.health() GET /v1/health
client.capsulefiles.create() POST /v1/capsulefiles (+ files + finalize)
client.capsulefiles.list() GET /v1/capsulefiles
client.capsulefiles.get(id) GET /v1/capsulefiles/:id
client.capsulefiles.delete(id) DELETE /v1/capsulefiles/:id
client.capsules.create() POST /v1/capsules
LiveCapsule.upload() POST /v1/capsules/:id/upload
LiveCapsule.execute() POST /v1/capsules/:id/exec
LiveCapsule.list_outputs() GET /v1/capsules/:id/output
LiveCapsule.download() GET /v1/capsules/:id/output/:filename
LiveCapsule.destroy() DELETE /v1/capsules/:id
client.capsules.list() GET /v1/capsules
client.capsules.get(id) GET /v1/capsules/:id
client.cdp.get() / client.audit_proofs.get() GET /v1/capsules/:id/cdp
client.cdp.verify() GET /v1/capsules/:id/cdp/verify
client.egress_profiles.* /v1/egress/profiles/...
client.webhooks.* /v1/webhooks/...
client.usage.* /v1/usage/...
nanorix.verifier.verify_auditproof() offline (no network)

client.capsules.run(), .batch(), and .session() are SDK-side compositions over create + upload + exec + destroy, not separate endpoints.


Cross-references

Forever-Standard discipline. Every public Python SDK method, every keyword argument, every exception class, every model field is permanent. New methods / args / classes / fields are additive only. The internal-vs-external naming alias (client.cdpclient.audit_proofs) is structural, not transitional — both names exist forever.