[AVAILABLE], published,
@parmana/sdk on
npm. Real, public, installable
with a plain npm install @parmana/sdk. This page said otherwise (“private,
not published to any registry”) until 2026-09-14; that was stale even at
v1.1.1, confirmed live against the npm registry while fixing this page.
Bearer-key authentication, a real thrown error taxonomy, and full test
coverage are all in place, along with corrected model types (see below),
client.verify()/client.version(), and client.createTransaction().
v1.5.0, published 2026-10-04, is the current version. It adds the external
connector methods, policyInEffect() and verifyParmanaRelease(), and is
licensed under the Apache License 2.0: see New in 1.5.0.
v1.4.0, published 2026-09-29, added signApproval() and the approver methods:
see New in 1.4.0. v1.3.0, published 2026-09-25, added policy
governance with step up signing, offline verification, caller(),
publicKey(), trustRecords(), latestReceipt() and statusCode on errors:
see New in 1.3.0. v1.2.0, published 2026-09-21, added the
Execution Intent methods. v1.1.6, published 2026-09-20, builds a default
HttpTransport when you omit transport, and sends userAgent. v1.1.3 added
serverCode on InternalServerError (see below). v1.1.2, published
2026-09-14, added createBusinessTransaction(). See the fix history at the
bottom of this page.v1.1.2’s published package metadata (
homepage/repository/bugs in npm view @parmana/sdk@1.1.2) still points at this repository’s old name,
parmana-exp, because registry metadata is immutable per version. v1.1.3 and
later point at parmana. Neither affects installing or using the package.Package name history, for anyone who finds an old reference: renamed from
@parmana/legacy-reference, itself renamed from @parmana/typescript-sdk.
See Changelog for when. @parmana/sdk is the current, correct,
published name.Install
npm view @parmana/sdk version returns the current published
version directly from npm, confirmed while fixing this page’s own stale claim otherwise.
Bearer-key authentication
Configuration.apiKey is sent as Authorization: Bearer <apiKey> on every request, by
HttpTransport:
apiKey is optional in Configuration, 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, thrown as
AuthenticationError (see below), not a silent failure. See
Authentication for how keys are minted.
Errors, they actually throw now
HttpTransport.send() checks response.status and throws the matching typed error for every
non-2xx response, built from the real {error, code?} envelope
(packages/api/src/middleware/error-handler.ts):
Telling the 5xx signing and record errors apart (1.1.3)
InternalServerError.serverCode carries the Runtime’s own code when the response had one. Two values change what
you should do next:
SIGNING_UNAVAILABLE(503): the Runtime refused before releasing the action, because it could not sign an Execution Trust Record. Nothing was executed. Retry once signing is healthy, with a newbusinessTransactionId, because the original was already accepted.EXECUTION_INTENT_UNAVAILABLE(503): the Runtime refused before releasing the action, because it could not sign and store the Execution Intent. Nothing was executed. Retry once healthy, with a newbusinessTransactionId.EXECUTION_RECORD_INCOMPLETE(500): the action was released to the execution system, but its signed record could not be produced. Do not retry as a new transaction. Tell the operator: a signed Execution Intent exists, and the operator can rebuild the record without repeating the action. The method isfinalizeExecutionIntent. It is available from version 1.2.0. On 1.1.6 and earlier the operator usesPOST /execution-intents/<businessTransactionId>/finalizeover HTTP.EXECUTION_OUTCOME_UNKNOWN(502, since 2026-09-25): the action was released and the connector call failed, so it may have been performed. Do not retry as a new transaction. Tell the operator, who checks the target system and closes the intent withresolveExecutionIntent.
serverCode is available in v1.1.3 and later, published 2026-09-20. On v1.1.2 and earlier it is not on InternalServerError, and you can read the code from the error message instead.
All extend ParmanaError, which carries a stable code (ErrorCode enum) and optional
cause/requestId. 403 (AuthorizationError) is a real status this API returns: an
authenticated caller asserting an authority.principalId it isn’t permitted to assert
(isPrincipalAllowed, packages/api/src/routes/execute.ts), documented in the
Error catalog and the OpenAPI spec.
One route is deliberately exempt from this mapping.
POST /policies/validate never uses
the shared {error, code?} envelope: every status it returns (200, 400, 404) is
{valid, errors}, the caller’s answer, not an SDK-level failure. PolicyApi.validate opts
those two statuses out via TransportRequest.nonThrowingStatuses; a 401 on the same route
still throws AuthenticationError, since that one is generated by caller-auth middleware
before the route’s own handler runs, using the shared envelope like every other route. See
Error handling.Model types, matched to the real schemas
typescript/src/models/*.ts is hand-maintained, unlike the Python SDK’s generated models, and
kept in sync field-for-field against the real JSON schemas (schemas/common/*.schema.json):
Authority.displayNameandVerification.messageare optional, matching the real API.Authorization.expiresAtis present (optional).BusinessTransactionMetadatarequires onlybusinessTransactionId; every other field (tenantId,correlationId,sourceSystem,submittedBy,submittedAt) is optional, matching the real API.ExecutionTrustRecordincludes the requiredsignaturefield and the optionalsettlementConfirmationsfield, with their ownSettlementConfirmation/Signaturemodels.Override.approvedByis the real field name (the real schema has noauthorityIdonOverride), plus the optionaljustificationfield. Currently unreachable in practice, no route in this repository creates anOverride.Decision.outcome,Execution.status,Execution.mode, andBusinessTransaction.statusare literal unions matching the real enums, not barestring.ExecutionincludescompletedAt,evidence(where Connector evidence lives), andmetadata.
client.verify() and client.version()
client.verify(businessTransactionId):POST /verify, runs a fresh verification and appends a newVerificationto the record’s history.getLatestVerification()(GET /verification/:id) reads a cached verification; this triggers a fresh one.client.version():GET /version.
POST /transactions
client.createTransaction(transaction) is a second, independent entry point into the
identical execution pipeline as execute() (POST /execute), differing only in its 201
status code. It’s a stable, long-standing route with dedicated test coverage
(packages/api/tests/unit/transactions-api.test.ts), not a new or evolving surface.
createBusinessTransaction(), no more hand-synced ids
Added 2026-09-14, ships in v1.1.2, published, available via a plain
npm install @parmana/sdk. A BusinessTransaction has three id pairs the server’s own
BusinessTransactionValidator cross-checks before policy ever runs:
metadata.businessTransactionId must equal businessTransactionId,
authorization.authorityId must equal authority.authorityId, intent.authorizationId must
equal authorization.authorizationId. Every “X must match Y” 400 documented in
the end-to-end Paytm guide came from hand-building a request
object and getting one of those wrong. createBusinessTransaction() derives all three
automatically:
businessTransactionId defaults to a fresh crypto.randomUUID() 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. authorityType defaults to "SERVICE" (never
"AGENT", which doesn’t exist server-side). See
typescript/examples/06-create-business-transaction.ts (the same flow as
02-execute.ts, side by side, for direct comparison) and
typescript/test/createBusinessTransaction.test.ts (11 unit tests) /
typescript/test/integration/examples.integration.test.ts (proves the generated ids round-trip
correctly through a real running server, not just in isolation).
New in 1.5.0
What a request must carry, from the server
policyInEffect(capability) calls GET /policies/in-effect and returns the policy to declare and what the request
must carry: every fact the policy’s rules read (signals.facts), their types (signals.schema), the signals that must
equal a value of the Intent (signals.bound), and the signals that need a signed approval, with where the approval’s
resource and amount are in the request (signals.approval). It never returns the rules.
policy exactly as returned. It throws 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.externalConnectorChanges(status?) lists proposals and rejectExternalConnectorChange(changeId, reason, stepUp)
rejects one. To end a registration, propose { action: "revoke", capability, reason } and approve it the same way.
Tutorial 123 runs these calls against a real server. The whole path, with the endpoint, is in
Connect any external system.
New in 1.4.0
Published to npm on 2026-09-29.
npm install @parmana/sdk installs it.Sign an approval on the approver’s machine
signApproval() makes the signed approval a policy with approvalSignals asks for, the same
one scripts/sign-approval.ts makes. The private key never leaves the process.
ttlSeconds, 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.rejectApproverChange(changeId, reason, stepUp) rejects one, and action: "revoke" proposes a
revocation. See Manage approvers.
New in 1.3.0
Published to npm on 2026-09-25.
npm install @parmana/sdk 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
publicKey() needs no API key. Fetch it once and keep it.
Verify records offline
signatures array is reported as not valid, with an error that says why, because the SDKs do not verify ML-DSA-65.
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:typescript/examples/07-external-connector-endpoint.ts; Tutorial 123
(examples/tutorials/123-external-connector/run.ts) registers it and releases to it.
Approve a policy change
The approver signs on their own machine; the private key never leaves it.action: "reject" and call rejectPolicyChange(id, "Why it is rejected.", stepUp). A signed authorization is valid once, for one change and one action, for 120 seconds unless you pass ttlSeconds. The rules and every error are on Approve a policy.
List Trust Records, and the latest receipt
trustRecords() returns only records of transactions this key submitted, newest first. A page counts transactions, so it may hold fewer records than pageSize. pageSize is at most 100; a larger value is refused with 400. latestReceipt() reads the most recent receipt without generating a new one, unlike receipt().
Every HTTP error carries its status
statusCode is set on every error made from an HTTP response, matching the Python SDK’s status_code. It is absent on network errors and timeouts, where no response arrived.
Building from source / contributing
Installing the published package (above) is the right path for using the SDK. To build from source inside this monorepo instead:Test suite
191 tests across 18 files (2026-09-25, 1.3.0). Runnpx vitest run from the repo root, not typescript/:
several tests read PARMANA_POLICY_DIR relative to process.cwd() and fail with confusing
PolicyNotFoundError-shaped errors otherwise. Coverage includes: unit tests for every
error-mapping case (test/HttpTransport.test.ts, test/Errors.test.ts), unit tests for
createBusinessTransaction() (test/createBusinessTransaction.test.ts, 11 cases), unit tests
for client.verify()/client.version()/client.createTransaction()
(test/NewApiMethods.test.ts), a retry-logic suite (test/RetryPolicy.test.ts, real backoff
on idempotent GETs against 502/503/504, POSTs never retried, matching the Python SDK’s own
retry behavior), per-route unit suites (test/AuditApi.test.ts, test/HealthApi.test.ts,
test/PolicyApi.test.ts, test/RefusalApi.test.ts, test/ReplayApi.test.ts,
test/RuntimeApi.test.ts, test/VerificationApi.test.ts), and two integration suites that
boot the actual @parmana/api Express application: real StaticKeyAuthenticator, real
PolicyEngine, real Ed25519 signing, the real, NODE_ENV=test-only generic test-fixture
connector (createTestFixtureConnector.ts, successor to the now-removed vendor-payment), on
a real OS-assigned TCP port, driven by the real ParmanaClient over real HTTP:
test/integration/parmana-client.integration.test.ts (a real 401, a real 403, a real 400,
a real policy rejection, a real 404, a real 409 duplicate, plus version(), verify(), and
createTransaction(), each asserted against the exact typed result and exact message the server
returned) and test/integration/examples.integration.test.ts (runs the quickstart example
script itself, see Changelog). test/Alignment.test.ts covers everything added in
1.3.0, including offline verification of a real server signed Execution Intent and a step up
authorization accepted by the server’s own verifier.
Errors defined but never thrown
typescript/src/errors/ also declares VerificationError and ReplayError. Neither is thrown
anywhere: no verified HTTP condition in this API corresponds to them (POST /verify and
POST /replay return their semantic result, VERIFIED/FAILED, or a replay outcome, inside
an ordinary 200, not as an error). They remain defined, unused: inventing a throw condition
for them would describe behavior the real API doesn’t have.
Next
End-to-end: agent → Parmana → Paytm
The full, no-ambiguity runbook this SDK builds toward: real infrastructure,
every error message, every fix.
Python SDK
The other maintained SDK: same bearer-key model, generated (not
hand-maintained) models.
Error catalog
Every error the real API returns, independent of any SDK.
TypeScript SDK reference
Every class, interface, and type, generated from the real JSDoc, in the
sidebar under SDKs.