> ## Documentation Index
> Fetch the complete documentation index at: https://docs.genlook.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Official `@genlook/api` SDK — a typed method for every endpoint, automatic retries, and a one-line generation poller.

[`@genlook/api`](https://www.npmjs.com/package/@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.

<Card title="Example app — Next.js + @genlook/api" icon="github" href="https://github.com/GenlookLabs/virtual-try-on-api-example">
  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`.
</Card>

## Install

```bash theme={null}
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:

```ts theme={null}
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

```ts theme={null}
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)
});
```

| Option       | Default                            | Notes                                                                  |
| ------------ | ---------------------------------- | ---------------------------------------------------------------------- |
| `apiKey`     | —                                  | Required. Get one on [app.genlook.app](https://app.genlook.app).       |
| `baseUrl`    | `https://api.genlook.app/tryon/v1` | Override for sandbox/staging.                                          |
| `timeoutMs`  | `60_000`                           | Per-request. Large customer-photo uploads on slow links may need more. |
| `maxRetries` | `2`                                | Retries `429` / `5xx` / transport failures; respects `Retry-After`.    |
| `fetch`      | global `fetch`                     | Pass your own if the runtime hides the global.                         |

## Resources

| Resource             | Methods                                               | Purpose                                                                       |
| -------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------- |
| `client.images`      | `upload`                                              | Pre-upload customer photos, reuse the `imageId` across generations.           |
| `client.tryOn`       | `create`                                              | Queue a try-on (inline-upsert the product or reference it by `externalId`).   |
| `client.generations` | `retrieve`, `waitFor`                                 | Poll status; `waitFor` replaces hand-rolled polling loops.                    |
| `client.products`    | `upsert`, `list`, `iterate`, `get`, `delete`, `stats` | Explicit catalog management — optional, most integrations use inline `tryOn`. |
| `client.account`     | `credits`                                             | Remaining credit balance.                                                     |
| `client.customers`   | `delete`                                              | GDPR 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.

```ts theme={null}
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`](/tryon-api/endpoints/generation-status).

## Error handling

Every failed request throws a typed `GenlookError` subclass. Use `instanceof` or branch on the stable `error.code`:

```ts theme={null}
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;
  }
}
```

| Class                                            | Thrown for                                                    |
| ------------------------------------------------ | ------------------------------------------------------------- |
| `AuthError`                                      | `401` — missing or invalid API key                            |
| `ValidationError`                                | `400` — invalid body, unsupported image, file too large, …    |
| `InsufficientCreditsError`                       | `402 INSUFFICIENT_CREDITS`                                    |
| `RateLimitError`                                 | `429` — carries `retryAfterSeconds`                           |
| `ProductNotFoundError`                           | `404 PRODUCT_NOT_FOUND` — upsert inline and retry             |
| `GenerationNotFoundError`                        | `404 GENERATION_NOT_FOUND`                                    |
| `GenerationFailedError`                          | `waitFor` reached a `FAILED` generation                       |
| `GenerationTimeoutError`                         | `waitFor` exceeded its `timeoutMs`                            |
| `GenlookConnectionError` / `GenlookTimeoutError` | Transport 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](/tryon-api/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:

```ts theme={null}
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:

```ts theme={null}
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:

```ts theme={null}
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:

```ts theme={null}
await client.customers.delete("user-123"); // idempotent
```

See [`DELETE /customers/:id`](/tryon-api/endpoints/delete-customer) for the underlying semantics.

## Next steps

<CardGroup cols={2}>
  <Card title="Example app (Next.js)" icon="github" href="https://github.com/GenlookLabs/virtual-try-on-api-example">
    Full working integration: upload widget, server-side routes, polling UI
  </Card>

  <Card title="Try-On endpoint" icon="image" href="/tryon-api/endpoints/create-try-on">
    Every option on /try-on — the SDK's `tryOn.create` maps 1:1
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/tryon-api/errors">
    The full error-code catalog behind the typed classes
  </Card>

  <Card title="npm package" icon="npm" href="https://www.npmjs.com/package/@genlook/api">
    @genlook/api on the npm registry
  </Card>
</CardGroup>
