Text and image to 3D
Reference for POST /api/generate — the session or extension-token auth it really uses, every parameter it reads, credit cost, the response shape and the errors.
POST /api/generate turns a prompt, a photo, or two to four photos of the same object into a printable mesh. It repairs the result, measures it, prices it as a print, and returns the whole report in one JSON body. It charges credits before it calls the engine and refunds them if the call fails.
/api/generateSigned inGenerate a 3D model from text, one image, an image plus text, or 2–4 photos. Synchronous.
Authentication#
| Credential | How it is sent | Who has one |
|---|---|---|
| Session cookie | Set by signing in at x3dstudios.com. Sent by the browser. | Anyone with an account. This is what /design uses. |
| Extension token | Authorization: Bearer x3d_ext_… or x-api-key: x3d_ext_… | Issued to the Chrome extension when you pair it. Minted per connection, revocable individually, stored only as a hash. |
A request with neither gets 401 {"error":"Authentication required"}. Pairing the extension is covered in /docs/integrations/chrome-extension.
JSON request — text to 3D#
Send application/json to generate from a prompt alone. There is no inputMode field on this path; it is always text.
promptstringrequired- What to build. Trimmed, must be non-empty, 500 characters maximum. Moderated before any credit is charged.
modestringoptionaldefaultfast_draft- Anything other than the exact string hi_fi is treated as fast_draft. Draft skips both language-model round trips — the prompt is decorated locally and the engine is picked without routing — so it starts sooner and costs one credit less.
fast_drafthi_fi stylestringoptional- Free text. In draft it is appended to the prompt as ", <style> style". In hi-fi it is passed to the prompt-enhancement step. It is never sent to an engine as its own field.
advancedobjectoptional- Mesh, texture and engine controls. See the table below.
curl https://x3dstudios.com/api/generate \
-H "Authorization: Bearer $X3D_EXT_TOKEN" \
-H "content-type: application/json" \
-d '{
"prompt": "a hex-pattern desk tidy with three compartments",
"mode": "fast_draft",
"advanced": { "meshQuality": "high", "hollowing": "hollow", "wallThicknessMm": 2.4 }
}'Multipart request — image, image + text, multiview#
Send multipart/form-data for anything with a picture in it. The same form also accepts inputMode=text, in which case it behaves like the JSON path.
inputModestringoptionaldefaultimage- Omitting it means image, not text. Any other value is rejected with 400 invalid input mode.
textimageimage_textmultiview promptstringoptional- Required for text and image_text. Optional for image, where it falls back to the file name and then to "3D object from image". Optional for multiview, where it falls back to "Multiview from N photos". 500 characters maximum in every mode.
imagefilerequired- Required for image and image_text. 10 MB maximum. Content type must be image/png, image/jpeg or image/webp.
imagesfile (repeated)optional- Multiview only. Two to four files, each under 10 MB, each PNG, JPEG or WebP. Fewer than two or more than four is 400 multiview needs 2 to 4 images.
modestringoptionaldefaultfast_draft- Same rule as the JSON path. Image and image_text always run the full enhancement and routing pipeline regardless of mode; only text generation gets the draft shortcut. Multiview runs neither step — it is pinned to Tripo and sends your prompt through unenhanced — so mode changes nothing there but the price.
fast_drafthi_fi stylestringoptional- As above.
Advanced options are sent as flat top-level form fields on this path — meshQuality, engine, textureResolution and the rest, using the same names as the JSON advanced object. Booleans are the literal string true; textures is false only for the literal string false.
curl https://x3dstudios.com/api/generate \
-H "Authorization: Bearer $X3D_EXT_TOKEN" \
-F inputMode=image_text \
-F [email protected] \
-F "prompt=turn this sketch into a solid keyring fob" \
-F mode=hi_fi \
-F meshQuality=highAdvanced options#
Every value is validated by a whitelist. An unrecognised value is silently replaced with the default rather than rejected, so a typo costs you the setting, not the request.
| Name | Values | Default | Effect |
|---|---|---|---|
| meshQuality | standard · high · ultra | standard | Target triangles: 50,000, 200,000 or 500,000. |
| topology | triangles · quads | triangles | Quads are only produced by Tripo and Meshy. |
| smoothing | off · light · strong | light | Applied by our own repair pass, not by the engine. Two Taubin passes for light, five for strong. |
| hollowing | solid · hollow | solid | Applied by our repair pass. Backs off from your wall in 0.4mm steps down to 0.8mm, and refuses on an open surface. |
| wallThicknessMm | 0.4 to 10 | 2.0 | Hollow wall target. Anything outside the range falls back to 2.0. |
| negativePrompt | string | empty | Truncated to 1024 characters. Tripo only. |
| smartLowPoly | true · false | false | Tripo only. |
| generateParts | true · false | false | Tripo only, and it forces textures off — Tripo rejects the combination. |
| textures | true · false | true | Send the string false to turn textures off. |
| textureResolution | 1k · 2k · 4k · 8k | 2k | Mapped per engine. Meshy upgrades 1k to 2k; TRELLIS clamps 8k to 4096px. |
| engine | auto · trellis · tripo · tripo-p1 · hunyuan · meshy | auto | auto lets us route. Pinning one is a hard override. |
| outputFormat | glb · obj · stl · fbx · usdz · 3mf | glb | Rarely does anything — see below. |
| refinementPass | true · false | false | A second Meshy pass. Costs 2 extra credits. |
| seed | number | unset | Reaches Tripo text and TRELLIS. Ignored by Tripo image, Tripo multiview, Hunyuan and Meshy. |
What it costs#
| Component | Credits |
|---|---|
| Base — fast_draft | 1 |
| Base — hi_fi | 2 |
| Any non-text input (image, image_text, multiview) | +1 |
| meshQuality: high | +1 |
| meshQuality: ultra | +2 |
| textureResolution: 4k | +1 |
| textureResolution: 8k | +2 |
| refinementPass: true | +2 |
The 4k and 8k surcharge is only charged when the request can actually come back textured. A text draft cannot, unless you pin an engine whose name starts with tripo, so in that one case the surcharge is skipped. New accounts start with 5 credits; see /docs/studio/credits.
Response#
{
"modelUrl": "gs://trimod-assets/models/<taskId>/model.glb",
"previewUrl": "gs://trimod-assets/models/<taskId>-preview/model.glb",
"meshRepair": { "applied": true, "summary": "…", "weldedVertices": 41822, "filledHoles": 2 },
"printEstimate": {
"grams": 23, "printHours": 1.1, "total": 9.76, "currency": "USD",
"material": "PLA", "quality": "standard",
"sizeMm": { "x": 170, "y": 118, "z": 96 }, "scaledToPlate": true, "note": "…"
},
"printability": {
"watertight": true, "volumeCm3": 41.22, "holes": 0, "nonManifoldEdges": 0,
"triangles": 49812, "dimensionsMm": [170, 118.4, 96.2],
"minFeatureMm": 96.2, "featureSizeEstimated": true,
"maxOverhangDeg": 38, "fitsPlate": true
},
"persisted": true,
"taskId": "…", "source": "3daistudio", "mode": "fast_draft", "inputMode": "text",
"creditsCharged": 1, "generationId": "…",
"agent": { "enhancedPrompt": "…", "addedConstraints": ["clean geometry", "solid base"],
"engineChosen": "auto", "engineReasoning": "…", "complexity": "unknown",
"estimatedSeconds": 20, "promptLogId": "local", "routingLogId": "local",
"validationLogId": "…" },
"printStatus": "ready",
"printDetail": "Print-ready review · estimated walls 96.2mm · 38° max overhang",
"printabilityScore": 95,
"issues": [], "recommendations": []
}| Field | What it is |
|---|---|
| modelUrl | The repaired, welded, oriented mesh in our storage. Read it back through GET /api/model-proxy?url=… — see /docs/api/generation-status. |
| previewUrl | The engine's original mesh, textures intact. Repair drops UVs, so this is what the on-screen preview uses. Equal to modelUrl when no preview was written. |
| persisted | false means storage failed and modelUrl is the engine's own pre-signed URL, which expires within the hour. Download it immediately. |
| meshRepair | { applied: false, summary: "No repairs needed" }, or applied: true with the full repair report spread alongside — weldedVertices, removedDegenerates, flippedFaces, filledHoles, remainingBoundaryEdges, scaleApplied, smoothed, hollowed, reoriented, hollowWallMm, partCount, notes. |
| printEstimate | A print quote for the mesh at the size we normalised it to, as one PLA part at standard 0.2mm quality. total is the whole order including flat US shipping, not the part alone. null when the bounding box has a zero axis. The slicer sets the real price at checkout — see /docs/pricing. |
| printability | Raw measurements. holes counts open boundary edges, not rings. minFeatureMm is the smallest overall dimension of the part, not a measured wall — featureSizeEstimated is always true, and printDetail repeats the same number as "estimated walls". fitsPlate is max dimension ≤ 340mm, the H2S build volume. |
| source | 3daistudio, meshy, or demo. demo means no engine key was configured and you were handed a sample mesh; credits were still charged. |
| agent.engineChosen | The routed engine. In draft mode with engine: auto this is literally "auto" — routing is stubbed out for speed and the real engine is picked further down. |
| generationId | The history row. Pass it to /api/retexture to re-skin this model. |
printStatus#
| Value | Meaning | Score |
|---|---|---|
| ready | Watertight, walls above 0.8mm, nothing needed fixing. | 95 |
| supports | Printable with supports. Only the paths that ask the language model to read the measurements — hi-fi, image and multiview — can return this; the rule-based reviewer never does. | varies |
| repaired | We closed holes, fixed winding or re-oriented it, and the result is printable. | 85 |
| needs-redesign | Not watertight and not repairable, or the smallest dimension is under 0.8mm, or the mesh could not be read at all (score 0). | 0, 40 or 45 |
Errors#
| Status | error | When |
|---|---|---|
| 401 | Authentication required | No session cookie and no valid x3d_ext_ token. |
| 429 | Generation limit reached. Please try again later. | 20 generations per hour per account. Retry-After header carries the seconds. |
| 400 | invalid json body | The JSON body would not parse. |
| 400 | prompt is required | Empty prompt on the JSON path, or on multipart text / image_text. |
| 400 | prompt too long (max 500 chars) | Prompt over 500 characters. |
| 400 | invalid input mode | inputMode is not one of text, image, image_text, multiview. |
| 400 | invalid generation request | The multipart body could not be parsed. |
| 400 | multiview needs 2 to 4 images | Wrong number of images fields. |
| 400 | image exceeds 10MB limit | Single image over 10 MB. Multiview says "an image exceeds the 10MB limit". |
| 400 | unsupported image type: <mime> | Not PNG, JPEG or WebP. |
| 400 | image file is required | image or image_text with no image attached. |
| 400 | nsfw | Moderation refused the prompt. A message field carries the reason. |
| 403 | no_credits | Also returns message, needsPayment, creditsRequired and creditsAvailable. |
| 502 | <engine message> | The engine failed, timed out at 15 minutes, or is out of vendor credits. Your credits are refunded. |
{
"error": "no_credits",
"message": "This generation costs 4 credits but you only have 1. Add credits to continue.",
"needsPayment": true,
"creditsRequired": 4,
"creditsAvailable": 1
}Rate limit#
Twenty calls an hour per account, counted per email rather than per credential, so the website and the extension share one bucket. POST /api/generate/start has its own separate bucket of twenty an hour, and a job it starts does not consume a /api/generate slot. There are no tiers.