credits Workspaces OpenAPI Get your key

BitSoul3D Engine API

Everything the workspaces can do, from your own code: text and images to 3D, auto-rig and animation, PBR texturing, retopology, part splitting, format export and Gaussian splats. One API key, plain JSON over HTTPS, credits charged per job and refunded automatically when a job fails.

Checking engine status X-API-Key header JSON in, JSON out Poll every 3 s Failed jobs auto-refund Results hosted on our domain
Base URLhttps://bitsoulhosting.com/marketplace/api
1 CreatePOST /gen/create with an op and its fields. You get a task_id back within a second or two; credits are reserved up-front.
2 PollGET /gen/task/:id every 3 seconds. progress climbs to 100 and status becomes done or failed.
3 DownloadGET /gen/file/:id/:n streams each result file (GLB, FBX, PNG, USDZ, STL, .splat) from our servers, owner-only.

The engine never holds a connection open while it works. Your client drives the waiting, so a job survives network blips, and the same key works from a build script, a game-engine plugin, a CI pipeline or a browser.

#Authentication

Every engine request carries your member API key in the X-API-Key header. There are no tokens to refresh and no OAuth dance.

X-API-Key: YOUR_API_KEY
Content-Type: application/json
User-Agent: MyStudioApp/1.0 ([email protected])

Where your key lives

  1. Sign in (or join free) on the account page.
  2. Open your dashboard. The API key row shows the key masked, with reveal and copy controls.
  3. Regenerate issues a new key and retires the old one immediately. Changing your password also rotates the key, so update any script or plugin that stores it.
Signed in as you — your key ends in ····

Send a real User-Agent

Our edge blocks requests whose User-Agent is empty or looks like a generic scraper (the default strings of common command-line and scripting HTTP clients are on that list). Name your app, for example MyStudioApp/1.0 ([email protected]), and you will never see a 403. Browsers set this header for you.

Keep the key server-side

The key is as powerful as your login: it spends your credits and reads your generations. Put it in an environment variable or a secrets store, never in shipped client code or a public repository. If it leaks, regenerate it from the account page.

A missing or invalid key returns 401 with {"error": "..."}. Generations belong to the key that created them; another member's task id simply returns 404.

#Quick start: create, poll, download

The three calls below turn a sentence into a downloadable GLB. The same pattern applies to every operation: only the op and its fields change.

BASE=https://bitsoulhosting.com/marketplace/api
KEY=your_api_key
UA="MyStudioApp/1.0 ([email protected])"

# 1. Create a job — credits are reserved now, refunded if it fails
curl -s -A "$UA" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"op":"text-to-3d","prompt":"a weathered bronze compass, game prop","quality":"standard","visibility":"private"}' \
  "$BASE/gen/create"
# → {"task_id":"6f1c2d…","cost":20,"status":"running"}

# 2. Poll every 3 seconds until status is "done" or "failed"
TASK=6f1c2d…
while true; do
  BODY=$(curl -s -A "$UA" -H "X-API-Key: $KEY" "$BASE/gen/task/$TASK")
  echo "$BODY" | grep -q '"status":"running"' || break
  sleep 3
done
echo "$BODY"
# → {"task_id":"…","op":"text-to-3d","status":"done","progress":100,"cost":20,
#    "files":[{"name":"model.glb","file":"…","bytes":2481920,"mime":"model/gltf-binary"}]}

# 3. Download file 0
curl -s -A "$UA" -H "X-API-Key: $KEY" -o compass.glb "$BASE/gen/file/$TASK/0"

Job runtimes vary from a few seconds for images to a few minutes for cinematic-quality 3D. Keep the 3-second interval; polling faster does not make the engine finish sooner and can trip the per-key poll limiter.

#Operations & costs

Costs are in engine credits and come straight from GET /api/gen/config, the same public endpoint the workspaces read. Your plan's monthly allowance is spent first, then purchased packs. rig-check and import are free.

Loading live costs from the engine…
OpWhat it makesCreditsResult files
3D generation
text-to-3dText prompt to a textured 3D modelmodel.glb (model.fbx when quad)
image-to-3dOne image to a 3D modelmodel.glb
photo-scanTwo to four photos of one object to a modelmodel.glb
splatOne photo to a Gaussian splat scenescene.splat
Images
text-to-imagePrompt to an image, priced by tier (see below)image-1.png
image-editEdit, restyle or fuse up to three reference images, priced by tierimage-1.png
multiviewOne image to front / left / back / right viewsfront.png left.png back.png right.png
multiview-editRe-prompt one or more views of a turntable sheetfour view files again
Mesh tools
texturePaint full PBR textures onto a modelmodel.glb
retopoRetopology: smart (clean loops, optional quads) or basic (fast decimation)model.glb (model.fbx when quad)
ai-segmentSplit a model into named partsmodel.glb with separated parts
smart-segmentPart split from an image or a model, with a mask and part namesparts.glb mask.png + output.parts[]
completeClose or rebuild the hidden geometry of split partsmodel.glb
restyleRebuild as a brick build, voxels, cells or a block worldmodel.glb
convertExport to USDZ, FBX, OBJ, STL, GLTF or 3MFexport.<ext>
Rig & motion
rig-checkIs this model riggable, and as what?no files — output.riggable, output.rig_type
rigAuto-rig: skeleton plus skin weightsmodel.glb or model.fbx
retargetApply up to five motion presets to a rigged modelclip-1.glbclip-5.glb
Utilities
importRegister an uploaded model as a task so every tool above can use itmodel.glb

Image tiers

text-to-image and image-edit take a style tier. The tier sets the cost and which size values are allowed; 4K adds a size surcharge.

styleTierCreditsSizes
turboFastsee config1K
standardStandardsee config2K
proProsee config2K
ultraUltrasee config2K, 4K

Variable-cost operations

OpModeCreditsNotes
retopomode: "smart"face_limit see config; quad output up to see config faces
retopomode: "basic"face_limit see config
completemode: "ai"rebuilds hidden geometry
completemode: "cap"quick cap of open parts
smart-segmentfrom an imageimage_token or image_task
smart-segmentfrom a modelinput = one of your model tasks
convertwith any advanced optionquad, symmetry, texture size or format, pivot, scale, orientation, preset, animation
text-to-image / image-editsize: "4K"on top of the Ultra tier
retargetper presetup to five presets per call
ai-segmentsemantic: trueon top of the base cost; labels parts by meaning

3D quality ladder

The quality field on text-to-3d, image-to-3d and photo-scan moves the price up or down from the op's base cost. These steps are fixed and not listed in config; the cost in every create response is the final amount charged, so show that to your users.

qualityWhat you getCredits
draftGeometry only, no textures, fastest previewbase − 10 (never below 5)
standardTextured model (default)base
proDetailed texture passbase + 10
cinematicDetailed textures and detailed geometrybase + 30 (base + 10 with game_ready)

3D add-ons

Optional booleans on text-to-3d, image-to-3d and photo-scan. Each adds to the base cost; prices come from config.addons.

FieldEffectCredits
quadQuad-dominant topology; result arrives as model.fbx
low_polySmart low-poly output for real-time use (face_limit is kept within 500–20000)
partsGenerate as separate, named parts (geometry only, no textures; takes precedence over quad)
ultra_textureUltra texture resolution
pro_textureNot a field on generation; this is the price of texture with quality: "pro"
hd_geometryNot a field; this is the geometry upgrade included in quality: "cinematic"

game_ready uses a different pipeline

With game_ready: true the engine builds a clean low-polygon game mesh (about 5000 faces). On that pipeline quad, low_poly, parts and ultra_texture are ignored and not charged, and cinematic is priced as pro.

#Request bodies

POST /api/gen/create takes a JSON object with an op plus the fields below. Fields marked optional can be omitted. Unknown fields are ignored, so send only what is listed.

Chaining: input is always one of your task ids

Mesh, rig and motion operations work on an earlier result. Pass that result's task_id as input. Image operations accept either a fresh image_token from an upload or an image_task id of an image you generated. There are no other identifiers to juggle.

Common fields

op
One of the operation names in the table above. Required.
visibility
"public" or "private" optional. Public results can appear in the community gallery. Generations default to public; import, splat, multiview, multiview-edit, smart-segment, convert, complete and retopo default to private. You can change it later (see Visibility & delete).
seed
Integer optional where listed. Same seed and inputs reproduce the same result as closely as the engine allows.

3D generation

OpBody fields
text-to-3dprompt (3–1200 chars) · optional: negative (≤255) · quality draft|standard|pro|cinematic · game_ready · seed · face_limit · quad · low_poly · parts · auto_size · ultra_texture · visibility
image-to-3dimage_token or image_task · optional: quality · game_ready · seed · face_limit · quad · low_poly · parts · auto_size · ultra_texture · align_image · autofix · visibility
photo-scanviews object with front, left, back, right image tokens (two to four), or multiview_task = one of your multiview / multiview-edit tasks · same add-ons as text-to-3d · visibility
splatimage_token or image_task · optional: seed

Images

OpBody fields
text-to-imageprompt · style turbo|standard|pro|ultra · size "1K"|"2K"|"4K" · aspect (see list) · optional: negative · template · visibility
image-editimage_token or image_task · prompt (required unless a template is given) · style · size · aspect · optional: refs array of up to 3 image tokens · template · negative · visibility
multiviewimage_token or image_task · optional: visibility
multiview-editinput = one of your multiview tasks · prompts array of up to 4 { "prompt": "...", "view": "front"|"left"|"back"|"right" }
aspect
1:1 3:2 2:3 4:3 3:4 16:9 9:16 21:9 4:5 5:4 (live list: config.aspects)
template
Prompt presets. For text-to-image: asset_extraction (isolate subject), character_completion (complete character), t_pose (T-pose for rigging), variants (design variations), figure (full-body figure). For image-edit: t_pose, character_completion, 3d_enhance (enhance for 3D), variants, figure. Live list: config.templates.

Mesh tools

OpBody fields
textureinput · optional: prompt (≤400 chars; ignored when ref_image_token is set) · quality pro|ultra (omit for Standard) · style_image_token · ref_image_token · part_names array · seed · alignment original|geometry · pbr
retopoinput · mode smart|basic · face_limit (ranges in the cost table; out-of-range values are clamped, not rejected) · optional: quad (smart only) · bake
ai-segmentinput · optional: semantic (labels parts by meaning; adds config.addons.semantic) · with semantic only: granularity simple|balanced|detailed · split
smart-segmentone of image_token / image_task / input (a model task) · granularity coarse|medium|fine · optional: hint
completeinput = one of your ai-segment tasks · mode ai|cap · optional: part_names array
restyleinput · style one of lego (brick build) · voxel · voronoi (cells) · minecraft (block world) · optional: block_size
convertinput · format USDZ|FBX|OBJ|STL|GLTF|3MF · optional (advanced, see cost table): print_ready · quad · symmetry · face_limit · texture_size 1024|2048|4096 · texture_format JPEG|PNG|WEBP · pivot_bottom · scale · orientation +x|-x|+y|-y · fbx_preset blender|3dsmax|mixamo (FBX only) · in_place · with_animation. print_ready applies to STL and 3MF. A rig or retarget result cannot be exported as OBJ, STL or 3MF (400); use FBX, GLTF or USDZ.

Rig & motion

OpBody fields
rig-checkinput. Free. Result in output: { "riggable": true, "rig_type": "biped" }
riginput · rig_type biped|quadruped|hexapod|octopod|avian|serpentine|aquatic · spec native|mixamo (default mixamo, industry-standard humanoid bone naming) · out_format glb|fbx · library current|classic (classic is for biped rigs only and always uses native bone names)
retargetinput = one of your rig tasks · animations array of 1–5 preset names · optional: in_place · out_format glb|fbx · bake
importmodel_token from POST /api/gen/upload-model · optional: visibility

Motion presets: the current rig line ships preset:idle, preset:walk, preset:run, preset:jump, preset:dive, preset:climb, preset:slash, preset:shoot, preset:hurt, preset:fall, preset:turn plus non-biped gaits such as preset:quadruped:walk. Rigs built with library: "classic" use the 90+ humanoid preset:biped:* moves instead. Both lists are in config.animations and config.animations_classic.

#Responses & errors

Create

200 OK
{ "task_id": "6f1c2d0a-…", "cost": 20, "status": "running" }

cost is the final number of credits reserved for this job, after quality, add-ons, per-view or per-animation multipliers. It is the figure to show your users.

Task

GET /api/gen/task/:id
{
  "task_id": "6f1c2d0a-…",
  "op": "text-to-3d",
  "status": "running" | "done" | "failed",
  "progress": 0100,
  "cost": 20,
  "error": "…",                    // only when failed
  "files": [                         // empty until done
    { "name": "model.glb", "file": "6f1c2d0a-…-0.glb", "bytes": 2481920, "mime": "model/gltf-binary" }
  ],
  "output": { … }                    // rig-check: { riggable, rig_type } · smart-segment: { parts: [names] }
}

Download files by their index in files: GET /api/gen/file/:task_id/0, /1 and so on. Check files[0].name before you assume a GLB: quad-mesh results and FBX rigs arrive as .fbx, exports as export.usdz and similar, splats as scene.splat.

Error codes

HTTPBodyMeaningCharged?
400{ error }Validation: unknown op, missing prompt, bad token, out-of-range value. Fix the request.No
401{ error }Key missing, invalid or inactive.No
402{ error, need, allowance_left, balance }Not enough credits. need is the job's cost; allowance_left is what remains of this month's plan allowance; balance is purchased credits. Top up on Plans & credits.No
404{ error }No such task for this key, or the file index does not exist.
429{ error, retry_after, lane }That engine lane is busy. Wait retry_after seconds, then send the same create again.No
429{ error } (no retry_after)You already have the maximum number of running jobs (limits.max_running), or you exceeded a per-key request limit. Wait for a job to finish; do not retry in a tight loop.No
502{ error }The engine could not process the request after it was accepted.Charged, then refunded automatically
503{ error }Engine at capacity or generation temporarily disabled. Try again in a few minutes.No
403HTML, from the edgeBlocked User-Agent (empty or generic scraper string). Set a real one and retry.No

A task that reaches status: "failed" after creation is refunded in full, automatically, to wherever the credits came from (allowance or balance). The error string is safe to show to your users.

#Polling rules

  • Poll every 3 seconds per task. The per-key poll budget (2400 requests per 15 minutes) comfortably covers three jobs at that rate; tighter loops waste it and earn a 429 with "Slow down.".
  • Concurrency: at most limits.max_running jobs per key (live value: see config). A create beyond that returns 429 without retry_after; wait for a job to finish.
  • Lane busy (429 with retry_after and lane): nothing was charged. Sleep retry_after seconds (clamp to 3–120), then repeat the same create. Give up after about six attempts and tell the user the engine is busy. Lanes are per-op families; the live capacities are in config.lanes.
  • 402: stop, show need versus allowance_left + balance, and point the user at top-up. Retrying will not help.
  • Transient poll errors (network, a non-JSON body, a 5xx from the edge): keep polling. Abandon only after about twenty consecutive failures.
  • progress: 99 means the engine has finished and we are copying the results to our servers. Keep polling; the next response usually carries files.
  • Create limit: 40 creates per key per 15 minutes, 30 image uploads and 10 model uploads per 15 minutes. Batch work accordingly.
  • Results stay available under your account until you delete them. There is no expiry to race.
Reference loop (JavaScript)
async function runJob(body, { onWait, onProgress } = {}) {
  let created = null;
  for (let i = 0; i < 6 && !created; i++) {
    const r = await fetch(`${BASE}/gen/create`, { method: 'POST', headers: H, body: JSON.stringify(body) });
    const j = await r.json().catch(() => ({}));
    if (r.ok) { created = j; break; }
    if (r.status === 429 && j.retry_after) {
      const wait = Math.min(120, Math.max(3, j.retry_after | 0));
      onWait && onWait(wait, j.lane);
      await sleep(wait * 1000); continue;
    }
    throw Object.assign(new Error(j.error || `HTTP ${r.status}`), { status: r.status, data: j });
  }
  if (!created) throw new Error('The engine stayed busy — please try again in a minute.');

  let errors = 0;
  for (;;) {
    await sleep(3000);
    let t;
    try { const r = await fetch(`${BASE}/gen/task/${created.task_id}`, { headers: H }); if (!r.ok) throw 0; t = await r.json(); errors = 0; }
    catch { if (++errors >= 20) throw new Error('Lost track of that job.'); continue; }
    onProgress && onProgress(t.progress || 0, t);
    if (t.status === 'done') return t;
    if (t.status === 'failed') throw Object.assign(new Error(t.error || 'The job failed — you were not charged.'), { task: t });
  }
}

#Uploads

Uploads are raw bytes in the request body, not multipart forms. Each returns a token you pass to create. Tokens are single-purpose handles, so upload once and reuse the token across several jobs.

EndpointBodyLimitsReturns
POST /api/gen/uploadPNG or JPEG bytes; Content-Type: image/png or image/jpegup to see config MB · 30 per 15 min{ "image_token": "…" }
POST /api/gen/upload-modelGLB bytes with Content-Type: model/gltf-binary; or FBX / OBJ / STL bytes as application/octet-stream with ?ext=fbx, obj or stlup to see config MB · 10 per 15 min{ "model_token": "…" }

Convert WebP or other formats to PNG before uploading. A model_token becomes usable by the mesh and rig tools after one free { "op": "import", "model_token": "…" } job, whose task_id you then pass as input.

# Image → image_token → image-to-3d
curl -s -A "$UA" -H "X-API-Key: $KEY" -H "Content-Type: image/png" \
  --data-binary @photo.png "$BASE/gen/upload"
# → {"image_token":"…"}
curl -s -A "$UA" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"op":"image-to-3d","image_token":"…","quality":"standard"}' "$BASE/gen/create"

# Model → model_token → import (free) → rig
curl -s -A "$UA" -H "X-API-Key: $KEY" -H "Content-Type: model/gltf-binary" \
  --data-binary @hero.glb "$BASE/gen/upload-model"
# → {"model_token":"…"}
# FBX / OBJ / STL: name the extension and send octet-stream
curl -s -A "$UA" -H "X-API-Key: $KEY" -H "Content-Type: application/octet-stream" \
  --data-binary @hero.fbx "$BASE/gen/upload-model?ext=fbx"
curl -s -A "$UA" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"op":"import","model_token":"…"}' "$BASE/gen/create"
# poll that task to done, then:
curl -s -A "$UA" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"op":"rig","input":"IMPORT_TASK_ID","rig_type":"biped","spec":"native","out_format":"glb","library":"current"}' "$BASE/gen/create"

#Files & history

EndpointReturns
GET /api/gen/file/:task_id/:nThe bytes of files[n] with its mime as Content-Type (model/gltf-binary, image/png, application/octet-stream for FBX, USDZ, STL and .splat). Owner-only; 404 until the task is done. Cacheable privately for a day.
GET /api/gen/thumb/:task_idA small WebP preview (engine render for 3D results, a 320 px downscale for images). 404 when none exists, for example a convert or rig-check. Handy for pickers and history views.
GET /api/gen/tasks{ "tasks": [ … ] }: your 50 most recent jobs, newest first. Each item is the task shape plus visibility, likes, has_thumb, created_at and params_json (a JSON string of your own inputs, such as the prompt and preset choices, for display).
GET /api/gen/balance{ "credits": 120, "allowance": { "total": 300, "used": 80, "left": 220 }, "tier": "pro" }: purchased credits, this billing period's plan allowance, and your plan tier. Spendable total is credits + allowance.left.
GET /api/gen/configPublic, no key needed: enabled, every op's cost, credit packs, motion presets, rig types, lane capacities, image tiers, aspects, templates, add-on prices, retopo / complete / segment / restyle pricing and limits. Read it at start-up instead of hardcoding numbers.
Pick your done models
const { tasks } = await (await fetch(`${BASE}/gen/tasks`, { headers: H })).json();
const models = tasks.filter(t => t.status === 'done' && t.files[0] && /\.glb$/.test(t.files[0].name));
for (const t of models) console.log(t.task_id, t.op, t.files[0].bytes, t.created_at);

#Visibility & delete

EndpointBodyReturns
POST /api/gen/task/:id/visibility{ "visibility": "public" | "private" }{ "ok": true, "visibility": "private" }. Public results may be shown in the community gallery with a non-identifying creator handle; private ones are yours alone.
DELETE /api/gen/task/:idnone{ "ok": true }. Removes the task, every result file and its thumbnail. This is permanent and does not refund credits, so download first.
# make a result private
curl -s -A "$UA" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"visibility":"private"}' "$BASE/gen/task/$TASK/visibility"

# delete it for good
curl -s -A "$UA" -H "X-API-Key: $KEY" -X DELETE "$BASE/gen/task/$TASK"

#Limits

Live values from config.limits and config.lanes. Request-rate limits are per API key unless noted.

LimitValueApplies to
Running jobs per keysee configcreate returns 429 beyond this
Image upload sizesee configupload, PNG or JPEG
Model upload sizesee configupload-model
Prompt lengthsee configall prompts
Negative prompt lengthsee confignegative
Photo-scan viewssee configphoto-scan / multiview-edit prompts
Reference imagessee configimage-edit refs
Creates40 per 15 minPOST /api/gen/create
Uploads30 images, 10 models per 15 minupload, upload-model
Polls2400 per 15 minGET /api/gen/task/:id
Edge rate10 requests/s per IP, burst 20; 20 open connections per IPeverything under /marketplace/; over the limit answers 429 from the edge

Engine lanes

Each op family runs in a lane with a fixed number of simultaneous slots across all members. When a lane is full, create answers 429 with retry_after and the lane name, and nothing is charged.

LaneSlots
see config.lanes

#Chaining jobs

Pipelines are just sequential creates where each input is the previous task_id. Costs add up per step; check rig-check (free) before paying for a rig, and read config.ops to show the total up-front.

Text → model → rig → two animations
const cfg = await (await fetch(`${BASE}/gen/config`)).json();
const total = cfg.ops['text-to-3d'].cost + cfg.ops.rig.cost + cfg.ops.retarget.cost * 2;
console.log(`This pipeline costs ${total} credits`);

const model = await runJob({ op: 'text-to-3d', prompt: 'a stylised knight, T-pose, clean silhouette', quality: 'standard' });
const check = await runJob({ op: 'rig-check', input: model.task_id });
if (!check.output || !check.output.riggable) throw new Error('Not riggable — try a clearer pose');

const rig  = await runJob({ op: 'rig', input: model.task_id, rig_type: check.output.rig_type || 'biped', spec: 'native', out_format: 'glb', library: 'current' });
const anim = await runJob({ op: 'retarget', input: rig.task_id, animations: ['preset:walk', 'preset:idle'], in_place: true });
// anim.files → clip-1.glb (walk), clip-2.glb (idle)
for (let i = 0; i < anim.files.length; i++) await fs.writeFile(anim.files[i].name, await download(anim.task_id, i));

Other useful chains: text-to-image with template: "t_pose"image-to-3drig; ai-segmentcomplete; image-to-3dretopoconvert to USDZ for AR or STL for printing; any model → restyle for a voxel or brick-build variant.

#OpenAPI spec

The whole surface is described in an OpenAPI 3.1 document: every path above, the per-op request schemas with their enums, the task and error shapes, and the X-API-Key security scheme. Import it into any OpenAPI-compatible client generator, request explorer or AI coding assistant to get typed calls for free.

Spechttps://bitsoulhosting.com/marketplace/api/openapi.json Open

Costs are deliberately not baked into the spec; it points at GET /api/gen/config so generated clients always read the live numbers.

#MCP server Coming soon

A small open-source MCP server is in the works so AI coding agents and assistants that speak the Model Context Protocol can call the engine as tools: generate a model, rig it, animate it, retopologise it, make an image, all with the same API key and the same credits. Until it ships, the HTTP API above is complete on its own, and the OpenAPI document gives any agent the full contract today.

Want early access?

Open a ticket from your account page with the subject "MCP" and we will let you know the moment the package is published.