# SDK and middleware reference

> The public surface of @zanora/sdk, @zanora/middleware-mcp and @zanora/middleware-express, in one place.

## `@zanora/sdk`

### `new ZanoraAgent(options)`

| Option | Type | Notes |
|---|---|---|
| `agentId` | `string` | required |
| `walletId` | `string` | required |
| `privateKeyPem` | `string` | required: the ed25519 private key |
| `apiKey` | `string` | workspace key. Required on any real deployment |
| `maxPriceMinor` | `bigint` | client-side ceiling |
| `mcpCaller` | `McpToolCaller` | required for `invokeMcp`. See `@zanora/mcp` |
| `gatewayUrl` | `string` | defaults to `DEFAULT_GATEWAY_URL` (`https://api.zanora.dev`) |

| Method | Returns |
|---|---|
| `discover({ query?, category?, protocol?, maxPriceMinor?, maxLatencyMs?, limit? })` | `{ results: [{ capability, version, provider, score }] }` |
| `invoke<T>(url, { body?, method?, headers?, signal? })` | `InvokeResult<T>` |
| `invokeMcp<T>(address, { tool, arguments?, signal? })` | `InvokeResult<T>`, plus `mcpResult` |
| `verifyReceipt(receipt)` | `Promise<boolean>` |
| `balance()` | `{ balanceMinor, availableMinor, currency }` |
| `rateProvider(providerId, transactionId, rating)` | — |

```ts
interface InvokeResult<T> {
  status: number;
  data: T;
  receipt?: Receipt;
  receiptVerified: boolean;       // Zanora signed it
  responseHashVerified: boolean;  // it covers this response
  transactionId?: string;
  mcpResult?: McpToolCallResult;  // invokeMcp only: the seller's result, unmodified
}
```

| Error class | `code` values | Money moved? |
|---|---|---|
| `ZanoraPolicyRejection` | `POLICY_DENIED`, `APPROVAL_REQUIRED` (+ `approvalId`), `INSUFFICIENT_FUNDS`, … | no |
| `ZanoraPaymentError` | `PRICE_ABOVE_CLIENT_LIMIT`, `CANCELLED`, `INVALID_CHALLENGE`, `MCP_TRANSPORT_UNAVAILABLE` | no |

Also exported: `ZanoraClient` (typed API client, same `gatewayUrl` default), `generateEd25519KeyPair()`, `DEFAULT_GATEWAY_URL`.

## `@zanora/middleware-mcp`

| Export | Use |
|---|---|
| `zanoraSeller({ gatewayUrl?, apiKey? })` | gateway: the option, then `ZANORA_GATEWAY_URL`, then `https://api.zanora.dev`. Key: the option, then `ZANORA_API_KEY`, and it must be a provider key |
| `seller.sell(input)` | publishes or reconciles one tool, and returns the `paid` wrapper |
| `seller.sellWithDetails(input)` | returns `{ paid, describe, capability: { capabilityId, published, changed, priceMinor, currency, endpoint } }` |
| `seller.sellAll({ tools?, from?, prices, skip?, category, url? \| runsLocally?, … })` | returns `{ paid(tool), describe(tool, def), tools, capabilities }` |
| `zanoraTool({ backend, capabilityId })` | the low-level wrapper: `paid(params, handler)` |

`paid` has two forms: `paid(handler)` for `McpServer.tool()`, and `paid(params, handler)` for `setRequestHandler`. The handler's second argument is `{ transactionId, walletId, agentId }`.

## `@zanora/middleware-express`

| Export | Use |
|---|---|
| `new HttpBackend({ gatewayUrl?, apiKey })` | the gateway connection, using your provider key. The gateway defaults to `https://api.zanora.dev`. The positional form `new HttpBackend(gatewayUrl, apiKey)` still works |
| `zanora({ backend, capabilityId })` | Express middleware. Sets `req.zanora = { transactionId, walletId, agentId }` |

## `@zanora/core`

| Export | Use |
|---|---|
| `DEFAULT_GATEWAY_URL` | `"https://api.zanora.dev"`, the default every client package uses |
| `resolveCapabilityAddress(version)` | the `url` or `package` address to call or run |
| `canonicalJson`, `signPayload`, `verifyPayload` | the signing scheme for challenges, proofs and receipts |
| `generateEd25519KeyPair()` | `{ publicKeyPem, privateKeyPem }` |
| `HttpBackend`, `ZanoraBackend` | the seller-side gateway interface both middlewares use |
