Developers

KeenTools Cloud API reference

Every endpoint, parameter, and response of the KeenTools Cloud API, as plain text.

Interactive referenceMarkdownOpenAPI spec

KeenTools Cloud API for 3D head reconstruction.

Quick Start

  1. Initialize: Send POST /v1/avatar/init with the number of photos (2-15). You'll receive an avatar ID and pre-signed upload URLs.
  2. Upload: Upload each photo directly to its pre-signed URL using HTTP PUT with the raw image bytes.
  3. Process: Start reconstruction with POST /v1/avatar/{id}/process.
  4. Monitor: Poll GET /v1/avatar/{id}/get-status until status: completed.
  5. Download: Fetch your 3D model via GET /v1/avatar/{id}/get-3d-model.

Authentication

Include your API key as a Bearer token in the Authorization header:

Authorization: Bearer ak_live_...

Obtain an API key at https://cloud.keentools.io (Settings > API Keys).

Legacy *.keentools.workers.dev hosts remain supported for existing integrations; new integrations should use the server URL above.

Credits

Credit-bearing endpoints place a temporary credit hold before processing starts:

  • POST /v1/avatar/{id}/process holds generation credits when the request is accepted (2xx). Credits are captured after the session completes successfully, or released if processing fails or the session is deleted.
  • GET /v1/avatar/{id}/get-3d-model creates a new charge for every response with event: redirect, including repeated calls with identical parameters. retry-after and non-2xx responses release the hold.

If a credit-bearing request is rejected outright (non-2xx), the hold is released immediately.

Check your balance and top up at https://cloud.keentools.io. See https://cloud.keentools.io for current pricing.

Base URL

All endpoints below are relative to https://api.keentools.io.

POST /v1/avatar/init

Creates a new avatar job for a given number of photos. Returns avatar id and pre-signed AWS S3 PUT URLs for direct photo upload.

To upload photos, send an HTTP PUT request to each returned URL with the raw image bytes as the request body and the appropriate Content-Type header (e.g., image/jpeg or image/png).

Request body

  • image_count (integer, required) — The number of photos to use for reconstruction (2–15)

Responses

  • 200 — Avatar initialized
    • avatar_id (string, required) — Avatar ID
    • img_urls (array of string, required) — Pre-signed AWS S3 PUT URLs for direct photo upload. Upload each photo by sending an HTTP PUT request to the corresponding URL with: The raw image bytes as the request body; Content-Type header set to the image MIME type (e.g., image/jpeg, image/png)
  • 400 — Too many photos. Reduce number to max 15 / Too few photos. Provide at least 2
  • 401 — Missing, malformed, or invalid API key
  • 500 — Unexpected internal error
  • 502 — Cloud infrastructure unexpected error. Please try again later

POST /v1/avatar/{avatar_id}/process

Billed request: This request is billed. See pricing details.

Start avatar reconstruction. Call this after uploading all photos to the pre-signed URLs from /init. This endpoint charges generation only; model downloads are charged separately by /get-3d-model.

Returns 400 if reconstruction is already in progress for this avatar.

Parameters

Name In Required Type Description
avatar_id path yes string Avatar ID.

Request body

  • expressions_enabled (boolean, optional) — Create avatar with facial expressions. When true, the "expression" blendshape group becomes available in /get-3d-model.
  • focal_length_type (FocalLengthType, required) — Select how the 35 mm equivalent focal length should be handled for reconstruction

Responses

  • 200 — Avatar reconstruction started
  • 400 — Malformed Focal Length data: the quantity of focal length values does not match the quantity of photos / Reconstruction already in progress
  • 401 — Missing, malformed, or invalid API key
  • 402 — Insufficient credits
  • 404 — Avatar not found
  • 410 — The requested avatar is no longer supported because the API was upgraded. Please initialise a new avatar
  • 500 — Internal server error (e.g. credit hold infrastructure failure)
  • 502 — Cloud infrastructure unexpected error. Please try again later

GET /v1/avatar/{avatar_id}/get-status

Check reconstruction status. Poll this endpoint after calling /process.

Returns one of five statuses: not_started, running (with progress 0.0–1.0), completed, failed (with error message), or deleted.

Parameters

Name In Required Type Description
avatar_id path yes string Avatar ID.

Responses

  • 200 — Avatar reconstruction status
    • NotStartedResponse — The reconstruction has not started yet
      • status (string (not_started), required)
    • RunningResponse — The reconstruction is in progress
      • data (object, required) — The reconstruction is in progress
        • data.progress (number, required) — Reconstruction progress (0 = just started, 0.5 = 50%, 1 = completed)
      • status (string (running), required)
    • FailedResponse — Reconstruction failed
      • data (object, required) — Reconstruction failed
        • data.error_message (string, required) — Error message
      • status (string (failed), required)
    • CompletedResponse — Reconstruction finished successfully
      • status (string (completed), required)
    • DeletedResponse — Session was deleted
      • status (string (deleted), required)
  • 401 — Missing, malformed, or invalid API key
  • 404 — No avatar found
  • 502 — Cloud infrastructure encountered an unexpected error. Please try again later

GET /v1/avatar/{avatar_id}/get-3d-model

⚠️ EVERY response with event: redirect creates a new charge.

Calling this endpoint again, even with the same avatar and identical parameters, charges your account again. Stop polling immediately after the first redirect response and download data.url directly. Responses with event: retry-after and non-2xx responses are not charged.

See pricing details.

Generates and returns head mesh data.

This endpoint does NOT return the mesh directly. Instead, it uses a polling protocol:

  1. If the mesh is still being generated, returns { "event": "retry-after", "data": { "time_sec": N } }. Wait N seconds, then call again.
  2. When the mesh is ready, returns { "event": "redirect", "data": { "url": "https://..." } }. Stop polling and download the mesh from that pre-signed URL.

Correct polling

for (;;) {
  const response = await fetch(modelEndpoint, {
    headers: { Authorization: "Bearer " + apiKey },
  });
  const result = await response.json();

  if (result.event === "retry-after") {
    await delay(result.data.time_sec * 1000);
    continue;
  }

  // This redirect response is billed. Stop polling now.
  await download(result.data.url);
  break;
}

Incorrect: continues charging after the model is ready

// Never poll on a fixed interval without stopping after redirect.
setInterval(() => fetch(modelEndpoint), 5000);

OBJ format note: Returns a ZIP archive (.obj + .mtl + texture files).

GLB format note: The GLB file contains two meshes - one textured mesh with 4 primitives (Head, EyeLeft, EyeRight, Teeth) each with its own texture, and one wireframe mesh with 4 line primitives. All textures are JPEG/PNG, up to 4096x4096 each.

Blendshape support: GLB supports expression and arkit blendshapes. OBJ supports expression blendshapes only.

Blendshapes serialization: The blendshapes array parameter must be serialized as a single comma-separated value (e.g., ?blendshapes=arkit,expression). Using repeated query parameters will result in a "duplicate field" error.

Parameters

Name In Required Type Description
avatar_id path yes string Avatar ID.
mesh_format query no string (obj, glb) Mesh format. glb: Single binary file with embedded textures, morph targets, and wireframe edges. Recommended for most use cases. obj: Wavefront OBJ. Returns a ZIP archive (.obj + .mtl + texture files). Default: obj.
mesh_lod query no string (high_poly) Level of detail. Default: high_poly.
blendshapes query no array of string (expression, arkit) Blendshape groups to include in the output mesh. Serialized as comma-separated values, NOT repeated params. expression: Numbered expression morphs (Expression 01, Expression 02, ...). Available for GLB and OBJ when expressions_enabled=true was set during processing. arkit: 51 ARKit-compatible morph targets (browDownLeft, eyeBlinkLeft, mouthSmileRight, etc.). GLB only. Format support:; GLB: Supports both expression and arkit blendshapes. OBJ: Supports expression blendshapes only.
texture query no null | string (jpg, png) Add texture.
edges query no boolean Add wireframe edge lines to the mesh (GLB only) Default: false.

Responses

  • 200 — Check the "event" field in the response: retry-after: Model is still being generated. Wait data.time_sec seconds and retry. redirect: Model is ready. Download from the pre-signed URL in data.url.
    • RetryAfter — The mesh is still being generated. Wait the specified number of seconds, then call the endpoint again. Typical generation takes 10–60 seconds depending on mesh options.
      • data (object, required) — The mesh is still being generated. Wait the specified number of seconds, then call the endpoint again. Typical generation takes 10–60 seconds depending on mesh options.
        • data.time_sec (integer, required) — Number of seconds to wait before retrying
      • event (string (retry-after), required)
    • Redirect — The mesh is ready. Download it from the pre-signed S3 URL. For GLB: the URL points to a binary GLB file; For OBJ: the URL points to a ZIP archive (.obj + .mtl + texture files)
      • data (object, required) — The mesh is ready. Download it from the pre-signed S3 URL. For GLB: the URL points to a binary GLB file; For OBJ: the URL points to a ZIP archive (.obj + .mtl + texture files)
        • data.url (string, required) — Pre-signed S3 download URL for the generated mesh
      • event (string (redirect), required)
  • 401 — Missing, malformed, or invalid API key
  • 404 — No avatar found
  • 405 — Unsupported request. Possibly outdated or wrong format. Please check current API documentation
  • 422 — Avatar reconstruction failed. Try initialising a new one with different photos
  • 425 — Avatar is reconstructing. Please try again later
  • 500 — Unexpected internal error. Please check documentation or try again later
  • 502 — Cloud infrastructure encountered an unexpected error. Please try again later

GET /v1/avatar/{avatar_id}/get-info

Returns reconstruction metadata including estimated camera positions and projections for each input image.

Camera matrices are returned as 4×4 nested arrays in row-major order. Entries are null for images that failed processing.

  • camera_positions: World-to-camera (view) matrices. To get camera-to-world for 3D reconstruction, transpose (row-major → column-major) then invert.
  • camera_projections: Intrinsic projection matrices (NOT standard OpenGL format). Encodes focal length in pixels and image dimensions. See API description for decomposition formulas.

img_urls contains pre-signed download URLs for the preprocessed input images.

Parameters

Name In Required Type Description
avatar_id path yes string Avatar ID.

Responses

  • 200 — Avatar reconstruction metadata
    • camera_positions (array of array | null, required)
    • camera_projections (array of array | null, required)
    • expressions_enabled (boolean, required) — Whether the avatar was created with facial expressions enabled
    • focal_length_type (string (manual, exif, estimated_common, estimated_per_image), required) — The focal length method that was actually used during reconstruction. Note: this is an OUTPUT field and may differ from the input focal_length_type: "manual": user-provided focal length values were used; "exif": all photos had valid FocalLengthIn35mmFilm EXIF data, which was used instead of estimation (possible when input was "estimate_common" or "estimate_per_image"); "estimated_common": a single shared focal length was estimated; "estimated_per_image": individual focal lengths were estimated per image (EXIF data was not found)
    • img_urls (array | null, required) — URLs for downloading preprocessed images.
  • 401 — Missing, malformed, or invalid API key
  • 404 — Avatar not found
  • 422 — Avatar reconstruction failed. Try initialising a new one with different photos
  • 425 — Avatar is not ready yet
  • 502 — Cloud infrastructure encountered an unexpected error. Please try again later

DELETE /v1/avatar/{avatar_id}

Permanently deletes the avatar together with all its photos and generated 3D assets

Parameters

Name In Required Type Description
avatar_id path yes string Avatar ID.

Responses

  • 200 — Avatar deleted
  • 401 — Missing, malformed, or invalid API key
  • 404 — Avatar not found
  • 423 — Cannot delete avatar - processing in progress
  • 502 — Cloud infrastructure unexpected error. Please try again later