> ## 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.

# Upload Customer Image

> Upload a customer photo and get back an imageId you can reuse across generations.

Upload a customer photo. The server validates the image, normalizes orientation and color, optionally applies a 4:5 person-aware crop, and stores the result. The returned `imageId` is an opaque token — pass it to [`POST /try-on`](/tryon-api/endpoints/create-try-on) as `person.image.source.id` to run as many generations as you want against the same image.

**This is the recommended path.** The `/try-on` endpoint also accepts inline `person.image.source.url` and `person.image.source.fileKey` — convenient for one-shots, but they re-download/re-upload on every call. Pre-uploading is cheaper if you'll run more than one generation against the same photo.

Supported formats: JPEG, PNG, WebP, HEIC. Max file size: 10 MB.

## Request

Send as `multipart/form-data`.

<ParamField body="file" type="file" required>
  The customer photo.
</ParamField>

<ParamField body="crop" type="boolean" default="true">
  Apply the 4:5 person-aware crop. Set to `false` for studio shots, model previews, or any case where you want to preserve the original framing.
</ParamField>

<ParamField body="externalUserId" type="string">
  Optional opaque identifier for the end user this image belongs to. **No PII.** When set, the upload is tracked server-side so you can later wipe it with [`DELETE /customers/:customerId`](/tryon-api/endpoints/delete-customer) (GDPR / right-to-erasure — pass the same value as the path segment).
</ParamField>

<ParamField body="keepForDays" type="integer">
  Optional retention preset for this upload. One of `1`, `3`, `7`. Defaults to your account's configured customer-retention window. After this many days the underlying file is deleted automatically.
</ParamField>

<Note>The previous form fields `customerId` and `retentionDays` are still accepted but **deprecated** (not a breaking change). Use `externalUserId` and `keepForDays`.</Note>

<RequestExample>
  ```bash cURL (default — crop on) theme={null}
  curl -X POST "https://api.genlook.app/tryon/v1/images/upload" \
    -H "x-api-key: gk_your_api_key" \
    -F "file=@customer-photo.jpg"
  ```

  ```bash cURL (skip crop) theme={null}
  curl -X POST "https://api.genlook.app/tryon/v1/images/upload" \
    -H "x-api-key: gk_your_api_key" \
    -F "file=@customer-photo.jpg" \
    -F "crop=false"
  ```

  ```javascript JavaScript theme={null}
  const formData = new FormData();
  formData.append('file', fileInput.files[0]);
  formData.append('crop', 'true'); // omit for the default

  const { imageId, imageUrl } = await fetch(
    'https://api.genlook.app/tryon/v1/images/upload',
    { method: 'POST', headers: { 'x-api-key': API_KEY }, body: formData }
  ).then((r) => r.json());
  ```

  ```python Python theme={null}
  import requests

  with open('customer-photo.jpg', 'rb') as f:
      r = requests.post(
          'https://api.genlook.app/tryon/v1/images/upload',
          headers={'x-api-key': API_KEY},
          files={'file': ('customer.jpg', f, 'image/jpeg')},
          data={'crop': 'true'},
      )
  r.raise_for_status()
  image_id = r.json()['imageId']
  ```

  ```ts Node SDK theme={null}
  import { Genlook } from "@genlook/api";
  import { readFile } from "node:fs/promises";

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

  const { imageId, imageUrl } = await client.images.upload(
    await readFile("./customer-photo.jpg"),
    {
      mimeType: "image/jpeg",
      // crop: false,               // skip the 4:5 person-aware crop
      // externalUserId: "user_42", // tag for GDPR delete (no PII)
      // keepForDays: 7,            // override account default
    },
  );
  ```
</RequestExample>

## Response

<ResponseField name="imageId" type="string" required>
  Opaque token. Pass to [`POST /try-on`](/tryon-api/endpoints/create-try-on) as `person.image.source.id`.
</ResponseField>

<ResponseField name="imageUrl" type="string" required>
  Temporary URL pointing to the processed image. Use it for previews; the URL expires after a short window, so re-fetch the upload response if you need a fresh one.
</ResponseField>

<ResponseExample>
  ```json Success theme={null}
  {
    "imageId": "img_9f86d081ab",
    "imageUrl": "https://storage.googleapis.com/..."
  }
  ```

  ```json Invalid format (400) theme={null}
  {
    "code": "UNSUPPORTED_IMAGE_TYPE",
    "message": "Unsupported image format — accepted formats are JPEG, PNG, WebP, and HEIC.",
    "status": 400
  }
  ```
</ResponseExample>

## Retention

Uploaded images are auto-deleted after `keepForDays` (or your account's default window if you omit it; default **7 days**). The `imageId` is valid for that window — after that, you'll need to re-upload. If you set `externalUserId`, the image can also be wiped on demand via [`DELETE /customers/:customerId`](/tryon-api/endpoints/delete-customer).
