# Huhu Studio Public API — Complete LLM Agent Skill Reference

> This document is the authoritative reference for the Huhu Studio Public API. It is designed to be loaded into LLM agent context windows, AI assistants, and automation tools. It contains everything needed to fully automate Huhu Studio workflows programmatically.

---

## 1. What is Huhu Studio?

Huhu Studio is an AI-powered e-commerce image and video generation platform. It enables brands to:

- **Virtual Try-On**: Generate photos of AI models wearing real garments
- **Model Generation**: Create AI fashion model images from prompts
- **Multipose Generation**: Generate model images in specific poses
- **Image Editing**: Background removal, face swap, upscaling, inpainting, logo transfer, accessory try-on
- **Video Generation**: Create product videos from still images (5s or 10s)
- **Bulk Processing**: Process hundreds of SKUs through automated pipelines

The Public API exposes these capabilities for programmatic access.

---

## 2. Base URLs

```
Production:  https://api.huhu.ai/public/v1
Gamma/Dev:   https://api-gamma.huhu.ai/public/v1
```

All endpoint paths below are relative to these base URLs.

---

## 3. Authentication

Every request requires a Bearer token:

```http
Authorization: Bearer sk-<your-api-key>
```

**Creating a key:** Go to Studio UI → API → API Keys → Create API Key. The full key is shown once — store it securely.

**Key properties:**
- Format: `sk-` followed by 48 hex characters
- Bound to a single **team** — `team_id` is auto-resolved from the key (never passed in requests)
- Optional **default program** — set `program_id` at key creation to skip passing it in every call
- Long-lived (no expiration unless revoked)

**Key with default program:** When creating a key, you can bind it to a program. If set, all endpoints auto-use that program when `program_id` is omitted. This is recommended for single-brand usage. For agencies with multiple brands, create separate keys per brand.

**Error responses:**
- Missing/invalid key → `401 {"detail": "Invalid API key"}`
- Revoked key → `401 {"detail": "API key has been revoked"}`
- Missing program_id (no default set) → `400 {"detail": "program_id is required..."}`

---

## 4. Data Model

Understanding the entity hierarchy is essential for using the API:

```
Team
├── Program (workspace / brand)
│   ├── Model (AI fashion model identity)
│   │   └── Model Image (photos of this model)
│   ├── Style (background/scene template)
│   ├── Project (batch of work)
│   │   └── SKU (single product/garment)
│   │       ├── Garment Images (product photos)
│   │       └── Task (generation job)
│   │           ├── Input: garment + model + style
│   │           ├── Output: generated image(s)
│   │           └── Status: lifecycle tracking
└── Credits (shared pool for all operations)
```

### Team
The top-level billing and access unit. Each API key belongs to one team. All credits, programs, and members are scoped to a team. Your API key is bound to your team — `team_id` is auto-resolved.

### Program
A workspace within a team, typically representing a brand or product line. Programs contain models, styles, and projects. Programs can be shared across teams for collaboration. One team owns the program (and pays credits); other teams can be invited.

### Project
A batch of work within a program. Projects organize SKUs for a collection, season, or campaign. Projects have statuses:
- `Creating` → `Created` → `In process` → `Completed`

### SKU (Stock Keeping Unit)
A single product/garment within a project. Each SKU has:
- **Garment images** — photos of the actual product
- **Product category** — minidress, t-shirt, pants, bikini, etc. (13 categories)
- **Garment type** — top, bottom, fullbody, shoe
- **Image requirements** — which views to generate (Front Full, Back Full, etc.)
- **Status**: `Created` → `In process` → `Generated` → `Reviewing pending` → `Completed`

### Task
A generation job for a specific SKU. A task takes garment images + model + style and produces AI-generated output images. Tasks flow through a detailed lifecycle:

```
Task created
  → Task submitted
    → Generation started
      → Generation in process
        → Generation completed ✓  (or Generation failed ✗)
          → Editing submitted → Editing in process → Editing completed
            → Review submitted → Review in process → Review approved (or rejected)
              → Client approved ✓ (or Client rejected ✗)
```

### Model
An AI fashion model identity. Models have:
- **Type**: Men, Women, Baby, Toddler, Kid, Maternity
- **Size**: Petit, Regular, Plus size
- **Visibility**: public (shared) or private
- **Model Images**: reference photos of this model used for generation

### Credits
Every generation operation costs credits from the team's shared pool:

| Operation | Credits |
|-----------|---------|
| Image Generation (SD) | 1 |
| Image Generation (HD) | 2 |
| Same-ID Image Generation | 2 |
| Image Editing (prompt/inpaint) | 2 |
| Face Swap | 2 |
| Upscale | 2 |
| Background Change | 2 |
| Remove Background | 2 |
| Hand/Foot Fix | 2 |
| Logo Transfer | 2 |
| Accessory Try-On | 2 |
| Ghost Mannequin | 2 |
| Prompt Generation | 2 |
| Video Generation (5s) | 25 |
| Video Generation (10s) | 40 |

New teams receive **50 free credits** and **10 concurrent requests**.

---

## 5. API Endpoints — Complete Reference

### 5.1 Check Credits / Plan

```http
GET /metering/plan
```

**Use case:** Always call this before generation requests to verify sufficient credits.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| program_id | query | string | no | If provided, resolves the owner team for shared programs |

**Response (200):**
```json
{
  "credits": 99999,
  "plan_status": "free",
  "concurrent_requests": 10,
  "platform": null,
  "customer_id": null,
  "plan_id": null,
  "plan_name": null,
  "current_period_end": null,
  "subscription_id": null,
  "auto_reload": null,
  "auto_reload_threshold": null
}
```

**Key fields:**
- `credits` (int) — remaining credits. Generation returns 402 if ≤ 0.
- `concurrent_requests` (int) — max parallel generation jobs.
- `plan_status` (string) — `"free"` or `"active"` (paid plan).

---

### 5.2 Credit History

```http
GET /metering/credit-history
```

**Use case:** Audit credit spending, analyze cost per operation, track API vs UI usage.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| program_id | query | string | no | Program ID |
| pagination_token | query | string | no | For paginated results |
| fetch_all | query | boolean | no | `true` to get all records (max 10,000) |

**Response (200):**
```json
{
  "items": [
    {
      "program_id": "abc-123",
      "request_id": "req-456",
      "credits": 2,
      "timestamp": 1773702421471,
      "operation": "StudioModelSaas",
      "request_source": "api"
    }

  "pagination_token": null
}
```

**Operation values:** `StudioModelSaas`, `StudioMultiPose`, `StudioI2I`, `StudioMisc`, `StudioLoraTrain`, `StudioInpaint`, `ImageGeneration`, `ImageEditing`, `Faceswap`, `Upscale`, `ChangeBackground`, `HandFix`, `FootFix`, etc.

**`request_source`:** `"api"` for API calls, `"ui"` for Studio UI usage.

---

### 5.3 Get Programs

```http
GET /program
```

**Use case:** List programs accessible to your team.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| owned | query | bool | no | If true, only programs you own (default: false) |
| pagination_token | query | string | no | For paginated results |

**Response (200):**
```json
{
  "programs": [
    {
      "program_id": "prog-123",
      "program_name": "Spring 2026",
      "create_time": 1773702421471,
      "user_id": "user-789"
    }
  ],
  "pagination_token": null
}
```

---

### 5.4 Create Program

```http
POST /program={name}
```

**Use case:** Create a new program (workspace) for a brand or product line.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| program_name | query | string | yes | Name for the new program |

**Response (200):**
```json
{
  "program_id": "prog-new-456",
  "program_name": "Summer 2026",
  "user_id": "user-789",
  "create_time": 1773702421471
}
```

---

### 5.5 Get Projects

```http
GET /project
```

**Use case:** List projects, or get a single project detail by adding `project_id`.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| project_id | query | string | no | If provided, returns single project detail |
| program_id | query | string | no | Program ID (uses key default if omitted) |
| pagination_token | query | string | no | For paginated results |

**List response (200):** Returns `projects[]` array with pagination.

**Detail response (200):** Returns single project object.

**`project_status` values:** `"Creating"`, `"Created"`, `"In process"`, `"Completed"`
```

---

### 5.6 Create Project

```http
POST /project
```

**Use case:** Create a new project to organize generation work.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| project_name | query | string | yes | Name for the new project |
| program_id | query | string | no | Program ID |

**Response (200):** Same shape as 5.6.

---

### 5.7 Create Model

```http
POST /model
Content-Type: application/json
```

**Use case:** Create a new AI model entry in the gallery. After creation, add reference images via `POST /model/image`.

**Request body:**
```json
{
  "model_name": "Caucasian Plus-Size Woman A",
  "model_type": "Women",
  "model_size": "Plus size",
  "attributes": {
    "ethnicity": "Caucasian",
    "age_range": "Middle-aged",
    "hair_color": "Blonde",
    "hair_style": "Long",
    "skin_tone": "Fair",
    "eye_color": "Blue"
  }
}
```

**Fields:**
| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| model_name | string | yes | — | Display name for the model |
| model_type | string | no | null | `"Men"`, `"Women"`, `"Baby"`, `"Toddler"`, `"Kid"`, `"Maternity"` |
| model_size | string | no | null | `"Petit"`, `"Regular"`, `"Plus size"` |
| attributes | object | no | null | Descriptive attributes for search/filtering (see below) |
| program_id | string | no | key default | Program ID (uses key default if omitted) |

**Attributes object fields (all optional):**
| Name | Type | Example values |
|------|------|----------------|
| ethnicity | string | Caucasian, East Asian, South Asian, Black, Hispanic, Middle Eastern, Mixed |
| age_range | string | Young, Young Adult, Middle-aged, Mature, Senior |
| hair_color | string | Black, Brown, Blonde, Red, Auburn, Silver, Platinum Blonde |
| hair_style | string | Short, Medium, Long, Curly, Straight, Wavy, Bob, Ponytail, Bun, Bald |
| skin_tone | string | Fair, Light, Medium, Olive, Tan, Brown, Dark |
| eye_color | string | Blue, Green, Brown, Hazel, Amber, Gray |

**Response (200):**
```json
{
  "success": true,
  "model_id": "e7c446c3-..."
}
```

**Typical workflow:**
1. `POST /model/face` → generate face images
2. `POST /model` → create model entry with `attributes`, get `model_id`
3. `POST /model/image` → attach generated face image to the model using `model_id`

---

### 5.8 List Models

```http
GET /model
```

**Use case:** List AI models available in a program. Filter by type, size, and descriptive attributes to find the right model for a specific SKU. **LLM agents should use attribute filters to match user intent** (e.g., "caucasian plus-size woman" → `model_type=Women&model_size=Plus+size&ethnicity=Caucasian`).

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| model_type | query | string | no | Filter: `Men`, `Women`, `Baby`, `Toddler`, `Kid`, `Maternity` |
| model_size | query | string | no | Filter: `Petit`, `Regular`, `Plus size` |
| ethnicity | query | string | no | Filter by ethnicity (fuzzy match): `Caucasian`, `East Asian`, `Black`, etc. |
| age_range | query | string | no | Filter by age: `Young`, `Young Adult`, `Middle-aged`, `Mature`, `Senior` |
| hair_color | query | string | no | Filter by hair color: `Blonde`, `Brown`, `Black`, `Red`, etc. |
| skin_tone | query | string | no | Filter by skin tone: `Fair`, `Light`, `Medium`, `Olive`, `Tan`, `Brown`, `Dark` |
| program_id | query | string | no | Program ID |

**Response (200):**
```json
{
  "models": [
    {
      "model_id": "e7c446c3-...",
      "model_name": "Dae Nakamura",
      "model_type": "Women",
      "model_size": "Regular",
      "model_visibility": "public",
      "cover_image_url": "https://s3.../cover.jpg?signed...",
      "create_time": 1773702421471,
      "attributes": {
        "ethnicity": "East Asian",
        "age_range": "Young Adult",
        "hair_color": "Black",
        "hair_style": "Long",
        "skin_tone": "Light",
        "eye_color": "Brown"
      }
    }
  ]
}
```

**LLM agent model selection pattern:**
```
User says: "I need a caucasian middle-aged plus-size woman"
1. GET /model?model_type=Women&model_size=Plus+size&ethnicity=Caucasian&age_range=Middle-aged
2. If results found → use the model
3. If empty → generate one:
   a. POST /model/face with descriptive prompt
   b. POST /model with attributes matching the description
   c. POST /model/image to save face to the new model
```

---

### 5.9 List Model Images

```http
GET /model?model_id={model_id}
```

**Use case:** Get reference images for a specific model. Use these as `reference_image` in generation requests.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| model_id | path | string | yes | Model ID from the models list |
| program_id | query | string | no | Program ID |

**Response (200):**
```json
{
  "model_images": [
    {
      "model_image_id": "assets/model_images/xxx.png",
      "model_image_url": "https://s3.../xxx.png?signed...",
      "model_id": "model-123",
      "style_id": "style-456",
      "view_type": "front_facing",
      "crop_type": "fullbody"
    }
  ]
}
```

**Image classification fields (auto-detected on upload):**
- `view_type`: `"front_facing"` or `"back_facing"` (or null if not detected)
- `crop_type`: `"fullbody"`, `"head_half_cropped"`, `"top"`, `"bottom"` (or null)
- `model_id`, `style_id`: which model and style the image belongs to

Use `view_type` and `crop_type` to select the right image for task pairing (e.g. front_facing + fullbody for front-view try-on).

---

### 5.10 Get SKUs

```http
GET /sku
```

**Use case:** List SKUs in a project, or get a single SKU detail by adding `sku_id`. The detail response includes **AI-classified garment images** — each image has `garment_type`, `view`, and `presentation` assigned automatically after zip upload.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| project_id | query | string | yes | Project ID |
| sku_id | query | string | no | If provided, returns single SKU detail with garment classification |
| program_id | query | string | no | Program ID (uses key default if omitted) |
| pagination_token | query | string | no | For paginated results |

**Detail response (with sku_id):**
```json
{
  "skus": [{
    "sku_id": "xxx",
    "sku_name": "sku1",
    "garment_type": "Full body",
    "garment_images": [
      {
        "garment_id": "abc",
        "image_id": "assets/sku_images/xxx.png",
        "image_url": "https://s3.../xxx.png?signed...",
        "garment_type": "Top",
        "view": "front",
        "presentation": "flatlay_clean"
      },
      {
        "garment_id": "def",
        "image_id": "assets/sku_images/yyy.png",
        "garment_type": "Bottom",
        "view": "front",
        "presentation": "flatlay_clean"
      }
    ]
  }]
}
```

**Garment classification (auto-detected after zip upload):**
- `garment_type`: `"Top"`, `"Bottom"`, `"Shoes"`, `"Accessory"` — what type of garment
- `view`: `"front"`, `"back"` — which angle the photo shows
- `presentation`: `"flatlay_clean"`, `"on_model"`, `"on_mannequin"` — how the garment is presented
- `accessory_sub_type`: `"bag"`, `"hat"`, etc. — for accessories only

This classification is used by `POST /task` to automatically group garments by view (front task + back task) for try-on generation.

**SKU statuses:** `"Created"`, `"In process"`, `"Generated"`, `"Reviewing pending"`, `"Completed"`

---

### 5.11 Save/Upload Model Image

See **5.15 Model Image** → Mode 2 (Upload). Use `POST /model/image` with `model_id` + `style_id` + `image_url` or `file_data`.

---

### 5.12 Upload Project Assets

```http
POST /project/upload
Content-Type: application/json
```

**Use case:** Upload garment images, reference images, or initiate a multipart zip upload for bulk SKU creation.

**For image uploads (garment_image, model_image, reference_image):**
```json
{
  "program_id": "prog-123",
  "project_id": "proj-456",
  "file_type": "garment_image",
  "file_data": "base64-encoded-image..."
}
```

**For zip uploads (sku_zip — multipart):**
```json
{
  "program_id": "prog-123",
  "project_id": "proj-456",
  "file_type": "sku_zip",
  "file_size": 11264257
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| program_id | string | yes | Program ID |
| project_id | string | yes | Project ID |
| file_type | string | yes | `"garment_image"`, `"sku_zip"`, `"model_image"`, `"reference_image"`, `"output_image"` |
| file_data | string | no | Base64-encoded image (for image types) |
| file_size | number | no | File size in bytes (for sku_zip) |

**Response (200) — image types:**
```json
{ "success": true, "image_id": "assets/sku_images/abc123.png" }
```

**Response (200) — sku_zip (multipart init):**
```json
{
  "message": "success",
  "key": "team/program/project/assets/sku_zip/1773769878176.zip",
  "upload_id": "rtNs5mDAJLBUvSUrJ7o_...",
  "upload_urls": ["https://s3.../partNumber=1&uploadId=..."]
}
```

After receiving `upload_urls`, upload each part via PUT to the presigned URL, then call the complete endpoint.

---

### 5.13 Complete Multipart Upload

```http
POST /project/complete_multipart_upload
Content-Type: application/json
```

**Use case:** Finalize a multipart zip upload after all parts have been PUT to presigned URLs.

**Request body:**
```json
{
  "program_id": "prog-123",
  "project_id": "proj-456",
  "key": "team/program/project/assets/sku_zip/1773769878176.zip",
  "upload_id": "rtNs5mDAJLBUvSUrJ7o_...",
  "parts": [
    { "PartNumber": 1, "ETag": "\"6116b8476da333391acc0756eecd1cbd\"" }
  ]
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| program_id | string | yes | Program ID |
| project_id | string | yes | Project ID |
| key | string | yes | S3 key from upload response |
| upload_id | string | yes | Upload ID from upload response |
| parts | object[] | yes | Array of `{PartNumber, ETag}` — ETag from S3 PUT response headers |

**Response (200):**
```json
{ "message": "success" }
```

**Zip upload workflow:**
1. `POST /project/upload` with `file_type: "sku_zip"` → get presigned URLs
2. `PUT` each file chunk to the presigned URL → save ETag from response headers
3. `POST /project/complete_multipart_upload` with parts array → done
4. **Wait for async processing** — poll `GET /sku?project_id=` until SKUs appear (typically 30-90 seconds)

**What happens after upload:** The zip is processed asynchronously by an AI worker that:
- Extracts images from each folder (one folder = one SKU)
- Classifies each garment image: `garment_type` (Top, Bottom, Shoes, Accessory), `view` (front, back), `presentation` (flatlay, on_model)
- Creates pairing selections — groups garments by view for try-on task creation
- Updates project status from `"Creating"` to `"Created"`

---

### 5.14 Generate Face Image

```http
POST /model/face
Content-Type: application/json
```

**Use case:** Generate AI face/model images from attribute descriptions. Use this to create reference faces for try-on.

**Request body:**
```json
{
  "program_id": "prog-123",
  "prompt": "Headshot front facing portrait of a Young Caucasian Petite Female with Blonde Long hair with light blue eye with Fair skin tone in the White Studio setting with Neutral expression. Standing pose, professional lighting, high detail.",
  "mode": "variety",
  "num_output_images": 2
}
```

**Fields:**
| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| prompt | string | yes | — | Describe the face: age, race, gender, hair, eyes, skin, background, expression, pose |
| program_id | string | no | key default | Program ID (uses key default if omitted) |
| mode | string | no | "variety" | `"variety"` (diverse outputs) or `"fidelity"` (higher prompt adherence) |
| num_output_images | int | no | 1 | Number of images (1-4) |

**Mode differences:**
- **`variety`** — Standard pipeline, produces more diverse/creative outputs. Default for most use cases.
- **`fidelity`** — V2 pipeline, follows prompt attributes more closely. Better for specific face requirements.

**Prompt template for best results:**
```
Headshot front facing portrait of a {Age} {Race} {BodySize} {Gender}
with {HairColor} {HairStyle} hair with {EyeColor} eye with {SkinColor}
skin tone in the {Background} setting with {Expression}.
{Pose} pose, professional lighting, high detail.
```

**Attribute options:**
- **Age:** Child (4-7), Preteen (8-13), Young Adult (18-30), Adult (30-50), Senior (50+)
- **Race:** Caucasian, African American, East Asian, South Asian, Southeast Asian, Hispanic, Middle Eastern, Mixed
- **Gender:** Female, Male
- **Hair Style:** Long hair, Short hair, Curly hair, Bob, Buzz cut, Bald, Braids, Ponytail, Afro
- **Hair Color:** Black, Blonde, Brown, Red, Grey, Auburn, Platinum
- **Eye Color:** brown, light blue, green, hazel, grey
- **Skin Color:** Fair, Light, Medium, Olive, Tan, Brown, Dark
- **Background:** White Studio, Grey Studio, Outdoor Natural, Urban Street, Beach, Minimalist
- **Body Size:** Petite Size, Regular Size, Plus Size
- **Expression:** Neutral expression, Slight smile, Confident expression, Joyful smile

**Response (200):**
```json
{
  "team_id": "...", "program_id": "...", "request_id": "req-123",
  "timestamp": 1773809211391, "status": "Request Submitted",
  "images_generated": [], "num_output_images": 2
}
```

**Next steps:**
1. Poll `GET /model/image?request_id=req-123` until `status` = `"Generation Completed"`
2. Create model: `POST /model` → get `model_id`
3. Save image to model: `POST /model/image` with `model_id` + `image_url` from generation result

**Credits:** 2 per image.

---

### 5.15 Model Image (Generate or Upload)

```http
POST /model/image
Content-Type: application/json
```

**Use case:** This endpoint has two modes — **generate** a new image or **upload/save** an existing image to a model.

#### Mode 1: Generate (provide `prompt`)

Generate AI model images from text prompts. **Costs credits** (2 per image).

**Request body:**
```json
{
  "prompt": "professional studio photo of a woman wearing the garment, white background",
  "reference_image": "assets/model_images/xxx.png",
  "num_output_images": 2
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| prompt | string | yes | Text prompt for image generation |
| reference_image | string | no | Gallery image ID, HTTP URL, or base64 |
| num_output_images | int | no | Number of images (default: 1, max: 8) |
| program_id | string | no | Program ID (uses key default if omitted) |

**`reference_image` accepts 3 formats:**
1. **Image ID from gallery** — e.g. `"assets/model_images/xxx.png"` (from `GET /model?model_id=`)
2. **HTTP/HTTPS URL** — e.g. `"https://example.com/photo.jpg"` (downloaded automatically)
3. **Base64 image data** — e.g. `"iVBORw0KGgo..."` (decoded automatically)

**Response (200):**
```json
{
  "request_id": "req-789",
  "status": "Request Submitted",
  "images_generated": [],
  "num_output_images": 2
}
```

**Next:** Poll `GET /model/image?request_id=req-789` until `status` = `"Generation Completed"`, then use `images_generated[].model_image_url` to save to a model.

#### Mode 2: Upload/Save to model (provide `model_id`)

Save an existing image (from generation results, URL, or base64) to a model in the gallery.

**Request body:**
```json
{
  "model_id": "model-123",
  "style_id": "style-456",
  "image_url": "https://s3.../generated-image.png?signed..."
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| model_id | string | yes | Model ID to save image to |
| style_id | string | yes | Style ID |
| image_url | string | no | Image URL (e.g. from generation result) |
| file_data | string | no | Base64 image to upload directly |
| program_id | string | no | Program ID (uses key default if omitted) |

**Response (200):**
```json
{
  "success": true,
  "image_id": "assets/model_images/xxx.png",
  "image_url": "https://s3.../xxx.png?signed...",
  "view_type": "front_facing",
  "crop_type": "fullbody"
}
```

`view_type` and `crop_type` are **auto-detected** by AI when the image is uploaded. Use these to select the right image for task pairing.

**Errors:**
- `400` — Must provide either `prompt` (generate) or `model_id` + `file_data`/`image_url` (upload)
- `402` — Insufficient credits (generate mode only)

**Polling for results (generate mode):** Use the `request_id` with `GET /model/image` to poll for completion.

---

### 5.16 Poll Generation Status

```http
GET /model/image?request_id={request_id}
```

**Use case:** Poll for results after any generation request (`POST /model/face`, `POST /model/image`, or `POST /model/pose`). Call every 5-10 seconds until status is `Generation Completed` or `Generation Failed`.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| request_id | query | string | yes | Request ID from the submit response |
| program_id | query | string | no | Program ID |

**Response (200) — completed:**
```json
{
  "program_id": "prog-123",
  "request_id": "req-789",
  "timestamp": 1773770457214,
  "status": "Generation Completed",
  "images_generated": [
    {
      "model_image_id": "team/program/assets/training/req-789/0/output/1773770520035.png",
      "model_image_url": "https://s3.amazonaws.com/...?X-Amz-Signature=...",
      "file_name": null
    }

  "num_output_images": 2
}
```

**Status values:**
- `"Request Submitted"` — job queued
- `"Generation Completed"` — images ready, download from `model_image_url`
- `"Generation Failed"` — error occurred

**Image URLs:** Presigned S3 URLs valid for ~24 hours.

---

### 5.17 Get Pose Library (Presets)

```http
GET /pose/library
```

**Use case:** Browse the built-in library of 143 preset poses. Each pose has a descriptive `pose_name`, `model_category`, `prompt_type`, and reference/sketch images. LLM agents should use this endpoint to select appropriate poses based on user intent (e.g. "front view" → filter for `fullbody` poses with "front" in `pose_name`, "close-up" → filter `prompt_type=close_up_upper_body`).

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| model_category | query | string | no | Filter by category: `Women`, `Men`, `Child`, `Infant` |
| prompt_type | query | string | no | Filter by type: `fullbody`, `head_half_cropped`, `top`, `bottom`, `close_up_upper_body`, `close_up_lower_body` |

**Response (200):**
```json
{
  "poses": [
    {
      "pose_id": "f00cb596a721e4eb...",
      "pose_name": "Full body upright",
      "model_category": "Women",
      "prompt_type": "fullbody",
      "reference_image_url": "https://cdn.huhu.ai/poses/xxx.png",
      "sketch_url": "https://cdn.huhu.ai/poses/yyy.png"
    }
  ],
  "total": 14
}
```

**Pose selection tips for LLM agents:**
- "front view" → `prompt_type=fullbody`, look for `pose_name` containing "front" or "upright"
- "back view" → look for `pose_name` containing "back"
- "profile" / "side view" → look for "Semi-profile" or "profile" in `pose_name`
- "close-up" / "closeshot" → `prompt_type=close_up_upper_body` or `close_up_lower_body`
- "head shot" → `prompt_type=close_up_upper_body`, look for "head shot" in `pose_name`
- Use the `reference_image_url` from selected poses as the `reference_image` in `POST /model/pose`

---

### 5.18 Get Custom Poses

```http
GET /pose
```

**Use case:** List your team's custom-created poses, or get a single pose detail by adding `pose_id`. Custom poses are created via `POST /pose`. For preset poses, use `GET /pose/library` instead.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| pose_id | query | string | no | If provided, returns single pose detail |
| program_id | query | string | no | Program ID (uses key default if omitted) |
| pagination_token | query | string | no | For paginated results |

**Response (200):**
```json
{
  "poses": [
    {
      "pose_id": "xxx",
      "title": "Standing Front",
      "prompt": "standing pose, front facing, arms at sides, relaxed...",
      "reference_image_presigned_url": "https://s3.../reference.png?signed...",
      "prompt_type": "fullbody"
    }
  ]
}
```

---

### 5.19 Create Pose

```http
POST /pose
Content-Type: application/json
```

**Use case:** Create a new pose from a reference image. AI automatically extracts the pose description (prompt) from the person in the image. The pose can then be used with `POST /model/pose` to generate model images in that pose.

**Request body:**
```json
{
  "reference_image": "https://example.com/person-in-pose.jpg",
  "pose_title": "Walking Side View"
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| reference_image | string | yes | Base64 image or HTTP URL of a person in the desired pose |
| pose_title | string | yes | Name for this pose |
| program_id | string | no | Program ID (uses key default if omitted) |

**Response (200):**
```json
{
  "status": "success",
  "pose_id": "xxx",
  "pose_prompt": "walking pose, side view, left foot forward, arms swinging naturally...",
  "pose_title": "Walking Side View"
}
```

**Workflow:**
1. `POST /pose` with reference image → AI extracts pose, returns `pose_prompt`
2. `GET /pose` → see all saved poses
3. `POST /model/pose` with the `pose_prompt` → generate model in that pose

---

### 5.20 Generate Pose

```http
POST /model/pose
Content-Type: application/json
```

**Use case:** Generate images of a model in specific poses using a reference image from the gallery. **Costs credits.** The reference image belongs to a specific model_id and style_id — after generation, save the results back to the same model.

**Request body:**
```json
{
  "prompt": "professional fashion photo, full body, studio lighting",
  "reference_image": "assets/model_images/xxx.png"
}
```

**Fields:**
| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| prompt | string | yes | — | Prompt describing the desired pose |
| reference_image | string | yes | — | Model image ID from gallery (from `GET /model?model_id=`) |
| program_id | string | no | key default | Program ID (uses key default if omitted) |

**Response (200):**
```json
{
  "request_id": "req-pose-123"
}
```

**Next steps:**
1. Poll `GET /model/image?request_id=req-pose-123` until `status` = `"Generation Completed"`
2. Save generated pose images to the same model: `POST /model/image` with `model_id` + `style_id` + `image_url` from result
3. The `reference_image`'s `model_id` and `style_id` can be looked up from the model images list (`GET /model?model_id=`)

---

### 5.21 Get Tasks

```http
GET /task
```

**Use case:** List tasks (poll generation status), or get a single task detail with output images by adding `project_id`, `sku_id`, and `task_id`.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| project_id | query | string | no | For task detail |
| sku_id | query | string | no | For task detail |
| task_id | query | string | no | For task detail — returns output[] with image URLs |
| program_id | query | string | no | Program ID (uses key default if omitted) |
| pagination_token | query | string | no | For paginated results |

**List:** `GET /task` → tasks grouped by SKU with status.
**Detail:** `GET /task?project_id=...&sku_id=...&task_id=...` → output images.

**Response (200):**
```json
{
  "program_id": "prog-123",
  "skus": [
    {
      "team_name": "TQ Test",
      "program_id": "prog-123",
      "program_name": "Spring 2026",
      "project_id": "proj-456",
      "project_name": "Spring Collection",
      "sku_id": "sku-001",
      "sku_name": "Blue Dress",
      "sku_status": "In process",
      "due_time": null,
      "tasks": [
        {
          "task_id": "task-789",
          "task_status": "Generation completed",
          "task_status_time": 1773702421471,
          "garment_image_id": "gimg-001",
          "garment_image_url": "https://s3.../garment.jpg",
          "garment_type": "fullbody",
          "garment_view": "Front Full",
          "output_image_id": "oimg-001",
          "output_image_url": "https://s3.../output.jpg",
          "output_video_id": null,
          "output_video_url": null
        }
      ]
    }

  "total_skus": 1
}
```

**Task status values (in order):**
1. `"Task created"` — task exists but not submitted
2. `"Task submitted"` — queued for processing
3. `"Generation started"` — worker picked up the job
4. `"Generation in process"` — actively generating
5. `"Generation completed"` — images ready (check `output_image_url`)
6. `"Generation failed"` — error occurred, may retry
7. `"Editing submitted"` → `"Editing in process"` → `"Editing completed"` / `"Editing failed"`
8. `"Review submitted"` → `"Review in process"` → `"Review approved"` / `"Review rejected"`
9. `"Client approved"` / `"Client rejected"` — final state

**Garment types:** `"Top"`, `"Bottom"`, `"Full body"`, `"Accessory"`, `"Shoes"`

**Garment views:** `"Front Full"`, `"Front Half"`, `"Back Full"`, `"Back Half"`, `"Semi-Profile Full"`, `"Semi-Profile Half"`

---

### 5.22 Update SKU

```http
PUT /sku
Content-Type: application/json
```

**Use case:** Assign a model, style, and garment type to SKUs before generation.

**Request body:**
```json
{
  "program_id": "prog-123",
  "project_id": "proj-456",
  "sku_ids": ["sku-001", "sku-002"],
  "model_id": "model-789",
  "style_id": "style-abc",
  "model_images_front": ["assets/model_images/xxx.png"],
  "garment_type": "Full body"
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| program_id | string | yes | Program ID |
| project_id | string | yes | Project ID |
| sku_ids | string[] | yes | SKU IDs to update |
| model_id | string | no | Model ID to assign |
| style_id | string | no | Style ID to assign |
| model_images_front | string[] | no | Model image IDs for front view |
| model_images_back | string[] | no | Model image IDs for back view |
| garment_type | string | no | `"Top"`, `"Bottom"`, `"Full body"`, `"Accessory"`, `"Shoes"` |

**Response (200):** Updated SKU list.

---

### 5.23 Create Tasks

```http
POST /task
Content-Type: application/json
```

**Use case:** Create generation tasks for SKUs. Tasks are **automatically structured** based on the AI garment classification from zip upload — garments are grouped by view (front/back), and one task is created per view. Each task contains `selected_garment_image_ids` with the matched garment images (e.g. a front-view task includes the Top front + Bottom front + Shoes front images).

**Request body:**
```json
{
  "program_id": "prog-123",
  "project_id": "proj-456",
  "sku_ids": ["sku-001"]
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| program_id | string | yes | Program ID |
| project_id | string | yes | Project ID |
| sku_ids | string[] | yes | SKU IDs to create tasks for |

**Response (200):** `{"message": "success"}`

After creation, call `GET /task` to get the new task IDs (status: `"Task created"`). Each task will have `selected_garment_image_ids` pre-populated from the garment classification.

---

### 5.24 Update Task (Set Model Image)

```http
PUT /task
Content-Type: application/json
```

**Use case:** Set `model_image` on a task before generation. **Required** — tasks without `model_image` will fail generation.

**Request body:**
```json
{
  "program_id": "prog-123",
  "project_id": "proj-456",
  "task_id": "task-789",
  "sku_id": "sku-001",
  "model_id": "model-abc",
  "model_image": "assets/model_images/xxx.png"
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| program_id | string | yes | Program ID |
| project_id | string | yes | Project ID |
| task_id | string | yes | Task ID to update |
| sku_id | string | yes | SKU ID the task belongs to |
| model_id | string | no | Model ID to assign |
| model_image | string | no | Model image ID (from `GET /model/{...}/images`). **Required before generation.** |

**Response (200):** Updated task object.

---

### 5.25 Generate Try-On

```http
POST /task/generate
Content-Type: application/json
```

**Use case:** Submit try-on generation for tasks. Each task must have `model_image` set via `PUT /task` first.

**Request body:**
```json
{
  "program_id": "prog-123",
  "project_id": "proj-456",
  "task_ids": ["task-001", "task-002"],
  "num_output_images": 1
}
```

**Fields:**
| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| project_id | string | yes | — | Project ID |
| task_ids | string[] | yes | — | Task IDs to generate (each must have model_image set) |
| program_id | string | no | key default | Program ID (uses key default if omitted) |
| num_output_images | int | no | 1 | Images per task (1-4) |
| prompt | string | no | null | Optional text prompt to guide generation |

**Response (200):** `{"message": "success"}`

**Errors:** `429` — Concurrent requests limit exceeded.

**Poll via:** `GET /task` — check task_status until `"Generation completed"`. Typically completes in ~90 seconds.

---

### 5.26 Create Video

```http
POST /video
Content-Type: application/json
```

**Use case:** Generate a video from a try-on output image.

**Request body:**
```json
{
  "program_id": "prog-123",
  "project_id": "proj-456",
  "sku_id": "sku-001",
  "source_task_id": "task-789",
  "video_length": "5",
  "prompt": "A woman modeling casual fashion, gentle natural movement",
  "first_frame_image": {
    "image_base64": "base64-encoded-output-image..."
  }
}
```

**Fields:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| program_id | string | yes | Program ID |
| project_id | string | yes | Project ID |
| sku_id | string | yes | SKU ID |
| video_length | string | yes | `"5"` (5 seconds) or `"10"` (10 seconds) |
| prompt | string | yes | Video motion prompt |
| first_frame_image | object | yes | `{image_base64}` or `{image_id}` |
| source_task_id | string | no | Source task ID |

**Response (200):**
```json
{
  "program_id": "prog-123",
  "task_id": "video-task-001"
}
```

**Credits:** 5s = 25 credits, 10s = 40 credits.

---

### 5.27 List Video Requests

```http
GET /video/requests?limit=10
```

**Use case:** Poll video generation status and get completed video URLs.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| program_id | query | string | no | Program ID |
| request_id | query | string | no | Specific request ID |
| limit | query | int | no | Max results (default: 10) |
| pagination_token | query | string | no | For pagination |

**Response (200):**
```json
{
  "program_id": "prog-123",
  "requests": [
    {
      "request_id": "req-001",
      "request_status": "Generation completed",
      "video_url": "https://s3.../video.mp4?signed...",
      "duration": "5",
      "prompt": "A woman modeling casual fashion"
    }

  "pagination_token": null
}
```

**Status values:** `"Generation in process"` → `"Generation completed"` or `"Generation failed"`.

---

### 5.28 Upscale Image

```http
POST /upscale
Content-Type: application/json
```

**Use case:** Upscale an image to 2x, 3x, or 4x resolution. Maximum output is 18 million total pixels. Useful for converting try-on or generation results to 4K quality. **Costs 2 credits per image.**

**Request body:**
```json
{
  "image": "<base64 or HTTP URL>",
  "upscale_factor": 2,
  "num_images": 1
}
```

**Fields:**
| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| image | string | yes | — | Base64-encoded image or HTTP URL |
| upscale_factor | float | no | 2 | Upscale factor: 1-4 (2x, 3x, or 4x) |
| num_images | int | no | 1 | Number of output variations (1-4) |
| program_id | string | no | key default | Program ID |

**Response (200):**
```json
{
  "status": "success",
  "request_id": "req-upscale-123",
  "cost": 2
}
```

**Resolution limits:**
- Input 1024x1024 → 2x = 2048x2048 (4.2M px) ✓
- Input 1024x1024 → 4x = 4096x4096 (16.8M px) ✓
- Input 2048x2048 → 4x = 8192x8192 (67.1M px) ✗ exceeds 18M

---

### 5.29 Poll Upscale Status

```http
GET /upscale?request_id={request_id}
```

**Use case:** Poll for upscale results. Returns result URLs when completed.

**Parameters:**
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| request_id | query | string | yes | Request ID from POST /upscale |
| program_id | query | string | no | Program ID |

**Response (200):**
```json
{
  "request_id": "req-upscale-123",
  "status": "success",
  "operation": "Supir_Upscale",
  "result_urls": ["https://s3.../upscaled.png?signed..."]
}
```

**Status values:** `"pending"` → `"success"` or `"failed"`

---

## 6. Error Codes

| Code | Name | Description | Recommended Action |
|------|------|-------------|-------------------|
| 400 | Bad Request | Malformed request | Fix request syntax |
| 401 | Unauthorized | Missing, invalid, or revoked API key | Check API key in Authorization header |
| 402 | Payment Required | Insufficient credits | Check balance, purchase credits |
| 403 | Forbidden | API key not authorized for this team | Use correct team_id matching your key |
| 404 | Not Found | Resource doesn't exist | Verify all IDs (team, program, project, etc.) |
| 422 | Validation Error | Invalid request parameters | Check field types, required fields, enum values |
| 429 | Rate Limited | Too many requests | Exponential backoff, wait 5-30 seconds |
| 500 | Server Error | Internal error | Retry after 5-10 seconds, max 3 retries |

All error responses follow the format:
```json
{"detail": "Human-readable error message"}
```

---

## 7. Complete Workflows

### Workflow A: Generate Model Images (End-to-End)

This is the most common workflow — generating AI fashion model images.

```bash
# Step 1: Check your credit balance
curl "https://api.huhu.ai/public/v1/metering/plan" \
  -H "Authorization: Bearer sk-..."
# → Verify credits > 0 and note concurrent_requests limit

# Step 2: Find your program
curl "https://api.huhu.ai/public/v1/program" \
  -H "Authorization: Bearer sk-..."
# → Pick a program_id from the response

# Step 3: Submit generation request
curl -X POST "https://api.huhu.ai/public/v1/model/image" \
  -H "Authorization: Bearer sk-..." \
  -H "Content-Type: application/json" \
  -d '{
    "program_id": "{program_id}",
    "prompt": "professional studio photo of a woman wearing the garment, white background",
    "num_output_images": 4
  }'
# → Save the request_id from response

# Step 4: Poll for results (every 5-10 seconds)
curl "https://api.huhu.ai/public/v1/model/image?\
request_id={request_id}" \
  -H "Authorization: Bearer sk-..."
# → Check status until "Generation Completed"
# → Download images from images_generated[].model_image_url

# Step 5: Check remaining credits
curl "https://api.huhu.ai/public/v1/metering/plan" \
  -H "Authorization: Bearer sk-..."
```

### Workflow B: Project Organization

```bash
# Step 1: List programs
curl "https://api.huhu.ai/public/v1/program" \
  -H "Authorization: Bearer sk-..."

# Step 2: List existing projects
curl "https://api.huhu.ai/public/v1/project" \
  -H "Authorization: Bearer sk-..."

# Step 3: Create a new project
curl -X POST "https://api.huhu.ai/public/v1/project" \
project_name=Summer%20Campaign" \
  -H "Authorization: Bearer sk-..."

# Step 4: Get project details
curl "https://api.huhu.ai/public/v1/project?project_id={project_id}" \
  -H "Authorization: Bearer sk-..."
```

### Workflow C: Cost Monitoring

```bash
# Check current balance
curl "https://api.huhu.ai/public/v1/metering/plan" \
  -H "Authorization: Bearer sk-..."
# → credits: 99999

# Review detailed spending
curl "https://api.huhu.ai/public/v1/metering/credit-history?\
fetch_all=true" \
  -H "Authorization: Bearer sk-..."
# → Analyze per-operation costs, identify API vs UI usage via request_source
```

### Workflow D: Full Try-On Pipeline (End-to-End)

```bash
AUTH="Authorization: Bearer sk-..."
BASE="https://api.huhu.ai/public/v1"

# 1. Create project
POST $BASE/project  # body: {"project_name":"My Project"}

# 2. Upload zip with garment images
POST $BASE/project/upload  # file_type=sku_zip, file_size=...
# → PUT zip to presigned URL
POST $BASE/project/complete_multipart_upload
# → Poll GET /sku?project_id={project_id} until SKUs appear

# 3. Get a model and its images
GET $BASE/model
GET $BASE/model?model_id={model_id}

# 4. Update SKU with model and style
PUT $BASE/sku
{
  "sku_ids":["sku-001"],
  "model_id":"model-123", "style_id":"style-456",
  "model_images_front":["assets/model_images/xxx.png"],
  "garment_type":"Full body"
}

# 5. Create tasks
POST $BASE/task
# → GET tasks to find task IDs with status "Task created"

# 6. Set model_image on each task (REQUIRED)
PUT $BASE/task
{
  "task_id":"task-001", "sku_id":"sku-001",
  "model_id":"model-123", "model_image":"assets/model_images/xxx.png"
}

# 7. Generate try-on (ALWAYS use HD_P resolution)
POST $BASE/task/generate
{
  "task_ids":["task-001","task-002"],
  "num_output_images":1
}

# 8. Poll until completed (~90s for HD_P)
GET $BASE/task
# → Check task_status until "Generation completed"

# 9. Get output images
GET $BASE/task?project_id={project_id}&sku_id={sku_id}&task_id={task_id}
# → output[].image_url → download PNG

# 10. Generate video from output
POST $BASE/video
{
  "source_task_id":"task-001", "video_length":"5",
  "prompt":"A woman modeling casual fashion, gentle movement",
  "first_frame_image":{"image_base64":"<base64 of output image>"}
}

# 11. Poll video status (~80s)
GET $BASE/video/requests?limit=1
# → request_status: "Generation completed", video_url: "https://..."
```

---

## 8. Decision Trees for AI Agents

Use these decision trees to determine the correct API calls based on user intent. Each tree starts with the user's goal and branches to the right sequence of endpoints.

### 8.1 "How Do I Find or Create the Right Model?"

```
User describes a model: "caucasian middle-aged plus-size woman with blonde hair"
│
├── Step 1: SEARCH existing models
│   GET /model?model_type=Women&model_size=Plus+size&ethnicity=Caucasian&age_range=Middle-aged&hair_color=Blonde
│   ├── Found a match? → Use that model_id. Done.
│   └── No match? → Step 2: CREATE a new model
│
├── Step 2: GENERATE face images
│   POST /model/face
│   {
│     "prompt": "Headshot front facing portrait of a Middle-aged Caucasian Plus size
│                Female with Blonde Long hair with Blue eye with Fair skin tone
│                in the White Studio setting with Neutral expression.
│                Standing pose, professional lighting, high detail.",
│     "mode": "variety",
│     "num_output_images": 4
│   }
│   → Poll GET /model/image?request_id= until completed
│   → Pick best image from images_generated[]
│
├── Step 3: CREATE model entry with attributes
│   POST /model
│   {
│     "model_name": "Caucasian Plus-Size Blonde Woman",
│     "model_type": "Women",
│     "model_size": "Plus size",
│     "attributes": {
│       "ethnicity": "Caucasian", "age_range": "Middle-aged",
│       "hair_color": "Blonde", "hair_style": "Long",
│       "skin_tone": "Fair", "eye_color": "Blue"
│     }
│   }
│   → Save model_id
│
└── Step 4: SAVE generated images to model
    POST /model/image with model_id + image_url from generation result
    → Model is now in gallery with searchable attributes
```

### 8.2 "Which Model Generation Mode Should I Use?"

```
User wants model images
├── Need a specific FACE identity?
│   └── YES → POST /model/face
│       ├── "variety" mode → diverse poses/angles, same face (use_v2_workflow=false)
│       └── "fidelity" mode → closer match to face reference (use_v2_workflow=true)
│
├── Need to replicate a STYLE/COMPOSITION from a reference image?
│   └── YES → POST /model/image with prompt + reference_image
│       └── reference_image = gallery ID, HTTP URL, or base64
│
├── Need model in specific POSES?
│   └── YES → POST /model/pose
│       ├── First: GET /pose/library to browse 143 presets
│       │   ├── "front view" → prompt_type=fullbody, pose_name containing "front"/"upright"
│       │   ├── "back view" → pose_name containing "back"
│       │   ├── "side/profile" → pose_name containing "Semi-profile"/"profile"
│       │   ├── "close-up/headshot" → prompt_type=close_up_upper_body
│       │   └── "full body" → prompt_type=fullbody
│       └── Use reference_image_url from selected pose as reference_image
│
├── Need a model from TEXT DESCRIPTION (attributes)?
│   └── YES → POST /model/image with prompt only
│       └── Prompt template:
│           "Headshot front facing portrait of a {Age} {Race} {BodySize}
│            {Gender} with {HairColor} {HairStyle} hair with {EyeColor} eye
│            with {SkinColor} skin tone in the {Background} setting with
│            {Expression}. {Pose} pose, professional lighting, high detail."
│
└── Need to UPLOAD an existing image to the gallery?
    └── YES → POST /model/image with model_id + (file_data or image_url)
        └── System auto-detects view_type and crop_type via AI
```

### 8.3 "What's the Full Try-On Pipeline?"

```
Goal: Generate model wearing garment(s)

SETUP PHASE (one-time per campaign)
├── 1. GET /metering/plan → verify credits > 0
├── 2. GET /program or POST /program → get/create program_id
├── 3. POST /project → create project
├── 4. Upload garments:
│   ├── Single image → POST /project/upload (file_type=image)
│   └── Bulk (zip) → POST /project/upload (file_type=sku_zip)
│       ├── Zip structure: sku_folder_1/front.jpg, sku_folder_1/back.jpg, ...
│       ├── Do NOT wrap SKU folders in a parent folder
│       ├── PUT file to presigned upload_url
│       ├── POST /project/complete_multipart_upload
│       └── Poll GET /sku?project_id= until SKUs appear (~30s)
│
├── 5. Prepare model images (choose one path):
│   ├── Use existing model → GET /model → GET /model?model_id= → pick images
│   ├── Generate new face → POST /model/face → poll → save to gallery
│   ├── Generate from prompt → POST /model/image (with prompt) → poll → save
│   └── Upload real photos → POST /model/image (with model_id + file_data)
│
└── 6. Assign model to SKUs → PUT /sku with model_id, style_id, model_images_front

GENERATION PHASE (per batch)
├── 7. POST /task → create tasks for selected SKUs
├── 8. PUT /task → set model_image on EACH task (REQUIRED before generation)
├── 9. POST /task/generate → submit generation (always HD_P internally)
│   ├── Optional: prompt for detail control ("slim fit, tucked in")
│   └── num_output_images: 1-4 per task
├── 10. Poll GET /task every 5-10s until "Generation completed" (~90s)
└── 11. GET /task?project_id=&sku_id=&task_id= → download output images

OPTIONAL: POST-GENERATION
├── Change pose → POST /model/pose with output image as reference
├── Generate video → POST /video with output image as first frame
├── Regenerate → POST /task/generate with same task_ids (new results added)
└── Use result as model input → save output, create new task referencing it
```

### 8.4 "How Should I Handle Poses?"

```
User mentions poses/views
├── "Generate different poses of the SAME model"
│   └── POST /model/pose
│       ├── prompt: describe desired pose
│       ├── reference_image: an existing model image from gallery
│       └── Results maintain face/clothing/background consistency
│
├── "I need front view, back view, profile, etc."
│   └── Step 1: GET /pose/library?model_category={Women|Men|Child|Infant}
│   └── Step 2: Filter by prompt_type or search pose_name:
│       ├── Front view → "Full body upright", "Full body straight on"
│       ├── Back view → "Full body straight on back view"
│       ├── Profile → "Full body Semi-profile", "Semi-profile left"
│       ├── Close-up → prompt_type=close_up_upper_body
│       ├── Walking → "Full body walking"
│       └── Seated → check pose_name for "seated" or "sitting"
│   └── Step 3: POST /model/pose with each selected pose's reference_image_url
│
├── "Create a custom pose from a photo"
│   └── POST /pose with reference_image (person in desired pose)
│       └── AI extracts pose_prompt automatically → reuse in POST /model/pose
│
└── "Select multiple poses for batch generation"
    └── GET /pose/library → select up to 6 poses
    └── For each pose: POST /model/pose separately → poll each request_id
```

### 8.5 "How Do I Control Try-On Details?"

```
Detail control in POST /task/generate prompt field:

TOP GARMENT
├── Tucking → "tucked in" or "untucked"
├── Sleeve length → "short sleeves", "long sleeves", "3/4 sleeves"
├── Cuff style → "cuffed sleeves", "bishop sleeves", "bell sleeves", "bat wing"
└── Outer layer → "zipped up", "open/unbuttoned"

BOTTOM GARMENT
├── Tucked into boots → "pants tucked into boots"
├── Length → "ankle length", "capri", "over-long/pooling"
├── Opening → "narrow/tapered", "flared", "wide-leg"
└── Note: Shorts do NOT support bottom garment options (may cause artifacts)

OVERALL FIT
├── "slim fit" → tighter on body
├── "moderate fit" (default)
├── "loose fit" → relaxed drape
└── "oversize" → deliberately oversized look

MIX & MATCH (top + bottom pairing)
├── Upload separate top/bottom garments in SKU zip
├── System auto-pairs via garment classification
└── garment_type in PUT /sku: "top", "bottom", or "Full body"
```

### 8.6 "What Video Template Should I Use?"

```
User wants video from generated images
├── Single product shot, one angle?
│   └── template: "Single clip" (5s or 10s)
│
├── Multiple scenes/transitions between images?
│   └── template: "Multi clip" (customizable duration)
│   └── Provide multiple images for different scenes
│
├── Rotating front-to-back view?
│   └── template: "180-degree turn around" (5s or 10s)
│   └── Need at least 1 front + 1 back image
│
├── Full 360° rotation?
│   └── template: "360-degree turn around" (10s, 15s, or 20s)
│   └── Need front + back + side images for best results
│
└── Bulk video for many SKUs?
    └── Max 50 SKUs per batch, max 20 concurrent jobs
    └── Minimum 1024px resolution, multiple angles per SKU recommended
    └── Test with 3-5 SKUs first before full batch

Credit costs:
  5s video = 25 credits
  10s video = 40 credits
```

### 8.7 "How Do I Handle Regeneration?"

```
Unhappy with try-on result?
├── Everything looks wrong (pose, fit, details)
│   └── POST /task/generate with same task_ids again
│   └── New results are added to the same task (previous kept)
│
├── Like the pose but details are wrong
│   └── Save result → use as model_image in a new task
│   └── PUT /task → POST /task/generate
│   └── Preserves pose while improving garment fit
│
├── Need visual consistency across all SKU images
│   └── Save a good result → use as garment reference for other tasks
│   └── Ensures uniform look across product line
│
└── Need different pose of same result
    └── POST /model/pose with result image as reference_image
    └── Select target pose from GET /pose/library
```

### 8.8 Master Decision Flowchart

```
USER REQUEST
│
├── "Check my credits/plan" → GET /metering/plan
├── "Show spending history" → GET /metering/credit-history
│
├── "Create/list programs" → GET /program or POST /program
├── "Create/list projects" → GET /project or POST /project
│
├── "Generate model images"
│   ├── Has face reference? → POST /model/face (variety or fidelity)
│   ├── Has style reference? → POST /model/image (prompt + reference_image)
│   ├── Text description only? → POST /model/image (prompt only)
│   └── Specific pose needed? → POST /model/pose
│       └── Don't know which pose? → GET /pose/library first
│
├── "Upload product images"
│   ├── Single image → POST /project/upload (file_type=image)
│   └── Bulk zip → POST /project/upload (sku_zip) → complete_multipart_upload
│
├── "Do virtual try-on"
│   └── PUT /sku (assign model) → POST /task → PUT /task (set model_image)
│       → POST /task/generate → poll GET /task
│
├── "Create video" → POST /video → poll GET /video/requests
│
├── "Browse/create poses"
│   ├── Preset library → GET /pose/library
│   ├── Custom poses → GET /pose or POST /pose
│   └── Generate with pose → POST /model/pose
│
└── ANY generation request → always poll:
    ├── Image/face/pose gen → GET /model/image?request_id=
    ├── Try-on → GET /task (check task_status)
    └── Video → GET /video/requests
```

---

## 9. OpenAPI Specification

The public API provides a machine-readable OpenAPI 3.x specification for tool-use agents, SDK generation, and automated integration:

```
GET /public/v1/openapi.json
```

This endpoint requires **no authentication** and returns the complete OpenAPI spec for all public endpoints. Use it to:
- Auto-generate function/tool definitions for LLM agents
- Import into Postman, Swagger UI, or API testing tools
- Generate client SDKs in any language

**Servers defined in spec:**
- Production: `https://api.huhu.ai`
- Gamma/Dev: `https://api-gamma.huhu.ai`

---

## 10. Best Practices for AI Agents

### Resource Discovery Pattern
```
1. GET /program              → find program_id
2. GET /project → find project_id
3. GET /task    → find tasks and outputs
```

### Generation Safety Pattern
```
1. GET /metering/plan → check credits > estimated_cost
2. POST /model/image → submit job
3. GET /task → poll every 5-10 seconds until completed
4. GET /metering/plan → verify credits deducted correctly
```

### Polling Best Practices
- Poll `/task` every **5-10 seconds** (not faster)
- Stop polling after **5 minutes** — the generation likely failed
- Check for `"Generation failed"` status and surface the error
- Completed tasks have `output_image_url` with presigned S3 URLs (valid for ~1 hour)

### Error Handling
- **401**: Re-check API key. May have been revoked.
- **402**: Stop all generation. Notify user to add credits.
- **403**: You're using the wrong team_id. Verify it matches your API key.
- **422**: Log the full error detail and fix the request body.
- **429/500**: Exponential backoff starting at 5s, max 3 retries.

### Cost Estimation
Before generation, estimate cost:
```
cost = num_output_images × credit_per_image
```
For image generation requests: `credit_per_image = 2` (ImageSameIDGeneration)
For HD multipose: `credit_per_image = 2` (ImageGenerationHD)

### Timestamps
All timestamps in the API are **Unix epoch in milliseconds** (not seconds).
```
timestamp_ms = 1773702421471
timestamp_s  = timestamp_ms / 1000
date         = new Date(timestamp_ms)  // 2026-03-16T...
```

### IDs
All IDs are UUIDs or hex strings. Always store and compare as strings, never parse as numbers.

---

## 11. Quick Reference Card

```
BASE: https://api.huhu.ai/public/v1
AUTH: Authorization: Bearer sk-<key>
NOTE: team_id auto-resolved from key. program_id optional if key has default.

METERING
  GET  /metering/plan                                → credits & plan
  GET  /metering/credit-history                      → spending history

PROGRAMS
  GET  /program                                      → all programs
  GET  /program?owned=true                          → owned programs
  POST /program=                 → create program

PROJECTS
  GET  /project                             → list projects (add ?project_id= for detail)
  POST /project                             → create project

MODELS
  POST /model                               → create model
  GET  /model                               → list models (add ?model_id= for images)
  POST /model/face                         → generate face (variety/fidelity)
  POST /model/image                        → generate or upload model image
  GET  /model/image?request_id=            → poll generation status
  POST /model/pose                         → generate with poses

POSES
  GET  /pose/library                        → browse 143 preset poses (filter by model_category, prompt_type)
  GET  /pose                                → list custom poses (add ?pose_id= for detail)
  POST /pose                                → create pose from reference image

SKUS
  GET  /sku?project_id=                     → list SKUs (add &sku_id= for detail)
  PUT  /sku                                 → update SKU (model, style)

FILE UPLOADS
  POST /project/upload                      → upload images or init zip
  POST /project/complete_multipart_upload   → finalize zip upload

TASKS & TRY-ON
  POST /task                         → create tasks for SKUs
  PUT  /task                                → set model_image on task
  POST /task/generate                       → try-on generation
  GET  /task                                → list tasks (add ?project_id=&sku_id=&task_id= for detail)

VIDEO
  POST /video                               → create video
  GET  /video/requests                      → poll video status

UPSCALE
  POST /upscale                             → upscale image (2x-4x, max 18M px)
  GET  /upscale?request_id=                 → poll upscale status

CREDIT COSTS
  Same-ID / HD Generation: 2     Multipose: 2       Editing/Upscale: 2
  Video (5s): 25                 Video (10s): 40
```

// 1773982518
