Skip to content
Heimdallm
Marketplace/Documentation
Documentation · API reference

Mesh generation

POST https://api-staging.impossiblecarrot.cc/api/v1/meshes
GET  https://api-staging.impossiblecarrot.cc/api/v1/meshes/{jobId}
GET  https://api-staging.impossiblecarrot.cc/api/v1/meshes/{jobId}/content?index=0
GET  https://api-staging.impossiblecarrot.cc/api/v1/meshes/models

Requires a bearer API key. Use a model whose output modality is mesh: GET /meshes/models lists exactly those and carries the envelope each one builds inside, and the same entries read "output_modalities": ["mesh"] in the catalogue.

Jobs: submit, poll, download

A mesh takes far longer to generate than a request can be held open — sampling a dense surface is minutes of card time, with additional work for textures and export — so mesh generation is a job rather than a reply. POST /meshes answers 202 with { id, polling_url, status } the moment the job is accepted; nothing is returned inline. Read polling_url until status is terminal, then fetch the asset from unsigned_urls, or name a callback_url and skip the polling loop entirely.

One of prompt and input_references has to be present. TRELLIS.2 and Pixal3D read images, not text alongside images. The reference provider renders a prompt into an image when no view is supplied; it refuses a prompt sent with a view rather than silently ignoring the instructions. Prepare the reference image before submitting it when its design needs changes.

curl ${API_BASE_URL}/meshes \
  -H "Authorization: Bearer $HEIMDALL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "microsoft/trellis-2",
    "prompt": "A ceramic teapot with a bamboo handle, on a plain turntable",
    "target_faces": 40000,
    "generate_texture": true
  }'
StatusWhat it means
pendingAccepted, not yet started.
in_progressGenerating or repairing.
completedGenerated; the asset can be fetched until the job's window closes, or for 10 minutes after its first delivery.
failederror carries the reason.
cancelledStopped before it produced one.
expiredThe delivery window closed with nothing fetched from the job.

A job can return from in_progress to pending when the marketplace interrupts its generation before anything was charged for it, as a deployment replacing a gateway does. It then starts again from the beginning on another gateway, and only the generation that completes is charged. A job is charged for at most one generation. A generation that was charged and never stored its result — interrupted after the endpoint's reply was selected for billing, or ended by a gateway that stopped before recording it — is not tried again, since that would charge twice: the job fails with fault: "marketplace" and carries the usage it was charged.

A member appears once there is something to say: generation_id once the job has been processed, unsigned_urls once there is content to fetch, meshes beside it naming what each asset actually came to, usage once it was charged (on completion, or on the marketplace failure above), error once it failed, and fault only where that failure was the marketplace's own rather than the endpoint's. An absent member is a fact not yet established rather than an empty one, and usage.cost is null where the units consumed carry no declared price — recorded unpriced and flagged, never reported as free.

{
  "id": "mjob_8d3b06a1f4c7425e9f2a1d5b7c3e0846",
  "polling_url": "/api/v1/meshes/mjob_8d3b06a1f4c7425e9f2a1d5b7c3e0846",
  "status": "completed",
  "generation_id": "gen_01J9...",
  "unsigned_urls": ["/api/v1/meshes/mjob_8d3b06a1f4c7425e9f2a1d5b7c3e0846/content?index=0"],
  "meshes": [
    {
      "faces": 38412,
      "vertices": 19317,
      "has_texture": true,
      "bounds": { "min": [-0.5, 0, -0.5], "max": [0.5, 0.82, 0.5] }
    }
  ],
  "usage": { "cost": 0.35 }
}

Every figure under meshes is measured

faces, vertices, has_texture and bounds are read off the delivered container's own glTF chunk, never taken from what the endpoint reported. An engine that claims one face count and writes another is described by the count it wrote. That is also why an asset the marketplace cannot open is never stored: the job fails instead, because handing back bytes nobody could read would publish a figure nothing measured.

bounds is the axis-aligned box the scene occupies, composed through its node transforms, in the scene's coordinate system. glTF defines linear distances in metres, but a generated asset's scale is not a measurement of the depicted real-world object. Check and set its scale before using it.

Delivered meshes stay 10 minutes, and undelivered ones expire

The creation workspace keeps accepted job identifiers in this browser. After a reload, select an authorized API key and choose Resume to poll the saved job and collect its available assets. Resuming sends no new generation request. A download that breaks off resumes from the bytes already received. Browser storage limits and the asset expiry window still apply.

A download that starts at the first byte and is written through the last — a whole file, or Range: bytes=0- — is the asset's delivery. Delivery does not remove the asset at once: it stays available, in whole or by range, for 10 minutes from its first delivery, and a download started after that answers 410, so keep what the download gave you. Two downloads under way at once both complete. Nothing cuts a download short: the asset is never removed while a download of it is still running, so a slow connection finishes whatever windows close meanwhile. A download that breaks off leaves the asset in place: send Range: bytes=N-, where N is the number of bytes you already hold, and append what comes back. One byte range is honoured, in the forms bytes=first-last, bytes=first- and bytes=-count, and answers 206; a range that starts past the end answers 416. A range that starts after the first byte is not a delivery, so a resumed download or the segments of a parallel one leave the asset in place until one response carries it from the first byte to the last, or its job's window closes. A Range header in any other form is ignored and the whole file is sent. A HEAD request reports the length and delivers nothing.

An asset never delivered stays available until its job's window closes, 24 hours after generation or optional repair finished, and then answers 410 and is removed. A job nobody fetched anything from then becomes expired, and its callback is told; a job fetched from, even in part, stays completed and is told nothing more, since its content was claimed. A job that has not completed answers 409, which is a different answer from content that is gone.

Between them those two rules are the whole retention story: nothing generated on this rail outlives its expiry window, or the 10 minutes after its first delivery, whichever comes first, except for as long as a download already under way is still reading it. The job row itself survives, because what you were charged stays answerable after the asset has gone. That is what lets a zero-data-retention provider serve this rail and stay zero-data-retention.

Sampling controls steps, guidance_scale, negative_prompt, sampler and scheduler can be sent directly on the job request where the selected endpoint declares them. The creation workspace shows those declared controls and validates their limits before submission. Pixal3D also declares camera_fov from 1 to 170 degrees. It sets the horizontal field of view used to condition the supplied or rendered image: the single-view graph defaults to 49.13 degrees and the multi-view rig defaults to 20 degrees when the request omits it. TRELLIS.2 has no camera field-of-view control and does not declare this parameter.

Optional repair

Add refinement to the generation request to inspect and repair the mesh with Blender and a selected vision model. The same controls appear in the marketplace creation workspace. Repair runs on a medium Playground machine (2 vCPUs, 8 GiB RAM); it is optional and deployment availability is published by GET /meshes/refinement/models. That response lists live models accepting images and tool calls, and the exact machine.price_per_minute_usd rate. It returns enabled: false when repair is unavailable.

{
  "model": "tencentarc/pixal3d",
  "prompt": "A ceramic teapot with a bamboo handle",
  "target_faces": 20000,
  "generate_texture": true,
  "refinement": {
    "model": "provider/vision-model",
    "max_duration_seconds": 2400,
    "max_tool_calls": 60,
    "max_model_cost_usd": "2"
  }
}

Replace the repair model with an ID from the availability response. The defaults are 40 minutes, 60 tool calls and a $2 ceiling for model use. The model ceiling is an exact decimal string and does not include machine time or the original mesh generation. Generation uses the provider's per-mesh rate; repair model calls use regular marketplace inference prices; machine time uses Playground's per-started-minute rate. The scoped model grant cannot call another model or create another machine. The repair time limit includes machine startup and begins after the repair leaves the queue. Generation and time waiting in the queue are additional.

The job remains in_progress until repair stops. Its refinement report distinguishes waiting for generation, queued, starting, running, completed, cancelled and failed. It includes model-generation IDs, actual model and machine charges as decimal USD strings, geometry measurements, an assessment when available, and inspection images. Charges reflect settled usage and can update as settlement finishes. A repair failure is separate from a generation failure and does not affect the generator's uptime.

DELETE /meshes/{jobId}/refinement requests cancellation. Poll until repair is terminal: the original GLB stays at asset index 0; the last validated repair, when one exists, is at index 1. refinement.selected_asset_index identifies the kept result. Download each listed asset you need within its window. The creation workspace labels both downloads and offers the inspection images and JSON report. A completion webhook waits for repair to finish, including a failed or cancelled repair.

Each kept export is reopened in a separate Blender process and its topology is compared with the working mesh. A damaged export is rejected. Cancellation, timeout and worker interruption preserve the original and any validated checkpoint already saved. These checks do not certify artistic quality, rigging, game-engine import settings or a particular polygon budget after repair. Inspection images are removed when the delivery window expires; the job's billing and geometry metadata remain.

The container

GET /meshes/{jobId}/content answers model/gltf-binary bytes with no JSON envelope: one binary glTF container, with any baked texture inside it rather than beside it. GLB is the one container this rail serves, which is what makes a single delivered file a complete asset — a format whose textures sit in sibling files would arrive as a mesh with nothing on it.

Geometry and images must be stored in the GLB binary chunk. Buffer and image URI fields are rejected, including data URIs; export embedded images through binary buffer views.

curl -o teapot.glb \
  "${API_BASE_URL}/meshes/mjob_8d3b06a1f4c7425e9f2a1d5b7c3e0846/content?index=0" \
  -H "Authorization: Bearer $HEIMDALL_API_KEY"

Webhooks

Name callback_url (HTTPS only) and skip the polling loop: the marketplace posts the job document — the same shape GET /meshes/{jobId} answers, wrapped in { id, type, created, data } — the moment the job reaches completed or expired. type is mesh.generation.completed or mesh.generation.expired, and each fires at most once per job.

The signature scheme, the retry schedule and the verification code are identical to video job webhooks: a Heimdall-Signature header of t=<unix seconds>,v1=<hex HMAC-SHA256> over <t>.<raw request body>, retried with exponential backoff for up to eight attempts. The signing secret is per account rather than per rail, so a receiver taking notifications from both verifies them with one key.

What a model will take

GET /meshes/models publishes mesh_envelope, supported_inputs, reference_inputs, generate_texture, seed, pricing_skus and allowed_passthrough_parameters. Each declared member is the union across the model's live endpoints, so it says what can be asked for somewhere rather than what one endpoint keeps, and it is null where no endpoint declares it — undeclared, not empty.

{
  "id": "microsoft/trellis-2",
  "canonical_slug": "microsoft/trellis-2",
  "name": "Microsoft: TRELLIS 2",
  "mesh_envelope": { "max_faces": 500000, "min_faces": 2000, "texture": true },
  "supported_inputs": ["text", "image"],
  "reference_inputs": { "formats": { "image": ["image/png", "image/jpeg"] }, "max_items": 1 },
  "generate_texture": true,
  "seed": true,
  "pricing_skus": { "mesh_output": "0.04", "mesh_output_untextured": "0.025" },
  "allowed_passthrough_parameters": []
}

pricing_skus gives the per-mesh rate as a decimal string under mesh_output, and under that name suffixed for each condition an endpoint prices separately — mesh_output_untextured for a rate that applies only when no texture was asked for. A tier is the same meter at a different rate, never a second unit. A listing carrying only suffixed names declares no untiered rate, which is a partial declaration rather than a free tier.

The envelope, and why it has a floor

An endpoint publishes what it builds as mesh_envelope in the catalogue: max_faces, min_faces and texture. A request outside it is not routed there.

target_faces is a ceiling rather than a target. A decimator lands at or below it, so a mesh under the ask is the ask met, and the faces you read back is what was built.

min_faces is the smallest face budget an endpoint accepts; it is not a minimum measured output count. Unsupported budgets are refused before generation. The delivered original must fit the requested ceiling. Optional repair can change the face count; compare the measured original and repaired assets before choosing one.

Quality and detail

Face budget, generation resolution and texture resolution measure different things. A larger face budget lets an export retain more geometry; it does not generate missing details or correct proportions. Texture resolution controls the image mapped onto that geometry. A larger texture cannot repair the silhouette, and a low-poly asset can still have a detailed texture.

Generation resolution and enhancement stages depend on the provider's pipeline. They are not selected by target_faces. The reference provider generates a coarse shape, upsamples its internal representation, samples finer geometry, then simplifies the surface and optionally bakes textures. Its default configuration uses a 1536 shape-generation resolution and a 4096-pixel texture atlas. When declared by an endpoint, voxel_resolution and texture_size select these values per request; the reference provider accepts 1024 or 1536 voxels and 1024, 2048 or 4096 texture pixels. These settings do not guarantee the final asset's detail.

Use consistent reference images to communicate shape, including a clear frontal view when proportions and symmetry matter. Follow the selected endpoint's view count and ordering. Inspect the result from the back and underneath as well as the front. Repair can address local defects, but neither generation nor repair guarantees accurate dimensions, animation-ready topology or correct unseen surfaces.

Reference views

input_references conditions the generation on views of one object: an image_url part, inline base64 only, at most four. The marketplace fetches nothing on this rail, so an https URL it accepted would be an ask it could not carry out. Four is the marketplace cap; the selected endpoint may accept fewer and may require a particular view order. Additional views help only when the model supports and uses them.

An endpoint must declare image input and input_references on its mesh output; its reference_inputs.mesh entry in the model's endpoint listing publishes accepted MIME types and the reference count limit. The whole reference payload is held to the same 20 MiB budget every generation reference is.

The reference provider serves TRELLIS.2 with one image. Its Pixal3D endpoint accepts up to three consistent views in front, left, back order. This example uses those Pixal3D views; replace the placeholder data with the corresponding image bytes.

{
  "model": "tencentarc/pixal3d",
  "input_references": [
    { "type": "image_url", "image_url": { "url": "data:image/png;base64,FRONT_VIEW" } },
    { "type": "image_url", "image_url": { "url": "data:image/png;base64,LEFT_VIEW" } },
    { "type": "image_url", "image_url": { "url": "data:image/png;base64,BACK_VIEW" } }
  ],
  "target_faces": 40000
}

Texture

generate_texture: true bakes an image into the same container; false asks for untextured geometry. Either explicit value routes only to an endpoint declaring and admitting that choice, and it is never silently dropped. Omission leaves the choice to the selected endpoint's own default, and mesh_envelope.texture describes capability rather than the request's selected value.

A texture is billed by the declared per-mesh rate like the rest of the asset unless the endpoint declares a tier for it, so asking for one adds no second charge. has_texture on the completed job is read off the container — whether a material in the delivered asset actually samples an image it carries — rather than echoed from the request.

Engine knobs

Send steps, guidance_scale, voxel_resolution, texture_size, negative_prompt, sampler and scheduler directly on the job request when an endpoint declares support. The catalogue publishes the admitted ranges and choices. Provider-specific controls use provider.options, keyed by provider slug; only names published under allowed_passthrough_parameters are forwarded through that map.

{
  "model": "microsoft/trellis-2",
  "prompt": "A ceramic teapot",
  "steps": 40,
  "guidance_scale": 7.5
}

Request body

FieldTypeNotes
modelstringRequired.
promptstringRequired unless input_references is present.
target_facesintegerA ceiling. Matched against mesh_envelope. See above.
voxel_resolutionintegerShape cascade grid size; select a value declared by the endpoint.
texture_sizeintegerSquare texture atlas edge in pixels; select a value declared by the endpoint.
generate_texturebooleanBake a texture (true) or not (false); endpoint default when omitted.
input_referencesarrayViews of one object, inline base64, at most four.
seedintegerReuse the sampling seed; reproducibility also depends on the endpoint and its pipeline.
stepsintegerDenoising steps, within the selected endpoint's declared bounds.
guidance_scalenumberGuidance strength, within the endpoint's declared bounds.
negative_promptstringContent to avoid, where the endpoint supports it.
samplerstringA declared sampler choice.
schedulerstringA declared scheduler choice.
refinementobjectOptional vision-model repair and its time, tool-call and model-spend bounds.
callback_urlstringHTTPS only. See Webhooks.
providerobjectProvider preferences.

Endpoints differ in which options they honour: each publishes its own supported_parameters in the catalogue, and that list is the promise.

What you are charged

The endpoint's output-mesh tariff charges per completed mesh, with any declared conditions for supported settings. The public mesh_output price is USD per asset. Generation is charged when the mesh completes, whether or not it is downloaded.

Face count and generation time are not billable units. faces describes the measured output; target_faces sets a ceiling and does not guarantee added detail. Settings can affect the work a provider performs and the rate its tariff selects, so compare the published price for the requested configuration. Optional repair adds the selected model's inference usage and machine time.

Budget for the whole asset

A generation price is not an all-in repair price. For example, at $0.04 per textured generation, allow roughly $0.07–$0.14 for an asset with repair. Nine completed repair trials with DeepSeek and GLM added about $0.03–$0.09 in combined model and machine charges. This small sample is a planning example, not a quote, a success-rate estimate or a spending limit. Other model prices, settings and repeated attempts can increase the total. Use the selected provider's live tariff for generation and the repair availability response for the machine rate.

Texture is part of the selected generation tariff, which can price textured and untextured geometry differently. It is not an additional repair charge. Optional repair is metered separately:

  • Vision model: actual inference usage, bounded by max_model_cost_usd.
  • Machine: time charged per started minute, including startup and cleanup. This is outside the model spend ceiling.
  • Failed or unchanged repairs: usage already consumed is still chargeable. A successful repair is not guaranteed.

At a machine rate of approximately $0.000833 per started minute, 40 minutes is about $0.0334 in machine charges alone. A $2 model ceiling therefore does not cap the entire asset at $2.

This is a slow workflow: allow tens of minutes and plan for 20–60+ minutes with repair. Generation runs before repair; complex assets, queues and cleanup can extend the wait. The repair time limit begins after its queue and includes machine startup, but excludes generation. The job's status and repair report distinguish these stages; elapsed time is not a promise of completion.

The selected rate is frozen for the request and used for reservation, settlement and recovery. A tariff without a supported measurement or finite reservation bound is refused before dispatch, and a delivered asset outside the admitted request fails the job rather than creating an unexpected charge.

Errors

The error model is the same as everywhere. Submission fails with 400 when the body does not parse or a member is outside the range the API accepts — a request naming neither a prompt nor a view, a target_faces outside the wire's own bounds, more than four views, or a callback_url that is not HTTPS. A model nobody serves an active mesh endpoint for answers 404 at submission, before any work begins.

Everything a generation itself can fail on — no eligible endpoint for the requested face ceiling or texture, an endpoint that stopped answering, an asset the marketplace could not read as a mesh — surfaces as status: "failed" with error naming the reason, on the next poll or in place of the mesh.generation.completed webhook. mesh_envelope in the catalogue has the bounds an endpoint builds within.

DiscoverFind your next modelModel libraryAll models, versions, prices and capabilitiesBenchmarksMeasured quality, cost and time by serving configurationShowcaseImages, video and 3D made on the marketplaceProvidersCompare independent inference providersActivityMarketplace traffic and usageNetwork statusAvailability, latency and endpoint healthChatConversations, model settings and filesImage, video & 3DGenerate images, video and 3D assetsScene studioStage a shot in 3D, direct it and render it as a videoCoding playgroundBuild software with an AI agent on a machine of your ownOverview & routingIdentity, workspaces and routing preferencesCredits & billingBalance, deposits, ledger and reserved fundsAPI keysKeys scoped to workspaces, limits and expirationOrganizationsTeams, members and shared workspacesRequest historyRequests, receipts, sandbox sessions and account historyReferralsInvitations and referral earningsPrivacyData settings, exports and deletionDocumentationQuickstart, SDKs, pricing and guidesAPI referenceInteractive endpoint reference