npm package: @outfitcanvas/fitview-sdk
Description: TypeScript SDK for FitView API - virtual try-on service
Author: FitView Team
Latest version: 1.0.3

# FitView TypeScript SDK

Official TypeScript SDK for the FitView API - virtual try-on service.

## Installation

```bash
npm install @outfitcanvas/fitview-sdk
# or
yarn add @outfitcanvas/fitview-sdk
# or
pnpm add @outfitcanvas/fitview-sdk
```

## API key

To use the FitView API you need an API key. **Purchase and register at [https://outfitcanvas.app](https://outfitcanvas.app)** to acquire your API key. Use it in the client as shown below.

## Quick Start

```typescript
import { FitViewClient, OutfitCanvasModel } from '@outfitcanvas/fitview-sdk';

// Initialize the client
const client = new FitViewClient({
  apiKey: 'pk_live_your_api_key_here',
  baseURL: 'https://api.fitview.io', // Optional, defaults to production
});

// Generate a virtual try-on
const result = await client.generateFitView({
  person_image: 'https://example.com/person.jpg',
  garment_image: 'https://example.com/garment.jpg',
  model: OutfitCanvasModel.CanvasStandard,
  quality: 'high',
});

// Wait for completion
const completed = await result.waitForCompletion();

// Get the result image URL
console.log('FitView URL:', completed.fitviewUrl);
```

**Security:** Prefer environment variables for your API key (e.g. `process.env.FITVIEW_API_KEY`). Never embed API keys in client-side or public code—use the SDK only in server-side or trusted environments.

**Quick reference:** For a one-page TypeScript method/signature overview (IDE autocomplete and LLM code generation), see [Quick Reference — TypeScript](../../docs/sdk/quick-reference-typescript.md).

## API Reference

### FitViewClient

Main client class for interacting with the FitView API.

#### Constructor

```typescript
const client = new FitViewClient({
  apiKey: string,              // Required: Your API key
  baseURL?: string,            // Optional: API base URL (default: https://api.fitview.io)
  timeout?: number,            // Optional: Request timeout in ms (default: 300000)
  apiVersion?: string,         // Optional: API version (default: 'v1')
  maxRetries?: number,        // Optional: Retries for 429/5xx (default: 3, set 0 to disable)
  retryDelay?: number,        // Optional: Base delay in ms between retries (default: 1000)
  authMethod?: 'bearer' | 'apiKey', // Optional: Bearer token (default) or X-API-Key header
  fetch?: typeof fetch,        // Optional: Custom fetch implementation
  headers?: Record<string, string>, // Optional: Additional headers
  validateInputImages?: boolean,   // Optional: Validate image format/size before sending (default: true)
  optimizeImages?: boolean,        // Optional: Optimize base64 images client-side when sharp is available (default: false)
  optimizeImageOptions?: { maxDimension?: number; jpegQuality?: number; outputFormat?: 'jpeg' | 'png' | 'webp' | 'original' },
});
```

#### Methods

##### `generateFitView(request: FitViewRequest): Promise<FitViewResult>`

Generate a virtual try-on result.

```typescript
const result = await client.generateFitView({
  person_image: 'https://example.com/person.jpg',
  garment_image: 'https://example.com/garment.jpg',
  model: OutfitCanvasModel.CanvasStandard,
  quality: 'high',
  use_case: 'fashion',
  idempotency_key: '550e8400-e29b-41d4-a716-446655440000',
  webhook_url: 'https://your-app.com/webhook',
});
```

##### `getFitView(jobId: string): Promise<FitViewResult>`

Get the status of a FitView job.

```typescript
const result = await client.getFitView('fv_abc123');
console.log(result.status); // 'queued' | 'processing' | 'completed' | 'failed'
```

##### `batchGenerateFitView(requests: FitViewRequest[]): Promise<FitViewBatchResponse>`

Generate multiple virtual try-on results in a batch (max 50).

```typescript
const batch = await client.batchGenerateFitView([
  { person_image: '...', garment_image: '...' },
  { person_image: '...', garment_image: '...' },
]);

console.log(`Success: ${batch.success_count}, Failed: ${batch.failure_count}`);
```

##### `listModels(): Promise<ModelInfo[]>`

List available models for your account tier.

```typescript
const models = await client.listModels();
models.forEach(model => {
  console.log(`${model.displayName}: ${model.creditCost} credits`);
});
```

##### `waitForFitView(jobId: string, options?: WaitOptions): Promise<FitViewResult>`

Wait for a job to complete with polling. **Defaults** (used when you omit options): wait **10 seconds** before the first status fetch, then poll every **2 seconds** until done.

> **Warning:** Consult FitView support before changing `initialDelay` or `interval`. Other values may affect rate limits, reliability, and support eligibility.

```typescript
// Use defaults: 10s before first fetch, 2s between polls (recommended)
const result = await client.waitForFitView('fv_abc123');

// Or customize (consult support before changing polling defaults)
const result = await client.waitForFitView('fv_abc123', {
  timeout: 300000,
  initialDelay: 10000,   // default: 10s before first fetch
  interval: 2000,       // default: 2s between subsequent polls
  onProgress: (result) => console.log(`Status: ${result.status}`),
});
```

##### `getRateLimitAddons(): Promise<RateLimitAddonsResponse>`

Get active rate limit add-ons and total boost (billing).

```typescript
const { active_addons, total_boost } = await client.getRateLimitAddons();
```

##### `healthCheck(): Promise<{ status: string; timestamp?: string }>`

Basic health check (no auth). Returns `{ status, timestamp }`.

##### `readinessCheck(): Promise<HealthStatus>`

Readiness check with dependency status (no auth). Returns status, version, uptime, checks.

##### `livenessCheck(): Promise<{ status: string }>`

Liveness check (no auth). Returns `{ status: 'alive' }`.

##### `verifyWebhook(payload: string | Buffer, signature: string, secret: string): boolean`

Verify webhook signature.

```typescript
const isValid = client.verifyWebhook(
  rawBody,
  signature,
  webhookSecret
);
```

### FitViewResult

Wrapper class for FitView API responses with convenient methods.

#### Properties

- `jobId: string` - Job identifier
- `status: 'queued' | 'processing' | 'completed' | 'failed'` - Current status
- `fitviewUrl?: string` - URL of the generated image (if completed)
- `thumbnailUrl?: string` - URL of the thumbnail (if available)
- `modelUsed?: string` - Model used for generation
- `modelVersion?: string` - Model version
- `generationTimeMs?: number` - Generation time in milliseconds
- `enrichedData?: EnrichedData` - Additional enriched data
- `error?: FitViewError` - Error information (if failed)
- `metadata: ResponseMetadata` - Response metadata

#### Methods

##### `isCompleted(): boolean`

Check if the job is completed.

##### `isFailed(): boolean`

Check if the job has failed.

##### `isProcessing(): boolean`

Check if the job is still processing.

##### `download(): Promise<Buffer | Blob>`

Download the fitview image.

```typescript
const image = await result.download();
// In Node.js: Buffer
// In browser: Blob
```

##### `refresh(): Promise<FitViewResult>`

Refresh the result by fetching the latest status.

```typescript
const updated = await result.refresh();
```

##### `waitForCompletion(options?: WaitOptions): Promise<FitViewResult>`

Wait for the job to complete. Uses the same defaults as `waitForFitView` (10s before first fetch, 2s between polls). Consult FitView support before changing `initialDelay` or `interval`.

```typescript
// Defaults (recommended)
const completed = await result.waitForCompletion();

// With options (consult support before changing polling defaults)
const completed = await result.waitForCompletion({
  timeout: 300000,
  initialDelay: 10000,
  interval: 2000,
  onProgress: (result) => console.log(result.status),
});
```

## Examples

A runnable quick start (same flow as the Python example) is in `examples/`:

- **TypeScript:** `npx tsx examples/quickstart.ts` (from `packages/sdk-typescript`)
- **JavaScript:** `npm run build && node examples/quickstart.mjs`

Set `FITVIEW_API_KEY` and optionally `FITVIEW_BASE_URL` in the environment.

### Basic Generation

```typescript
import { FitViewClient, OutfitCanvasModel } from '@outfitcanvas/fitview-sdk';

const client = new FitViewClient({
  apiKey: process.env.FITVIEW_API_KEY!,
});

// Generate
const result = await client.generateFitView({
  person_image: 'https://example.com/person.jpg',
  garment_image: 'https://example.com/garment.jpg',
  model: OutfitCanvasModel.CanvasStandard,
});

// Wait for completion
const completed = await result.waitForCompletion();

// Use the result
if (completed.isCompleted()) {
  console.log('Image URL:', completed.fitviewUrl);
  
  // Download the image
  const image = await completed.download();
  // Save or process the image...
}
```

### Image sources (URLs vs device)

- **Images on your servers:** Pass a public **HTTP or HTTPS URL** in `person_image` and `garment_image`. FitView fetches the image from that URL; no separate upload step is required. The URL must be reachable by the FitView API (public or allowlisted).
- **Images on user devices:** The API accepts **base64 (or data URL) directly** in `person_image` and `garment_image` for generate requests—no upload step required. For very large images or when you prefer to upload once and reuse the URL, use **upload first, then generate:** call `client.uploadImage(base64)` or `client.uploadImageFromFile(filePath)` (Node.js) to get a URL, then pass that URL into `generateFitView`.

### Using Base64 Images

```typescript
const result = await client.generateFitView({
  person_image: {
    type: 'base64',
    data: 'data:image/jpeg;base64,/9j/4AAQSkZJRg...',
  },
  garment_image: {
    type: 'base64',
    data: 'data:image/jpeg;base64,/9j/4AAQSkZJRg...',
  },
});
```

### Upload from file (Node.js)

```typescript
import { FitViewClient } from '@outfitcanvas/fitview-sdk';

const client = new FitViewClient({ apiKey: process.env.FITVIEW_API_KEY! });

// Upload a local file and get a URL for generate
const { url } = await client.uploadImageFromFile('/path/to/person.jpg');
const result = await client.generateFitView({
  person_image: url,
  garment_image: 'https://example.com/garment.jpg',
});
```

### Idempotency key (safe retries)

To retry a create-job request without creating duplicate jobs, send an `idempotency_key`. Generate a new UUID per logical request:

```typescript
import { FitViewClient, generateIdempotencyKey } from '@outfitcanvas/fitview-sdk';

const client = new FitViewClient({ apiKey: process.env.FITVIEW_API_KEY! });

const result = await client.generateFitView({
  person_image: 'https://example.com/person.jpg',
  garment_image: 'https://example.com/garment.jpg',
  idempotency_key: generateIdempotencyKey(), // or FitViewClient.generateIdempotencyKey()
});
```

### Batch Processing

```typescript
const batch = await client.batchGenerateFitView([
  {
    person_image: 'https://example.com/person1.jpg',
    garment_image: 'https://example.com/garment1.jpg',
  },
  {
    person_image: 'https://example.com/person2.jpg',
    garment_image: 'https://example.com/garment2.jpg',
  },
]);

// Process results
batch.results.forEach((item, index) => {
  if (item.response) {
    console.log(`Request ${index + 1}: ${item.response.job_id}`);
  } else if (item.error) {
    console.error(`Request ${index + 1} failed:`, item.error.message);
  }
});
```

### Error Handling

```typescript
import {
  FitViewClient,
  FitViewValidationError,
  FitViewInsufficientCreditsError,
  FitViewRateLimitError,
} from '@outfitcanvas/fitview-sdk';

try {
  const result = await client.generateFitView({
    person_image: 'invalid-url',
    garment_image: 'https://example.com/garment.jpg',
  });
} catch (error) {
  if (error instanceof FitViewValidationError) {
    console.error('Validation error:', error.validationErrors);
  } else if (error instanceof FitViewInsufficientCreditsError) {
    console.error(`Need ${error.required} credits, have ${error.available}`);
  } else if (error instanceof FitViewRateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
  } else {
    console.error('Unexpected error:', error);
  }
}
```

### Webhook Verification

```typescript
import express from 'express';

const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-fitview-signature'] as string;
  const secret = process.env.WEBHOOK_SECRET!;
  
  const isValid = client.verifyWebhook(
    req.body,
    signature,
    secret
  );
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  const payload = JSON.parse(req.body.toString());
  // Process webhook...
  
  res.status(200).send('OK');
});
```

### Custom Timeout, Retries, and X-API-Key

```typescript
const client = new FitViewClient({
  apiKey: process.env.FITVIEW_API_KEY!,
  timeout: 60000,    // 1 minute timeout
  maxRetries: 3,     // Retry 429 and 5xx (default)
  retryDelay: 1000,  // Base delay before retry (exponential backoff)
  authMethod: 'apiKey', // Use X-API-Key header instead of Bearer
});

// With custom wait options (consult support before changing initialDelay/interval)
const result = await client.waitForFitView('fv_abc123', {
  timeout: 600000,
  initialDelay: 10000,
  interval: 5000,
  onProgress: (result) => {
    console.log(`Progress: ${result.status}`);
  },
});
```

### Listing Available Models

```typescript
const models = await client.listModels();

console.log('Available models:');
models.forEach(model => {
  console.log(`
    ${model.displayName} (${model.id})
    Cost: ${model.creditCost} credits
    Estimated latency: ${model.estimatedLatencyMs}ms
  `);
});
```

## Types

### FitViewRequest

```typescript
interface FitViewRequest {
  person_image: string | ImageInput;
  garment_image: string | ImageInput;
  model?: OutfitCanvasModel;
  quality?: 'fast' | 'balanced' | 'high';
  use_case?: 'casual' | 'fashion' | 'formal';
  user_id?: string;
  session_id?: string;
  product_id?: string;
  idempotency_key?: string;
  webhook_url?: string;
  context?: UserContext;
  metadata?: Record<string, any>;
}
```

### OutfitCanvasModel

```typescript
enum OutfitCanvasModel {
  CanvasPro = 'canvas-pro',
  CanvasProFast = 'canvas-pro-fast',
  CanvasStandard = 'canvas-standard',
  CanvasPlus = 'canvas-plus',
  CanvasExpress = 'canvas-express',
  CanvasInstant = 'canvas-instant',
  CanvasFashion = 'canvas-fashion',
  CanvasCasual = 'canvas-casual',
  CanvasFormal = 'canvas-formal',
  Canvas4K = 'canvas-4k',
  CanvasVideo = 'canvas-video',
}
```

## Image validation and optimization

To reduce unnecessary backend compute, the SDK can validate and optionally optimize images on the client:

- **Validation (default: on)**  
  Before each request, the SDK checks that base64 images are valid, within the 20MB limit, and in a supported format (JPEG, PNG, WebP, HEIC/HEIF). Invalid inputs throw `FitViewValidationError` immediately, so you get clear errors without calling the API. Set `validateInputImages: false` in the client to disable.

- **Optimization (default: off)**  
  With `optimizeImages: true`, base64 images are resized (default max 2048px) and recompressed (e.g. JPEG 85%) on the client when [sharp](https://www.npmjs.com/package/sharp) is installed. This sends smaller payloads and can reduce backend processing. URL inputs are not optimized. Install sharp optionally: `npm install sharp`.

**Best practices**

- Pre-optimize images when possible: max dimension ~2048px, under ~5MB, JPEG/PNG/WebP. The API accepts up to 20MB and the same formats; smaller, well-formatted images are faster and cheaper to process.
- Use validation (default) to fail fast on bad input.
- Enable `optimizeImages` in Node.js if you often send large base64 images and have sharp installed.

```typescript
import { FitViewClient, validateBase64Image, optimizeBase64Image } from '@outfitcanvas/fitview-sdk';

// Optional: validate or optimize a single image before building the request
const result = validateBase64Image('data:image/jpeg;base64,...');
if (!result.valid) {
  console.error(result.message);
}
const optimized = await optimizeBase64Image('data:image/jpeg;base64,...', { maxDimension: 2048, jpegQuality: 85 });

// Client with validation (default) and optional optimization
const client = new FitViewClient({
  apiKey: process.env.FITVIEW_API_KEY!,
  validateInputImages: true,
  optimizeImages: true,
  optimizeImageOptions: { maxDimension: 2048, jpegQuality: 85 },
});
```

## Error Handling

The SDK provides specific error classes for different scenarios:

- `FitViewError` - Base error class
- `FitViewAPIError` - API errors (4xx, 5xx)
- `FitViewValidationError` - Validation errors (400)
- `FitViewAuthenticationError` - Authentication errors (401)
- `FitViewInsufficientCreditsError` - Insufficient credits (402)
- `FitViewRateLimitError` - Rate limit errors (429)
- `FitViewTimeoutError` - Timeout errors
- `FitViewNetworkError` - Network errors

## Troubleshooting

### `getaddrinfo ENOTFOUND api.fitview.io` (or similar)

This means the hostname of the API could not be resolved (DNS failure). Common causes:

1. **Wrong or placeholder base URL** – The default `baseURL` is `https://api.fitview.io`. If your API is hosted elsewhere (e.g. your own Cloud Run URL), set the correct URL when creating the client:
   ```typescript
   const client = new FitViewClient({
     apiKey: process.env.FITVIEW_API_KEY!,
     baseURL: process.env.FITVIEW_BASE_URL || 'https://your-actual-api.run.app',
   });
   ```
   Or set the `FITVIEW_BASE_URL` environment variable (e.g. `https://fitview-api-xxxxx-uc.a.run.app` without `/api/v1`).

2. **Network or DNS issues** – Firewall, VPN, or local DNS may block or fail to resolve the host. Try from another network or verify the API URL in a browser/curl.

3. **Custom domain not set up** – If `api.fitview.io` is not yet configured in DNS for your environment, use the actual API URL provided by your dashboard or deployment (e.g. Cloud Run URL) as `baseURL`.

## Requirements

- Node.js 18.0.0 or higher
- TypeScript 5.0.0 or higher (for TypeScript projects)

## Publishing (maintainers)

To publish to npm via GitHub Actions, push a tag matching `sdk-ts-v*` (e.g. `sdk-ts-v1.0.1`). Ensure the repository secret **NPM_TOKEN** is set (npm Automation or Classic token with publish permission). The workflow runs build, type-check, and `npm publish --access public`.

## License

MIT

## Support

For issues and questions:
- Documentation: https://docs.fitview.com
- Email: support@fitview.com