> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parmanasystems.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Real client code, bearer-key authentication, and a typed error taxonomy that is actually thrown.

<Info>**\[AVAILABLE]**, `typescript/src/`, v1.0.0. The original three gaps (no bearer-key auth, an
unused error taxonomy, an empty test suite) were closed in an earlier pass. A follow-up full SDK
audit found and fixed three more real issues: inaccurate model types (fields marked required that
the real API doesn't require, and a missing `settlementConfirmations`/`signature` field), two
missing capabilities (`POST /verify`, `GET /version`), and one capability missing from both
maintained SDKs (`POST /transactions`). All re-verified against a real running local server. See
the fix history at the bottom of this page.</Info>

<Warning>
  **The package is named `@parmana/sdk` in `typescript/package.json`**, private,
  not published to any registry a normal `npm install` could resolve. Install from
  a local path or a git reference instead. (Renamed from `@parmana/legacy-reference`, itself
  renamed from `@parmana/typescript-sdk` — see [Changelog](/changelog) for when.)
</Warning>

## Bearer-key authentication

`Configuration.apiKey` is sent as `Authorization: Bearer <apiKey>` on every request, by
`HttpTransport`:

```typescript theme={null}
import { ParmanaClient, HttpTransport } from "@parmana/sdk";

const endpoint = "http://localhost:3000";
const apiKey = "my-secret-api-key";

const client = new ParmanaClient({
  endpoint,
  apiKey,
  transport: new HttpTransport({ endpoint, apiKey }),
});

const health = await client.health(); // GET /health, no key required
```

`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](/api-reference/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`):

```typescript theme={null}
import {
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  ConflictError,
  ExecutionRejectedError,
  InternalServerError,
  NetworkError,
  TimeoutError,
} from "@parmana/sdk";

try {
  await client.execute(transaction);
} catch (error) {
  if (error instanceof ExecutionRejectedError) {
    console.log(error.message); // "Execution rejected: <policy-specific reason>"
  } else if (error instanceof ValidationError) {
    // 400 — malformed request
  }
}
```

| Status                                                           | Exception                |
| ---------------------------------------------------------------- | ------------------------ |
| 400                                                              | `ValidationError`        |
| 401                                                              | `AuthenticationError`    |
| 403, code `POLICY_DENIED`, message starting `Execution rejected` | `ExecutionRejectedError` |
| 403 (no `code`)                                                  | `AuthorizationError`     |
| 404                                                              | `NotFoundError`          |
| 409                                                              | `ConflictError`          |
| any other non-2xx                                                | `InternalServerError`    |
| connection failure                                               | `NetworkError`           |
| request timeout                                                  | `TimeoutError`           |

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`) — found live during this pass and
added to the [Error catalog](/api-reference/error-catalog) and the OpenAPI spec alongside this
fix, both re-verified against source and a live server.

<Note>
  **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](/api-reference/error-handling).
</Note>

## Model types, corrected against the real schemas

`typescript/src/models/*.ts` is hand-maintained, unlike the Python SDK's generated models — a
full audit spot-checked every field against the real JSON schemas
(`schemas/common/*.schema.json`) and found real inaccuracies, all fixed this pass:

* `Authority.displayName`, `Verification.message` — were marked required; the real API makes them
  optional.
* `Authorization.expiresAt` — missing entirely; now present (optional).
* `BusinessTransactionMetadata` — every field except `businessTransactionId` was marked required;
  the real API requires only `businessTransactionId`. The most significant of these: it forced
  callers to supply fields (`tenantId`, `correlationId`, `sourceSystem`, `submittedBy`,
  `submittedAt`) the real API doesn't need.
* `ExecutionTrustRecord` — was missing the required `signature` field and the optional
  `settlementConfirmations` field (and the `SettlementConfirmation`/`Signature` models
  themselves) entirely.
* `Override.authorityId` — **wrong field name**, should be `approvedBy` (the real schema has no
  `authorityId` on `Override` at all); also missing the optional `justification` field. Currently
  unreachable in practice (no route in this repository creates an `Override`), but wrong is wrong.
* `Decision.outcome`, `Execution.status`, `Execution.mode`, `BusinessTransaction.status` — were
  bare `string`; now literal unions matching the real enums exactly.
* `Execution` — was missing `completedAt`, `evidence`, and `metadata` entirely: real, commonly-used
  fields (evidence in particular is where Connector evidence lives) with no way to read them
  through this SDK's types before this pass.

## Two capabilities added this pass, previously unreachable from this SDK

* **`client.verify(businessTransactionId)`** — `POST /verify`, runs a fresh verification and
  appends a new `Verification` to the record's history. Before this pass, the SDK could only read
  a cached verification via `getLatestVerification()` (`GET /verification/:id`); there was no way
  to trigger a fresh one at all.
* **`client.version()`** — `GET /version`. Had no method anywhere in the SDK.

## `POST /transactions`, missing from both maintained SDKs, now added

`client.createTransaction(transaction)` — a second, independent entry point into the identical
execution pipeline as `execute()` (`POST /execute`), differing only in its `201` status code.
Confirmed stable before adding: the route has existed since near the start of this repository's
history (18 commits into a 141-commit history), was touched by the most recent security-fix
commit in lockstep with `/execute`, and has dedicated test coverage
(`packages/api/tests/unit/transactions-api.test.ts`) — not a new or evolving surface.

## Install

```bash theme={null}
npm install --workspace=typescript
# or, from another project:
npm install /path/to/parmana-exp/typescript
```

```bash theme={null}
npm run build   # typescript/
npm run test    # vitest
```

## Test suite

Previously 9 files, 0 bytes each, "9 passed" trivially. Now 129 tests across 15 files, `npm run
test` (`vitest run`), confirmed against a real run this pass: unit tests for every error-mapping
case (`test/HttpTransport.test.ts`, `test/Errors.test.ts`), unit tests for the three
capabilities added in the model-audit pass (`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`, see `docs/VERIFICATION-GAPS.md` G-27) — on a
real OS-assigned TCP port and drive it with 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](/changelog)).

## What's still true from before

`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, for the same reason they were
before this pass: inventing a throw condition for them would be describing behavior the real API
doesn't have.

## Next

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="/sdks/python">
    The other maintained SDK — same bearer-key model, generated (not hand-maintained) models.
  </Card>

  <Card title="Error catalog" icon="list" href="/api-reference/error-catalog">
    Every error the real API returns, independent of any SDK.
  </Card>
</CardGroup>
