[AVAILABLE], published,
parmana on
PyPI. Real, public, installable with a
plain pip install parmana. This page said v1.0.5 and described only an
editable local install until 2026-09-14; both were stale, confirmed against
the live PyPI registry while fixing this page. 119 passing tests (1.3.0), 0
placeholders, black/ruff/mypy --strict all clean across the whole
package. Bearer-key authentication, the error taxonomy below, and
PolicyApi.validate() are all closed and verified against a real running
local server. test_quickstart_example.py proves the documented quickstart
script itself runs. v1.1.2 added create_business_transaction() (see below)
and the offline_verifier.py fix. Two quick follow-up releases, v1.1.3 and
v1.1.4, corrected the published package metadata (repo name, docs domain,
added a Website URL); v1.1.5 and v1.1.6, both published 2026-09-20, follow;
v1.5.0, published 2026-10-04, is the current version. It adds the external
connector methods, policy_in_effect() and verify_parmana_release(), fixes
offline verification of a decoded record, and is licensed under the Apache
License 2.0: see New in 1.5.0. v1.4.0, published 2026-09-29,
added sign_approval() and the approver methods: see New in
1.4.0. v1.3.0, published 2026-09-25, added policy governance,
step up signing, caller(), public_key(), trust_records.list() and
latest_receipt(), and lets the offline verifiers take the SDK’s own models:
see New in 1.3.0. v1.2.0, published 2026-09-21, added the
Execution Intent methods and an offline intent verifier. v1.1.6 adds
server_code to InternalServerError. v1.1.5 lets the offline verifier
accept records over 4096 bytes that AWS KMS signed as a fixed size commitment,
and declares its cryptography dependency as the optional verify extra
(pip install "parmana[verify]"). v1.1.4 was published 2026-09-14. See
Changelog and the fix history at the bottom of this page.Install
parmana.crypto), install the optional
verify extra, which adds the cryptography package it needs for Ed25519:
py.typed is shipped, this is a PEP 561 typed distribution, confirmed by installing into
a clean venv and checking parmana.__file__’s directory for the marker.
Bearer-key authentication
ParmanaClient(api_key=...) is sent as Authorization: Bearer <api_key> on every request, set
once on the underlying requests.Session():
api_key is optional, for the same reason it’s optional server-side: local development against a
server started with PARMANA_AUTH_DISABLED=true needs no key. Every real deployment requires
one. An omitted or wrong key gets a real 401, raised as AuthenticationError (see below), not
a silent failure. See Authentication for how keys are minted.
Models are generated, not hand-maintained
Every model inpython/parmana/models/*.py is generated directly from the TypeScript AST
of packages/shared/src/domain/*.ts (and CryptoAlgorithms.ts) by
python/scripts/generate_models.ts, not hand-aligned copies. A drift guard
(npm run check:python-models, wired into CI) regenerates into memory and fails the build
if the committed output would change. Spot-checked field-for-field against the real JSON
schemas (Authority.display_name optional, Authorization.expires_at optional,
BusinessTransactionMetadata correctly optional-except-business_transaction_id,
ExecutionTrustRecord.settlement_confirmations optional): accurate, no drift found.
Enums are real Python str, Enum classes (e.g. SignatureAlgorithm,
VerificationStatus), not bare strings.
The auto-generated Python SDK reference
(built from real docstrings, see below) deliberately excludes
python/parmana/models/*.py: these dataclasses carry no per-field docstrings
for a doc-generation tool to render, since they’re generated code, not
hand-written. For field-by-field shape, see the TypeScript SDK reference’s
Interfaces,
generated from the exact same source this page describes above, field for
field identical modulo the camelCase/snake_case naming convention each
language uses.Errors, correctly mapped to real conditions
All inherit
ParmanaHttpError → ApiError. Proven against the real HttpTransport (not a
test double) using responses-mocked HTTP built from real, verified response shapes
(python/tests/test_http_transport.py), and against an actual running server in
python/tests/test_live_server_integration.py.
The client also reuses a requests.Session() (connection pooling) and retries idempotent
GETs with backoff on 429/502/503/504, POSTs are never retried.
Every product endpoint has a method
client.version (a property, not a call) is the SDK’s version. The server’s version is client.execution.version().
The routes that have no method, and why, are listed on SDK coverage of the API: the readiness probe, the JWKS document, the API’s own description files and the handbook download. That page also maps every method to its TypeScript equivalent.
client.transactions.create() covers POST /transactions, a second independent entry point
into the identical execution pipeline as execute(), differing only in its 201 status
code. policy.validate takes (policy_id, policy_version), not a policy document, matching
what the route actually reads.
POST /policies/validate never uses the shared {error, code?} envelope at 400/404.
Every status it returns (200, 400, 404) is {valid, errors}, the caller’s answer, not
an SDK-level failure, so policy.validate() returns {"valid": False, "errors": [...]} for
an unknown policy rather than raising NotFoundError. 401 is not exempted: it’s generated
by caller-auth middleware before this route’s own handler runs, using the shared envelope like
every other route. See Error handling.create_business_transaction(), no more hand-synced ids
Added 2026-09-14, ships in v1.1.2, published, available via a plain
pip install parmana. A BusinessTransaction has three id pairs the server’s own
BusinessTransactionValidator cross-checks before policy ever runs:
metadata.business_transaction_id must equal business_transaction_id,
authorization.authority_id must equal authority.authority_id, intent.authorization_id
must equal authorization.authorization_id. Every “X must match Y” 400 documented in
the end-to-end Paytm guide came from hand-building a request
and getting one of those wrong. create_business_transaction() derives all three
automatically:
business_transaction_id defaults to a fresh uuid4() if you don’t supply one. A value you
do supply is your own responsibility to keep unique per attempt, since it also doubles as the
server’s idempotency key. authority_type defaults to AuthorityType.SERVICE (never
"AGENT", which doesn’t exist server-side). See examples/builder/run.py (the same flow as
examples/quickstart/run.py, side by side, for direct comparison) and
tests/test_builders.py (9 unit tests) / tests/test_builder_example.py (proves the
generated ids round-trip correctly through a real running server, not just in isolation).
New in 1.5.0
A decoded record verifies offline (fixes a 1.4.0 bug)
A model the SDK decoded keeps the server’s own JSON, and encodes back to exactly that JSON. Soverify_execution_trust_record_offline(record, keys) verifies the ExecutionTrustRecord returned by
client.execute() or client.trust_record() directly, including records with previousChainHash: null, and
refusal_record and Execution Intent verification send the server exactly what it signed. In 1.4.0 those records
failed with a hash mismatch (see the warning under Verify records offline).
A model changed with dataclasses.replace() is encoded from its fields, so a changed record still fails.
What a request must carry, from the server
client.policy_in_effect(capability) (also client.policy.in_effect(...)) calls GET /policies/in-effect and returns
a PolicyInEffect: the policy to declare and what the request must carry. Signal names keep their exact spelling; they
are never converted to snake_case.
name, version and schemaVersion exactly as returned. It raises
AuthorizationError (403, CAPABILITY_NOT_ALLOWED), NotFoundError (404, CAPABILITY_NOT_BOUND), ConflictError
(409, NO_APPROVED_POLICY_VERSION) or InternalServerError (503, POLICY_VERSION_UNAVAILABLE); in each case send
nothing. The step by step use
is in Integrate Parmana: specification for AI agents, step 3.
Register an external connector
Connect your own system with no Parmana code change: one person proposes registering a capability to your HTTPS endpoint, a different person approves it with a step up authorization. Both need a human key.external_connector_changes(status=None) lists proposals and reject_external_connector_change(change_id, reason, step_up) rejects one. To end a registration, call external_connectors.propose_revoke(capability=..., reason=...) and
approve it the same way. The whole path, with the endpoint, is in
Connect any external system.
New in 1.4.0
Published to PyPI on 2026-09-29.
pip install "parmana[verify]" installs it
with sign_approval().Sign an approval on the approver’s machine
parmana.crypto.sign_approval() makes the signed approval a policy with approvalSignals
asks for, the same one scripts/sign-approval.ts makes. It needs pip install "parmana[verify]".
The private key never leaves the process.
ttl_seconds, up to a day) and can be used
once. See Human approval.
Manage approvers
Add, rotate and revoke the keys trusted to sign approvals, through maker checker, with no deploy. Every call needs an API key added as a human.reject_approver_change(change_id, reason, step_up) rejects one, and
approvers.propose_revoke(...) proposes a revocation. See
Manage approvers.
New in 1.3.0
Published to PyPI on 2026-09-25.
pip install parmana installs it. On 1.2.0
and earlier none of this section exists. The full list of what each version
covers is on SDK coverage of the API.Who the key is, and the deployment’s public key
public_key() needs no API key. Fetch it once and keep it.
Verify records offline, straight from the client’s results
pip install "parmana[verify]".
Check a release at an external connector endpoint (1.5.0)
An endpoint registered as an external connector receives each approved request as a signed release. It checks the release before it acts. Needspip install "parmana[verify]".
verifyParmanaRelease. A whole endpoint built on it is
python/examples/13_external_connector_endpoint.py.
Approve a policy change
The approver signs on their own machine; the private key never leaves it. Needspip install "parmana[verify]".
action="reject" and call reject_policy_change(change_id, "Why it is rejected.", step_up). A signed authorization is valid once, for one change and one action, for 120 seconds unless you pass ttl_seconds. proposed_content is sent exactly as given: keys are never rewritten, unlike the SDK’s model encoding. The rules and every error are on Approve a policy.
List Trust Records, and the latest receipt
trust_records.list() returns only records of transactions this key submitted, newest first. A page counts transactions, so it may hold fewer records than page_size. latest_receipt() reads the most recent receipt without generating a new one, unlike receipt().
Test suite
119 tests,pytest, confirmed against a real run on 2026-09-25 (1.3.0): unit tests for every error-mapping case
including the two classification cases above, bearer-key header
attachment tests, create_business_transaction() (9 cases, tests/test_builders.py), and three
real-server integration suites. test_live_server_integration.py spawns the actual
@parmana/api process (npx tsx packages/api/src/server.ts, the same entry point npm run dev
runs) on a real OS-assigned TCP port and drives it with the real ParmanaClient over real HTTP:
a real 401, a real 403, a real 400, a real policy rejection, a real 404, a real 409
duplicate, and the policy.validate() 404 case, each asserted against the exact typed result
and exact message the server returned. test_quickstart_example.py does the same for
run_quickstart(), the quickstart example script itself, additionally asserting its documented
printed output still matches what it actually prints. test_builder_example.py does the same
for run_builder_example() (see above), additionally asserting every builder-derived id pair
round-trips correctly. tests/test_sdk_alignment.py covers everything added in 1.3.0, including
a step up authorization signed in Python and accepted by the server’s own TypeScript verifier.
ruff/black/mypy --strict are clean across the whole package: python -m ruff check ., python -m black --check ., and python -m mypy all pass with
zero findings.Next
End-to-end: agent → Parmana → Paytm
The full, no-ambiguity runbook this SDK builds toward: real infrastructure,
every error message, every fix.
TypeScript SDK
The other maintained SDK: same bearer-key model, same error taxonomy shape.
Error catalog
Every error the real API returns, independent of any SDK.
Python SDK reference
Every class and method, generated from the real docstrings, in the sidebar
under SDKs.