3D mesh generation
Serve image-to-3D models as jobs that each caller creates, polls, downloads, deletes and lists through the gateway, the same way as video.
2 min readUpdated for v2.4.0Edit on GitHub
model_type: "mesh"cost_per_video)A mesh model turns a picture of an object into a textured 3D model. Like a video, a mesh takes minutes, so it is a job: the create call returns a job id at once, the backend works in the background, and the client polls the job until it can download the model as binary glTF (.glb).
POST /v1/meshes create a job (an image, optional settings)
GET /v1/meshes/{id} job status, as the backend reports it
GET /v1/meshes/{id}/content the finished model (model/gltf-binary)
DELETE /v1/meshes/{id} delete the job
GET /v1/meshes the caller's own jobs
The gateway handles mesh jobs exactly like video jobs: the same ownership, routing, billing and retention, with its own path. A mesh job is only ever found through /v1/meshes; its id sent to /v1/videos is a 404, and the other way round.
What the backend serves
There is no OpenAI standard for 3D, so the gateway asks little of the backend:
POST {api_base}/meshesacceptsmultipart/form-data(the image as a file field) or JSON, and answers with a JSON object whoseidis the job id.GET {api_base}/meshes/{id}andGET {api_base}/meshes/{id}/contentreturn the job and the file. While the job runs, the download may answer any not-ready status; it is passed through.DELETE {api_base}/meshes/{id}removes the job.GET {api_base}/meshesreturns{"data": [...]}with anidandcreated_aton each job. The gateway keeps only the caller's entries, so a backend may list every job it holds.
Which settings a create takes (resolution, face count, texture size, seed) and the statuses a job goes through are the backend's; the gateway does not check them. A backend that is reachable directly can require the model's upstream key on every call, so that only the gateway can use it.
Run a job
- 1
Register the model
curl -X POST http://localhost:9180/api/v1/models \ -H "Authorization: Bearer $OBLETH_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model_name": "image-to-3d", "model_type": "mesh", "upstream_model": "my-mesh-model", "api_base": "https://mesh.example.internal/v1", "api_key": "the-backend-token", "cost_per_video": 0.20, "enabled": true }'api_baseends in/v1; the gateway appends/meshes. Mesh models share the flat per-job price field with video models, so the price goes incost_per_video; the model's form on the Models page labels it Per mesh. - 2
Create a job
curl -s http://localhost:8088/v1/meshes \ -H "Authorization: Bearer $OBLETH_KEY" \ -F model=image-to-3d -F image=@chair.png -F seed=42The response is the backend's own job object.
- 3
Poll, download and clean up
curl -s http://localhost:8088/v1/meshes/$MESH_ID -H "Authorization: Bearer $OBLETH_KEY" curl -s http://localhost:8088/v1/meshes/$MESH_ID/content -H "Authorization: Bearer $OBLETH_KEY" -o chair.glb curl -s -X DELETE http://localhost:8088/v1/meshes/$MESH_ID -H "Authorization: Bearer $OBLETH_KEY" curl -s http://localhost:8088/v1/meshes -H "Authorization: Bearer $OBLETH_KEY"
In Python:
import os
import time
import requests
url = "http://localhost:8088/v1/meshes"
headers = {"Authorization": f"Bearer {os.environ['OBLETH_KEY']}"}
with open("chair.png", "rb") as image:
job = requests.post(url, headers=headers, files={"image": image}, data={"model": "image-to-3d"}).json()
while job["status"] not in ("completed", "failed"):
time.sleep(5)
job = requests.get(f"{url}/{job['id']}", headers=headers).json()
if job["status"] == "completed":
with open("chair.glb", "wb") as f:
f.write(requests.get(f"{url}/{job['id']}/content", headers=headers).content)
Ownership, billing and limits
Everything in Video generation applies, with mesh in place of video:
- Who can see a job follows
OBLETH_VIDEO_JOB_SCOPE(keyby default, ortenant). A job id you don't own returns 404no such mesh job, the same as an id that never existed. - Billing. A create is charged the model's per-job price once the backend accepts the job and the gateway has recorded it, as request type
mesh. Polls, downloads, deletes and listings are free and write no usage row. - Admission. The create takes a slot in the model's pool only while the create call runs, so fairshare limits how fast jobs are created, not how many run at once. A backend with a queue limit can answer
429, which passes through. - Records share the job table and its retention (
OBLETH_VIDEO_JOB_RETENTION_HOURS), with the job's kind recorded on each row. - Health. Mesh models get no generation probe; the check is whether the backend's catalog lists them.
GET /v1/modelsreports them asmodel_type: "mesh".
The input image is not scanned by guardrails, and a multipart create is sent once and never retried.
In the Playground
The Playground's 3D mode takes an image (choose, drop or paste one, or send a generated picture from Image mode), shows the job's stage and progress, and opens the finished model in a viewer you can turn around, with a download button.
