| project | projects/useknockout-node |
|---|---|
| type | readme |
Official TypeScript / Node.js client for useknockout — state-of-the-art background removal & image-processing API.
Install · Quick start · Configuration · API · Framework examples · Self-hosting
One method call. Transparent PNG out.
@useknockout/node is the official Node.js / TypeScript SDK for the useknockout image API — a GPU-backed service (deployed on Modal) that performs background removal (BiRefNet) plus a suite of related image-processing presets: background replacement, masking, smart cropping, stickers/outlines, drop shadows, e-commerce studio shots, headshots, super-resolution upscaling, face restoration, colorization, silhouettes, and inpainting.
The SDK is a thin, fully-typed wrapper over the HTTP API. Every transform is one method call; most return a Node Buffer of the processed image bytes.
Design goals:
- Zero runtime dependencies — uses the native
fetchandFormDatabuilt into Node 18+. - First-class TypeScript — fully typed public API, no
any. - Runs anywhere
fetchworks — Node, Bun, Deno, Vercel / Cloudflare Workers, serverless. - MIT licensed.
The package ships dual ESM + CommonJS builds with .d.ts types (built with tsup).
npm install @useknockout/node
# or
pnpm add @useknockout/node
# or
yarn add @useknockout/nodeRequires Node 18+ (for global fetch and FormData). To supply your own fetch on older/edge runtimes, see Configuration.
import { writeFile } from "node:fs/promises";
import { Knockout } from "@useknockout/node";
const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
// Remove background — returns a transparent PNG Buffer
const png = await client.remove({ file: "./input.jpg" });
await writeFile("out.png", png);
// Replace background with a hex color
const jpg = await client.replaceBackground({
file: "./input.jpg",
bgColor: "#FF5733",
format: "jpg",
});
await writeFile("out.jpg", jpg);
// Replace background with a remote image
const composed = await client.replaceBackground({
file: "./input.jpg",
bgUrl: "https://example.com/beach.jpg",
});
// Batch — remove bg from up to 10 URLs in one call
const batch = await client.removeBatchUrl({
urls: ["https://example.com/a.jpg", "https://example.com/b.jpg"],
});
for (const r of batch.results) {
if (r.success) {
await writeFile(`${r.url}.png`, Buffer.from(r.data_base64!, "base64"));
}
}import { readFile } from "node:fs/promises";
// From a Buffer (e.g. an upload handled by multer)
const buf = await readFile("./input.jpg");
const out = await client.remove({ file: buf, filename: "input.jpg" });
// From a remote URL
const fromUrl = await client.removeUrl({ url: "https://example.com/cat.jpg" });Every method that takes a file accepts any of: a local path (string), a Buffer, a Blob, an ArrayBuffer, or a Uint8Array. When you pass a path, the filename is inferred from it; otherwise provide filename (defaults to "image").
Pass a bearer token to the constructor. The token is sent as Authorization: Bearer <token> on every request (except health and stats, which need no auth).
const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });There is no .env file in this repo — supply the token however your app manages secrets. A common convention is a KNOCKOUT_TOKEN environment variable:
# .env (your app, not this package)
KNOCKOUT_TOKEN=your_token_heretoken may be omitted when pointing at a self-hosted instance that has no auth.
new Knockout(options?: KnockoutOptions)| Option | Type | Default | Description |
|---|---|---|---|
token |
string |
— | Bearer token. Required unless self-hosting without auth. |
baseUrl |
string |
https://useknockout--api.modal.run |
Override the API endpoint (e.g. a self-hosted deployment). Trailing slashes are stripped. |
timeoutMs |
number |
60000 |
Per-request timeout in milliseconds. Aborts via AbortController. |
fetch |
typeof fetch |
globalThis.fetch |
Custom fetch implementation for edge runtimes or polyfills. Throws at construction if no fetch is available. |
The exported constant DEFAULT_BASE_URL holds the hosted endpoint.
All image methods return a Buffer of the processed bytes. Persist with writeFile(path, buf), or convert with buf.toString("base64"). removeBatch / removeBatchUrl return a JSON BatchResponse instead (per-image base64). health, stats, and estimate return JSON objects.
Remove the background from a local file or in-memory buffer.
| Field | Type | Description |
|---|---|---|
file |
string | Buffer | Blob | ArrayBuffer | Uint8Array |
File path or raw image bytes. |
filename |
string? |
Optional override — auto-inferred from a path. |
format |
"png" | "webp" |
Output format. Default "png". |
Returns a Buffer (PNG or WebP with transparent alpha).
Remove the background from a remote image URL.
| Field | Type | Description |
|---|---|---|
url |
string |
Remote image URL. |
format |
"png" | "webp" |
Output format. Default "png". |
Remove backgrounds from up to 10 local images in a single call. Throws if files is empty or longer than 10.
| Field | Type | Description |
|---|---|---|
files |
Array<string | Buffer | Blob | ArrayBuffer | Uint8Array> |
1–10 file paths or buffers. |
filenames |
string[]? |
Optional filenames aligned to each file. |
format |
"png" | "webp" |
Output format. Default "png". |
Returns BatchResponse:
{
count: number;
format: "png" | "webp";
results: Array<{
filename?: string;
url?: string;
success: boolean;
format?: "png" | "webp";
size_bytes?: number;
data_base64?: string; // present on success
error?: string; // present on failure
}>;
}Decode each result with Buffer.from(r.data_base64!, "base64").
Same as removeBatch but takes a JSON array of up to 10 remote URLs. Each result carries url instead of filename.
| Field | Type | Description |
|---|---|---|
urls |
string[] |
1–10 remote image URLs. |
format |
"png" | "webp" |
Output format. Default "png". |
Remove the background and composite the subject onto a new background — solid color or remote image.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Foreground image. |
filename |
string? |
Optional filename. |
bgColor |
string? |
Hex color. Default "#FFFFFF". Ignored if bgUrl is set. |
bgUrl |
string? |
Remote URL of a background image. Takes precedence over bgColor. |
format |
"png" | "webp" | "jpg" |
Default "png". "jpg" for smallest file. |
Composite the subject onto a new background with a configurable drop shadow.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image to process. |
bgColor |
string? |
Background hex. |
bgUrl |
string? |
Background image URL. Takes precedence over bgColor. |
shadowColor |
string? |
Shadow hex. |
shadowOffsetX |
number? |
Horizontal offset (px). |
shadowOffsetY |
number? |
Vertical offset (px). |
shadowBlur |
number? |
Blur radius (px). |
shadowOpacity |
number? |
0.0–1.0. |
format |
"png" | "webp" | "jpg" |
Default "png". |
Return just the alpha mask as a grayscale PNG/WebP (0 = bg, 255 = subject). Useful when chaining into your own compositing pipeline.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image to process. |
format |
"png" | "webp" |
Default "png". |
Auto-crop to the subject's tight bounding box + padding.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image to process. |
padding |
number? |
Padding in px. Default 24. |
transparent |
boolean? |
true → transparent cutout; false → cropped region from the original. Default true. |
format |
"png" | "webp" | "jpg" |
Default "png" when transparent, "jpg" otherwise. |
Subject with a thick outline on transparent background — iMessage / WhatsApp / Telegram sticker style.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image to process. |
strokeColor |
string? |
Outline hex. Default "#FFFFFF". |
strokeWidth |
number? |
Outline width (px). Default 20. |
format |
"png" | "webp" |
Default "png". |
Subject with a thin outline on transparent background.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image to process. |
outlineColor |
string? |
Outline hex. Default "#000000". |
outlineWidth |
number? |
Outline width (px). Default 4. |
format |
"png" | "webp" |
Default "png". |
E-commerce preset: cutout + tight crop + centered on a canvas + optional drop shadow on a standardized aspect ratio.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image to process. |
bgColor |
string? |
Canvas color. |
aspect |
string? |
"W:H", e.g. "1:1", "4:5", "16:9". Default "1:1". |
padding |
number? |
Padding (px). |
shadow |
boolean? |
Include a drop shadow. |
transparent |
boolean? |
Keep a transparent background. Ignores bgColor and shadow; output is PNG (jpg is coerced). Default false. |
enhance |
boolean? |
Subtle brightness + saturation lift for ecommerce-ready output. Default false. |
enhanceStrength |
number? |
Lift amount, 0.0–0.5. Default 0.15. Only applies when enhance is true. |
format |
"png" | "webp" | "jpg" |
Default "jpg". |
// Transparent product cutout, centered & squared
const cutout = await client.studioShot({ file: "./product.jpg", transparent: true });
// Ecommerce-ready shot with a brightness + saturation boost
const bright = await client.studioShot({ file: "./product.jpg", aspect: "1:1", enhance: true });Two-tone silhouette portrait — subject in one solid color, background in another (Apple Music / Spotify avatar aesthetic). Reuses the BiRefNet mask path.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image to process. |
subjectColor |
string? |
Subject hex. Default "#7C3AED". |
bgColor |
string? |
Background hex. Default "#FFFFFF". |
format |
"png" | "webp" | "jpg" |
Default "png". |
Before/after side-by-side preview — original on the left, transparent cutout (on a checkerboard) on the right.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image to process. |
format |
"png" | "webp" |
Default "png". |
LinkedIn-ready headshot — background removal + portrait crop + face centering + a solid background color or a blurred copy of the original.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Source portrait. |
bgColor |
string? |
Background hex. Default "#FFFFFF". Ignored if bgBlur is true. |
bgBlur |
boolean? |
Use a blurred copy of the original as the background. Default false. |
blurRadius |
number? |
Gaussian blur radius when bgBlur is true. Default 20. |
aspect |
string? |
Output aspect "W:H". Default "4:5" (portrait). |
padding |
number? |
Padding around the subject bbox (px). Default 64. |
headTopRatio |
number? |
Vertical headroom as a ratio of canvas height (0–0.5). Default 0.18. |
format |
"png" | "webp" | "jpg" |
Default "jpg". |
// Solid background
const jpg = await client.headshot({ file: "./photo.jpg", bgColor: "#0A0A0A" });
// Blurred original as the background
const blurred = await client.headshot({ file: "./photo.jpg", bgBlur: true, blurRadius: 24 });GFPGAN v1.4 portrait restoration — fix blurry / damaged / low-res faces. Background is upscaled 2× by Real-ESRGAN. Pairs with headshot.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Image with one or more faces. |
onlyCenterFace |
boolean? |
Restore only the most prominent face (faster). Default false. |
bgEnhance |
boolean? |
Also upscale the background 2× via Real-ESRGAN. Default false. |
format |
"png" | "webp" | "jpg" |
Default "png". |
2× / 4× super-resolution. Defaults to Swin2SR (SwinV2 transformer) — sharper on real photos. Pass model: "realesrgan" for the legacy backend (better on anime / illustrations); faceEnhance: true routes portraits through GFPGAN (implies realesrgan).
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Source image. |
scale |
2 | 4 |
Default 4. |
model |
"swin2sr" | "realesrgan" |
Default "swin2sr". |
faceEnhance |
boolean? |
Route through GFPGAN for facial detail. Slower. |
format |
"png" | "webp" | "jpg" |
Default "png". |
// Cutout → 4x upscale (print-ready)
const png = await client.remove({ file: "./photo.jpg" });
const big = await client.upscale({ file: png, scale: 4 });DDColor colorization — turn a black-and-white or grayscale photo into natural color. Single feed-forward pass (no diffusion). Color inputs are converted to grayscale internally before color is predicted.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Grayscale or faded source. |
format |
"png" | "webp" | "jpg" |
Default "png". |
LaMa large-mask inpainting (Apache-2.0). Three modes, auto-detected from what you pass:
- auto-subject — pass only
file. BiRefNet derives the subject mask, inverts it, and LaMa fills the subject region with plausible background (erases the subject). - mask — pass
file+mask(any image, white = inpaint). - bbox — pass
file+bbox: { x, y, w, h }. Mutually exclusive withmask.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Source image. |
mask |
FileInput? |
White = inpaint, black = keep. |
maskFilename |
string? |
Optional mask filename. Defaults to "mask.png". |
bbox |
{ x: number; y: number; w: number; h: number }? |
Rectangular region to inpaint. |
dilation |
number? |
Mask dilation (px). Default 8, range 0..32. |
format |
"png" | "webp" | "jpg" |
Default "png". |
const clean = await client.inpaint({ file: "./photo.jpg" }); // auto-subject
const masked = await client.inpaint({ file: "./photo.jpg", mask: "./mask.png" }); // mask
const region = await client.inpaint({ file: "./photo.jpg", bbox: { x: 100, y: 100, w: 300, h: 400 } }); // bboxFast low-res preview cutout (skips pymatting refinement, downscales to maxDim on the long edge). Use for UX progress indicators / thumbnails.
| Field | Type | Description |
|---|---|---|
file |
FileInput |
Source image. |
maxDim |
number? |
Long-edge cap in px (64–1024). Default 512. |
format |
"png" | "webp" |
Default "png". |
Predict latency + cost for an endpoint and image size without running the model.
| Field | Type | Description |
|---|---|---|
endpoint |
string |
Endpoint name without leading slash, e.g. "remove", "headshot". |
width |
number |
Image width in px. |
height |
number |
Image height in px. |
Returns EstimateResponse:
{
endpoint: string;
image_pixels: number;
est_latency_ms_warm: number;
est_latency_ms_cold: number;
est_cost_usd: number;
free_during_beta: boolean;
note: string;
}const est = await client.estimate({ endpoint: "remove", width: 1024, height: 1024 });Public usage counter — total images processed all-time, today, and a 7-day breakdown. No auth required. Eventually consistent across containers.
Returns StatsResponse:
{
total_processed: number;
today: number;
last_7_days: Array<{ date: string; count: number }>;
error?: string;
detail?: string;
}Hit GET /health. No auth required. Returns { status: string; model: string }.
Every non-2xx response throws a KnockoutError. Fields:
status— HTTP status code.code— one of"auth" | "rate_limit" | "bad_request" | "payload_too_large" | "server" | "unknown", classified from the status (401/403 →auth, 429 →rate_limit, 413 →payload_too_large, other 4xx →bad_request, 5xx →server).body— raw response body string.
import { KnockoutError } from "@useknockout/node";
try {
await client.remove({ file: "./huge.jpg" });
} catch (err) {
if (err instanceof KnockoutError && err.code === "payload_too_large") {
// retry with a resized image
}
throw err;
}Argument-validation problems (empty/oversized batch, unsupported file type) throw a plain Error / TypeError before any request is made.
// app/api/remove/route.ts
import { Knockout } from "@useknockout/node";
const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
export async function POST(req: Request) {
const form = await req.formData();
const file = form.get("file") as File;
const buf = Buffer.from(await file.arrayBuffer());
const png = await client.remove({ file: buf, filename: file.name });
return new Response(new Uint8Array(png), {
headers: { "Content-Type": "image/png" },
});
}import express from "express";
import multer from "multer";
import { Knockout } from "@useknockout/node";
const app = express();
const upload = multer();
const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
app.post("/remove", upload.single("file"), async (req, res) => {
const png = await client.remove({
file: req.file!.buffer,
filename: req.file!.originalname,
});
res.type("image/png").send(png);
});import { Knockout } from "@useknockout/node";
const client = new Knockout({ token: env.KNOCKOUT_TOKEN });
export default {
async fetch(req: Request) {
const { searchParams } = new URL(req.url);
const imageUrl = searchParams.get("url")!;
const png = await client.removeUrl({ url: imageUrl });
return new Response(new Uint8Array(png), {
headers: { "Content-Type": "image/png" },
});
},
};Point the SDK at your own Modal deployment:
const client = new Knockout({
token: "your-self-hosted-token",
baseUrl: "https://YOUR_WORKSPACE--api.modal.run",
});See useknockout/api for the Modal deployment.
useknockout-node/
├── src/
│ └── index.ts # Entire SDK: Knockout class, types, KnockoutError, toBlob helper
├── dist/ # Build output (ESM + CJS + .d.ts), generated by tsup
├── package.json # Package manifest — dual exports, zero runtime deps
├── tsconfig.json # TypeScript config (strict, ES2022, Bundler resolution)
├── tsup.config.ts # Build config — esm + cjs, dts, sourcemaps, target node18
├── LICENSE # MIT
└── README.md
The whole client lives in a single file, src/index.ts. All HTTP calls go through one private request() method that injects the Authorization and User-Agent headers and enforces the timeout via AbortController.
| Script | Command | Purpose |
|---|---|---|
build |
tsup |
Build dual ESM/CJS bundles + type declarations into dist/. |
typecheck |
tsc --noEmit |
Type-check without emitting. |
prepublishOnly |
npm run build |
Ensures a fresh build before npm publish. |
- Node 18+ is required for the global
fetch/FormDatathe SDK relies on. On other runtimes, passoptions.fetch. - The default endpoint is
https://useknockout--api.modal.run(a Modal deployment).healthandstatsare unauthenticated; everything else sends your bearer token. - Batch endpoints are capped at 10 images per call and validate this client-side.
- The package is published under the
@useknockoutnpm scope with public access.
MIT — see LICENSE.