Developers · REST API
The MetaHuman head API, built for batches.
Send photos of a person, hear back when the head is done, fetch the format you need. The same call makes one head or five hundred. Every head is finished: 4K skin, groom recommendations, a fitted neck and 52 ARKit shapes. A head takes about 4 minutes, so the API is asynchronous by design.
- REST + JSON
- Bearer keys
- Signed webhooks
- Idempotency keys
- OpenAPI 3.1
- llms.txt
Not to be confused with Epic's MetaHuman Python and C++ APIs, which run inside Unreal Engine. FaceNinja is a web API that makes the head you bring into them.
POST /v1/headsphotos of one person- head id
head.completedsigned webhook when it is donePOST /v1/heads/{id}/filesformat- short-lived signed link
Send. Hear back. Fetch.
Send.
POST /v1/headswith 1 to 15 photos of one person, as multipart files or ids you uploaded first. Add anIdempotency-Key, and a retry never makes or bills a second head.Hear back.
Choose how: a signed webhook to your server, a server-sent event stream, or a
?wait=60long poll for scripts.Fetch.
Ask for
glb,fbx,obj,stl,plyormetahuman_zip, with or without ARKit shapes. You get a signed link that needs no key. Store the head id, not the link. What each format holds
A failed head's credits come back automatically, and error.code says why.
A webhook is all your backend needs.
Create the head, store its id, and act when we call you. This is the whole integration, in Python.
# when the user confirms their photos
head = api.post("/v1/heads", files=photos,
data={"webhook_url": "https://you.example/hooks/faceninja"},
headers={"Idempotency-Key": str(uuid4())}).json()
save(user, head["id"])
# later, FaceNinja calls you
@app.post("/hooks/faceninja")
def on_head(event = verified(request)): # check FaceNinja-Signature first
if event["type"] == "head.completed":
link = api.post(f"/v1/heads/{event['data']['id']}/files",
json={"format": "glb", "arkit": True}).json()
store_on_your_cdn(link["url"])// when the user confirms their photos
const form = new FormData();
for (const p of photos) form.append("photos", p);
form.append("webhook_url", "https://you.example/hooks/faceninja");
const head = await api.post("/v1/heads", form,
{ headers: { "Idempotency-Key": crypto.randomUUID() } });
await save(user, head.id);
// later, FaceNinja calls you
app.post("/hooks/faceninja", async (req, res) => {
const event = verified(req); // check FaceNinja-Signature first
if (event.type === "head.completed") {
const link = await api.post(`/v1/heads/${event.data.id}/files`,
{ format: "glb", arkit: true });
await storeOnYourCdn(link.url);
}
res.sendStatus(204);
});Shortened for reading. The reference has the full request, the signature check and every error.
A cast from a folder, safe to run twice.
A batch is many single creates. Give each one an idempotency key you derive from your own data, such as the folder name, and you can rerun the whole script after a crash without making or paying for a head twice. Then let the webhooks tell you which heads are done.
for person in sorted(Path("cast").iterdir()): # one folder per person
photos = [("photos", open(p, "rb")) for p in sorted(person.glob("*.jpg"))]
api.post("/v1/heads", files=photos,
data={"name": person.name, "webhook_url": HOOK},
headers={"Idempotency-Key": f"cast-2026-10/{person.name}"})- Pace: up to 30 head creations a minute per account. On
429, waitRetry-Afterseconds and send the same key again. - Check first:
?dry_runchecks a create and returns a quote without making a head.GET /v1/accountshows the balance. - Track:
GET /v1/headslists your heads, page by page, with their status. - Fail one, not all: a head that fails reports its own
error.code, and its credits return. The rest of the batch carries on.
Or let your agent write the integration.
The API reference, the OpenAPI file and llms.txt are written for people and for language models. Give your agent the skill and it can make heads for you directly, or write and test the pipeline script for your stack.
Read https://faceninja.net/llms.txt and https://faceninja.net/skill/SKILL.md. Write a Python script for our pipeline: for each new folder in ./incoming, create one FaceNinja head from its photos with an Idempotency-Key derived from the folder name and a webhook_url I give you. Add a small web handler that checks the FaceNinja-Signature header, and on head.completed downloads the FBX to ./heads/<folder name>; on head.failed it logs error.code. Read the API key from FACENINJA_API_KEY. Don't run anything against my account until I say so.Built for retries, queues and bad photos.
Idempotent creates
The same Idempotency-Key within 24 hours returns the same head, never a second charge.
Signed webhooks
head.completed and head.failed, with an HMAC signature in FaceNinja-Signature.
Honest queue
While waiting, a head reports its place in line and an estimate.
Errors you can branch on
Stable error.code values (no_face, not_same_person, content_rejected, …) and a hint.
Refunds without tickets
Credits are held at create, charged at completion and released on failure or delete.
Delete on demand
Delete a head and its photos whenever you want. Files expire 30 days after the head finishes.
The numbers to design around.
| Photos per head | 1 to 15, one person; JPEG, PNG, WebP, HEIC/HEIF or AVIF, up to 25 MiB each |
|---|---|
| Time per head | About 4 minutes, plus any queue |
| Long poll | ?wait up to 60 s per call |
| Download links | Start within 60 s; a started download finishes |
| Rate limits | 600 requests a minute per account; 30 head creations a minute |
| Retention | Photos and files 30 days after the head finishes |
| Price | 1,500 credits ($15) per head, every format included |
The reference is the source of truth. If a number here differs, the reference wins.
A player's face on day one of the save file.
Let players bring their own face, or build an NPC cast from consenting actors. Every head is the same mesh with a different face, so one rig, one material and one animation set fit them all. The 52 ARKit shapes take lip sync or the player's camera.
Plan for the wait. A head takes about 4 minutes, not milliseconds. Start it early, during onboarding or a tutorial, show a stand-in, and swap the head in when the webhook arrives.
GLB for Unity, Godot and the web; FBX and the MetaHuman ZIP for Unreal. Unreal automation is in early access: ask for it.
Our terms require permission from the person in the photo and forbid photos of children. Build the same rule into your flow.




Already using another photo-to-MetaHuman API?
If your integration already creates a head, waits for it and downloads a file, it keeps its shape here. Paths, field names and the file step differ, and the migration guide maps them call by call.
Developer questions.
- 01Is there a sandbox?
- Add
?dry_runto a create call. It checks the request and returns a quote without making a head or charging you. - 02Can I call it from a browser or a mobile app?
- No. Keep the key on your server. Signed upload and download links need no key, so a client can upload or download directly.
- 03Which SDKs are there?
- Plain HTTP and an OpenAPI 3.1 file you can generate a client from. The reference has curl, Node and Python examples. The agent skill's CLI is also a single Python file you can read and call from scripts.
- 04How many heads can I make at once?
- Up to 30 head creations a minute per account, and each runs in the queue. For more than 1,000 heads, write to us about invoicing and capacity.
- 05Is this Epic's MetaHuman API?
- No. Epic's MetaHuman APIs script MetaHuman Creator inside Unreal Engine. FaceNinja makes the head from photos, as a web service, and hands you the files and the MetaHuman ZIP to take into Unreal.
- 06Can it build a head from a set of photos?
- Yes, in beta: reconstruction from several photos of one session also returns each photo's camera position and focal length. Write to us if you want to try it with your capture set.