Open source
Headshot AI: the open-source AI headshot generator
Leap AI released Headshot AI in September 2023: a Next.js app that trained a model on someone's selfies and generated their headshots, with Stripe payments added later that month. The code is still on GitHub under the MIT licence, now maintained by Astria. Here is where it stands, what an open-source headshot app needs in 2026, and which open models can power one.
UpdatedWhere Headshot AI lives now
Leap AI published the starter as leap-ai/headshots-starter in September 2023, with a live demo and a blog post. It was meant to be forked: add Stripe and you had a headshot business in a box, and Stripe credits joined the code later that month. Next.js ran the app and landing page, Supabase the database and sign-in, Resend the email when headshots were ready, and shadcn/ui with Tailwind CSS the interface. Leap's fine-tuning API trained a model on each user's photos and generated the results.In March 2024 the project moved to Astria's API. The repository now lives at github.com/astriaai/headshots-starter, and the old leap-ai address redirects there. Leap doesn't maintain it. On October 4, 2026 it had 4,461 stars and 839 forks, its last commit was from June 19, 2025, and it was still MIT licensed.Its README lists Astria for training and inference, Vercel Blob for uploads, Stripe for credits (one credit pays for one model training) and Resend as optional. It also supports Astria's prompt packs and FLUX fine-tuning.How the starter works
The flow is still 2023's per-user fine-tuning:- A user signs in with a Supabase magic link.
- They add photos, which the app checks with Astria's image inspection endpoint and uploads to Vercel Blob.
- They spend a credit, bought through a Stripe pricing table.
- The app asks Astria to train a model on the photos and run a set of headshot prompts on it. Astria calls the app's webhooks when training ends and as the images arrive.
- Resend emails the user when the model is ready, and the headshots appear in their dashboard.
git clone https://github.com/astriaai/headshots-starter.gitcd headshots-startercp .env.local.example .env.local # then fill in the keysnpm installnpm run devWhat an open-source headshot app needs in 2026
The starter's shape still works (sign in, upload, pay, generate, notify), but the middle has changed. Most headshot products no longer train a model for each person; they send a few photos to a model that takes reference images, which is one call instead of a training job. A 2026 build looks like this:- Frontend: any framework. The starter's Next.js and shadcn/ui are fine.
- Upload with checks: one to four photos, turned away early for no face, two faces, blur, sunglasses or a different person. MediaPipe's face detector runs in the browser under Apache 2.0.
- Generation: a reference-image model, self-hosted or through an API, called from a background job.
- Results: a webhook into a queue, then copied into your own storage, because output links expire (Leap's after 24 hours).
- Storage with deletion: photos and results deleted on request and on a schedule. Face scans and embeddings count as biometric data under laws such as Illinois' BIPA and the EU's GDPR.
- Payments per result or per pack, charged once the run has succeeded.
- A likeness test before launch: outputs scored against held-out photos of testers who agreed to it.
Open-weight models for keeping a face
If you want to run everything yourself, these are the open identity methods worth knowing in October 2026. Read each licence before you build a paid product on it, because several of the strongest are research-only.| Model | Built on | Licence | Trade-offs |
|---|---|---|---|
| InfiniteYou (ByteDance, 2025) | FLUX.1 [dev] | Code Apache 2.0; weights CC BY-NC 4.0, for research | Second only to GPT Image 2 on identity in a July 2026 benchmark (Beyond Facial Consistency). Not for commercial use as released. |
| PuLID-FLUX (v0.9.1, October 2024) | FLUX.1 [dev] | Code Apache 2.0; the FLUX.1 [dev] and InsightFace licences apply | Runs on a 16 GB GPU with FP8 weights. InfiniteYou's authors report weaker prompt following and some face copy-paste. |
| PhotoMaker V2 (Tencent, July 2024) | SDXL | Apache 2.0, plus InsightFace's licence | Takes several photos of a person. SDXL quality trails the FLUX-era models. |
| InstantID (2024) | SDXL | Code Apache 2.0; its checkpoints and InsightFace's models are research-only | Strong likeness from one photo, but by default it uses the reference's facial keypoints as a pose guide, so results tend to copy its head pose. |
| Qwen-Image-Edit (Alibaba) | Qwen-Image | Apache 2.0 | Open weights under a permissive licence. Takes several input images, and Alibaba says its 2511 release keeps characters more consistent. |
| FLUX.3 Image (Black Forest Labs) | Its own model | Not announced | Released through the API in early October 2026. Black Forest Labs says open weights will follow in the coming weeks. |
Self-host or call an API
| Self-host open weights | Call an API | |
|---|---|---|
| Hardware | A GPU for each worker (PuLID-FLUX needs 16 GB with FP8), and cold starts to manage | None |
| Licences | Research-only weights rule out several of the best methods for a paid product | The provider's terms; check them for commercial use |
| Models | Open models only | Closed models too, such as GPT Image and Nano Banana |
| Cost | GPU time whether busy or idle; can be cheaper at high, steady volume | Per result, nothing when idle |
| Control | Everything: prompts, adapters, LoRAs | What the API exposes |
| Privacy | Photos stay on your servers | Photos go to the API and the model provider behind it |
Bring your own UI and call leap/headshot
If you like the starter's pages but not the training step, keep the frontend and swap the backend. Four calls replace the Astria training and prompt flow:- Upload each photo to POST /v1/files and keep the file IDs.
- Quote the run with POST /v1/quotes and show the price.
- Start it with POST /v1/generations and a webhook URL.
- In the webhook handler, copy the outputs to your storage and email the user.
// fileIds: one to four IDs from POST /v1/filesconst response = await fetch("https://api.tryleap.ai/v1/generations", { method: "POST", headers: { "x-api-key": process.env.LEAP_API_KEY!, "content-type": "application/json", "idempotency-key": crypto.randomUUID(), }, body: JSON.stringify({ model: "leap/headshot", input: { photos: fileIds, style: "office", outfit: "match", aspect_ratio: "4:5", n: 4 }, webhook: `${process.env.DEPLOYMENT_URL}/api/webhooks/leap`, }),});const generation = (await response.json()) as { id: string; error?: { message: string } };if (!response.ok) throw new Error(generation.error?.message);// Save generation.id with the user's order. The webhook names it when the run ends.