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

# Profiles

> Understand how profiles enable identity-preserving photo editing and generation

export const MIN_QUICK_PROFILE_PHOTOS = 5;

export const MIN_PROFILE_PHOTOS = 10;

export const MAX_QUICK_PROFILE_PHOTOS = 10;

export const MAX_PROFILE_PHOTOS = 50;

Profiles are the core concept that makes Phota different from generic image generation. A profile teaches the API what a
specific person looks like, so their identity is preserved across all edits and generations.

***

## What is a profile?

A profile is a learned representation of a specific person's appearance. You create one by uploading reference
photos of the same person — {MIN_PROFILE_PHOTOS}–{MAX_PROFILE_PHOTOS} for the default Full Train tier, or {MIN_QUICK_PROFILE_PHOTOS}–{MAX_QUICK_PROFILE_PHOTOS} for Quick Train (see [Training tiers](#training-tiers)).
Phota processes these photos to learn the subject's facial features, hair, skin tone, and overall appearance.

Once a profile is trained, you can use it across unlimited edit, generate, and enhance requests. The API preserves the
person's identity even through dramatic transformations -- placing them on a beach, changing their outfit, or restyling
the entire scene.

<Card>
  <img src="https://storage.googleapis.com/photaserverless/phota_api/docs/grid.jpg" alt="Training photos" />

  Upload {MIN_PROFILE_PHOTOS}–{MAX_PROFILE_PHOTOS} reference photos to create a profile ({MIN_QUICK_PROFILE_PHOTOS}–{MAX_QUICK_PROFILE_PHOTOS} for Quick Train). Group shots are OK as long as your intended subject is the
  most common one across all photos.
</Card>

***

## How profiles work

Profiles work in two phases:

1. **Training** -- you upload reference photos and the API learns the subject's appearance (facial features, hair, skin tone, etc.).
2. **Inference** -- when you call `/edit`, `/generate`, or `/enhance`, the API recognizes people in the input image
   (or described in the prompt) and decides which profiles from the candidate profiles to use to preserve their
   identities in the output. The fewer candidate profiles you pass, the easier it is for the API to match the right one.
   If a profile is not provided in the candidate profiles, the API will not consider it when matching.

**A typical integration:** your user uploads photos -> you call the profile training endpoint -> you get back a
`profile_id` -> you pass that profile in every edit or generation you run for them. Multi-person scenes work too —
reference multiple `[[profile_id]]` values in a single prompt.

<Warning>
  Profiles are opt-in per request. If you don't pass any profile IDs, the API will not attempt to preserve anyone's identity, even if trained profiles exist in your account. For anything where the subject needs to look like a specific person, include the relevant profile IDs.
</Warning>

***

## Profile creation

Creating and using a profile follows this flow:

```mermaid actions={false} theme={null}
flowchart LR
    Upload["Upload 10-50 photos (5-10 for Quick Train)"] --> Training["Training starts"]
    Training --> Poll["Poll status"]
    Poll -->|VALIDATING / QUEUING / IN_PROGRESS| Wait["Wait 10-30s"]
    Wait --> Poll
    Poll -->|READY| Use["Use in /edit, /generate, /enhance"]
    Poll -->|ERROR| Error["Check error message"]
```

<Steps>
  <Step title="Create the profile">
    Send the reference image URLs to `POST /profiles/add`. Training starts immediately and runs asynchronously. You receive
    a `profile_id` right away.

    ```json theme={null}
    {
      "image_urls": ["https://example.com/photo1.jpg", "https://example.com/photo2.jpg", "https://example.com/photo3.jpg"],
      "tag": "user_abc123"
    }
    ```

    <Tip>
      The `tag` field is optional. Use it to group profiles by your own identifiers (e.g., end-user usernames) so you can
      filter them later with `GET /profiles/ids?tag=user_abc123`. See [Tagging profiles](#tagging-profiles) below.
    </Tip>
  </Step>

  <Step title="Poll for training status">
    Call `GET /profiles/{profile_id}/status` until the status is `READY`. Training typically takes \~20 minutes.

    | Status        | Meaning                                                                                          |
    | ------------- | ------------------------------------------------------------------------------------------------ |
    | `VALIDATING`  | Images are being validated and prepared. Poll again in 10-30 seconds.                            |
    | `QUEUING`     | Training job is queued. Poll again in 10-30 seconds.                                             |
    | `IN_PROGRESS` | Model is actively training. Expected to finish within \~20 minutes. Poll again in 10-30 seconds. |
    | `READY`       | Training completed. The profile is ready for use.                                                |
    | `ERROR`       | Training failed. Check the `message` field for details.                                          |
  </Step>

  <Step title="Use the profile">
    Once ready, reference the profile in any edit, generate, or enhance request. See
    [Referencing profiles in requests](#referencing-profiles-in-requests) below.
  </Step>
</Steps>

***

## Tagging profiles

Tags let you group profiles by your own identifiers -- for example, your end-user's username or account ID. This is
useful when your API key manages profiles for many end-users and you need to list only the profiles belonging to a
specific one.

### Setting a tag

Pass the optional `tag` field when creating a profile:

```json theme={null}
{
  "image_urls": ["https://example.com/photo1.jpg", "..."],
  "tag": "user_abc123"
}
```

Tags are free-form strings up to 128 characters. A profile can have at most one tag, and multiple profiles can share
the same tag.

A tag is permanent: once a profile has one it cannot be changed or removed, so choose an identifier that stays stable
for the life of the profile. Profiles created before you adopted tags can still be tagged -- see
[Backfilling tags](#backfilling-tags-onto-existing-profiles) below.

### Backfilling tags onto existing profiles

Profiles created before you adopted tags can be tagged after the fact with `POST /profiles/tags`. Send up to 1,000
assignments per request (each profile at most once per batch):

```json theme={null}
{
  "tags": [
    { "profile_id": "abc123...", "tag": "user_abc123" },
    { "profile_id": "def456...", "tag": "user_def456" }
  ]
}
```

A tag is set only where the profile has none yet -- an existing tag is never overwritten. The `200` response reports
every outcome: `updated` counts the profiles tagged by this request, and the `already_tagged` (same value resubmitted),
`conflicts` (profile already carries a different tag, echoed as `existing_tag`), and `not_found` (unknown, deleted, or
not owned by your account) lists name the profiles that were left unchanged. Resubmitting the same mapping is safe, so
you can re-run a backfill after a partial failure. To find profiles that still need tagging, list your profiles with
`GET /profiles/ids` and collect entries whose `tag` is `null`.

### Filtering by tag

Use the `tag` query parameter on `GET /profiles/ids` to retrieve only profiles with a specific tag:

```bash theme={null}
curl https://api.photalabs.com/v1/phota/profiles/ids?tag=user_abc123 \
  -H "X-API-Key: YOUR_API_KEY"
```

Omitting the `tag` parameter returns all profiles.

***

## Referencing profiles in requests

There are two mechanisms for referencing a profile: the `profile_ids` request field and inline `[[profile_id]]` prompt
syntax. **In most cases you pick one, not both.** If you do use both, the API merges them automatically — there's no
benefit to duplicating the same ID in both places.

<Note>
  Your developer account holds all profiles, but only pass the ones belonging to the end-user whose photo is being
  processed. This ensures correct identity matching and billing.
</Note>

### The `profile_ids` field (edit and enhance)

This array tells the API which profiles are available as candidates for the request — typically all profiles belonging
to a single end-user. The API matches the people in the input photo to these candidates automatically, so you don't
have to name anyone in the prompt:

```json theme={null}
{
  "prompt": "Make the sun shine through the trees",
  "images": ["https://example.com/photo.jpg"],
  "profile_ids": ["abc123", "def456"]
}
```

### The `[[profile_id]]` prompt syntax (edit and generate)

Inline `[[profile_id]]` in the `prompt` attaches a profile to a specific phrase, so the model knows exactly which
person the prompt is talking about. This works with any number of subjects — a single reference is perfectly fine:

```json theme={null}
{
  "prompt": "Make [[abc123]] smile wider",
  "images": ["https://example.com/photo.jpg"]
}
```

It's also the only way to reference profiles in generation (which has no `profile_ids` field) and the clearest way to
refer to multiple people in one prompt:

```json theme={null}
{
  "prompt": "A photo of [[abc123]] and [[def456]] at a dinner party"
}
```

```json theme={null}
{
  "prompt": "Change [[abc123]]'s clothes to red, and add [[def456]] standing next to them",
  "images": ["https://example.com/photo.jpg"]
}
```

When `[[profile_id]]` appears in an edit prompt, it's automatically merged into the `profile_ids` candidate list — you
don't need to list the same ID in both places.

<Note>
  Every profile referenced in one request — via `profile_ids`, `[[profile_id]]`, or both — must share the same
  [model version](#model-versions). Mixing `v1` and `v2` profiles returns HTTP 400 with the error code
  `MIXED_PROFILE_MODEL_VERSIONS`.
</Note>

### Which should I use?

| Endpoint    | Prompt doesn't name the subject | Prompt refers to a specific subject                            |
| ----------- | ------------------------------- | -------------------------------------------------------------- |
| `/edit`     | `profile_ids`                   | `[[profile_id]]` in the prompt (`profile_ids` optional)        |
| `/generate` | N/A — use `[[profile_id]]`      | `[[profile_id]]` in the prompt (no `profile_ids` field exists) |
| `/enhance`  | `profile_ids`                   | N/A — `/enhance` has no prompt                                 |

Either mechanism is enough on its own for `/edit`. If you use both, the API merges them, so there's no need to list
the same ID in both places.

***

## Training tiers

Profile training has two tiers, selected with the `training_tier` field on `POST /profiles/add`:

| Tier                 | `training_tier` | Images                                                | Cost   | When to use                                            |
| -------------------- | --------------- | ----------------------------------------------------- | ------ | ------------------------------------------------------ |
| Full Train (default) | `standard`      | {MIN_PROFILE_PHOTOS}–{MAX_PROFILE_PHOTOS}             | \$2.90 | Highest fidelity. Recommended for most production use. |
| Quick Train          | `fast`          | {MIN_QUICK_PROFILE_PHOTOS}–{MAX_QUICK_PROFILE_PHOTOS} | \$1.49 | Faster, cheaper profile from fewer photos.             |

Quick Train completes in **\~3 minutes**; Full Train completes in **\~8 minutes** (both excluding queue time). For the strongest profile, use Full Train with {MAX_PROFILE_PHOTOS} images.

## Model versions

Profiles are trained on one of two personalization model versions, selected with the `model_version` field on
`POST /profiles/add`:

| Version      | `model_version` | Notes                                                                                                    |
| ------------ | --------------- | -------------------------------------------------------------------------------------------------------- |
| v2 (default) | `v2`            | The current personalization model. Used when the field is omitted.                                       |
| v1           | `v1`            | The original personalization model. Pick this to keep new profiles compatible with existing v1 profiles. |

A single account can hold profiles of both versions. Each `/edit`, `/generate`, `/enhance`, or `/remix` request must
reference profiles of one version only — a request that mixes `v1` and `v2` profiles returns HTTP 400 with the error
code `MIXED_PROFILE_MODEL_VERSIONS` and lists which profiles belong to each version. If you are migrating an end-user
from v1 to v2, create new `v2` profiles for all of them before combining them in a request.

## Image requirements for training

* **Full Train** (`training_tier: "standard"`): {MIN_PROFILE_PHOTOS}–{MAX_PROFILE_PHOTOS} images
* **Quick Train** (`training_tier: "fast"`): {MIN_QUICK_PROFILE_PHOTOS}–{MAX_QUICK_PROFILE_PHOTOS} images
* **Format**: publicly accessible URLs (signed URLs work)

### Tips for best results

<div className="profile-tips-icons">
  <Icon icon="sun-bright" className="profile-tips-icon" />   Clear, well-lit faces\
  <Icon icon="image" className="profile-tips-icon" />   High-res 1K+ images\
  <Icon icon="face-smile" className="profile-tips-icon" />   Expressions: neutral, smiling, frowning, surprised\
  <Icon icon="cube" className="profile-tips-icon" />   Angles: front, slight left/right, a profile\
  <Icon icon="cloud-sun" className="profile-tips-icon" />   Lighting: indoor, outdoor, sunny, etc.\
  <Icon icon="people-group" className="profile-tips-icon" />   Group shots are OK if added person's face is clear
</div>

#### Good photos

<CardGroup cols={4}>
  <Card img="https://storage.googleapis.com/photaserverless/phota_api/docs/profile_tips/well_lit.jpg">
    <span className="photo-badge good">✅</span>
    Well lit and sharp
  </Card>

  <Card img="https://storage.googleapis.com/photaserverless/phota_api/docs/profile_tips/expression.jpg">
    <span className="photo-badge good">✅</span>
    Diverse expressions and angles
  </Card>

  <Card img="https://storage.googleapis.com/photaserverless/phota_api/docs/profile_tips/pose.jpg">
    <span className="photo-badge good">✅</span>
    Diverse poses
  </Card>

  <Card img="https://storage.googleapis.com/photaserverless/phota_api/docs/profile_tips/accessory.jpg">
    <span className="photo-badge good">✅</span>
    Diverse accessories (face visible)
  </Card>
</CardGroup>

#### Bad photos

<CardGroup cols={4}>
  <Card img="https://storage.googleapis.com/photaserverless/phota_api/docs/profile_tips/extreme_pose.jpg">
    <span className="photo-badge bad">❌</span>
    Extreme poses
  </Card>

  <Card img="https://storage.googleapis.com/photaserverless/phota_api/docs/profile_tips/small_noisy_blur.jpg">
    <span className="photo-badge bad">❌</span>
    Small, dark or noisy
  </Card>

  <Card img="https://storage.googleapis.com/photaserverless/phota_api/docs/profile_tips/occlude_facial_feature.jpg">
    <span className="photo-badge bad">❌</span>
    Largely covered and occluded
  </Card>

  <Card img="https://storage.googleapis.com/photaserverless/phota_api/docs/profile_tips/motion_blur.jpg">
    <span className="photo-badge bad">❌</span>
    Blurry from movement
  </Card>
</CardGroup>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/api/quickstart">
    Walk through the full workflow end to end.
  </Card>
</CardGroup>
