X3DStudios

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.

This is not an API-key endpoint
There is no developer key for generation. The route accepts a signed-in NextAuth session cookie, or a Chrome extension token (x3d_ext_…) sent as Authorization: Bearer or x-api-key. Your x3d_live_ print key does not work here and returns 401. Everything on this page describes what those two credentials can do.
POST/api/generateSigned in

Generate a 3D model from text, one image, an image plus text, or 2–4 photos. Synchronous.

Authentication#

CredentialHow it is sentWho has one
Session cookieSet by signing in at x3dstudios.com. Sent by the browser.Anyone with an account. This is what /design uses.
Extension tokenAuthorization: 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.
Both are billed and rate-limited the same way — everything downstream keys off the account email.

A request with neither gets 401 {"error":"Authentication required"}. Pairing the extension is covered in /docs/integrations/chrome-extension.

Use the async endpoint in production
Generation polls its engine every two seconds for up to 15 minutes. The synchronous route will be cut off by the proxy long before that. POST /api/generate/start takes exactly the same body and content type, returns {"jobId","status":"queued"} straight away, and runs this pipeline in the background. See /docs/api/generation-status.

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.

Body
promptstringrequired
What to build. Trimmed, must be non-empty, 500 characters maximum. Moderated before any credit is charged.
modestringoptionaldefault fast_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.
Text to 3D
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.

Form fields
inputModestringoptionaldefault image
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.
modestringoptionaldefault fast_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=high
Two form fields do nothing
removeBackground and reconstruction are parsed and then never reach an engine payload. If you are setting them today, they have no effect on the mesh you get back. They are still accepted so old clients keep working.

Advanced 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.

NameValuesDefaultEffect
meshQualitystandard · high · ultrastandardTarget triangles: 50,000, 200,000 or 500,000.
topologytriangles · quadstrianglesQuads are only produced by Tripo and Meshy.
smoothingoff · light · stronglightApplied by our own repair pass, not by the engine. Two Taubin passes for light, five for strong.
hollowingsolid · hollowsolidApplied by our repair pass. Backs off from your wall in 0.4mm steps down to 0.8mm, and refuses on an open surface.
wallThicknessMm0.4 to 102.0Hollow wall target. Anything outside the range falls back to 2.0.
negativePromptstringemptyTruncated to 1024 characters. Tripo only.
smartLowPolytrue · falsefalseTripo only.
generatePartstrue · falsefalseTripo only, and it forces textures off — Tripo rejects the combination.
texturestrue · falsetrueSend the string false to turn textures off.
textureResolution1k · 2k · 4k · 8k2kMapped per engine. Meshy upgrades 1k to 2k; TRELLIS clamps 8k to 4096px.
engineauto · trellis · tripo · tripo-p1 · hunyuan · meshyautoauto lets us route. Pinning one is a hard override.
outputFormatglb · obj · stl · fbx · usdz · 3mfglbRarely does anything — see below.
refinementPasstrue · falsefalseA second Meshy pass. Costs 2 extra credits.
seednumberunsetReaches Tripo text and TRELLIS. Ignored by Tripo image, Tripo multiview, Hunyuan and Meshy.
Pinning an engine drops four options
negativePrompt, smartLowPoly and generateParts only reach Tripo; topology: quads reaches Tripo and Meshy. Asking for one of them forces the engine to Tripo on exactly one path — a text draft left on engine: auto. In hi-fi, and on every image path, the router has already chosen an engine before that check runs. Pin hunyuan or trellis, or let the router land there, and all four are discarded without a word. Set engine to tripo yourself if you need them.
You get a GLB
outputFormat reaches an engine on exactly one path — direct Meshy text-to-3D, which is off unless Meshy is enabled on the server. Every other engine returns GLB, and the file we store is always uploaded with a .glb extension. Treat outputFormat as accepted but inert, and convert on your side.

What it costs#

ComponentCredits
Base — fast_draft1
Base — hi_fi2
Any non-text input (image, image_text, multiview)+1
meshQuality: high+1
meshQuality: ultra+2
textureResolution: 4k+1
textureResolution: 8k+2
refinementPass: true+2
Text draft standard 2k = 1. Image draft standard = 2. Hi-fi text ultra = 4. Hi-fi image ultra 8k with refinement = 9.

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.

Credits are charged first and refunded on failure
The deduction happens before the engine is called. Any non-2xx response or thrown error refunds the exact amount against an idempotency key and marks the history row failed with creditRefundedAt. A 502 has already cost you nothing.

Response#

200 — text, image and image_text
{
  "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": []
}
FieldWhat it is
modelUrlThe repaired, welded, oriented mesh in our storage. Read it back through GET /api/model-proxy?url=… — see /docs/api/generation-status.
previewUrlThe 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.
persistedfalse 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.
printEstimateA 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.
printabilityRaw 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.
source3daistudio, meshy, or demo. demo means no engine key was configured and you were handed a sample mesh; credits were still charged.
agent.engineChosenThe 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.
generationIdThe history row. Pass it to /api/retexture to re-skin this model.
Multiview returns a different shape
The multiview response adds viewsUsed (2–4) and omits agent, printabilityScore, issues and recommendations. Its printStatus is also only half-mapped: print_ready still becomes ready, but everything else comes back raw — ready_with_supports, auto_repaired or needs_redesign, rather than the supports, repaired and needs-redesign below. Branch on inputMode before reading it.

printStatus#

ValueMeaningScore
readyWatertight, walls above 0.8mm, nothing needed fixing.95
supportsPrintable 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
repairedWe closed holes, fixed winding or re-oriented it, and the result is printable.85
needs-redesignNot 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
Overhangs never produce an issue. Supports are applied automatically at print time; a steep overhang shows up in recommendations instead.

Errors#

StatuserrorWhen
401Authentication requiredNo session cookie and no valid x3d_ext_ token.
429Generation limit reached. Please try again later.20 generations per hour per account. Retry-After header carries the seconds.
400invalid json bodyThe JSON body would not parse.
400prompt is requiredEmpty prompt on the JSON path, or on multipart text / image_text.
400prompt too long (max 500 chars)Prompt over 500 characters.
400invalid input modeinputMode is not one of text, image, image_text, multiview.
400invalid generation requestThe multipart body could not be parsed.
400multiview needs 2 to 4 imagesWrong number of images fields.
400image exceeds 10MB limitSingle image over 10 MB. Multiview says "an image exceeds the 10MB limit".
400unsupported image type: <mime>Not PNG, JPEG or WebP.
400image file is requiredimage or image_text with no image attached.
400nsfwModeration refused the prompt. A message field carries the reason.
403no_creditsAlso 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.
403 — not enough credits
{
  "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.