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_idbody field that routes the output into a specific project. See Projects.
Endpoint
POST /api/v1/generate/3dRate 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
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes in text mode | Text 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. |
mode | string | No | "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_images | array of string | No | Public 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_prompt | string | No | Prompt used to guide texture generation |
model | string | No | Model 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. |
topology | string | No | Mesh topology preset (default: "triangle"). Reaches the Meshy models and the generic fal 3D path; the Trellis families never receive it. |
target_polycount | number | No | Target polygon count (default: 30000). Same scope as topology. |
enable_tpose | boolean | No | Force T-pose for character models (default: false). Same scope as topology. |
enable_pbr | boolean | No | Generate PBR textures. Enabled unless you send an explicit false (default: true). Same scope as topology. |
enable_prompt_expansion | boolean | No | Let the model expand the prompt before generating (default: false). Text-mode Meshy only. |
seed | number | string | No | Random seed for reproducible output, parsed as an integer. Read by the Trellis v1 and Trellis 2 families; ignored elsewhere. |
texture_size | number | string | No | Texture 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. |
resolution | number | string | No | Generation 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_id | string | No | Route 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:
| Mode | Reference Images | Description |
|---|---|---|
text | 0 (none) | Generate from text prompt only |
single | 1 | Generate from one reference image, optionally with a text prompt |
multi | 2+ | 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"
}| Field | Type | Description |
|---|---|---|
generation_id | string | Poll and cancel with this |
model | string | The model you sent, or "auto" when you omitted it and the built-in default was used |
credits_charged | number | null | Always 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_url | string | Status path without the /api prefix — prepend https://api.kolbo.ai/api |
poll_interval_hint | number | Suggested seconds between polls (8 for 3D) |
session_id / project_id | string | Both 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:
| Status | Body / code | Cause |
|---|---|---|
| 400 | prompt is required for text mode | mode resolved to text with no prompt |
| 400 | INVALID_PROMPT | prompt present but not a string |
| 400 | INVALID_MODEL | model sent as an array/object instead of a string identifier |
| 400 | reference_images required for image modes | mode is single or multi with an empty or missing array |
| 400 | Invalid mode. Must be single, multi, or text | Unrecognised mode value |
| 400 | Multi-image mode supports maximum N images | More than 4 images (6 on fal-trellis… models) in multi mode |
| 400 | Model not found | The model identifier is not in the catalogue |
| 403 | INSUFFICIENT_CREDITS | Either credit gate |
| 400 | SDK_PROJECT_INVALID_ID | project_id is not a valid ObjectId |
| 404 | SDK_PROJECT_NOT_FOUND | No project with that id is visible to you |
| 403 | SDK_PROJECT_ACCESS_DENIED | You 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_hintof 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": falseif 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.urlsentries by extension. - Use
result.thumbnail_urlfor 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_..."