Skip to content

useknockout/node

Repository files navigation

project projects/useknockout-node
type readme

🥊 @useknockout/node

Official TypeScript / Node.js client for useknockout — state-of-the-art background removal & image-processing API.

MIT License npm version npm downloads TypeScript Node Zero deps

Install · Quick start · Configuration · API · Framework examples · Self-hosting


One method call. Transparent PNG out.


What it is

@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 fetch and FormData built into Node 18+.
  • First-class TypeScript — fully typed public API, no any.
  • Runs anywhere fetch works — Node, Bun, Deno, Vercel / Cloudflare Workers, serverless.
  • MIT licensed.

The package ships dual ESM + CommonJS builds with .d.ts types (built with tsup).


Install

npm install @useknockout/node
# or
pnpm add @useknockout/node
# or
yarn add @useknockout/node

Requires Node 18+ (for global fetch and FormData). To supply your own fetch on older/edge runtimes, see Configuration.

Quick start

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"));
  }
}

From a Buffer or remote URL

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" });

Accepted file inputs

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").

Configuration

Authentication

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_here

token may be omitted when pointing at a self-hosted instance that has no auth.

Constructor options

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.

API

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.

Core background removal

client.remove(input)

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).

client.removeUrl(input)

Remove the background from a remote image URL.

Field Type Description
url string Remote image URL.
format "png" | "webp" Output format. Default "png".

client.removeBatch(input)

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").

client.removeBatchUrl(input)

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".

Background replacement & compositing

client.replaceBackground(input)

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.

client.shadow(input)

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".

Masking & cropping

client.mask(input)

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".

client.smartCrop(input)

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.

Stylized presets

client.sticker(input)

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".

client.outline(input)

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".

client.studioShot(input)

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.00.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 });

client.silhouette(input)

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".

client.compare(input)

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".

Portraits

client.headshot(input)

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 });

client.faceRestore(input)

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".

Enhancement & restoration

client.upscale(input)

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 });

client.colorize(input)

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".

client.inpaint(input)

LaMa large-mask inpainting (Apache-2.0). Three modes, auto-detected from what you pass:

  1. auto-subject — pass only file. BiRefNet derives the subject mask, inverts it, and LaMa fills the subject region with plausible background (erases the subject).
  2. mask — pass file + mask (any image, white = inpaint).
  3. bbox — pass file + bbox: { x, y, w, h }. Mutually exclusive with mask.
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 } }); // bbox

Utility & introspection

client.preview(input)

Fast 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".

client.estimate(input)

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 });

client.stats()

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;
}

client.health()

Hit GET /health. No auth required. Returns { status: string; model: string }.

Error handling — KnockoutError

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.

Framework examples

Next.js App Router

// 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" },
  });
}

Express

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);
});

Cloudflare Workers / Vercel Edge

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" },
    });
  },
};

Self-hosting

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.

Project structure

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.

Scripts

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.

Notes

  • Node 18+ is required for the global fetch / FormData the SDK relies on. On other runtimes, pass options.fetch.
  • The default endpoint is https://useknockout--api.modal.run (a Modal deployment). health and stats are 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 @useknockout npm scope with public access.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors