Skip to main content

Node SDK

The Node SDK provides typed helpers for the full authorization flow: create a client, authorize an action, handle the three outcomes, and verify the permit before execution.

Install

pnpm add @veritrellis/sdk-node

Requires Node 22 or later. The package ships ES modules.

Quick reference

ExportPurpose
createClient(config)Initialize a scoped client
client.authorizeRequest(params)Submit an action for authorization
verifyPermit(params)Verify a permit JWT via JWKS
createJwksCache(config)Build a standalone JWKS cache
createExpressMiddleware(config)Express permit verification middleware
createFastifyPlugin(config)Fastify permit verification plugin

createClient

client.ts
import { createClient } from "@veritrellis/sdk-node";

const client = createClient({
apiKey: process.env.VERITRELLIS_API_KEY!,
workspaceId: process.env.VERITRELLIS_WORKSPACE_ID!,
environment: "sandbox", // "sandbox" | "production"
apiUrl: "https://api.veritrellis.ai"
});

Configuration

OptionTypeRequiredDescription
apiKeystringYesWorkspace API key (vt_sandbox_… or vt_prod_…)
workspaceIdstringYesYour workspace ID (ws_…)
environment"sandbox" | "production"YesMust match the environment the API key is scoped to
apiUrlstringNoDefaults to https://api.veritrellis.ai

authorizeRequest

Call this before any action that requires a permit. It contacts the Veritrellis API and returns one of three outcomes.

refund.ts
const result = await client.authorizeRequest({
action_type: "issue_refund",
resource_ref: "cus_123",
payload: {
customer_id: "cus_123",
amount: 75,
currency: "EUR",
reason: "customer request"
}
});

Handling the three outcomes

handle-outcomes.ts
switch (result.decision) {
case "allowed":
// Permit is ready — verify it before executing
const claims = await verifyPermit({
permitJwt: result.permit!,
workspaceId: process.env.VERITRELLIS_WORKSPACE_ID!,
issuer: "https://api.veritrellis.ai"
});
// Proceed with the action using verified claims
await issueRefund(claims);
break;

case "pending_approval":
// Save result.request_id and poll GET /v1/permits/:requestId
await saveForPolling(result.request_id!);
break;

case "denied":
// Policy blocked this request — do not execute
throw new Error(`Action denied: ${result.reason_code}`);
}
Always verify the permit

Receiving a permit from authorizeRequest is not sufficient. You must call verifyPermit before executing the action. Permits that fail verification must not unlock execution.

Request parameters

ParameterTypeRequiredDescription
action_typestringYesThe canonical action type (e.g. issue_refund)
resource_refstringYesIdentifier of the resource being acted on
payloadobjectYesAction-specific fields; validated against shared schemas
actor_refstringNoID of the human or agent initiating the action
idempotency_keystringNoStable key for safe retries

Response shape

FieldPresent whenTypeDescription
decisionAlways"allowed" | "pending_approval" | "denied"Authorization outcome
request_idAlwaysstringUnique request identifier
permitallowed onlystringSigned ES256 permit JWT
reason_codedenied onlystringMachine-readable denial reason

verifyPermit

Verifies a permit JWT against the workspace JWKS. Throws if the signature, issuer, audience, or expiry check fails.

verify.ts
import { verifyPermit } from "@veritrellis/sdk-node";

const claims = await verifyPermit({
permitJwt: result.permit!,
workspaceId: process.env.VERITRELLIS_WORKSPACE_ID!,
issuer: "https://api.veritrellis.ai"
});

The returned claims object contains:

ClaimTypeDescription
substringRequest ID this permit is bound to
workspace_idstringWorkspace the permit was issued for
action_typestringAction type the permit authorizes
resource_refstringResource the permit is scoped to
iatnumberIssued-at timestamp (Unix)
expnumberExpiry timestamp (Unix)

Verification parameters

ParameterTypeRequiredDescription
permitJwtstringYesThe JWT from authorizeRequest or permit polling
workspaceIdstringYesMust match the workspace_id claim in the JWT
issuerstringYesMust match the iss claim (https://api.veritrellis.ai)
jwksCacheInstanceJwksCacheNoPass a shared JWKS cache to avoid redundant key fetches

Polling for a permit

When decision is pending_approval, poll the permit endpoint until the request resolves:

poll.ts
async function pollPermit(requestId: string, apiKey: string): Promise<string> {
const url = `https://api.veritrellis.ai/v1/permits/${requestId}`;

for (let attempt = 0; attempt < 60; attempt++) {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` }
});

if (res.status === 200) {
const { permit } = await res.json();
return permit; // Ready — verify before executing
}

if (res.status === 409) {
throw new Error("Request was denied or expired — no permit will be issued");
}

// 202: still pending, wait and retry
await new Promise((r) => setTimeout(r, 2000));
}

throw new Error("Timed out waiting for permit");
}

Express middleware

Protect a route by requiring a valid permit in the Authorization header:

app.ts
import express from "express";
import { createExpressMiddleware } from "@veritrellis/sdk-node";

const app = express();

const requirePermit = createExpressMiddleware({
workspaceId: process.env.VERITRELLIS_WORKSPACE_ID!,
issuer: "https://api.veritrellis.ai"
});

// Only requests with a valid permit JWT reach the handler
app.post("/refunds", requirePermit, async (req, res) => {
const { permit } = req; // verified permit claims
await processRefund(permit.resource_ref, req.body);
res.json({ status: "ok" });
});

Fastify plugin

server.ts
import Fastify from "fastify";
import { createFastifyPlugin } from "@veritrellis/sdk-node";

const fastify = Fastify();

await fastify.register(createFastifyPlugin, {
workspaceId: process.env.VERITRELLIS_WORKSPACE_ID!,
issuer: "https://api.veritrellis.ai"
});

fastify.post("/refunds", { config: { requirePermit: true } }, async (req) => {
const { permit } = req; // verified permit claims
await processRefund(permit.resource_ref, req.body);
return { status: "ok" };
});

Environment variables

VariableRequiredDescription
VERITRELLIS_API_KEYYesAPI key from the admin app
VERITRELLIS_WORKSPACE_IDYesWorkspace ID from the admin app
Using .env files

Both the API key and workspace ID are available in the admin app under Settings > API keys. The sandbox and production environments have separate keys.


Generated API reference

Full symbol-level documentation generated from source: