Skip to main content
@genlook/api is the official TypeScript SDK for the Virtual Try-On API. Every endpoint in this reference has a typed method; on top of that you get automatic retries on 429/5xx, typed error classes, and waitFor — a generation poller that replaces the hand-rolled polling loop. Works on Node 20+, Deno ≥ 1.40, Bun ≥ 1.0, and edge runtimes (Cloudflare Workers, Vercel Edge). Browser usage is intentionally unsupported — API keys must never reach client-side bundles, so proxy through your backend.

Example app — Next.js + @genlook/api

A runnable e-commerce demo: product grid, try-on widget, server-side API routes that keep the key off the browser. Clone it, add your API key, pnpm dev.

Install

pnpm add @genlook/api    # or npm i / yarn add / bun add

Quickstart

The full recommended flow — upload the customer photo once, run the try-on, wait for the result — is three calls:
import { Genlook } from "@genlook/api";
import { readFile } from "node:fs/promises";

const client = new Genlook({ apiKey: process.env.GENLOOK_API_KEY! });

// 1. Upload the person photo (reuse the imageId across many try-ons)
const { imageId } = await client.images.upload(await readFile("./customer.jpg"), {
  mimeType: "image/jpeg",
});

// 2. Run the try-on — reference an existing product, or upsert one inline
const { generationId } = await client.tryOn.create({
  products: [{
    externalId: "shirt-42",
    title: "Red tee",
    description: "Soft cotton regular fit",
    images: [{ source: { url: "https://cdn.example.com/red-tee.jpg" } }],
  }],
  person: { image: { source: { id: imageId } } },
});

// 3. Wait for the result — no polling loop to write
const result = await client.generations.waitFor(generationId);
console.log(result.resultImageUrl);

Configuration

const client = new Genlook({
  apiKey: process.env.GENLOOK_API_KEY!, // required, "gk_..."
  baseUrl: "https://api.genlook.app/tryon/v1", // default — override for testing
  timeoutMs: 60_000, // per-request budget (default 60s)
  maxRetries: 2, // automatic retries on 429 / 5xx / network failures
  fetch: customFetch, // inject your own fetch (tracing, proxies, sandboxes)
});
OptionDefaultNotes
apiKeyRequired. Get one on app.genlook.app.
baseUrlhttps://api.genlook.app/tryon/v1Override for sandbox/staging.
timeoutMs60_000Per-request. Large customer-photo uploads on slow links may need more.
maxRetries2Retries 429 / 5xx / transport failures; respects Retry-After.
fetchglobal fetchPass your own if the runtime hides the global.

Resources

ResourceMethodsPurpose
client.imagesuploadPre-upload customer photos, reuse the imageId across generations.
client.tryOncreateQueue a try-on (inline-upsert the product or reference it by externalId).
client.generationsretrieve, waitForPoll status; waitFor replaces hand-rolled polling loops.
client.productsupsert, list, iterate, get, delete, statsExplicit catalog management — optional, most integrations use inline tryOn.
client.accountcreditsRemaining credit balance.
client.customersdeleteGDPR right-to-erasure (wipe per-customer images + anonymize generations).
Each endpoint page in the left nav shows the SDK call alongside the raw HTTP example.

Waiting for a generation

client.generations.waitFor(id) polls until the generation terminates. It resolves with the completed generation, throws GenerationFailedError on FAILED, and GenerationTimeoutError if it doesn’t finish in time.
import { GenerationFailedError, GenerationTimeoutError } from "@genlook/api";

const result = await client.generations.waitFor(generationId, {
  timeoutMs: 180_000, // total budget (default 3 min)
  pollIntervalMs: 2_000, // spacing between polls (default 2s)
  signal: abortController.signal, // optional cancellation
  onStatus: (s) => console.log(s.status), // progress callback on every poll
});
Need a single read instead? client.generations.retrieve(generationId) maps to GET /generations/:id.

Error handling

Every failed request throws a typed GenlookError subclass. Use instanceof or branch on the stable error.code:
import {
  GenlookError,
  InsufficientCreditsError,
  ProductNotFoundError,
  RateLimitError,
} from "@genlook/api";

try {
  await client.tryOn.create({
    products: [{ externalId: "shirt-42" }],
    person: { image: { source: { id: imageId } } },
  });
} catch (err) {
  if (err instanceof ProductNotFoundError) {
    // Product expired or never existed — retry with the full inline payload.
  } else if (err instanceof InsufficientCreditsError) {
    // Surface a "top up" CTA.
  } else if (err instanceof RateLimitError) {
    // err.retryAfterSeconds tells you how long to back off.
  } else if (err instanceof GenlookError) {
    console.error(err.code, err.message, err.requestId);
  } else {
    throw err;
  }
}
ClassThrown for
AuthError401 — missing or invalid API key
ValidationError400 — invalid body, unsupported image, file too large, …
InsufficientCreditsError402 INSUFFICIENT_CREDITS
RateLimitError429 — carries retryAfterSeconds
ProductNotFoundError404 PRODUCT_NOT_FOUND — upsert inline and retry
GenerationNotFoundError404 GENERATION_NOT_FOUND
GenerationFailedErrorwaitFor reached a FAILED generation
GenerationTimeoutErrorwaitFor exceeded its timeoutMs
GenlookConnectionError / GenlookTimeoutErrorTransport failure / request exceeded the client’s timeoutMs
Every GenlookError carries code, status, details, requestId, and an err.is("PRODUCT_NOT_FOUND") helper. The full code catalog lives at Errors.

Uploads

client.images.upload accepts any standard byte source — Blob, File, Buffer, Uint8Array, ArrayBuffer, a web ReadableStream, or a Node Readable (e.g. fs.createReadStream). Pass filename / mimeType when the source doesn’t carry them:
import { createReadStream } from "node:fs";

const { imageId } = await client.images.upload(createReadStream("./customer.heic"), {
  filename: "customer.heic",
  mimeType: "image/heic",
  crop: true, // 4:5 person-aware crop (default true)
  externalUserId: "user-123", // link the upload for GDPR erasure later (no PII)
  keepForDays: 7, // 1 | 3 | 7 — defaults to your account window
});
To ship product image bytes (instead of URLs) on tryOn.create or products.upsert, reference them with fileKey and provide the bytes in the files map — the SDK marshals the multipart request for you:
await client.tryOn.create({
  products: [{
    externalId: "shirt-42",
    title: "Red tee",
    images: [{ source: { fileKey: "front" } }],
  }],
  person: { image: { source: { id: imageId } } },
  files: {
    front: { data: await readFile("./front.jpg"), mimeType: "image/jpeg" },
  },
});

Pagination

client.products.list is cursor-based. For a full catalog walk, iterate handles the cursor for you:
for await (const product of client.products.iterate()) {
  console.log(product.externalId, product.title);
}

GDPR / right-to-erasure

Pass an externalUserId when uploading images or creating try-ons, then wipe everything linked to that user in one call:
await client.customers.delete("user-123"); // idempotent
See DELETE /customers/:id for the underlying semantics.

Next steps

Example app (Next.js)

Full working integration: upload widget, server-side routes, polling UI

Try-On endpoint

Every option on /try-on — the SDK’s tryOn.create maps 1:1

Errors

The full error-code catalog behind the typed classes

npm package

@genlook/api on the npm registry