@useknockout/node 0.0.8 โ†’ 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,388 +1,457 @@
1
- <div align="center">
2
-
3
- # ๐ŸฅŠ @useknockout/node
4
-
5
- **Official TypeScript / Node.js client for [useknockout](https://github.com/useknockout/api) โ€” state-of-the-art background removal API.**
6
-
7
- [![MIT License](https://img.shields.io/badge/license-MIT-3da639)](./LICENSE)
8
- [![npm version](https://img.shields.io/npm/v/@useknockout/node?color=cb3837)](https://www.npmjs.com/package/@useknockout/node)
9
- [![npm downloads](https://img.shields.io/npm/dm/@useknockout/node?color=cb3837)](https://www.npmjs.com/package/@useknockout/node)
10
- [![GitHub stars](https://img.shields.io/github/stars/useknockout/node?style=social)](https://github.com/useknockout/node)
11
- [![TypeScript](https://img.shields.io/badge/TypeScript-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
12
- [![Node](https://img.shields.io/badge/node-%E2%89%A518-339933?logo=node.js&logoColor=white)](https://nodejs.org)
13
- [![Zero deps](https://img.shields.io/badge/runtime%20deps-0-success)](./package.json)
14
-
15
- [**Install**](#install) ยท [**Quick Start**](#quick-start) ยท [**API**](#api) ยท [**Framework examples**](#framework-examples) ยท [**API repo**](https://github.com/useknockout/api)
16
-
17
- <br/>
18
-
19
- <img src="https://raw.githubusercontent.com/useknockout/api/main/docs/hero.png" alt="useknockout before/after โ€” background removal demo" width="800"/>
20
-
21
- <br/>
22
-
23
- *One method call. Transparent PNG out. ~200ms per image.*
24
-
25
- </div>
26
-
27
- ---
28
-
29
- - **Zero runtime dependencies** โ€” uses the native `fetch` built into Node 18+
30
- - **First-class TypeScript** โ€” full types, no `any`s in the public API
31
- - **Works anywhere `fetch` works** โ€” Node, Bun, Deno, Vercel/Cloudflare Workers, serverless
32
- - **MIT licensed**
33
-
34
- ---
35
-
36
- ## Install
37
-
38
- ```bash
39
- npm install @useknockout/node
40
- # or
41
- pnpm add @useknockout/node
42
- # or
43
- yarn add @useknockout/node
44
- ```
45
-
46
- Requires **Node 18+** (for global `fetch` and `FormData`).
47
-
48
- ## Quick start
49
-
50
- ### Public beta token
51
-
52
- During the public beta, anyone can use this shared bearer token:
53
-
54
- ```
55
- kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f
56
- ```
57
-
58
- No signup โ€” copy, paste, call the API. Need your own key / production limits? DM [@useknockout](https://x.com/useknockout) on X.
59
-
60
- ### One-minute example
61
-
62
- ```ts
63
- import { writeFile } from "node:fs/promises";
64
- import { Knockout } from "@useknockout/node";
65
-
66
- const client = new Knockout({ token: "kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f" });
67
-
68
- // Remove background โ€” returns transparent PNG buffer
69
- const png = await client.remove({ file: "./input.jpg" });
70
- await writeFile("out.png", png);
71
-
72
- // Replace background with a hex color
73
- const jpg = await client.replaceBackground({
74
- file: "./input.jpg",
75
- bgColor: "#FF5733",
76
- format: "jpg",
77
- });
78
- await writeFile("out.jpg", jpg);
79
-
80
- // Replace background with a remote image
81
- const composed = await client.replaceBackground({
82
- file: "./input.jpg",
83
- bgUrl: "https://example.com/beach.jpg",
84
- });
85
-
86
- // Batch โ€” remove bg from up to 10 URLs in one call
87
- const batch = await client.removeBatchUrl({
88
- urls: ["https://example.com/a.jpg", "https://example.com/b.jpg"],
89
- });
90
- for (const r of batch.results) {
91
- if (r.success) {
92
- await writeFile(`out.png`, Buffer.from(r.data_base64!, "base64"));
93
- }
94
- }
95
- ```
96
-
97
- ### From a Buffer or remote URL
98
-
99
- ```ts
100
- // From a Buffer (e.g. uploaded via multer)
101
- const buf = await fs.readFile("./input.jpg");
102
- const out = await client.remove({ file: buf, filename: "input.jpg" });
103
-
104
- // From a remote URL
105
- const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
106
- ```
107
-
108
- ### New in 0.0.5 โ€” presets
109
-
110
- ```ts
111
- // Sticker โ€” thick outline, transparent bg (iMessage / WhatsApp style)
112
- const sticker = await client.sticker({ file: "./photo.jpg", strokeWidth: 24 });
113
-
114
- // Outline โ€” thin stroke around subject
115
- const outlined = await client.outline({ file: "./photo.jpg", outlineColor: "#000000", outlineWidth: 4 });
116
-
117
- // Smart crop โ€” auto-crop to subject bbox + padding
118
- const cropped = await client.smartCrop({ file: "./photo.jpg", padding: 32 });
119
-
120
- // Shadow โ€” drop shadow on a new background
121
- const withShadow = await client.shadow({ file: "./photo.jpg", bgColor: "#F3F4F6" });
122
-
123
- // Studio shot โ€” e-commerce preset (centered subject, white bg, shadow, square)
124
- const studio = await client.studioShot({ file: "./photo.jpg", aspect: "1:1" });
125
-
126
- // Compare โ€” before/after side-by-side image
127
- const preview = await client.compare({ file: "./photo.jpg" });
128
-
129
- // Mask โ€” just the black/white mask for your own pipeline
130
- const mask = await client.mask({ file: "./photo.jpg" });
131
- ```
132
-
133
- ## API
134
-
135
- ### `new Knockout(options?)`
136
-
137
- | Option | Type | Default | Description |
138
- |---|---|---|---|
139
- | `token` | `string` | โ€” | Bearer token. Required unless self-hosting without auth. |
140
- | `baseUrl` | `string` | `https://useknockout--api.modal.run` | Override the API endpoint. |
141
- | `timeoutMs` | `number` | `60_000` | Per-request timeout. |
142
- | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch (for edge runtimes or polyfills). |
143
-
144
- ### `client.remove(input)`
145
-
146
- Remove the background from a local file or in-memory buffer.
147
-
148
- | Field | Type | Description |
149
- |---|---|---|
150
- | `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | File path or raw image bytes. |
151
- | `filename` | `string?` | Optional override โ€” auto-inferred from path if omitted. |
152
- | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
153
-
154
- Returns: `Buffer` of the processed image (PNG or WebP with transparent alpha).
155
-
156
- ### `client.removeUrl(input)`
157
-
158
- Remove the background from a remote image URL.
159
-
160
- | Field | Type | Description |
161
- |---|---|---|
162
- | `url` | `string` | Remote image URL. |
163
- | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
164
-
165
- Returns: `Buffer` of the processed image.
166
-
167
- ### `client.replaceBackground(input)`
168
-
169
- Remove the background and composite the subject onto a new background โ€” solid color or remote image.
170
-
171
- | Field | Type | Description |
172
- |---|---|---|
173
- | `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | Foreground image (path or bytes). |
174
- | `filename` | `string?` | Optional filename. |
175
- | `bgColor` | `string?` | Hex color for the new background. Default `"#FFFFFF"`. Ignored if `bgUrl` is set. |
176
- | `bgUrl` | `string?` | Remote URL of a background image. Takes precedence over `bgColor`. |
177
- | `format` | `"png" \| "webp" \| "jpg"` | Output format. Default `"png"`. `"jpg"` for smallest file. |
178
-
179
- Returns: `Buffer` of the composited image. Edges refined via closed-form foreground matting (no halos).
180
-
181
- ### `client.removeBatch(input)`
182
-
183
- Remove backgrounds from up to 10 images in a single call.
184
-
185
- | Field | Type | Description |
186
- |---|---|---|
187
- | `files` | `Array<string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array>` | 1โ€“10 file paths or buffers. |
188
- | `filenames` | `string[]?` | Optional filename aligned to each file. |
189
- | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
190
-
191
- Returns: `BatchResponse` โ€” `{ count, format, results: [{ filename, success, format, size_bytes, data_base64 | error }] }`. Decode `data_base64` with `Buffer.from(b64, "base64")`.
192
-
193
- ### `client.removeBatchUrl(input)`
194
-
195
- Same as `removeBatch` but takes a JSON array of remote URLs.
196
-
197
- | Field | Type | Description |
198
- |---|---|---|
199
- | `urls` | `string[]` | 1โ€“10 remote image URLs. |
200
- | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
201
-
202
- Returns: `BatchResponse` with `url` in place of `filename` in each result.
203
-
204
- ### `client.mask(input)`
205
-
206
- Return just the black/white alpha mask as a grayscale PNG/WebP.
207
-
208
- | Field | Type | Description |
209
- |---|---|---|
210
- | `file` | `FileInput` | Image to process. |
211
- | `format` | `"png" \| "webp"` | Default `"png"`. |
212
-
213
- Returns: `Buffer` of a grayscale image (0 = bg, 255 = subject).
214
-
215
- ### `client.smartCrop(input)`
216
-
217
- Auto-crop to the subject's tight bounding box + padding.
218
-
219
- | Field | Type | Description |
220
- |---|---|---|
221
- | `file` | `FileInput` | Image to process. |
222
- | `padding` | `number` | Padding in pixels. Default `24`. |
223
- | `transparent` | `boolean` | `true` โ†’ transparent cutout. `false` โ†’ cropped from original. Default `true`. |
224
- | `format` | `"png" \| "webp" \| "jpg"` | Default `"png"` (or `"jpg"` when `transparent=false`). |
225
-
226
- ### `client.shadow(input)`
227
-
228
- Composite subject onto a new background with a configurable drop shadow.
229
-
230
- | Field | Type | Description |
231
- |---|---|---|
232
- | `file` | `FileInput` | Image to process. |
233
- | `bgColor` | `string` | Hex color. Default `"#FFFFFF"`. |
234
- | `bgUrl` | `string` | Remote URL. Takes precedence over `bgColor`. |
235
- | `shadowColor` | `string` | Default `"#000000"`. |
236
- | `shadowOffsetX` | `number` | Default `8`. |
237
- | `shadowOffsetY` | `number` | Default `12`. |
238
- | `shadowBlur` | `number` | Default `14`. |
239
- | `shadowOpacity` | `number` | 0.0โ€“1.0. Default `0.45`. |
240
- | `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
241
-
242
- ### `client.sticker(input)`
243
-
244
- Subject with a thick outline on transparent background โ€” sticker style.
245
-
246
- | Field | Type | Description |
247
- |---|---|---|
248
- | `file` | `FileInput` | Image to process. |
249
- | `strokeColor` | `string` | Default `"#FFFFFF"`. |
250
- | `strokeWidth` | `number` | Pixels. Default `20`, capped at `80`. |
251
- | `format` | `"png" \| "webp"` | Default `"png"`. |
252
-
253
- ### `client.outline(input)`
254
-
255
- Subject with a thin outline on transparent background.
256
-
257
- | Field | Type | Description |
258
- |---|---|---|
259
- | `file` | `FileInput` | Image to process. |
260
- | `outlineColor` | `string` | Default `"#000000"`. |
261
- | `outlineWidth` | `number` | Pixels. Default `4`, capped at `60`. |
262
- | `format` | `"png" \| "webp"` | Default `"png"`. |
263
-
264
- ### `client.studioShot(input)`
265
-
266
- E-commerce preset: cutout + centered on canvas + optional drop shadow + standardized aspect ratio.
267
-
268
- | Field | Type | Description |
269
- |---|---|---|
270
- | `file` | `FileInput` | Image to process. |
271
- | `bgColor` | `string` | Canvas color. Default `"#FFFFFF"`. |
272
- | `aspect` | `string` | `"W:H"` format, e.g. `"1:1"`, `"4:5"`, `"16:9"`. Default `"1:1"`. |
273
- | `padding` | `number` | Padding in pixels. Default `48`. |
274
- | `shadow` | `boolean` | Include drop shadow. Default `true`. |
275
- | `format` | `"png" \| "webp" \| "jpg"` | Default `"jpg"`. |
276
-
277
- ### `client.compare(input)`
278
-
279
- Before/after side-by-side preview โ€” original on the left, transparent cutout (on a checkerboard) on the right.
280
-
281
- | Field | Type | Description |
282
- |---|---|---|
283
- | `file` | `FileInput` | Image to process. |
284
- | `format` | `"png" \| "webp"` | Default `"png"`. |
285
-
286
- ### `client.health()`
287
-
288
- Returns: `Promise<{ status: string; model: string }>`. No auth required.
289
-
290
- ### `KnockoutError`
291
-
292
- Thrown on any non-2xx response. Fields:
293
-
294
- - `status` โ€” HTTP status code
295
- - `code` โ€” `"auth" | "rate_limit" | "bad_request" | "payload_too_large" | "server" | "unknown"`
296
- - `body` โ€” raw response body string
297
-
298
- ```ts
299
- import { KnockoutError } from "@useknockout/node";
300
-
301
- try {
302
- await client.remove({ file: "./huge.jpg" });
303
- } catch (err) {
304
- if (err instanceof KnockoutError && err.code === "payload_too_large") {
305
- // retry with a resized image
306
- }
307
- throw err;
308
- }
309
- ```
310
-
311
- ## Framework examples
312
-
313
- ### Next.js App Router
314
-
315
- ```ts
316
- // app/api/remove/route.ts
317
- import { Knockout } from "@useknockout/node";
318
-
319
- const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
320
-
321
- export async function POST(req: Request) {
322
- const form = await req.formData();
323
- const file = form.get("file") as File;
324
- const buf = Buffer.from(await file.arrayBuffer());
325
-
326
- const png = await client.remove({ file: buf, filename: file.name });
327
-
328
- return new Response(new Uint8Array(png), {
329
- headers: { "Content-Type": "image/png" },
330
- });
331
- }
332
- ```
333
-
334
- ### Express
335
-
336
- ```ts
337
- import express from "express";
338
- import multer from "multer";
339
- import { Knockout } from "@useknockout/node";
340
-
341
- const app = express();
342
- const upload = multer();
343
- const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
344
-
345
- app.post("/remove", upload.single("file"), async (req, res) => {
346
- const png = await client.remove({
347
- file: req.file!.buffer,
348
- filename: req.file!.originalname,
349
- });
350
- res.type("image/png").send(png);
351
- });
352
- ```
353
-
354
- ### Cloudflare Workers / Vercel Edge
355
-
356
- ```ts
357
- import { Knockout } from "@useknockout/node";
358
-
359
- const client = new Knockout({ token: env.KNOCKOUT_TOKEN });
360
-
361
- export default {
362
- async fetch(req: Request) {
363
- const { searchParams } = new URL(req.url);
364
- const imageUrl = searchParams.get("url")!;
365
- const png = await client.removeUrl({ url: imageUrl });
366
- return new Response(new Uint8Array(png), {
367
- headers: { "Content-Type": "image/png" },
368
- });
369
- },
370
- };
371
- ```
372
-
373
- ## Self-hosting
374
-
375
- Point the SDK at your own Modal deployment:
376
-
377
- ```ts
378
- const client = new Knockout({
379
- token: "your-self-hosted-token",
380
- baseUrl: "https://YOUR_WORKSPACE--api.modal.run",
381
- });
382
- ```
383
-
384
- See [useknockout/api](https://github.com/useknockout/api) for the Modal deployment.
385
-
386
- ## License
387
-
388
- MIT โ€” see [LICENSE](./LICENSE).
1
+ <div align="center">
2
+
3
+ # ๐ŸฅŠ @useknockout/node
4
+
5
+ **Official TypeScript / Node.js client for [useknockout](https://github.com/useknockout/api) โ€” state-of-the-art background removal API.**
6
+
7
+ [![MIT License](https://img.shields.io/badge/license-MIT-3da639)](./LICENSE)
8
+ [![npm version](https://img.shields.io/npm/v/@useknockout/node?color=cb3837)](https://www.npmjs.com/package/@useknockout/node)
9
+ [![npm downloads](https://img.shields.io/npm/dm/@useknockout/node?color=cb3837)](https://www.npmjs.com/package/@useknockout/node)
10
+ [![GitHub stars](https://img.shields.io/github/stars/useknockout/node?style=social)](https://github.com/useknockout/node)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
12
+ [![Node](https://img.shields.io/badge/node-%E2%89%A518-339933?logo=node.js&logoColor=white)](https://nodejs.org)
13
+ [![Zero deps](https://img.shields.io/badge/runtime%20deps-0-success)](./package.json)
14
+
15
+ [**Install**](#install) ยท [**Quick Start**](#quick-start) ยท [**API**](#api) ยท [**Framework examples**](#framework-examples) ยท [**API repo**](https://github.com/useknockout/api)
16
+
17
+ <br/>
18
+
19
+ <img src="https://raw.githubusercontent.com/useknockout/api/main/docs/hero.png" alt="useknockout before/after โ€” background removal demo" width="800"/>
20
+
21
+ <br/>
22
+
23
+ *One method call. Transparent PNG out. ~200ms per image.*
24
+
25
+ </div>
26
+
27
+ ---
28
+
29
+ - **Zero runtime dependencies** โ€” uses the native `fetch` built into Node 18+
30
+ - **First-class TypeScript** โ€” full types, no `any`s in the public API
31
+ - **Works anywhere `fetch` works** โ€” Node, Bun, Deno, Vercel/Cloudflare Workers, serverless
32
+ - **MIT licensed**
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ npm install @useknockout/node
40
+ # or
41
+ pnpm add @useknockout/node
42
+ # or
43
+ yarn add @useknockout/node
44
+ ```
45
+
46
+ Requires **Node 18+** (for global `fetch` and `FormData`).
47
+
48
+ ## Quick start
49
+
50
+ ### Public beta token
51
+
52
+ During the public beta, anyone can use this shared bearer token:
53
+
54
+ ```
55
+ kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f
56
+ ```
57
+
58
+ No signup โ€” copy, paste, call the API. Need your own key / production limits? DM [@useknockout](https://x.com/useknockout) on X.
59
+
60
+ ### One-minute example
61
+
62
+ ```ts
63
+ import { writeFile } from "node:fs/promises";
64
+ import { Knockout } from "@useknockout/node";
65
+
66
+ const client = new Knockout({ token: "kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f" });
67
+
68
+ // Remove background โ€” returns transparent PNG buffer
69
+ const png = await client.remove({ file: "./input.jpg" });
70
+ await writeFile("out.png", png);
71
+
72
+ // Replace background with a hex color
73
+ const jpg = await client.replaceBackground({
74
+ file: "./input.jpg",
75
+ bgColor: "#FF5733",
76
+ format: "jpg",
77
+ });
78
+ await writeFile("out.jpg", jpg);
79
+
80
+ // Replace background with a remote image
81
+ const composed = await client.replaceBackground({
82
+ file: "./input.jpg",
83
+ bgUrl: "https://example.com/beach.jpg",
84
+ });
85
+
86
+ // Batch โ€” remove bg from up to 10 URLs in one call
87
+ const batch = await client.removeBatchUrl({
88
+ urls: ["https://example.com/a.jpg", "https://example.com/b.jpg"],
89
+ });
90
+ for (const r of batch.results) {
91
+ if (r.success) {
92
+ await writeFile(`out.png`, Buffer.from(r.data_base64!, "base64"));
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### From a Buffer or remote URL
98
+
99
+ ```ts
100
+ // From a Buffer (e.g. uploaded via multer)
101
+ const buf = await fs.readFile("./input.jpg");
102
+ const out = await client.remove({ file: buf, filename: "input.jpg" });
103
+
104
+ // From a remote URL
105
+ const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
106
+ ```
107
+
108
+ ### New in 0.0.5 โ€” presets
109
+
110
+ ```ts
111
+ // Sticker โ€” thick outline, transparent bg (iMessage / WhatsApp style)
112
+ const sticker = await client.sticker({ file: "./photo.jpg", strokeWidth: 24 });
113
+
114
+ // Outline โ€” thin stroke around subject
115
+ const outlined = await client.outline({ file: "./photo.jpg", outlineColor: "#000000", outlineWidth: 4 });
116
+
117
+ // Smart crop โ€” auto-crop to subject bbox + padding
118
+ const cropped = await client.smartCrop({ file: "./photo.jpg", padding: 32 });
119
+
120
+ // Shadow โ€” drop shadow on a new background
121
+ const withShadow = await client.shadow({ file: "./photo.jpg", bgColor: "#F3F4F6" });
122
+
123
+ // Studio shot โ€” e-commerce preset (centered subject, white bg, shadow, square)
124
+ const studio = await client.studioShot({ file: "./photo.jpg", aspect: "1:1" });
125
+
126
+ // Compare โ€” before/after side-by-side image
127
+ const preview = await client.compare({ file: "./photo.jpg" });
128
+
129
+ // Mask โ€” just the black/white mask for your own pipeline
130
+ const mask = await client.mask({ file: "./photo.jpg" });
131
+ ```
132
+
133
+ ## API
134
+
135
+ ### `new Knockout(options?)`
136
+
137
+ | Option | Type | Default | Description |
138
+ |---|---|---|---|
139
+ | `token` | `string` | โ€” | Bearer token. Required unless self-hosting without auth. |
140
+ | `baseUrl` | `string` | `https://useknockout--api.modal.run` | Override the API endpoint. |
141
+ | `timeoutMs` | `number` | `60_000` | Per-request timeout. |
142
+ | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch (for edge runtimes or polyfills). |
143
+
144
+ ### `client.remove(input)`
145
+
146
+ Remove the background from a local file or in-memory buffer.
147
+
148
+ | Field | Type | Description |
149
+ |---|---|---|
150
+ | `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | File path or raw image bytes. |
151
+ | `filename` | `string?` | Optional override โ€” auto-inferred from path if omitted. |
152
+ | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
153
+
154
+ Returns: `Buffer` of the processed image (PNG or WebP with transparent alpha).
155
+
156
+ ### `client.removeUrl(input)`
157
+
158
+ Remove the background from a remote image URL.
159
+
160
+ | Field | Type | Description |
161
+ |---|---|---|
162
+ | `url` | `string` | Remote image URL. |
163
+ | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
164
+
165
+ Returns: `Buffer` of the processed image.
166
+
167
+ ### `client.replaceBackground(input)`
168
+
169
+ Remove the background and composite the subject onto a new background โ€” solid color or remote image.
170
+
171
+ | Field | Type | Description |
172
+ |---|---|---|
173
+ | `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | Foreground image (path or bytes). |
174
+ | `filename` | `string?` | Optional filename. |
175
+ | `bgColor` | `string?` | Hex color for the new background. Default `"#FFFFFF"`. Ignored if `bgUrl` is set. |
176
+ | `bgUrl` | `string?` | Remote URL of a background image. Takes precedence over `bgColor`. |
177
+ | `format` | `"png" \| "webp" \| "jpg"` | Output format. Default `"png"`. `"jpg"` for smallest file. |
178
+
179
+ Returns: `Buffer` of the composited image. Edges refined via closed-form foreground matting (no halos).
180
+
181
+ ### `client.removeBatch(input)`
182
+
183
+ Remove backgrounds from up to 10 images in a single call.
184
+
185
+ | Field | Type | Description |
186
+ |---|---|---|
187
+ | `files` | `Array<string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array>` | 1โ€“10 file paths or buffers. |
188
+ | `filenames` | `string[]?` | Optional filename aligned to each file. |
189
+ | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
190
+
191
+ Returns: `BatchResponse` โ€” `{ count, format, results: [{ filename, success, format, size_bytes, data_base64 | error }] }`. Decode `data_base64` with `Buffer.from(b64, "base64")`.
192
+
193
+ ### `client.removeBatchUrl(input)`
194
+
195
+ Same as `removeBatch` but takes a JSON array of remote URLs.
196
+
197
+ | Field | Type | Description |
198
+ |---|---|---|
199
+ | `urls` | `string[]` | 1โ€“10 remote image URLs. |
200
+ | `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
201
+
202
+ Returns: `BatchResponse` with `url` in place of `filename` in each result.
203
+
204
+ ### `client.mask(input)`
205
+
206
+ Return just the black/white alpha mask as a grayscale PNG/WebP.
207
+
208
+ | Field | Type | Description |
209
+ |---|---|---|
210
+ | `file` | `FileInput` | Image to process. |
211
+ | `format` | `"png" \| "webp"` | Default `"png"`. |
212
+
213
+ Returns: `Buffer` of a grayscale image (0 = bg, 255 = subject).
214
+
215
+ ### `client.smartCrop(input)`
216
+
217
+ Auto-crop to the subject's tight bounding box + padding.
218
+
219
+ | Field | Type | Description |
220
+ |---|---|---|
221
+ | `file` | `FileInput` | Image to process. |
222
+ | `padding` | `number` | Padding in pixels. Default `24`. |
223
+ | `transparent` | `boolean` | `true` โ†’ transparent cutout. `false` โ†’ cropped from original. Default `true`. |
224
+ | `format` | `"png" \| "webp" \| "jpg"` | Default `"png"` (or `"jpg"` when `transparent=false`). |
225
+
226
+ ### `client.shadow(input)`
227
+
228
+ Composite subject onto a new background with a configurable drop shadow.
229
+
230
+ | Field | Type | Description |
231
+ |---|---|---|
232
+ | `file` | `FileInput` | Image to process. |
233
+ | `bgColor` | `string` | Hex color. Default `"#FFFFFF"`. |
234
+ | `bgUrl` | `string` | Remote URL. Takes precedence over `bgColor`. |
235
+ | `shadowColor` | `string` | Default `"#000000"`. |
236
+ | `shadowOffsetX` | `number` | Default `8`. |
237
+ | `shadowOffsetY` | `number` | Default `12`. |
238
+ | `shadowBlur` | `number` | Default `14`. |
239
+ | `shadowOpacity` | `number` | 0.0โ€“1.0. Default `0.45`. |
240
+ | `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
241
+
242
+ ### `client.sticker(input)`
243
+
244
+ Subject with a thick outline on transparent background โ€” sticker style.
245
+
246
+ | Field | Type | Description |
247
+ |---|---|---|
248
+ | `file` | `FileInput` | Image to process. |
249
+ | `strokeColor` | `string` | Default `"#FFFFFF"`. |
250
+ | `strokeWidth` | `number` | Pixels. Default `20`, capped at `80`. |
251
+ | `format` | `"png" \| "webp"` | Default `"png"`. |
252
+
253
+ ### `client.outline(input)`
254
+
255
+ Subject with a thin outline on transparent background.
256
+
257
+ | Field | Type | Description |
258
+ |---|---|---|
259
+ | `file` | `FileInput` | Image to process. |
260
+ | `outlineColor` | `string` | Default `"#000000"`. |
261
+ | `outlineWidth` | `number` | Pixels. Default `4`, capped at `60`. |
262
+ | `format` | `"png" \| "webp"` | Default `"png"`. |
263
+
264
+ ### `client.studioShot(input)`
265
+
266
+ E-commerce preset: cutout + centered on canvas + optional drop shadow + standardized aspect ratio.
267
+
268
+ | Field | Type | Description |
269
+ |---|---|---|
270
+ | `file` | `FileInput` | Image to process. |
271
+ | `bgColor` | `string` | Canvas color. Default `"#FFFFFF"`. |
272
+ | `aspect` | `string` | `"W:H"` format, e.g. `"1:1"`, `"4:5"`, `"16:9"`. Default `"1:1"`. |
273
+ | `padding` | `number` | Padding in pixels. Default `48`. |
274
+ | `shadow` | `boolean` | Include drop shadow. Default `true`. |
275
+ | `format` | `"png" \| "webp" \| "jpg"` | Default `"jpg"`. |
276
+
277
+ ### `client.compare(input)`
278
+
279
+ Before/after side-by-side preview โ€” original on the left, transparent cutout (on a checkerboard) on the right.
280
+
281
+ | Field | Type | Description |
282
+ |---|---|---|
283
+ | `file` | `FileInput` | Image to process. |
284
+ | `format` | `"png" \| "webp"` | Default `"png"`. |
285
+
286
+ ### `client.headshot(input)` โ€” v0.4.0
287
+
288
+ Studio-quality professional headshot โ€” background removed, neutral studio backdrop, optional soft drop shadow, smart crop to bust framing.
289
+
290
+ | Field | Type | Description |
291
+ |---|---|---|
292
+ | `file` | `FileInput` | Source portrait. |
293
+ | `bgColor` | `string` | Studio backdrop hex. Default `"#f5f5f5"`. |
294
+ | `addShadow` | `boolean` | Soft drop shadow. Default `true`. |
295
+ | `crop` | `"bust" \| "head" \| "full"` | Default `"bust"`. |
296
+ | `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
297
+
298
+ ### `client.preview(input)` โ€” v0.4.0
299
+
300
+ Cheap, fast low-res preview โ€” 512px max, ~1.5s. Use for thumbnail UI before user pays for full-res.
301
+
302
+ | Field | Type | Description |
303
+ |---|---|---|
304
+ | `file` | `FileInput` | Source image. |
305
+ | `maxSize` | `number` | Max edge length. Default `512`. |
306
+ | `watermark` | `boolean` | Add `useknockout` watermark. Default `false`. |
307
+
308
+ ### `client.estimate(input)` โ€” v0.4.0
309
+
310
+ Returns expected processing time + output size **without running the model**. Show users "this'll take ~3s, ~1.2 MB" before they hit submit.
311
+
312
+ ```ts
313
+ const est = await client.estimate({ width: 2048, height: 1536, endpoint: "remove" });
314
+ // { estimated_seconds: 2.4, estimated_output_kb: 1180, warm: true }
315
+ ```
316
+
317
+ ### `client.stats()` โ€” v0.4.0
318
+
319
+ Public stats โ€” total images processed, last-24h count, last-7d trend. Powered by Modal Dict cross-container counter. No auth required.
320
+
321
+ ```ts
322
+ const stats = await client.stats();
323
+ // { total: 12340, last_24h: 312, last_7d: [...] }
324
+ ```
325
+
326
+ ### `client.upscale(input)` โ€” v0.6.0
327
+
328
+ **Swin2SR / Real-ESRGAN x2/x4 super-resolution.** Defaults to **Swin2SR** (SwinV2 Transformer) โ€” sharper detail and natural texture on real photos. Pass `model: "realesrgan"` for the legacy backend (better on anime / illustrations).
329
+
330
+ ```ts
331
+ const big = await client.upscale({ file: "./small.jpg", scale: 4 });
332
+ const anime = await client.upscale({ file: "./art.png", scale: 4, model: "realesrgan" });
333
+ ```
334
+
335
+ | Field | Type | Description |
336
+ |---|---|---|
337
+ | `file` | `FileInput` | Source image. |
338
+ | `scale` | `2 \| 4` | Default `4`. |
339
+ | `model` | `"swin2sr" \| "realesrgan"` | Default `"swin2sr"`. |
340
+ | `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
341
+
342
+ ### `client.faceRestore(input)` โ€” v0.5.0
343
+
344
+ **GFPGAN v1.4 face restoration.** Detects faces, restores blurred/compressed/damaged ones while preserving identity. Background also upscaled. Multi-face safe.
345
+
346
+ ```ts
347
+ const restored = await client.faceRestore({ file: "./blurry-portrait.jpg" });
348
+ ```
349
+
350
+ | Field | Type | Description |
351
+ |---|---|---|
352
+ | `file` | `FileInput` | Image with one or more faces. |
353
+ | `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
354
+
355
+ ### `client.health()`
356
+
357
+ Returns: `Promise<{ status: string; model: string }>`. No auth required.
358
+
359
+ ### `KnockoutError`
360
+
361
+ Thrown on any non-2xx response. Fields:
362
+
363
+ - `status` โ€” HTTP status code
364
+ - `code` โ€” `"auth" | "rate_limit" | "bad_request" | "payload_too_large" | "server" | "unknown"`
365
+ - `body` โ€” raw response body string
366
+
367
+ ```ts
368
+ import { KnockoutError } from "@useknockout/node";
369
+
370
+ try {
371
+ await client.remove({ file: "./huge.jpg" });
372
+ } catch (err) {
373
+ if (err instanceof KnockoutError && err.code === "payload_too_large") {
374
+ // retry with a resized image
375
+ }
376
+ throw err;
377
+ }
378
+ ```
379
+
380
+ ## Framework examples
381
+
382
+ ### Next.js App Router
383
+
384
+ ```ts
385
+ // app/api/remove/route.ts
386
+ import { Knockout } from "@useknockout/node";
387
+
388
+ const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
389
+
390
+ export async function POST(req: Request) {
391
+ const form = await req.formData();
392
+ const file = form.get("file") as File;
393
+ const buf = Buffer.from(await file.arrayBuffer());
394
+
395
+ const png = await client.remove({ file: buf, filename: file.name });
396
+
397
+ return new Response(new Uint8Array(png), {
398
+ headers: { "Content-Type": "image/png" },
399
+ });
400
+ }
401
+ ```
402
+
403
+ ### Express
404
+
405
+ ```ts
406
+ import express from "express";
407
+ import multer from "multer";
408
+ import { Knockout } from "@useknockout/node";
409
+
410
+ const app = express();
411
+ const upload = multer();
412
+ const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
413
+
414
+ app.post("/remove", upload.single("file"), async (req, res) => {
415
+ const png = await client.remove({
416
+ file: req.file!.buffer,
417
+ filename: req.file!.originalname,
418
+ });
419
+ res.type("image/png").send(png);
420
+ });
421
+ ```
422
+
423
+ ### Cloudflare Workers / Vercel Edge
424
+
425
+ ```ts
426
+ import { Knockout } from "@useknockout/node";
427
+
428
+ const client = new Knockout({ token: env.KNOCKOUT_TOKEN });
429
+
430
+ export default {
431
+ async fetch(req: Request) {
432
+ const { searchParams } = new URL(req.url);
433
+ const imageUrl = searchParams.get("url")!;
434
+ const png = await client.removeUrl({ url: imageUrl });
435
+ return new Response(new Uint8Array(png), {
436
+ headers: { "Content-Type": "image/png" },
437
+ });
438
+ },
439
+ };
440
+ ```
441
+
442
+ ## Self-hosting
443
+
444
+ Point the SDK at your own Modal deployment:
445
+
446
+ ```ts
447
+ const client = new Knockout({
448
+ token: "your-self-hosted-token",
449
+ baseUrl: "https://YOUR_WORKSPACE--api.modal.run",
450
+ });
451
+ ```
452
+
453
+ See [useknockout/api](https://github.com/useknockout/api) for the Modal deployment.
454
+
455
+ ## License
456
+
457
+ MIT โ€” see [LICENSE](./LICENSE).