Skip to main content

APIs

Each sub-API is a small class over the shared Transport (python/parmana/config/transport.py, an ABC), one module per concern under python/parmana/api/. Every class’s docstring lists what it explicitly does not do — the same convention used in the TypeScript SDK.

ExecutionApi

class ExecutionApi:
    def health(self) -> dict: ...                                    # GET /health
    def execute(self, transaction: BusinessTransaction) -> ExecutionTrustRecord: ...  # POST /execute
execute() runs the transaction through encode() (python/parmana/serialization/encoder.py) first, converting the dataclass’s snake_case fields to the camelCase JSON the Runtime expects, and decodes the response back into ExecutionTrustRecord via response_model=.

VerificationApi

class VerificationApi:
    def verify(self, business_transaction_id: str) -> Verification: ...  # POST /verify
Divergence from TypeScript. Python’s verify() calls POST /verify — it triggers a new Verification. TypeScript’s verify() calls GET /verification/:id — it reads the latest one. See ParmanaClient → verify() does not mean the same thing for the full explanation. This is the one place the two SDKs’ public surfaces are not just differently shaped but behaviorally different.

ReplayApi

class ReplayApi:
    def replay(self, business_transaction_id: str) -> ReplayResult: ...  # POST /replay
Matches TypeScript’s ReplayApi.replay() — both call POST /replay and type the result as the flat {business_transaction_id, trust_record_hash, verified} shape. See REST API → Replay for what this endpoint actually checks (a hash comparison, not deterministic policy re-evaluation via the replay package).

ReceiptApi

class ReceiptApi:
    def generate(self, business_transaction_id: str) -> Receipt: ...  # POST /receipt
Matches TypeScript: only generate() exists. Neither SDK has a method for GET /receipt/latest/:id — see REST API → Receipt.

TransactionApi

class TransactionApi:
    def get(self, business_transaction_id: str) -> BusinessTransaction: ...             # GET /transactions/:id
    def list(self, *, page: int = 1, page_size: int = 25) -> list[BusinessTransaction]: ...  # GET /transactions
Matches TypeScript’s TransactionApi one-for-one, aside from naming (page_size vs. pageSize, per each language’s convention).

TrustRecordApi

class TrustRecordApi:
    def get(self, business_transaction_id: str) -> ExecutionTrustRecord: ...  # GET /trust-records/:id
Matches TypeScript’s TrustRecordApi exactly.

PolicyApi

class PolicyApi:
    def validate(self, policy: dict) -> dict: ...  # POST /policies/validate
Python’s validate() takes and returns a plain dict — there’s no PolicyReference/Policy dataclass involved at all, unlike TypeScript’s PolicyApi.validate() which is typed against the Policy interface from @parmana/policy. Functionally equivalent (the server only reads policyId/policyVersion off the body either way), just untyped on the Python side.

Models

python/parmana/models/ defines one @dataclass(frozen=True) per domain concept. Field names are snake_case Python equivalents of the same camelCase fields used by the Runtime and by the TypeScript SDK’s models — encode()/decode() (python/parmana/serialization/) convert between the two automatically using regex-based case conversion, both directions.
ModelFileTypeScript equivalent
Authorityauthority.pyAuthority
Authorizationauthorization.pyAuthorization
Intentintent.pyIntent
BusinessTransaction, BusinessTransactionMetadatabusiness_transaction.pyBusinessTransaction, BusinessTransactionMetadata
Decision, Executionexecution.pyDecision, Execution
Overrideoverride.pyOverride
PolicyReferencepolicy.pyPolicyReference
Receiptreceipt.pyReceipt
ReplayResultreplay_result.py (also duplicated verbatim in replay.py, unused)ReplayResult
ExecutionTrustRecordtrust_record.pyExecutionTrustRecord
Verificationverification.pyVerification
Field-for-field, every model here matches its TypeScript counterpart. The one duplicate is models/replay.py, which defines a byte-for-byte identical ReplayResult dataclass to models/replay_result.pyreplay_api.py imports from replay_result.py, so replay.py’s copy is unused dead code, not a second, different result type.
Only Authority, Authorization, Intent, PolicyReference, BusinessTransaction, BusinessTransactionMetadata, Verification, Receipt, ReplayResult, and ExecutionTrustRecord are re-exported from the top-level parmana package (python/parmana/__init__.py). Execution, Decision, and Override are not — import them from parmana.models.execution / parmana.models.override directly.

Override has the same drift as TypeScript

python/parmana/models/override.py uses authority_id, matching the TypeScript SDK’s (also drifted) field name — but the canonical domain type (packages/shared/src/domain/override.ts) actually has approved_by/approvedBy, plus an optional justification neither SDK models at all. See TypeScript SDK → Models for the full comparison. Since neither SDK — nor the REST API — exposes any way to create or fetch an Override today (see Guides → Human Override), this drift has no observable effect yet.

Timestamps are handled better than in TypeScript

decode() (python/parmana/serialization/decoder.py) parses every datetime-typed field with datetime.fromisoformat(value.replace("Z", "+00:00")), so timestamps really do arrive as Python datetime objects. This is a genuine advantage over the TypeScript SDK, whose models declare fields as Date but never actually construct one — see TypeScript SDK → Models.

Errors

Python’s error hierarchy is much smaller than TypeScript’s, and behaves differently:
ClasscodeRaised when
ApiErrorAPI_ERROR (base)Never raised directly.
NetworkErrorNETWORK_ERRORrequests.exceptions.RequestException (python/parmana/transport/http_transport.py).
RuntimeErrorRUNTIME_ERRORAny non-2xx HTTP response.
from parmana.errors import ApiError, NetworkError, RuntimeError
RuntimeError shadows Python’s built-in RuntimeError. python/parmana/errors/runtime_error.py defines class RuntimeError(ApiError) in its own module namespace. from parmana.errors import RuntimeError (or from parmana import errors; errors.RuntimeError) shadows the built-in name in whatever scope you import it into — except RuntimeError after that import catches only Parmana’s SDK error, not Python’s built-in one. Import it under an alias (from parmana.errors import RuntimeError as ParmanaRuntimeError) if you need both in the same file.
This is the opposite of the TypeScript SDK’s error-handling gap. HttpTransport._parse_response checks response.ok and raises RuntimeError(message, request_id=...) — pulling message from the response body’s error or message field — for every non-2xx status. This means the Python SDK does turn HTTP error responses into exceptions, unlike the TypeScript SDK, whose HttpTransport resolves successfully regardless of status code (see TypeScript SDK → Errors). If you’re porting error-handling logic from one SDK to the other, this is not a safe assumption to carry over.

ParmanaClient

The facade composing these APIs.

TypeScript SDK

The equivalent TypeScript surface, for comparison.

Error Model (REST API)

What the server actually returns on failure.

Concepts

The domain concepts these models represent.