# @mandaitor/sdk

The official TypeScript SDK for the Mandaitor Delegation Mandate Registry API. This SDK provides a robust, type-safe interface for managing AI agent delegation mandates, verifying runtime actions, and retrieving cryptographic Proof-of-Mandate artifacts.

## Overview

Mandaitor is the runtime trust layer for delegated AI actions. Instead of assuming that an AI agent is allowed to act because it has application access, you verify whether a specific delegator granted a specific delegate the right to perform a specific action on a specific resource, under explicit constraints.

This SDK simplifies the integration of Mandaitor into Node.js, edge, and browser environments. It handles authentication, request retries, error mapping, and provides full TypeScript definitions for all Mandaitor domain objects including mandates, audit events, and Verifiable Credentials.

## Installation

Install the package via your preferred package manager. The SDK requires Node.js 18 or later.

```bash
npm install @mandaitor/sdk
```

## Initialization

Initialize the `MandaitorClient` with your API key and tenant identifier. You can obtain these credentials from the Mandaitor dashboard after completing the onboarding process.

```typescript
import { MandaitorClient } from "@mandaitor/sdk";

const client = new MandaitorClient({
  apiKey: process.env.MANDAITOR_API_KEY,
  tenantId: "tnt_your_tenant_id",
  timeout: 5000,
  retries: 2,
});
```

## Core Workflows

### Creating a Mandate

Delegation begins by creating a mandate. A mandate explicitly defines the boundaries of authority granted to an AI agent by a human principal. It specifies the allowed actions, target resources, and any constraints such as expiration times or financial limits.

```typescript
const mandate = await client.createMandate({
  principal: {
    type: "NATURAL_PERSON",
    subject_id: "user:jane.doe@example.com",
  },
  delegate: {
    type: "AGENT",
    subject_id: "agent:validation-v3",
  },
  scope: {
    actions: ["construction.validation.approve", "construction.validation.flag"],
    resources: ["project:proj_12345/*"],
    effect: "ALLOW",
  },
  constraints: {
    valid_until: new Date(Date.now() + 86400000).toISOString(),
    require_mfa_for_high_risk: true,
  },
});

console.log(`Created mandate: ${mandate.mandate_id}`);
```

### Verifying an Action

Before an agent executes an action, it must verify its authority against the Mandaitor registry. The verification engine evaluates the requested action against all active mandates bound to the agent.

```typescript
const result = await client.verify({
  delegate_subject_id: "agent:validation-v3",
  action: "construction.validation.approve",
  resource: "project:proj_12345/zone:EG/installation:stk_01",
});

if (result.decision === "ALLOW") {
  // Proceed with the action
  console.log("Action authorized");
} else {
  // Block the action and log the reason
  console.error(`Action denied: ${result.reason}`);
}
```

### Obtaining a Proof-of-Mandate

For high-assurance workflows, you can request a cryptographically signed Proof-of-Mandate (PoM) Verifiable Credential alongside the verification decision. This artifact serves as immutable evidence that the action was authorized at a specific point in time.

```typescript
const result = await client.verifyWithPoM(
  {
    delegate_subject_id: "agent:validation-v3",
    action: "construction.validation.approve",
    resource: "project:proj_12345/zone:EG",
  },
  { format: "sd-jwt-vc" },
);

console.log(`Proof artifact: ${result.pom_token}`);
```

### Requesting an Execution (Preview)

An agent can ask Mandaitor to perform a typed business operation instead of performing it itself. The agent names the tool and the record; Mandaitor reads the facts it decides on from the target system and answers once its decision is recorded. It then performs an allowed write itself, usually within seconds, reads it back, and signs a receipt; `waitForExecution` reads the execution until that is done. The agent authenticates as itself, with the key it registered, and never holds the target system's credential. Its session is bound to a second key the client makes and never lets out (DPoP, RFC 9449): every request carries a proof signed with it, so the session token alone is no use to anyone who takes it.

```typescript
import { MandaitorExecutionsClient, verifyReceiptForExecution } from "@mandaitor/sdk";

const executions = new MandaitorExecutionsClient({
  baseUrl: "https://api.mandaitor.io/v1",
  agent: { did: agentDid, keyId: agentKeyId, privateKey: agentPrivateKeyPem },
});

const { execution: requested } = await executions.requestExecution({
  tool: "sap.purchase_requisition.release",
  arguments: { purchase_requisition: "10000123" },
  idempotency_key: crypto.randomUUID(), // reuse it to retry: nothing is performed twice
});

// Final, waiting for a person's approval, or UNKNOWN.
const execution = await executions.waitForExecution(requested.execution_id);
if (execution.state === "SUCCEEDED") {
  const receipt = await verifyReceiptForExecution(execution, {
    allowedIssuers: ["did:web:api.mandaitor.io:v1"],
  });
  if (!receipt.valid) throw new Error(receipt.errors.join("; "));
}
```

A denial is an execution in `DENIED`, not an error. An execution that needs a person's approval waits in `AWAITING_APPROVAL`; that person decides it with `decideExecutionApproval`, using their own session token. An `UNKNOWN` execution, whose write may or may not have happened, is read back again by Mandaitor for about 41 hours, and a person of the tenant resolves what that does not settle; one they resolve (`RESOLVED_BY_OPERATOR`) carries no receipt. `verifyExecutionReceipt` verifies a receipt on its own, for an auditor working from an evidence pack: it accepts a token only when its `typ`, its `contract` and its signing key all make it a receipt, so a Proof-of-Mandate is never mistaken for one.

### Deciding an Operation Your System Performs (Preview)

When your own system performs an agent's operation, its enforcement point asks Mandaitor first, with your API key, never through the agent. It names the agent by its DID, the business action, the one resource, and the facts it read from your own system of record. An `ALLOW` carries an authorization for exactly that request and audience, valid five minutes. Redeem it immediately before you act, and act only if the redemption succeeds: it succeeds once.

```typescript
import { MandaitorClient } from "@mandaitor/sdk";

const request = {
  agent: "did:web:agents.mandaitor.io:agt_01JZ8Y3S6N0F4K7Q2W9E5R1T8V",
  action: "procurement.requisition.release",
  resource: "procurement:plant:1000/requisition:10004711",
  facts: { plant: "1000", amount: { currency: "EUR", value: "1250.00" } }, // read from your system
  audience: "https://erp.example.com", // your enforcement point
};

const decision = await client.requestDecision(request);
if (decision.decision !== "ALLOW") throw new Error(decision.reason); // DENY, or a person would have to approve

// Redeemed with the digest of `request`, computed here: a 409 means do not act.
await client.redeemDecision(decision.decision_id!, request);
await releaseRequisition("10004711"); // act once
```

`redeemDecision` computes the request digest from the request you pass, so what you redeem is what you are about to do: an authorization given for another amount or record is refused with `DIGEST_MISMATCH`. A refused redemption is a `MandaitorConflictError`, also `ALREADY_REDEEMED` after a retry whose first answer was lost; ask for a new decision then. Where the component that acts is not the one that asked, `verifyAuthorizationForRequest(decision, tenantId, request)` verifies the authorization's signature, audience, expiry and digest against the issuer's DID document first. A request that needs a person's approval is answered `REQUIRE_APPROVAL` with no authorization: approvals are not raised from decisions yet.

## API Reference

The `MandaitorClient` exposes methods covering the entire mandate lifecycle, audit trailing, and configuration management.

| Category         | Method                     | Description                                                         |
| :--------------- | :------------------------- | :------------------------------------------------------------------ |
| **Lifecycle**    | `createMandate(req)`       | Issues a new delegation mandate                                     |
|                  | `getMandate(id)`           | Retrieves a mandate's current state and configuration               |
|                  | `listMandates(params)`     | Queries mandates with filtering and pagination                      |
|                  | `revokeMandate(id)`        | Permanently invalidates a mandate                                   |
|                  | `suspendMandate(id)`       | Temporarily pauses a mandate's authority                            |
|                  | `reactivateMandate(id)`    | Restores a suspended mandate to active status                       |
| **Verification** | `verify(req)`              | Evaluates whether an action is currently authorized                 |
|                  | `verifyWithPoM(req, opts)` | Evaluates an action and returns a signed Verifiable Credential      |
|                  | `verifyWithDrift(req)`     | Evaluates an action and includes semantic drift signals             |
| **Audit**        | `getMandateEvents(id)`     | Retrieves the immutable audit log for a specific mandate            |
|                  | `listEvents(params)`       | Queries the tenant-wide audit log                                   |
|                  | `getEvidencePack(id)`      | Exports a cryptographically verifiable court-ready evidence package |
| **Identity**     | `initiateEudiSession()`    | Starts an eIDAS 2.0 wallet authentication session                   |

`requestDecision(request)` and `redeemDecision(id, request)` are an enforcement point's request-bound decisions (Preview); `decisionRequestDigest(tenantId, request)`, `verifyDecisionAuthorization(token, options)` and `verifyAuthorizationForRequest(decision, tenantId, request)` check them. `MandaitorExecutionsClient` is the agent's side of the execution API (Preview): `requestExecution(request)`, `getExecution(id)` and `waitForExecution(id)`. `decideExecutionApproval(params)` is a person's decision on an approval. `verifyReceiptForExecution(execution)` and `verifyExecutionReceipt(token)` verify execution receipts, and `verifyProofOfMandate(token)` verifies a Proof-of-Mandate.

## Error Handling

The SDK throws typed error classes that map directly to the Mandaitor API error responses. This allows for precise error handling and automated recovery strategies.

| Error Class                | HTTP Status | Typical Cause                                                             |
| :------------------------- | :---------- | :------------------------------------------------------------------------ |
| `MandaitorAuthError`       | 401         | The API key is missing, invalid, or expired                               |
| `MandaitorForbiddenError`  | 403         | The API key lacks the required scope for the operation                    |
| `MandaitorNotFoundError`   | 404         | The requested mandate, event, or resource does not exist                  |
| `MandaitorConflictError`   | 409         | Attempting an invalid state transition (e.g., revoking a revoked mandate) |
| `MandaitorRateLimitError`  | 429         | The tenant has exceeded its sustained or burst request limits             |
| `MandaitorValidationError` | 400         | The request payload violates schema constraints                           |

## Development and Testing

The SDK is built with TypeScript and bundled using tsup. We use Vitest for both unit and integration testing.

```bash
# Build the SDK
pnpm build

# Run unit tests
pnpm test:unit

# Run integration tests against the sandbox environment
pnpm test:integration
```

## License

Apache-2.0
