AI Photos (LoRAs)
Create LoRA
Upload subject photos and start a personal LoRA training run.
POST /v2/loras
Multipart endpoint. The body must contain 15–30 image files, a name, and (for the default person subject kind) a gender and ethnicity. Returns 202 Accepted with the new LoRA's id and initial status — training continues in the background and you should poll GET /v2/loras/:id (or use a webhook) to track progress.
Body — multipart/form-data
| Field | Type | Description |
|---|---|---|
images | file[] | Required. Between 15 and 30 image parts (jpg / png / webp). Each ≤ 10 MB, total ≤ 100 MB. The field name images may be repeated. |
name | string | Required. Display name for the LoRA (1–60 chars). Surfaced in your dashboard listings. |
subjectKind | string | Optional. One of person (default), style, object. |
gender | string | Required when subjectKind=person. One of female, male, non-binary. Baked into the captioning prompt and the trigger phrase to materially improve fidelity. |
ethnicity | string | Required when subjectKind=person. Free-form (1–40 chars). The studio constrains it to White, Black, East Asian, South Asian, Southeast Asian, Hispanic/Latino, Middle Eastern, Mixed, but the API accepts any string. |
age | string | Optional, free-form (1–40 chars). E.g. "25", "early 30s", "around 40". |
steps | integer | Optional. Number of training steps (500–3000, default 1000). Higher = better quality + slower + same flat 255cr cost. |
loraRank | integer | Optional. LoRA rank (8–64, default 32). |
webhookUrl | string | Optional HTTPS URL — receives a JSON POST when training reaches a terminal state. |
webhookEvents | string | Optional comma-separated subset of completed,failed. |
Response — 202
{
"id": "lora_8a4d2c…",
"status": "PENDING",
"imageCount": 18
}Errors
| Code | When |
|---|---|
400 | Wrong number of images, oversize file, unsupported mime, missing required fields. |
402 | Insufficient credits (the 2cr lora-create fee couldn't be deducted). |
429 | Team is at its concurrent training cap (maxConcurrentTrainings, default 10). |
Example
curl -X POST https://api.apiframe.ai/v2/loras \
-H "X-API-Key: afk_your_api_key_here" \
-F "name=Alex – studio portraits" \
-F "subjectKind=person" \
-F "gender=male" \
-F "ethnicity=White" \
-F "[email protected]" \
-F "[email protected]" \
# … repeat for all 15-30 images
-F "[email protected]"import requests
files = [("images", open(f"photo{i:02d}.jpg", "rb")) for i in range(1, 19)]
response = requests.post(
"https://api.apiframe.ai/v2/loras",
headers={"X-API-Key": "afk_your_api_key_here"},
data={
"name": "Alex – studio portraits",
"subjectKind": "person",
"gender": "male",
"ethnicity": "White",
},
files=files,
)
print(response.json())import fs from "node:fs";
const form = new FormData();
form.append("name", "Alex – studio portraits");
form.append("subjectKind", "person");
form.append("gender", "male");
form.append("ethnicity", "White");
for (let i = 1; i <= 18; i++) {
form.append("images", new Blob([fs.readFileSync(`photo${String(i).padStart(2, "0")}.jpg`)]), `photo${i}.jpg`);
}
const response = await fetch("https://api.apiframe.ai/v2/loras", {
method: "POST",
headers: { "X-API-Key": "afk_your_api_key_here" },
body: form,
});
console.log(await response.json());