Kolbo.AIKolbo.AI Docs
Developer API

3D Generation

Generate 3D models from text prompts or reference images using the Kolbo API.

Generate 3D models (.glb, .fbx, .obj, .usdz) from text descriptions or reference images.

No Smart Select for 3D — unlike image and video generation, omitting the model field does not trigger Smart Select. Built-in per-mode defaults are used instead: meshy/v6-preview/text-to-3d for text mode, meshy/v6-preview/image-to-3d for single-image mode, and meshy/v5/multi-image-to-3d for multi-image mode. Use GET /api/v1/models?type=three_d to discover and pin a model.

Model identifiers are Kolbo-specific — they do not match upstream provider names. Always fetch available models from GET /api/v1/models?type=three_d first. Never guess or hardcode identifiers.

All generation endpoints accept an optional project_id body field that routes the output into a specific project. See Projects.

Endpoint

POST /api/v1/generate/3d

Rate limited to 10 requests/minute in production. The bucket is keyed by account (every API key you own shares it) and shared with the other non-image generation endpoints — video, music, speech, sound, Creative Director, video-to-video, lipsync, elements, first/last frame, chat and the cancel route all draw from the same 10/min counter. Image generation and image editing use a separate 30/min bucket. Status polling is not rate limited.

Request Body

FieldTypeRequiredDescription
promptstringYes in text modeText description of the 3D model. Optional (but useful as a hint) in the image modes. Missing in text mode → 400; a non-string value in any mode → 400 INVALID_PROMPT.
modestringNo"text", "single", or "multi". Auto-resolved from reference_images when omitted (0 images = text, 1 = single, 2+ = multi). Any other value returns 400 Invalid mode. Must be single, multi, or text.
reference_imagesarray of stringNoPublic image URLs. Required in single / multi mode — those modes return 400 without at least one URL. In multi mode the array is capped at 4 images, or 6 when model is a fal-trellis… identifier; more returns 400 Multi-image mode supports maximum N images.
texture_promptstringNoPrompt used to guide texture generation
modelstringNoModel identifier from GET /api/v1/models?type=three_d. Must be a string, otherwise 400 INVALID_MODEL. An identifier that is not in the catalogue returns 400 Model not found. Omit — or send one of the auto-select aliases "auto", "smart-select", "smart_select", "smartselect", "auto-select" — to use the built-in per-mode defaults.
topologystringNoMesh topology preset (default: "triangle"). Reaches the Meshy models and the generic fal 3D path; the Trellis families never receive it.
target_polycountnumberNoTarget polygon count (default: 30000). Same scope as topology.
enable_tposebooleanNoForce T-pose for character models (default: false). Same scope as topology.
enable_pbrbooleanNoGenerate PBR textures. Enabled unless you send an explicit false (default: true). Same scope as topology.
enable_prompt_expansionbooleanNoLet the model expand the prompt before generating (default: false). Text-mode Meshy only.
seednumber | stringNoRandom seed for reproducible output, parsed as an integer. Read by the Trellis v1 and Trellis 2 families; ignored elsewhere.
texture_sizenumber | stringNoTexture map resolution in pixels. Trellis v1 accepts 512, 1024 or 2048 and silently falls back to 1024 for anything else. Read by the Trellis v1 family only — Trellis 2 ignores it entirely and always textures at 2048.
resolutionnumber | stringNoGeneration resolution. Trellis 2 accepts 512, 1024 or 1536 and silently falls back to 1024 for anything else. Read by the Trellis 2 family only; ignored elsewhere.
project_idstringNoRoute the model into a specific project. Defaults to your auto-created "API Generations" project.

The tuning fields above are passed straight through to the chosen 3D model, and each family reads a different subset — an ignored field fails silently rather than erroring. Check the model's entry in GET /api/v1/models?type=three_d before relying on one.

Two credit gates run before anything is queued. First a flat SDK floor of 20 credits — below that the request fails immediately with 403 INSUFFICIENT_CREDITS. Then the chosen model's real credit cost from GET /api/v1/models?type=three_d is checked against your balance, which also returns 403 INSUFFICIENT_CREDITS. Credits are deducted after the model generates successfully, not up front.

Unlike the session-based generation endpoints, 3D is project-scoped — there is no session bucket. The start response's session_id and project_id both carry the resolved project id.

Three Modes

3D generation supports three modes, determined automatically from the number of reference images:

ModeReference ImagesDescription
text0 (none)Generate from text prompt only
single1Generate from one reference image, optionally with a text prompt
multi2+Generate from multiple reference images for more accurate reconstruction

Examples

Text Mode (Prompt Only)

curl -X POST https://api.kolbo.ai/api/v1/generate/3d \
  -H "X-API-Key: kolbo_live_..." \
  -H "Content-Type: application/json" \
  -d '{"prompt": "A medieval sword with ornate handle"}'

Single-Image Mode

curl -X POST https://api.kolbo.ai/api/v1/generate/3d \
  -H "X-API-Key: kolbo_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "reference_images": ["https://example.com/shoe-front.jpg"],
    "prompt": "White running shoe",
    "enable_pbr": true
  }'

Multi-Image Mode

curl -X POST https://api.kolbo.ai/api/v1/generate/3d \
  -H "X-API-Key: kolbo_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "reference_images": [
      "https://example.com/shoe-front.jpg",
      "https://example.com/shoe-side.jpg",
      "https://example.com/shoe-back.jpg"
    ],
    "texture_prompt": "Photorealistic sneaker texture"
  }'

With Specific Model

To choose a specific model, first fetch identifiers from GET /api/v1/models?type=three_d, then pass the identifier value:

# Step 1: List available 3D models
curl "https://api.kolbo.ai/api/v1/models?type=three_d" \
  -H "X-API-Key: kolbo_live_..."

# Step 2: Use an identifier from the response
curl -X POST https://api.kolbo.ai/api/v1/generate/3d \
  -H "X-API-Key: kolbo_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A medieval sword with ornate handle",
    "model": "MODEL_IDENTIFIER",
    "enable_pbr": true,
    "target_polycount": 30000
  }'

Replace MODEL_IDENTIFIER with a real identifier from step 1.

Response

This endpoint is asynchronous and fire-and-forget: the POST returns 200 as soon as the job is queued and never contains a mesh.

Polling is the only completion mechanism. Kolbo never calls you back: there is no webhook, no callback_url field on this request body, and no route delivers outbound notifications. Socket.IO events are the web app's internal transport and are not part of the API contract — API-key generations are registered so the shared progress emitter drops their events, and the few emit sites that bypass that check are undocumented, unversioned and unsafe to build on. After the POST, loop against GET /api/v1/generate/{generation_id}/status until state is completed, failed or cancelled. Full contract: Polling & Cancellation.

Generation Started

{
  "success": true,
  "generation_id": "6612f0a1b2c3d4e5f6a7b8c9",
  "type": "three_d",
  "model": "auto",
  "credits_charged": null,
  "poll_url": "/v1/generate/6612f0a1b2c3d4e5f6a7b8c9/status",
  "poll_interval_hint": 8,
  "session_id": "6601a1b2c3d4e5f6a7b8c9d0",
  "project_id": "6601a1b2c3d4e5f6a7b8c9d0"
}
FieldTypeDescription
generation_idstringPoll and cancel with this
modelstringThe model you sent, or "auto" when you omitted it and the built-in default was used
credits_chargednumber | nullAlways null for 3D. The 3D controller reports its estimate under a field the shared SDK envelope does not map, and nothing is charged at start time anyway. Read the authoritative credits_used from the completed status response instead.
poll_urlstringStatus path without the /api prefix — prepend https://api.kolbo.ai/api
poll_interval_hintnumberSuggested seconds between polls (8 for 3D)
session_id / project_idstringBoth carry the resolved project id — 3D has no session bucket

Completed Status

{
  "success": true,
  "generation_id": "6612f0a1b2c3d4e5f6a7b8c9",
  "type": "three_d",
  "state": "completed",
  "progress": 100,
  "result": {
    "urls": [
      "https://media.kolbo.ai/3d/model.glb",
      "https://media.kolbo.ai/3d/model.fbx",
      "https://media.kolbo.ai/3d/model.obj",
      "https://media.kolbo.ai/3d/model.usdz"
    ],
    "thumbnail_url": "https://media.kolbo.ai/3d/preview.png",
    "mode": "text",
    "prompt_used": "A medieval sword with ornate handle",
    "model": null,
    "created_at": "2026-04-10T14:20:00Z"
  },
  "credits_used": 60,
  "credits_breakdown": [
    { "model": "MODEL_IDENTIFIER", "amount": 60, "base": 60, "final": 60, "duration_multiplier": null, "pricing": null }
  ]
}

result.model is always null for 3D — the 3D generation record does not persist the resolved identifier. Read the identifier from credits_breakdown[].model instead, or keep the one you sent. The credit figures above are illustrative: the real cost is the chosen model's credit value from GET /api/v1/models?type=three_d, and credits_used reports what was actually charged.

The output lives in result.urls — always an array of strings. One 3D generation produces every format in that single array; there is no separate generation per format. It starts with the primary model file (the GLB used for preview) and is followed by whichever per-format files the model produced — glb, fbx, obj, usdz. Entries are not de-duplicated (the GLB commonly appears twice) and shorter arrays are normal, so match by file extension rather than indexing by position.

result (and credits_used / credits_breakdown) appear only under state: "completed"; a processing body has exactly five keys and no result at all. There is no partial read — and progress sits at 0 for the entire run before jumping to 100, so never treat it as a completion signal.

failure.category, code, retryable, severity and provider are always null for 3D — the 3D record stores its error category under a field the status projection does not read. Branch on state plus the error string. Credits are deducted only after a successful generation, so a failed 3D job costs nothing.

Polling, failure envelopes and cancellation are otherwise identical to every other async generation — see Polling & Cancellation.

Errors

Returned by POST /api/v1/generate/3d before anything is queued:

StatusBody / codeCause
400prompt is required for text modemode resolved to text with no prompt
400INVALID_PROMPTprompt present but not a string
400INVALID_MODELmodel sent as an array/object instead of a string identifier
400reference_images required for image modesmode is single or multi with an empty or missing array
400Invalid mode. Must be single, multi, or textUnrecognised mode value
400Multi-image mode supports maximum N imagesMore than 4 images (6 on fal-trellis… models) in multi mode
400Model not foundThe model identifier is not in the catalogue
403INSUFFICIENT_CREDITSEither credit gate
400SDK_PROJECT_INVALID_IDproject_id is not a valid ObjectId
404SDK_PROJECT_NOT_FOUNDNo project with that id is visible to you
403SDK_PROJECT_ACCESS_DENIEDYou hold less than edit permission on that project

Downstream failures from the 3D controller are re-wrapped by the SDK as { "success": false, "error": "...", "code": "GENERATION_ERROR" } unless the controller supplied its own code. Status-endpoint errors are documented in Polling & Cancellation.

Only models whose provider is fal / fal-ai can run 3D. A model from any other provider is accepted at start time and then fails asynchronously — the status endpoint reports state: "failed" with Provider '<name>' does not support 3D generation yet. Pick identifiers from GET /api/v1/models?type=three_d and you will not hit this.

Cancel

curl -X POST https://api.kolbo.ai/api/v1/generate/6612f0a1b2c3d4e5f6a7b8c9/cancel \
  -H "X-API-Key: kolbo_live_..."

Returns state: "cancelled" and credits_refunded. A model that already finished returns 409 CANNOT_CANCEL.

JavaScript Example

const KOLBO_API_KEY = "kolbo_live_..."; // Replace with your API key
const BASE_URL = "https://api.kolbo.ai";

async function generate3D(body) {
  const response = await fetch(`${BASE_URL}/api/v1/generate/3d`, {
    method: "POST",
    headers: {
      "X-API-Key": KOLBO_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });

  const started = await response.json();
  if (!started.success) throw new Error(started.error);

  const status = await pollUntilDone(started.generation_id, started.poll_interval_hint);
  if (status.state !== "completed") throw new Error(status.error || status.state);

  console.log("3D model URLs:", status.result.urls); // every format, one array
  console.log("Thumbnail:", status.result.thumbnail_url);
  return status.result;
}

const TERMINAL = new Set(["completed", "failed", "cancelled"]);

// Minimal loop. A production client should also handle transient HTTP errors and
// an overall timeout — see /docs/developer-api/polling-and-cancellation.
async function pollUntilDone(generationId, intervalSeconds) {
  while (true) {
    await new Promise((r) => setTimeout(r, intervalSeconds * 1000));
    const status = await fetch(
      `${BASE_URL}/api/v1/generate/${generationId}/status`,
      { headers: { "X-API-Key": KOLBO_API_KEY } }
    ).then((r) => r.json());
    if (TERMINAL.has(status.state)) return status;
  }
}

async function main() {
  // Text mode — prompt only
  const textResult = await generate3D({
    prompt: "A low-poly tree with autumn leaves",
    enable_pbr: true,
  });
  console.log("Text mode result:", textResult.urls);

  // Single-image mode — one reference image
  const singleResult = await generate3D({
    reference_images: ["https://example.com/chair-photo.jpg"],
    prompt: "Wooden dining chair",
  });
  console.log("Single-image result:", singleResult.urls);

  // Multi-image mode — multiple reference images
  const multiResult = await generate3D({
    reference_images: [
      "https://example.com/mug-front.jpg",
      "https://example.com/mug-side.jpg",
    ],
    texture_prompt: "Ceramic mug with glossy finish",
  });
  console.log("Multi-image result:", multiResult.urls);
}

main().catch(console.error);

JavaScript — Choosing a Specific Model

const KOLBO_API_KEY = "kolbo_live_..."; // Replace with your API key
const BASE_URL = "https://api.kolbo.ai";

async function main() {
  // Step 1: Fetch available 3D models
  const modelsRes = await fetch(`${BASE_URL}/api/v1/models?type=three_d`, {
    headers: { "X-API-Key": KOLBO_API_KEY },
  });
  const { models } = await modelsRes.json();
  console.log("Available 3D models:", models.map(m => `${m.identifier} (${m.credit} credits)`));

  // Step 2: Generate with a specific model
  const response = await fetch(`${BASE_URL}/api/v1/generate/3d`, {
    method: "POST",
    headers: {
      "X-API-Key": KOLBO_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      prompt: "A futuristic helmet with visor",
      model: models[0].identifier,
      enable_pbr: true,
    }),
  });

  const started = await response.json();
  if (!started.success) throw new Error(started.error);

  // pollUntilDone() from the example above
  const status = await pollUntilDone(started.generation_id, started.poll_interval_hint);
  if (status.state !== "completed") throw new Error(status.error || status.state);

  console.log("3D model URLs:", status.result.urls);
}

main().catch(console.error);

Python Example

import requests
import time

KOLBO_API_KEY = "kolbo_live_..."  # Replace with your API key
BASE_URL = "https://api.kolbo.ai"
HEADERS = {"X-API-Key": KOLBO_API_KEY}


TERMINAL = {"completed", "failed", "cancelled"}


def poll_until_done(generation_id, interval_seconds):
    """Minimal loop. Add transient-error retries and an overall timeout for
    production — see /docs/developer-api/polling-and-cancellation."""
    while True:
        time.sleep(interval_seconds)
        status = requests.get(
            f"{BASE_URL}/api/v1/generate/{generation_id}/status",
            headers=HEADERS,
        ).json()
        if status["state"] in TERMINAL:
            return status


def generate_3d(body):
    """Generate a 3D model and poll until complete."""
    started = requests.post(
        f"{BASE_URL}/api/v1/generate/3d",
        headers=HEADERS,
        json=body,
    ).json()
    if not started.get("success"):
        raise Exception(started.get("error", "Request failed"))

    status = poll_until_done(started["generation_id"], started["poll_interval_hint"])
    if status["state"] != "completed":
        raise Exception(status.get("error", status["state"]))
    return status["result"]


# Text mode — prompt only
result = generate_3d({"prompt": "A low-poly tree with autumn leaves", "enable_pbr": True})
print("3D model URLs:", result["urls"])
print("Thumbnail:", result["thumbnail_url"])

# Single-image mode
result = generate_3d({
    "reference_images": ["https://example.com/chair-photo.jpg"],
    "prompt": "Wooden dining chair",
})
print("Single-image URLs:", result["urls"])

# Multi-image mode
result = generate_3d({
    "reference_images": [
        "https://example.com/mug-front.jpg",
        "https://example.com/mug-side.jpg",
    ],
    "texture_prompt": "Ceramic mug with glossy finish",
})
print("Multi-image URLs:", result["urls"])

Python — Choosing a Specific Model

import requests

KOLBO_API_KEY = "kolbo_live_..."  # Replace with your API key
BASE_URL = "https://api.kolbo.ai"
HEADERS = {"X-API-Key": KOLBO_API_KEY}

# Step 1: Fetch available 3D models
models = requests.get(
    f"{BASE_URL}/api/v1/models?type=three_d", headers=HEADERS
).json()["models"]
print("Available 3D models:")
for m in models:
    print(f"  {m['identifier']} ({m['credit']} credits)")

# Step 2: Generate with a specific model — generate_3d() from the example above
# already handles the submit-then-poll round trip.
result = generate_3d({
    "prompt": "A futuristic helmet with visor",
    "model": models[0]["identifier"],
    "enable_pbr": True,
})
print("3D model URLs:", result["urls"])
print("Thumbnail:", result["thumbnail_url"])

Tips

  • 3D is the slowest generation type on the API. The server gives a job 15 minutes before it times out, so size your poll loop accordingly and use the poll_interval_hint of 8 seconds rather than tight polling.
  • Multi-image mode reconstructs from several views, so it needs more input preparation than single-image mode.
  • PBR textures are on by default — send "enable_pbr": false if you want an unlit mesh.
  • Output formats can include .glb (universal), .fbx (game engines), .obj (legacy) and .usdz (Apple AR). Which are produced depends on the model, so match result.urls entries by extension.
  • Use result.thumbnail_url for quick previews without loading the full mesh.

Finding Models

Use the Models endpoint to discover available 3D models. The type=three_d filter covers all four sub-types (3d_text_to_model, 3d_image_to_model, 3d_multi_image_to_model, 3d_world), and you can pass any of those sub-types as type directly to narrow further:

curl "https://api.kolbo.ai/api/v1/models?type=three_d" \
  -H "X-API-Key: kolbo_live_..."