> For the complete documentation index, see [llms.txt](https://docs.saas.haut.ai/haut.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.saas.haut.ai/haut.ai/quick-start-guides/build-custom-app-powered-by-haut.ai/e2e-anonymization-pipeline.md).

# E2E Anonymization Pipeline

This page explains when and how to use Skin Atlas anonymization in a custom app before sending images to Haut.AI SaaS analysis.

Use this guide when your custom app needs to collect face images, anonymize them with [Skin Atlas](/haut.ai/haut.ai-features/algorithms/skin-atlas.md) on the user's device, and send the anonymized image to Haut.AI SaaS for [Face Skin Analysis 3.0](/haut.ai/haut.ai-features/algorithms/face-skin-analysis-3-0.md).

{% hint style="warning" %}
Skin Atlas is a premium feature. Please contact your account manager to confirm that it is enabled for your account. The LIQA license key is not part of that request — on the Professional plan you create it yourself in SaaS, see [LIQA](/haut.ai/haut.ai-features/liqa.md#manage-liqa-licenses-in-saas).
{% endhint %}

## Choose the right privacy option

Before implementation, decide whether your product needs standard image analysis or pre-upload anonymization.

| Option                                                    | Use when                                                                                                                                                                                               | Pros                                                                                                                                                                                                                                                                                                | Cons                                                                                                                                                                                                                                         |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Standard original-image analysis**                      | Your app can upload and store the original face image in SaaS, and the product needs simpler integration, easier operational review, or full visual auditability.                                      | Fastest path to production and the clearest source image for review.                                                                                                                                                                                                                                | Higher identifiable-data exposure because the original face image enters SaaS storage and processing.                                                                                                                                        |
| **LIQA on-device Skin Atlas anonymization before upload** | Your app serves privacy-first, research, clinical, enterprise, or strict data-minimization workflows where identifiable facial features should be minimized before the image leaves the user's device. | Removes identifiable features—eyes, mouth, hair, and background—before the image leaves the device, reconstructing non-skin regions with photorealistic skin texture so only the skin attributes needed for analysis remain. Improves privacy and processing speed for high-volume image workflows. | Requires premium access, a dependency on LIQA, more client-side implementation work, device and browser requirements, less ability to inspect original images in SaaS, and may hit canvas-size limits for very large uploaded images on iOS. |

For most consumer apps, standard original-image analysis is the simpler default. Choose the anonymized pipeline when privacy positioning, consent requirements, research protocol, or enterprise procurement makes pre-upload data minimization important enough to justify the extra implementation work.

## Choose the capture preset

| Preset     | Use when                                                                                                                                                                               | Pros                                                           | Cons                                                                                                        |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `face`     | Your experience needs one frontal face image for skin analysis, recommendations, or a fast selfie flow.                                                                                | Shorter user journey and simpler upload mapping.               | Less side-face coverage.                                                                                    |
| `face-180` | Your experience needs front, left, and right face images so the analysis algorithms receive more skin coverage, which produces more complete results—especially for side-face regions. | More skin data for the analysis and better side-face coverage. | Longer user journey, higher drop-off risk, three anonymized image outputs, and more upload/result handling. |

If you are not sure which preset to start with, use `face` for the first production iteration and move to `face-180` when you need the analysis to cover more of the face, such as side-face regions.

## End-to-end flow

The anonymized pipeline follows the same SaaS batch flow described in [Integrate Haut.AI Image Analysis with Your App](/haut.ai/quick-start-guides/build-custom-app-powered-by-haut.ai/integrate-haut.ai-image-analysis-with-your-app.md), with one important difference: your app uploads the anonymized image generated by LIQA instead of uploading the original captured image.

1. Configure LIQA with the required preset and `postprocessing="anonymized"`.
2. Listen for the LIQA `captures` event.
3. For each capture, call `capture.anonymized()`.
4. Select the `anonymizedImage` blob from the returned pipeline artifacts.
5. Initiate upload in SaaS for `front`, or for `front`, `left`, and `right` when using `face-180`.
6. Upload each anonymized image blob to the signed storage URL returned by SaaS.
7. Call the batch `/compute/` method.
8. Receive results by webhook or fetch them with the batch results API.

For the LIQA side of this flow, see the LIQA guide: [Access anonymized image processing pipeline](https://docs.liqa.haut.ai/getting-started/advanced-usage.html#access-anonymized-image-processing-pipeline).

## Configure LIQA

Use `postprocessing="anonymized"` to enable Skin Atlas processing. Replace `YOUR_LIQA_LICENSE_KEY` with a LIQA license key created in SaaS. See [LIQA](/haut.ai/haut.ai-features/liqa.md#manage-liqa-licenses-in-saas).

```html
<hautai-liqa
  license="YOUR_LIQA_LICENSE_KEY"
  preset="face"
  postprocessing="anonymized"
></hautai-liqa>
```

For a Face 180 flow, switch the preset:

```html
<hautai-liqa
  license="YOUR_LIQA_LICENSE_KEY"
  preset="face-180"
  postprocessing="anonymized"
></hautai-liqa>
```

The same setting is available in the LIQA JavaScript configuration:

```ts
const liqa = new Liqa({
  license: "YOUR_LIQA_LICENSE_KEY",
  target: "#liqa-container",
  preset: "face",
  postprocessing: "anonymized",
})
```

For the full configuration surface, see the [LIQA API Reference](https://docs.liqa.haut.ai/reference.html).

## Handle anonymized captures

The `anonymized()` method returns the processing artifacts for each capture. Use `anonymizedImage` as the file you upload to SaaS when the product goal is pre-upload anonymization.

```ts
function getAnonymizedImageBlob(anonymized) {
  const image = anonymized.blobs.find(
    (blobData) => blobData.type === "anonymizedImage"
  )

  if (!image) {
    throw new Error("LIQA did not return an anonymized image")
  }

  return image.data
}

async function handleCaptures(event) {
  const captures = event.detail

  for (const capture of captures) {
    const anonymized = await capture.anonymized()
    const imageBlob = getAnonymizedImageBlob(anonymized)

    // Upload imageBlob to the SaaS signed URL for this capture.
    console.log(capture.metadata?.side ?? capture.source, imageBlob)
  }
}

document
  .querySelector("hautai-liqa")
  .addEventListener("captures", handleCaptures)
```

LIQA also returns these artifacts for traceability and debugging:

| Artifact            | What it is for                                                                                                                                                                                      |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `originalImage`     | The captured image before anonymization. Avoid storing or uploading it if your product goal is strict data minimization.                                                                            |
| `imageRestored`     | The restored or enhanced intermediate image used during processing. It still shows identifiable facial features, so avoid storing or uploading it if your product goal is strict data minimization. |
| `segmentationMask`  | The mask showing detected face and skin regions used by the privacy processing.                                                                                                                     |
| `anonymizedImage`   | The final Skin Atlas image to upload to SaaS for the anonymized pipeline.                                                                                                                           |
| `transformSequence` | The list of transformations applied during anonymization. Store it only if your workflow needs reproducibility or audit support.                                                                    |
| `mesh`              | Face mesh and landmark data used by the anonymization pipeline. Store it only if your workflow needs reproducibility or audit support.                                                              |

## Prepare images for SaaS upload

For the `face` preset, upload one anonymized image as `front`.

```ts
const uploadFiles = {
  front: anonymizedImageBlob,
}
```

For `face-180`, upload the three anonymized images with the SaaS side keys `front`, `left`, and `right`.

```ts
function sideFromCapture(capture) {
  const side = capture.metadata?.side

  if (side === "front") return "front"
  if (side === "left") return "left"
  if (side === "right") return "right"

  throw new Error(`Unsupported capture side: ${side}`)
}

async function buildUploadFiles(captures) {
  const files = {}

  for (const capture of captures) {
    const anonymized = await capture.anonymized()
    files[sideFromCapture(capture)] = getAnonymizedImageBlob(anonymized)
  }

  return files
}
```

## Send anonymized images to SaaS

After preparing `uploadFiles`, use the regular [API for Images](/haut.ai/developers/saas-api-overview/api-for-face-skin-analysis-3.0/api-for-images.md) flow. The same API flow works for both one-side and three-side captures.

### Initiate upload for the Subject

Create an upload session for the Subject and pass the side keys you are going to upload.

For a one-side `face` flow, initiate upload with only the `front` side:

```json
{
  "front": "front-anonymized.jpg"
}
```

For a three-side `face-180` flow, initiate upload with all three side keys:

```json
{
  "front": "front-anonymized.jpg",
  "left": "left-anonymized.jpg",
  "right": "right-anonymized.jpg"
}
```

```ts
async function initiateUpload({ companyId, subjectId, token, fileNamesBySide }) {
  const response = await fetch(
    `/api/v3/companies/${companyId}/subjects/${subjectId}/upload/`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(fileNamesBySide),
    }
  )

  if (!response.ok) {
    throw new Error("Failed to initiate SaaS image upload")
  }

  return response.json()
}
```

### Upload each anonymized blob

The upload response contains a signed storage URL for each side. Upload each anonymized image blob to the matching side target.

```ts
async function uploadToSignedUrls(uploadResponse, uploadFiles) {
  for (const [side, blob] of Object.entries(uploadFiles)) {
    const uploadTarget = uploadResponse[side]

    if (!uploadTarget) {
      throw new Error(`SaaS upload response is missing ${side}`)
    }

    const response = await fetch(uploadTarget.url, {
      method: uploadTarget.method || "PUT",
      headers: uploadTarget.headers,
      body: blob,
    })

    if (!response.ok) {
      throw new Error(`Failed to upload ${side} image`)
    }
  }

  return uploadResponse.image_batch_id
}
```

### Save the image batch ID

Keep `image_batch_id` from the upload response. You need it to start computation and fetch results for the same batch.

```ts
const imageBatchId = uploadResponse.image_batch_id
```

### Send the batch to computation

After every signed URL upload succeeds, call the compute endpoint for the saved batch ID.

```ts
async function computeBatch({ companyId, imageBatchId, token }) {
  const response = await fetch(
    `/api/v3/companies/${companyId}/batches/${imageBatchId}/compute/`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
      },
    }
  )

  if (!response.ok) {
    throw new Error("Failed to start Face Skin Analysis 3.0 computation")
  }
}
```

### Receive or fetch the completed results

Your backend can either receive the completed result through webhooks or fetch it with the batch results endpoint. The result-handling examples below use the fetch option because it is easier to show in one sequence.

### One-side full sequence

For the `face` preset, convert the single LIQA capture to one `front` upload and run computation:

```ts
async function runOneSideAnonymizedAnalysis({
  capture,
  companyId,
  subjectId,
  token,
}) {
  const anonymized = await capture.anonymized()
  const uploadFiles = {
    front: getAnonymizedImageBlob(anonymized),
  }

  const uploadResponse = await initiateUpload({
    companyId,
    subjectId,
    token,
    fileNamesBySide: {
      front: "front-anonymized.jpg",
    },
  })

  const imageBatchId = await uploadToSignedUrls(uploadResponse, uploadFiles)

  await computeBatch({ companyId, imageBatchId, token })

  return fetchBatchResults({ companyId, imageBatchId, token })
}
```

### Three-side full sequence

For the `face-180` preset, convert all LIQA captures to `front`, `left`, and `right` uploads:

```ts
async function runThreeSideAnonymizedAnalysis({
  captures,
  companyId,
  subjectId,
  token,
}) {
  const uploadFiles = await buildUploadFiles(captures)
  const fileNamesBySide = {
    front: "front-anonymized.jpg",
    left: "left-anonymized.jpg",
    right: "right-anonymized.jpg",
  }

  const uploadResponse = await initiateUpload({
    companyId,
    subjectId,
    token,
    fileNamesBySide,
  })

  const imageBatchId = await uploadToSignedUrls(uploadResponse, uploadFiles)

  await computeBatch({ companyId, imageBatchId, token })

  return fetchBatchResults({ companyId, imageBatchId, token })
}
```

## Read analysis results

Face Skin Analysis 3.0 returns the calculated parameters in the same results structure used by the standard image-analysis flow. You can receive the result by webhook or fetch it for the completed batch:

```ts
async function fetchBatchResults({ companyId, imageBatchId, token }) {
  const response = await fetch(
    `/api/v4/companies/${companyId}/batches/${imageBatchId}/results/`,
    {
      method: "GET",
      headers: {
        Authorization: `Bearer ${token}`,
      },
    }
  )

  if (!response.ok) {
    throw new Error("Failed to fetch Face Skin Analysis 3.0 results")
  }

  return response.json()
}
```

A completed batch result can include Face Skin Analysis 3.0 data under `face_skin_analysis_3`:

```json
{
  "batch_id": "40043ef0-dd71-4d22-a1c4-23ad87786226",
  "subject_id": "351b71b4-2ad6-4ffc-9114-557ab7e9a75c",
  "can_watch_predicted_images": true,
  "face_skin_analysis_3": {
    "all_algorithms_calculated": true,
    "detection_errors": {
      "front": null,
      "left": null,
      "right": null
    },
    "predicted_images": {
      "front": {
        "original": "https://...",
        "anonymised": "https://...",
        "aligned_face": "https://..."
      },
      "left": {
        "original": "https://...",
        "anonymised": "https://...",
        "aligned_face": "https://..."
      },
      "right": {
        "original": "https://...",
        "anonymised": "https://...",
        "aligned_face": "https://..."
      }
    },
    "parameters": {
      "breakouts": {
        "errors": {
          "front": null,
          "left": null,
          "right": null
        },
        "tag": "Good",
        "grade": 2,
        "score": 85,
        "masks": {
          "front": {
            "anonymised": "https://..."
          }
        }
      },
      "quality": {
        "front": {
          "score": 98,
          "has_face": true,
          "has_no_blur": true,
          "has_good_resolution": true
        }
      }
    }
  }
}
```

Your app should still handle:

* `all_algorithms_calculated` to check whether all enabled algorithms completed.
* `parameters` as an object keyed by skin parameter name.
* Per-parameter `errors` values for available sides.
* `predicted_images` URLs for the image variant your product displays.
* `masks` URLs inside individual parameters when your product overlays detected features.

Use a small adapter so the UI code does not care whether the API response uses `face_skin_analysis_3` or an older result key:

```ts
function getFaceSkinAnalysis3Result(results) {
  const result = results.face_skin_analysis_3 || results.face_skin_metrics_3

  if (!result) {
    throw new Error("Face Skin Analysis 3.0 result is missing")
  }

  return result
}
```

## Show results in your app

For both one-side and three-side flows, choose the sides from the result object rather than hard-coding UI assumptions.

```ts
function getAvailableSides(fsa3) {
  return Object.keys(fsa3.predicted_images || {})
}

function getDisplayImage(fsa3, side) {
  return fsa3.predicted_images?.[side]?.anonymised
    || fsa3.predicted_images?.[side]?.aligned_face
    || fsa3.predicted_images?.[side]?.original
}

function getParameterSummary(fsa3, parameterName) {
  const parameter = fsa3.parameters?.[parameterName]

  if (!parameter || parameter.error) {
    return null
  }

  return {
    tag: parameter.tag,
    grade: parameter.grade,
    score: parameter.score,
    errors: parameter.errors,
  }
}
```

For a one-side `face` flow, render only the `front` side:

```ts
function buildOneSideViewModel(results) {
  const fsa3 = getFaceSkinAnalysis3Result(results)
  const side = "front"

  return {
    side,
    imageUrl: getDisplayImage(fsa3, side),
    quality: fsa3.parameters?.quality?.[side],
    breakouts: getParameterSummary(fsa3, "breakouts"),
    fineLines: getParameterSummary(fsa3, "fine_lines"),
  }
}
```

For a three-side `face-180` flow, render one panel per available side:

```ts
function buildThreeSideViewModel(results) {
  const fsa3 = getFaceSkinAnalysis3Result(results)

  return getAvailableSides(fsa3).map((side) => ({
    side,
    imageUrl: getDisplayImage(fsa3, side),
    detectionError: fsa3.detection_errors?.[side],
    quality: fsa3.parameters?.quality?.[side],
    masks: {
      breakouts: fsa3.parameters?.breakouts?.masks?.[side]?.anonymised,
      fineLines: fsa3.parameters?.fine_lines?.masks?.[side]?.anonymised,
      pigmentation: fsa3.parameters?.pigmentation?.masks?.[side]?.anonymised,
    },
  }))
}
```

When showing results:

* Display the `anonymised` image variant when available, because it matches the privacy promise of this pipeline.
* Use the matching `masks[side].anonymised` URL when overlaying masks on anonymized images.
* If `detection_errors[side]` or a parameter-level `errors[side]` is not `null`, hide that side or parameter and show a neutral fallback.
* Treat signed image and mask URLs as temporary. Store the analysis values you need, but fetch fresh URLs when the user opens the result later.

Do not assume the anonymized flow removes the need for product-level consent, retention, and access-control decisions. It reduces identifiable visual data before upload, but your app still controls how Subjects are created, which results are stored, and how long data is retained.

***

{% hint style="warning" %}
Not a Haut.AI client yet?
{% endhint %}

<a href="https://haut.ai/book-a-demo?utm_source=referral&#x26;utm_medium=saas_docs&#x26;utm_campaign=Docs_SaaS_HautAI" class="button primary" data-icon="user-vneck">Book a demo</a> <a href="https://haut.ai/product/ai-skin-analysis?utm_source=referral&#x26;utm_medium=saas_docs&#x26;utm_campaign=Docs_SaaS_HautAI" class="button secondary" data-icon="external-link">Platform overview on haut.ai</a>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.saas.haut.ai/haut.ai/quick-start-guides/build-custom-app-powered-by-haut.ai/e2e-anonymization-pipeline.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
