Govern AI content in Node.js — either on-device with tork-governance, or server-side via the REST API. Covers fetch, axios, middleware, and Express.
Prerequisites
Node.js 18+ (for native fetch) or Node.js 14+ with axios
No API key is needed for on-device governance. A Tork API key is required only for the REST examples (get one here)
Basic knowledge of async/await (for the REST path only)
Two different packages — pick deliberately
tork-governance (npm) is the on-device package. It exports Tork, and govern() is synchronous — do not await it. tork-governance makes the governance decision on your machine. PII detection, redaction and the returned verdict are computed on-device. With no API key it makes zero network calls: prompts, completions and detected PII values never leave the machine, and nothing appears in your dashboard.
@torknetwork/sdk (npm, v2.0.0) is the cloud package. It exports TorkClient, sends content to Tork, and Tork decides server-side and writes a receipt to your dashboard. The REST examples on this page are that same server-governed path without an SDK.
TorkClient is not exported by tork-governance and never has been — it is undefined if you import it from there.
Local governance is free and unlimited — decisions made on your own machine are never metered. Evidence is what is metered: attestations, receipts and anchoring.
Installation
Pick the package that matches where you want the decision made.
bash
# ON-DEVICE — the decision is made in your process.
# Exports the Tork class. govern() is synchronous.
npm install tork-governance
# CLOUD — Tork makes the decision server-side and writes a
# receipt to your dashboard. Exports TorkClient.
npm install @torknetwork/sdk
# The REST examples below need no SDK at all (fetch is built in).
npm install axios # optional, only for the axios example
On-device SDK (tork-governance)
Decisions are made in your process. No API key, no network call.
javascript
import { Tork } from 'tork-governance';
// No API key: the decision is made in this process and nothing
// is sent anywhere. This is free and unlimited.
const tork = new Tork();
// govern() is synchronous — there is no network call to await.
const result = tork.govern('Check this text for PII: john@example.com');
console.log(result.action); // "redact"
console.log(result.output); // "Check this text for PII: [EMAIL_REDACTED]"
console.log(result.receipt.receiptId); // "rcpt_..." — a LOCAL receipt, not in your dashboard
// Fields: action, output, pii, receipt, report.
// There is no "redacted" field — the governed text is "output".
if (result.pii.hasPII) {
console.log(result.pii.types); // ["email"]
console.log(result.pii.count); // 1
}
Supply an API key and the SDK additionally reports a metadata-only attestation of each decision — the action taken, PII type labels and counts, a risk classification, policy labels and a salted fingerprint. It never sends input text, output text or PII values. The decision itself is still made on-device and is never delayed or changed by reporting. Those attestations appear in your dashboard and are included in the daily on-chain anchor — each one recorded as a client attestation (capture_mode=edge, attested_by=client), a claim Tork recorded but did not execute and cannot independently verify. Requires tork-governance 0.24.0+ (PyPI) or 0.11.0+ (npm).
A decision reported by an on-device SDK is recorded as a client attestation (capture_mode=edge, attested_by=client): a claim Tork recorded but did not execute and cannot independently verify. A decision made by @torknetwork/sdk is recorded as capture_mode=cloud, attested_by=tork — Tork made that call itself. Both are equally immutable once anchored; they differ in what is immutable. A client attestation freezes your claim. A server-governed receipt freezes Tork's own decision.
Environment Setup
For the REST examples below. The on-device SDK does not use an API key.
bash.env
# .env
# Used by the REST examples below. The on-device package does not
# require an API key, and the published builds never send one.
TORK_API_KEY=tork_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Never commit your API key to version control. Add .env to your .gitignore.
Server-governed REST API
Send content to Tork and get a dashboard receipt back. Requires an API key.
javascript
// Using native fetch (Node.js 18+)
// This SENDS the content to Tork, which decides server-side.
const response = await fetch('https://tork.network/api/v1/govern', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TORK_API_KEY}`
},
body: JSON.stringify({
content: userMessage,
options: { mode: 'redact' }
})
});
const result = await response.json();
console.log(result.action); // "allow" | "redact" | "deny"
console.log(result.output); // the governed (redacted) text
console.log(result.receipt_id); // "rcpt_..." — appears in your dashboard
if (result.action !== 'allow') {
console.log('PII detected:', result.pii_detected);
}
Reusable Client
Create a wrapper function for cleaner code across your application.
Handle API errors, rate limits, and network issues gracefully.
javascript
async function safeGovern(content) {
try {
const response = await fetch('https://tork.network/api/v1/govern', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TORK_API_KEY}`
},
body: JSON.stringify({ content, options: { mode: 'redact' } })
});
// Handle HTTP errors
if (!response.ok) {
if (response.status === 401) {
throw new Error('Invalid API key');
}
if (response.status === 429) {
throw new Error('Rate limit exceeded. Please retry later.');
}
throw new Error(`API error: ${response.status}`);
}
return await response.json();
} catch (error) {
// Network errors, timeouts, etc.
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
}
// With timeout
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, { signal: controller.signal });
// ...
} finally {
clearTimeout(timeout);
}
Common Error Codes
401
Invalid or missing API key
429
Rate limit exceeded (limit depends on your plan)
400
Invalid request body
500
Server error (retry with backoff)
Express Middleware
Protect your API endpoints with a reusable middleware.
javascriptmiddleware/torkGuard.js
// middleware/torkGuard.js
import { Tork } from 'tork-governance';
// On-device: construct once and share. No client to close, and no
// network call in the request path.
const tork = new Tork();
export function torkGuard(options = {}) {
const { blockDenied = true, logOnly = false } = options;
return (req, res, next) => {
// Skip if there is no body to govern
if (!req.body?.message && !req.body?.content) {
return next();
}
const content = req.body.message || req.body.content;
try {
// Synchronous — no await.
const result = tork.govern(content);
// Attach the governed result for downstream handlers
req.torkResult = result;
if (result.action !== 'allow') {
console.warn('Governance action taken:', {
action: result.action,
piiTypes: result.pii.types,
receiptId: result.receipt.receiptId
});
if (result.action === 'deny' && blockDenied && !logOnly) {
return res.status(400).json({
error: 'Content blocked by policy',
receiptId: result.receipt.receiptId
});
}
}
next();
} catch (error) {
// Your own error-handling policy: decide whether a failure in
// YOUR governance call lets the request through or blocks it.
console.error('Governance call threw:', error);
if (options.failClosed) {
return res.status(503).json({ error: 'Governance check failed' });
}
next();
}
};
}
Complete Express Example
A full example showing Tork integration in an Express chat API.
javascriptserver.js
import express from 'express';
import { Tork } from 'tork-governance';
import { torkGuard } from './middleware/torkGuard.js';
const tork = new Tork();
const app = express();
app.use(express.json());
// Apply Tork guard to all AI-related routes
app.use('/api/chat', torkGuard({ context: 'chatbot' }));
app.use('/api/generate', torkGuard({ context: 'content-generation' }));
// Chat endpoint with Tork protection
app.post('/api/chat', async (req, res) => {
const { conversationId } = req.body;
// Content already governed by middleware.
// req.torkResult holds the full GovernanceResult.
const governed = req.torkResult;
try {
// Send the REDACTED text to your model, not the raw message.
const aiResponse = await generateAIResponse(governed.output, conversationId);
// Govern the model's output on the way back out too.
const outputResult = tork.govern(aiResponse);
res.json({
response: outputResult.output,
filtered: outputResult.action !== 'allow'
});
} catch (error) {
res.status(500).json({ error: 'Failed to generate response' });
}
});
app.listen(3000, () => {
console.log('Server running with Tork protection');
});
TypeScript Support
Type definitions for better IDE support and type safety.
typescripttypes/tork.ts
// The on-device package ships its own types — import them directly.
import {
Tork,
type GovernanceResult,
type GovernanceAction,
type PIIDetectionResult,
} from 'tork-governance';
const tork = new Tork();
// govern() is synchronous: GovernanceResult, not Promise<GovernanceResult>.
const result: GovernanceResult = tork.govern('john@example.com');
const action: GovernanceAction = result.action; // 'allow'|'deny'|'redact'|'escalate'
const pii: PIIDetectionResult = result.pii; // { hasPII, types, count, matches, redactedText }
const receiptId: string = result.receipt.receiptId;
// --- REST path (server-governed) types you write yourself ---
interface TorkGovernRequest {
content: string;
options?: { mode?: 'detect' | 'redact' | 'deny' };
}
interface TorkGovernResponse {
action: 'allow' | 'redact' | 'deny';
output: string;
receipt_id: string;
latency_ms: number;
pii_detected?: Array<{ type: string; count: number }>;
}
async function governContent(
request: TorkGovernRequest
): Promise<TorkGovernResponse> {
const response = await fetch('https://tork.network/api/v1/govern', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TORK_API_KEY}`
},
body: JSON.stringify(request)
});
if (!response.ok) {
throw new Error(`Tork API error: ${response.status}`);
}
return response.json();
}
Batch Processing
Evaluate multiple items while respecting rate limits.
javascript
import { Tork } from 'tork-governance';
// ON-DEVICE: no network, no rate limit, nothing to batch around.
// A plain synchronous loop is the fastest correct answer.
const tork = new Tork();
const messages = [
'Hello, how are you?',
'My email is john@example.com',
'Card 4111 1111 1111 1111',
];
const results = messages.map(content => tork.govern(content));
const flagged = results.filter(r => r.action !== 'allow');
console.log(`${flagged.length} of ${results.length} were redacted or denied`);
// ---------------------------------------------------------------
// CLOUD/REST: here batching and pacing DO matter, because each
// item is a metered HTTP round-trip subject to rate limits.
async function governBatchViaRest(items, { concurrency = 5, delayMs = 100 } = {}) {
const out = [];
for (let i = 0; i < items.length; i += concurrency) {
const batch = items.slice(i, i + concurrency);
const batchResults = await Promise.all(
batch.map(item =>
governContent(item.content).catch(err => ({ error: err.message, item }))
)
);
out.push(...batchResults);
if (i + concurrency < items.length) {
await new Promise(r => setTimeout(r, delayMs));
}
}
return out;
}
Best Practices
Know which package you installed
tork-governance exports Tork and decides on-device. TorkClient belongs to @torknetwork/sdk and decides server-side. Mixing them is the single most common integration failure.
Don't await the on-device govern()
It returns a GovernanceResult, not a Promise. Awaiting it still works in JS but signals the wrong mental model — there is no round-trip and no client to close.
Govern both input and output
Govern user messages before sending them to your model, and model responses before showing them to users. Pass result.output downstream, not the original text.
Implement retry logic on the REST path only
Use exponential backoff for 429 and 5xx. Don't retry 401 or 400. The on-device path has no HTTP errors to retry.
Log receipt IDs
Store receipt.receiptId (on-device) or receipt_id (REST) for debugging and compliance.
Decide how YOUR call fails
Wrap your own governance call and choose whether an unexpected error in your code path allows or blocks the request. This is your error-handling policy, not a statement about Tork's availability.
Next Steps
See the API reference for the full /api/v1/govern contract, or the quickstart for the shortest path from install to a first decision.