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

# Node SDK

> @inviolet/agent-sdk-core — the Inviolet client: mint mandates and guard tool calls.

`@inviolet/agent-sdk-core` is the canonical Node client. It exposes one
`Inviolet` object that wraps the gateway's mandate + decision endpoints, so you
mint a [mandate](/concepts/mandates) once and guard every tool call against it.

Framework wrappers ([Anthropic](/sdks/anthropic), [OpenAI](/sdks/openai),
[LangChain](/sdks/langchain), [LlamaIndex](/sdks/llamaindex),
[fetch](/sdks/fetch), [MCP client](/sdks/mcp-client)) all delegate to this
package and expose the same `Inviolet` client.

## Install

```bash theme={"dark"}
npm install @inviolet/agent-sdk-core
```

Required Node version: **20+**.

## Construct the client

```ts theme={"dark"}
import { Inviolet } from '@inviolet/agent-sdk-core'

const inviolet = new Inviolet({
  gatewayUrl: process.env.INVIOLET_GATEWAY_URL!,
  apiKey: process.env.INVIOLET_API_KEY!,
  agentId: 'support-copilot',
})
```

## Mint a mandate

`mintMandate()` wraps `POST /v1/mandate/dispense`. The requested scope must be a
subset of the intent card's allowed scope, or the gateway refuses with a
suggested narrower scope.

```ts theme={"dark"}
const mandate = await inviolet.mintMandate({
  intentId: 'intents:customer_support_lookup',
  actor: 'alice@example.com',
  requestedScope: {
    operations: ['postgres.select'],
    resources: ['contacts.first_name', 'contacts.last_name'],
  },
  audience: 'postgres-prod',
  // Optional: forward the mandate to a credential broker.
  // brokerDispense: { kind: 'vault', vault_role: 'inviolet-support' },
})
// → { credential, cred_id, expires_at, ttl_seconds, broker_dispense }
```

## Guard a tool call

`guard()` runs the decision engine against the mandate + the action and returns
a normalized verdict.

```ts theme={"dark"}
const verdict = await inviolet.guard({
  mandateJwt: mandate.credential,
  surface: 'sdk',
  operation: 'postgres.select',
  arguments: { table: 'contacts', id: 42 },
  audience: 'postgres-prod',
})

if (!verdict.allowed) {
  switch (verdict.action) {
    case 'request_approval': /* queued for a human — poll or render in your UI */ break
    case 'step_up':          /* trigger the IdP step-up flow, then retry */ break
    case 'reroute':          /* fall back to the rerouted handler */ break
    default:                 throw new Error(verdict.reason ?? 'Denied by Inviolet')
  }
}
```

`verdict.action` is one of `'proceed' | 'request_approval' | 'step_up' |
'reroute' | 'block'`. Prefer `inviolet.assert(...)` if you'd rather throw on
deny and skip the branch.

## Config options

| Option       | Required | Description                                                 |
| ------------ | -------- | ----------------------------------------------------------- |
| `gatewayUrl` | yes      | Base URL of your Inviolet gateway                           |
| `apiKey`     | yes      | Your Inviolet API key (`INVIOLET_API_KEY`)                  |
| `agentId`    | yes      | Stable id for the calling agent, recorded on every decision |

## Verifying mandates yourself

Downstream services can verify a mandate against the gateway's published JWKS
without calling back:

```ts theme={"dark"}
import { verifyMandate } from '@inviolet/mandate'

const claims = await verifyMandate(jwt, {
  audience: 'postgres-prod',
  jwksUrl: 'https://api.inviolet.ai/v1/mandate/jwks',
})
console.log(claims.inviolet_intent_id)
```

## Read next

* **[Mandates](/concepts/mandates)** — the JWT format and lifecycle
* **[Python / REST](/sdks/rest)** — the language-agnostic HTTP flow
* **[Quickstart](/quickstart)** — the full end-to-end walkthrough
