@visoracloud/client
@visoracloud/client
Official Node.js and TypeScript client for the Visora Cloud image moderation and redaction API.
Install
npm install @visoracloud/client
Moderate an image
import { readFile } from "node:fs/promises";
import { Visora } from "@visoracloud/client";
const visora = new Visora({
apiKey: process.env.VISORA_API_KEY!,
});
const image = await readFile("./image.jpg");
const result = await visora.moderateImage({
file: image,
filename: "image.jpg",
contentType: "image/jpeg",
});
console.log(result.action, result.riskScore, result.labels);
Moderate an existing image key
const result = await visora.moderateImageKey({
imageKey: "accounts/acc_123/projects/proj_123/uploads/image.jpg",
});
Redact an image
Redaction is available for paid plans and API keys attached to redaction projects. The SDK uploads the image and returns a processed image URL with configured regions blurred or black-boxed.
import { readFile } from "node:fs/promises";
import { Visora } from "@visoracloud/client";
const visora = new Visora({
apiKey: process.env.VISORA_REDACTION_API_KEY!,
});
const image = await readFile("./profile.jpg");
const result = await visora.redactImage({
file: image,
filename: "profile.jpg",
contentType: "image/jpeg",
});
console.log(result.redactionId, result.redactedImageUrl);
console.log(result.facesBlurred, result.textBlurred, result.licensePlatesBlurred);
Redact an existing image key
const result = await visora.redactImageKey({
imageKey: "accounts/acc_123/projects/proj_123/uploads/image.jpg",
});
The imageKey must belong to the same account and project as the API key.
Redaction response
interface RedactionResponse {
redactionId: string;
imageKey: string;
redactedImageKey: string;
redactedImageUrl: string;
facesBlurred: number;
textBlurred: number;
licensePlatesBlurred: number;
faces: RedactionFace[];
regions: RedactionRegion[];
}
Region bounding boxes are normalized values from 0 to 1:
type RedactionRegionType = "face" | "text" | "license_plate";
interface RedactionRegion {
type: RedactionRegionType;
text?: string;
confidence: number;
boundingBox: {
left: number;
top: number;
width: number;
height: number;
};
}
Redaction project settings
Redaction behavior is configured per project from the Visora dashboard. The SDK exports these types for dashboard integrations and shared app types:
type RedactionStyle = "blur" | "black_box";
type RedactionTextCategory =
| "id_document"
| "pii"
| "dates"
| "financial"
| "credentials"
| "medical"
| "sexual"
| "profanity";
interface RedactionSettings {
faceBlur: boolean;
textBlur: boolean;
licensePlateBlur: boolean;
redactionStyle: RedactionStyle;
textCategories: RedactionTextCategory[];
customWords: string[];
ignoredWords: string[];
minConfidence: number;
}
Defaults are face blur on, text blur off, license plate blur off, blur style, no text categories, no custom/ignored words, and minConfidence of 80.
Detection categories
textCategories requires textBlur to be on. With textBlur on and no categories, all detected text is blurred. With categories, only the selected data types are redacted:
| Category | Detects |
|---|---|
id_document |
Identity-document fields (passport, license, national ID) — blurs sensitive values near their labels, not the whole document. |
pii |
Emails, phone numbers, addresses, IDs, and tax numbers (RFC, CURP, SSN). |
dates |
Dates in any format — DOB, issue, expiry (01/02/2024, 2024-01-02, Jan 5 2024). |
financial |
Card numbers (Luhn-checked), bank accounts, IBAN, and CLABE. |
credentials |
Passwords, API keys (sk_, ghp_, AWS), tokens, JWTs, and secrets. |
medical |
Patient IDs, record numbers, and case numbers near their labels. |
sexual / profanity |
Explicit or offensive wording. |
customWords redacts exact words/phrases; ignoredWords prevents specific words (e.g. field labels) from being redacted.
Webhook signatures
Visora signs every webhook delivery with:
visora-timestampvisora-signature- HMAC SHA-256 over
timestamp.rawBody - signature format
v1=<hex>
import { verifyWebhookSignature } from "@visoracloud/client";
const valid = verifyWebhookSignature({
secret: process.env.VISORA_WEBHOOK_SECRET!,
payload: rawBody,
timestamp: request.headers["visora-timestamp"] as string,
signature: request.headers["visora-signature"] as string,
});
During secret rotation, pass the current and previous secret:
const valid = verifyWebhookSignature({
secret: [process.env.VISORA_WEBHOOK_SECRET!, process.env.VISORA_PREVIOUS_WEBHOOK_SECRET!],
payload: rawBody,
timestamp,
signature,
});
Next.js webhook route
import { createNextWebhookHandler } from "@visoracloud/client";
export const POST = createNextWebhookHandler({
secret: process.env.VISORA_WEBHOOK_SECRET!,
async onEvent(event) {
switch (event.type) {
case "moderation.completed":
await updateImageModeration(event.data.moderationId, event.data.action);
break;
case "moderation.review_required":
await notifyReviewTeam(event.data.reviewId);
break;
case "review.approved":
case "review.rejected":
await syncReviewDecision(event.data.reviewId, event.type);
break;
case "redaction.completed":
// event.data is narrowed to VisoraRedactionCompletedData
await storeRedactedImage(event.data.redactionId, event.data.redactedImageKey);
break;
}
},
});
Express webhook route
Use express.raw() so the SDK can verify the exact payload Visora signed.
import express from "express";
import { createExpressWebhookHandler } from "@visoracloud/client";
const app = express();
app.post(
"/webhooks/visora",
express.raw({ type: "application/json" }),
createExpressWebhookHandler({
secret: process.env.VISORA_WEBHOOK_SECRET!,
async onEvent(event) {
if (event.type === "moderation.completed") {
await updateImageModeration(event.data.moderationId, event.data.action);
}
},
})
);
Webhook event types
import type { VisoraWebhookEvent, VisoraWebhookEventType } from "@visoracloud/client";
const eventTypes: VisoraWebhookEventType[] = [
"moderation.completed",
"moderation.review_required",
"review.approved",
"review.rejected",
];
function handleEvent(event: VisoraWebhookEvent) {
if (event.type === "moderation.completed") {
console.log(event.data.moderationId, event.data.action);
}
}
Errors
import { VisoraRateLimitError } from "@visoracloud/client";
try {
await visora.moderateImage({ file: image });
} catch (error) {
if (error instanceof VisoraRateLimitError) {
console.log("Monthly limit exceeded");
}
}
This SDK is Node-first. Do not expose secret API keys or webhook signing secrets in browser code.