Skip to content

Using the API

Use the Palatial API to create SimReady 3D assets from text, photos, video, or CAD files, branch and regenerate them, track progress, and download the results programmatically. All routes below use your API key and live under:

https://dashboard.palatial.cloud/api/v1/external/

The Experimental unified Agent entry is available at the production base above with a workspace API key. Compatibility endpoints remain available.


API keys are created per workspace. Workspace asset operations are scoped to the workspace the key was created in, and only workspace members (or organization admins) can create one. The upcoming Library API also accepts published public Library sources from other workspaces. See Roles and permissions.

  1. Open the workspace in the dashboard and select the API tab next to Assets, Exports, and Team.

    The workspace API tab, showing the API Keys panel with a New key button and a Quick start example.
  2. Click New key. Give the key a name so you can recognise it later (for example the pipeline or machine that will use it). The expiration date is optional. The rate-limit fields in this dialog are optional and are not currently applied to API requests.

    The Create API Key dialog with the name field filled in and optional expiration and rate limiting fields.
  3. Click Create API Key. The key is shown once. Copy it and store it in your secret manager now; after you close this dialog it cannot be displayed again.

    The API Key Created dialog showing an example key value, Copy and Download buttons, and a usage example.
  4. Click I’ve saved my key. The key now appears in the list with a masked value, its scope, and when it was last used. Revoke removes it immediately; requests using a revoked key return 403.

    The API tab listing one key with a masked value, its read and generate scope, last-used time, and a Revoke button.

Never paste a key into a chat message, screenshot, public issue, or source file. If a key may have been exposed, revoke it from the API tab and create a new one.

  1. Send asset intent to the native Agent. The Agent understands text and supplied image/CAD/video evidence together with saved project data, chooses supported capabilities, and executes unattended.
  2. Poll the same request ID. Preserve the project across native billing pauses and report genuine structured blockers.
  3. Check authoritative asset/media/validation state and use structured download links. Accepted work is not finished generation.
  4. Continue by describing the requested change with the same project identity. The MCP exposes this complete workflow through the single palatial_agent tool.

The lower-level image, text, CAD, batch, research-job and Variant endpoints below remain compatibility interfaces. Their existing interaction contracts do not apply to the unattended Agent entry.


Send the key on every request in one of these headers:

  • x-api-key: YOUR_API_KEY
  • Authorization: Bearer YOUR_API_KEY

When both are present, Authorization wins. Query-string keys are not supported.

A key is bound to its user and workspace. Every request is also checked against the key’s capabilities and the user’s workspace membership:

  • Reads (asset, status, downloads, workspace) need workspace membership.
  • Writes (create, submit, reprocess, Variant, cancel, export) need edit access to the workspace.
  • Variants need the asset:variant-create capability. Keys created before Variants were released do not have it; create a new key to use the Variant endpoint.
  • Ordinary workspace asset operations stay scoped to the key’s workspace. Sending another destination workspace ID does not grant access.

Authentication and access failures return 403:

Message Meaning
Api key is required No key header was sent.
Invalid api key The key is unknown, revoked, or expired.
API key action denied The key lacks the capability for this route.
Workspace scope denied, Asset scope denied The workspace or asset is outside the key’s workspace. An asset ID that does not exist also returns Asset scope denied, not 404.

Code Meaning
200 Success
201 Created or accepted. Most POST routes return 201, including submit, reprocess, Variant, and batch status reads.
202 Accepted for asynchronous work (Agent message, Mad Max job create and answer)
307 Redirect to a download (export ZIP); follow it with curl -L
400 Invalid request. Unknown fields are refused with property … should not exist.
403 Invalid key, missing permission or capability, or a balance requirement is not met
404 Asset or job not found, or expected media artifact missing; inspect code
409 The request conflicts with the current state (for example a build is still running)
413 An upload exceeds its size limit
422 The requested media representation is not produced (MEDIA_NOT_SUPPORTED)
429 Too many Mad Max job requests, or too many research builds at once. Wait and retry; see rate limits.
500 Server error. Retry read-only polling with backoff. Do not replay a create, Variant, reprocess, or export whose outcome is unknown.
503 Temporarily unavailable, or the feature is not enabled for this environment. A 503 from a create or Variant request created and charged nothing. Retry reads; do not blindly replay a submission.

Every error has the same envelope:

{
"statusCode": 409,
"code": "VARIANT_SOURCE_NOT_SUCCESSFUL",
"message": "A failed or canceled build can only be retried in place.",
"correlationId": "…",
"timestamp": "2026-10-08T12:00:00.000Z",
"path": "/api/v1/external/assets/ASSET_ID/variants"
}
  • Branch on code. When the server gives no specific code, code is the status name, such as BAD_REQUEST or FORBIDDEN.
  • message is human-readable and may change. Validation messages for several fields are joined with ; .
  • Quote correlationId (also sent as the X-Request-Id response header) when you contact support.
  • Balance errors add tokens and admission; see Balance errors.

status.status moves through these states:

Status Meaning
INIT Draft, text-reference preparation, or research before the build starts
SUBMITTED Submitted for processing
QUEUED Accepted, waiting for a worker
PROCESSING_IMPORT Generation in progress. MESH_GENERATED, TEXTURE_GENERATED, and PROCESSING_EDIT are also in-progress states.
PROCESSING_PAUSED A checkpoint between stages, or a billing pause. Check billing.paused.
READY Generation is complete. Read media for available representations.
REVIEW Palatial is reviewing and finishing this build (displayText: "Reviewing through QA"). It is not terminal: keep polling. When the review finishes, the same asset moves to READY.
PROCESSING_FAILED Failed. Read displayText.
PROCESSING_CANCELED Canceled
  • Poll GET …/status every few seconds until READY or a terminal failure.
  • REVIEW is not a failure and needs nothing from you. Do not resubmit or create a replacement; keep the same asset ID and keep polling (a slower interval, such as once a minute, is fine).
  • READY does not promise every media type. Read media before downloading, and validationOutcome to see how validation went.
  • A failed build that is being retried automatically reports QUEUED with displayText: "Retrying automatically from where it stopped" and an automaticRetry object. Keep polling; do not resubmit it.
  • An asset with parameters.agentBuildJobId is a research job. It can sit in INIT while it researches or waits for your answer, so poll the job too.

Chat with the Palatial Agent to create or change a 3D asset. Send a message, and optionally files; the Agent reads them with the project’s saved information and does the work. Then check the returned status URL until the work finishes. You can also ask about progress, recover a failed build, or request downloads.

Paths below are relative to /api/v1/external:

Method Path Purpose
POST /agent Submit a new asset intent
POST /assets/{assetId}/agent Submit a message about an existing asset
GET /agent/{id} Read the same request
GET /agent/{id}/references/{artifactId} Read a request’s reference file

Release availability: These endpoints are Experimental and available in Production. Use https://dashboard.palatial.cloud and a workspace API key. The MCP client connects your AI assistant to Palatial tools. The 0.1.23-dev.0 prerelease client and MCP package accepts your message and local files in one call.

Swagger lists these four endpoints in the Experimental group. The name groups the documentation; it does not change the URLs, access rules, charges or how requests work. Mad Max compatibility jobs and Dashboard streaming are separate.

The Agent handles Palatial asset work only. It can create, configure, change and recover assets, check progress, and return results. It refuses unrelated requests. It works without asking you to choose settings, select reference files, answer questions or confirm each step. It chooses supported ways to build the asset and estimates dimensions when the available information supports them. The usual permission, billing, retry and cancellation rules still apply, as do the checks needed before work can start.

POST /api/v1/external/agent

To create a new asset, describe it in message:

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/agent" \
-H "x-api-key: $PALATIAL_API_KEY" -H "Content-Type: application/json" \
-d '{ "message": "Create a simulation-ready office chair for Isaac Sim." }'

POST /api/v1/external/assets/{assetId}/agent

Put the asset ID in the path and describe the change in message. The Agent decides from your message and the project whether to update this asset or create a new Variant. For example, “Raise the texture quality.” updates the asset, while “Create a variant of it in red.” keeps the original and adds a Variant. Changes may incur charges.

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/8c79b117edf2b094610edc43/agent" \
-H "x-api-key: $PALATIAL_API_KEY" -H "Content-Type: application/json" \
-d '{ "message": "Raise the texture quality." }'

Both POST endpoints also accept multipart/form-data: a JSON request part first, followed by your files.

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/agent" \
-H "x-api-key: $PALATIAL_API_KEY" \
-F 'request={"message":"Create a simulation-ready version of this cabinet for Isaac Sim."};type=application/json' \
-F 'photo=@./front.jpg;type=image/jpeg' \
-F 'video=@./turntable.mp4;type=video/mp4' \
-F 'cad=@./cabinet.step;type=application/step'

With JavaScript fetch, append the JSON field before any files. Let fetch set the multipart content type and boundary:

const form = new FormData();
form.append("request", JSON.stringify({
message: "Create a simulation-ready version of this cabinet for Isaac Sim."
}));
form.append("photo", new File([photoBytes], "front.jpg", { type: "image/jpeg" }));
form.append("video", new File([videoBytes], "turntable.mp4", { type: "video/mp4" }));
form.append("cad", new File([cadBytes], "cabinet.step", { type: "application/step" }));
const response = await fetch("https://dashboard.palatial.cloud/api/v1/external/agent", {
method: "POST",
headers: { "x-api-key": apiKey },
body: form
});
const request = await response.json();
  • message is required. It cannot be blank or longer than 16,000 characters.
  • Optional requestId makes a retry safe: sending the same ID and input again returns the original request instead of starting new work.
  • To continue an existing project, send the same request body to /assets/{assetId}/agent. Optional conversationId continues its saved conversation. Keep the returned IDs for later messages about that project. The older assetId body field on /agent remains accepted for compatibility; prefer the asset path for new integrations.
  • File fields can have any name except request. Files are attached in their part order. Optionally declare files: [{ part: "photo", sha256: "<SHA-256>", sizeBytes: 123456 }] in the JSON to name each file part and verify its contents. If you declare files, every file part must appear in that list, and every listed part must be sent.
  • The Agent accepts up to 50 attachments and a total file budget of 1 GiB (1,073,741,824 bytes) per message. A non-video file can use that full file budget through the JavaScript client or MCP. The JSON request part itself must fit within 1 MiB. Direct HTTP requests must also fit the target gateway’s body-size limit below.
  • Each MP4 or MOV video must be at most 300 MiB (314,572,800 bytes), the current Mad Max processing limit. Larger videos are refused when submitted, before a build starts. Use a smaller video; the JavaScript client and MCP cannot raise this video limit.
  • Send PNG/JPEG/WebP images, MP4/MOV videos, PDF/TXT documents and supported CAD/3D files as file parts. File requirements still depend on the requested work. Raw URLs, SVG files and empty files are refused. You cannot change input files while generation is running.
  • Do not put inlineData in a multipart request. JSON requests remain supported for text or small inline images: each inline image is at most 2 MiB, with at most eight images and 12 MiB of image contents per request.
  • Do not send execution, maxTokens, answer, selection or confirmation. The Agent makes these choices and continues the work itself.

For an existing asset, describe the change in message. To keep the original, ask for a new Variant in the message. The Agent chooses the action from the message and saved project context. The retired intent field is accepted for older callers and ignored; it does not select edit or Variant behavior.

HTTP gateway limits: Direct uploads must fit the target gateway’s body-size limit, including metadata and multipart framing. The Dev gateway currently accepts at most 100 MiB (104,857,600 bytes) for the entire HTTP body, including metadata and multipart framing. Direct HTTP file contents must fit below that body limit; a 1 GiB file cannot be sent in one Dev HTTP request. For larger local files in any environment, use one JavaScript client or MCP call; resumable file transfer is handled automatically. The total file budget remains 1 GiB, and each video is still limited to 300 MiB. A gateway refusal can return a non-JSON 413 response.

202 means the request was accepted. Save id, assetId, conversationId and statusUrl when returned. Acceptance does not mean generation has finished.

{
"id": "86ba8452-18a5-46e7-a3e4-f7e595d72f81",
"assetId": "8c79b117edf2b094610edc43",
"conversationId": "6ad8101732df22de4d8a5cf5",
"status": "running",
"statusUrl": "/api/v1/external/agent/86ba8452-18a5-46e7-a3e4-f7e595d72f81",
"replayed": false
}

replayed: true means the same requestId and input already exist. The API returns the same request and project without starting or charging for another build. Reusing the ID with changed input returns 409.

If the response is lost and you sent a requestId, keep the same requestId, message, URL and file order. Send a small JSON request containing each file’s original name, SHA-256 and byte length, without file contents:

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/agent" \
-H "x-api-key: $PALATIAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"requestId": "cabinet-build-1",
"message": "Create a simulation-ready version of this cabinet for Isaac Sim.",
"files": [
{ "originalName": "front.jpg", "sha256": "<PHOTO_SHA256>", "sizeBytes": 123456 },
{ "originalName": "turntable.mp4", "sha256": "<VIDEO_SHA256>", "sizeBytes": 20971520 },
{ "originalName": "cabinet.step", "sha256": "<CAD_SHA256>", "sizeBytes": 345678 }
]
}'

Calculate each digest from the original file contents, for example with sha256sum front.jpg, and use the exact file byte length. If you supplied file kind or mimeType, preserve those too.

  • If the request was already accepted, this returns its receipt with replayed: true.
  • If all files were saved but the request was not yet accepted, the server uses those saved files to admit the same request.
  • If any file contents are missing, 400 AGENT_SOURCE_BYTES_REQUIRED lists the missing files. Only then send the same multipart request again. Files already saved are reused.

Do not retry with a new request ID. That starts new work and may create another charge. If you received a receipt, poll its statusUrl instead of submitting again.

GET /api/v1/external/agent/{id}

Read statusUrl with the same API key. Keep checking while work continues. An accepted request, or an Agent reply, does not mean the asset is finished.

Use these fields to follow progress:

  • status says whether the Agent has finished handling this message.
  • automation: { state, code?, result? } describes the overall work. Its state is active, completed, blocked, paused or superseded. Here, superseded means a newer request has replaced this one.
  • assetStatus is the asset’s current build status. REVIEW means Palatial is finishing the build. Keep checking until READY.

The response keeps id, assetId, conversationId and statusUrl. It can also include viewerUrl to open the asset in Dashboard, the Agent’s reply, billing details and media showing which files are available. When present, assumptions: [{ decision, evidence }] explains choices the Agent made from uncertain information, and downloads contains requested file links.

Use automation.state and assetStatus together to decide whether the work is finished. Before claiming the result is ready for simulation, also check the available files and validation result. The API does not return questions, reference choices or confirmation steps for you to answer.

  • Product Research looks up information about the product you want to build. It uses your message, uploaded files, saved project information and supported defaults to choose a product, dimensions and reference files. If it must make an estimate, it records the choice and evidence in assumptions. Uncertainty alone does not stop the work or require an answer from you.
  • Work stops when an essential input file is missing and cannot be replaced, the request is outside the supported work, or a service refuses the action. Codes include AGENT_OUT_OF_SCOPE and AGENT_SOURCE_MISSING. Missing file contents return AGENT_SOURCE_BYTES_REQUIRED or AGENT_SOURCE_UPLOAD_REQUIRED. Permission errors use the service’s error code or AGENT_PERMISSION_DENIED. Resolve the reported problem; do not try another endpoint to get around it.
  • If the balance is too low, insufficient_tokens or insufficient_credits appears with automation.state: "paused". The server checks once a minute whether waiting work can start. Work on the same asset resumes after enough tokens are added. Keep the same request and asset IDs. This API does not add a separate token limit or start a replacement build.
  • Access is limited to this request and asset task. It does not grant access to unrelated tasks or later Dashboard conversations.
  • After asking to stop, wait until cancellation is confirmed. A response saying your stop request was received does not mean the work has stopped. Keep the same request and asset IDs while checking the outcome.
Terminal window
curl "https://dashboard.palatial.cloud/api/v1/external/agent/86ba8452-18a5-46e7-a3e4-f7e595d72f81" \
-H "x-api-key: $PALATIAL_API_KEY"
{
"id": "86ba8452-18a5-46e7-a3e4-f7e595d72f81",
"assetId": "8c79b117edf2b094610edc43",
"conversationId": "6ad8101732df22de4d8a5cf5",
"status": "completed",
"statusUrl": "/api/v1/external/agent/86ba8452-18a5-46e7-a3e4-f7e595d72f81",
"viewerUrl": "https://dashboard.palatial.cloud/viewer/8c79b117edf2b094610edc43",
"assetStatus": "INIT",
"automation": { "state": "active" },
"reply": "…",
"billing": { "…": "…" },
"media": { "…": "…" }
}

In this example, the Agent has handled the message (status: "completed"), but work is still active and the asset has not started building. Check automation.state and assetStatus together. Do not treat status: "completed" alone as a finished asset.

Errors use the standard error body. Use code to decide what to do next.

HTTP code Meaning
400 BAD_REQUEST Blank or oversized message, unknown field, or invalid base64
400 AGENT_SOURCE_BYTES_REQUIRED File contents are missing; send them as multipart file parts or supported inline images. A digest-only retry lists any missing files.
400 AGENT_SOURCE_UPLOAD_REQUIRED Video was sent inline; send it as a multipart file part.
400 AGENT_MULTIPART_REQUEST_FIRST The first multipart part must be the JSON request.
400 AGENT_MULTIPART_PART_UNKNOWN A file part is absent from the declared files list.
400 AGENT_MULTIPART_* Malformed multipart metadata, missing declared file, or invalid file declaration; read message.
400 AGENT_SOURCE_TYPE_UNSUPPORTED Unsupported file type.
404 AGENT_TURN_NOT_FOUND Unknown request ID, or one from another workspace
409 REQUEST_ID_CONFLICT The requestId is already bound to different input
400 AGENT_ASSET_ID_INVALID The asset ID in the URL is not a valid asset ID
400 AGENT_ASSET_ID_MISMATCH The request body names a different asset than the URL
409 AGENT_PROJECT_MISMATCH conversationId belongs to a different project than the asset
413 AGENT_VIDEO_TOO_LARGE A video exceeds 300 MiB (314,572,800 bytes), the current Mad Max processing limit. Use a smaller video. The request is refused at intake rather than accepted for a build that cannot use it.
413 AGENT_REQUEST_TOO_LARGE File contents total more than 1 GiB, or the multipart body exceeds its framing allowance. The current Dev gateway has a lower 100 MiB limit for the entire HTTP body.
413 AGENT_ATTACHMENT_TOO_LARGE or AGENT_PAYLOAD_TOO_LARGE One file exceeds its limit, an inline image exceeds 2 MiB, or JSON metadata/body exceeds its limit.
413 AGENT_INLINE_ATTACHMENT_COUNT_EXCEEDED More than eight inline files
413 AGENT_ATTACHMENT_BUDGET_EXCEEDED Inline files total more than 12 MiB
400 / 413 AGENT_ATTACHMENT_COUNT_EXCEEDED More than 50 attachments; multipart and file-manifest validation return 400, while JSON body admission can return 413.

Some problems are found after the API has accepted your request, such as AGENT_OUT_OF_SCOPE, AGENT_SOURCE_MISSING or insufficient_credits. These are reported in automation.code when you check the request, rather than as HTTP errors.

The old /external/agent/turns URL has been removed. Use the current URLs above.

Ask for the files you need, then use the links in downloads. Each downloadUrl is a temporary signed link. Use it promptly and exactly as returned. Do not build a download URL from the Agent’s reply or change the credentials in it.

Only the requested files are tracked for download. When you check again, the server checks your access and returns current links. If a file cannot be downloaded, its result includes an error code, status and whether you can retry. The media field lists which model, texture and export files are available.

Your workspace API key limits which assets, requests and conversations you can access. Sending a message needs asset:create. Working on an existing asset also needs asset:write. Checking a request needs asset:read. Actions such as submitting, creating a Variant, canceling and exporting still need their usual permissions: asset:submit, asset:variant-create, asset:cancel and asset:export.

Pass your message and local file paths in one call. The client handles file transfer, content checks and safe retries. Files total at most 1 GiB (1,073,741,824 bytes); each MP4 or MOV video is limited to 300 MiB (314,572,800 bytes). The client rejects an oversized local video before transferring it.

await client.startAgentWithFiles({
requestId: "cabinet-build-1",
message: "Create a simulation-ready version of this cabinet for Isaac Sim.",
files: ["/path/to/front.jpg", "/path/to/turntable.mp4", "/path/to/cabinet.step"]
});

The MCP has one tool, palatial_agent:

{
"request_id": "cabinet-build-1",
"message": "Create a simulation-ready version of this cabinet for Isaac Sim.",
"files": ["/path/to/front.jpg", "/path/to/turntable.mp4", "/path/to/cabinet.step"]
}

To change an existing asset, add its asset_id (assetId in JavaScript); the client sends the message to that asset:

{ "asset_id": "8c79b117edf2b094610edc43", "message": "Raise the texture quality." }

Keep a fixed request ID and unchanged local files after an uncertain response. The client saves file paths, hashes and sizes, then tries the small digest-only request before sending file contents again. It refuses changed local files. Uploads use a 60-second inactivity timeout rather than a 30-second total deadline, followed by up to 120 seconds for the acceptance response. Larger files use resumable transfer automatically.

Both paths preserve the same project access, input mutation, generation and billing checks. Without a request ID, each new call creates a new request. To check progress, call palatial_agent with only { "turn_id": "RETURNED_ID" }.


Base path: /api/v1/external/assets

GET /api/v1/external/assets

Returns a plain JSON array of the assets in your workspace, with no pagination envelope or total. Pass a JSON filter query parameter:

Field Description
limit Page size. Default 10, clamped to 1 to 200.
afterId The last asset ID of the previous page (cursor). Cannot be combined with where.ids.
skip Offset. Ignored when afterId is set.
where.status.status Exact status match, for example READY

Results are always sorted by _id ascending; a sort value is ignored. where fields match stored fields literally; they are not the dashboard’s search filters.

Terminal window
curl -G "https://dashboard.palatial.cloud/api/v1/external/assets" \
-H "x-api-key: YOUR_API_KEY" \
--data-urlencode 'filter={"limit":20}'
# Next page: replace this example ID with the last ID returned above.
curl -G "https://dashboard.palatial.cloud/api/v1/external/assets" \
-H "x-api-key: YOUR_API_KEY" \
--data-urlencode 'filter={"limit":20,"afterId":"507f1f77bcf86cd799439011"}'

GET /api/v1/external/assets/{id}

Returns the full asset record: id, name, type, parameters, status, export, media, metadata, viewerUrl, and, when present, automaticRetry and billing. A deleted asset returns 404.

POST /api/v1/external/assets

Creates a draft without starting generation; call Submit afterwards. This is the legacy draft contract. Prefer the one-shot generation endpoints for new integrations.

  • Required: name (4 to 50 characters), workspace, and owner (MongoDB IDs; the authenticated user becomes the owner).
  • Optional: description (at most 4,000 characters), type, parameters, and the flat build fields mesh_density, shape_model, texture_model, newton_solver, up_direction, units, run_simulation, plus the validation settings.
  • Response: 201 with the draft and, when available, billing.

PATCH /api/v1/external/assets/{id}

Updates name, description, parameters, or metadata. Parameter updates merge with the existing settings and keep server-owned fields. Changing build inputs while generation is running returns 409 HEAVY_JOB_IN_FLIGHT.

POST /api/v1/external/assets/{id}/submit

Starts or restarts processing for an existing asset. A draft must meet its route’s start minimum; a submitted asset needs a positive balance.

Field Description
type Pipeline type, for example imagetosimready or cadtosimready
retry Retry reason (stored on the asset)
reprocessFrom Optional stage to start from
sourceRunId Optional prior run whose results seed the run
executionMode auto (default) or step

Response: 201 with the submit result and, when available, billing.

GET /api/v1/external/assets/{id}/status

The polling route. The response contains:

Field Meaning
status, progress, completedSteps, totalSteps, displayText, createdAt, updatedAt Coarse progress. On failure, displayText is a public failure message.
processingSummary Current run: runId, attempt, runStatus, currentStageKey, failedStageKey, activeStages, progress, staleStages, awaitingContinue, executionMode
export Export state: status (NOT_STARTED, REQUESTED, PROCESSING_EXPORT, PROCESSING_FAILED, READY, EXPIRED), size, expiryDate
media Availability of mesh, texture, and export; see Media availability.
generationAgent The route that built the asset: diffusion, parametric, or mad_max
validationOutcome For a settled asset: delivery (delivered or pipeline_failed), validation (passed, simulation_issues, checklist_issues, incomplete, not_run, unknown), exportAvailable, and per-simulator results
automaticRetry When a failed build is retried automatically: status, fromStage, reason
billing See Billing response

If billing.paused is true, the status reads PROCESSING_PAUSED: add tokens and keep polling. If the status is PROCESSING_PAUSED with billing.paused false and processingSummary.awaitingContinue true, it is a normal checkpoint that continues on its own.

For the compatibility create and Variant routes, use the field for the work being tracked:

What you need to know Read How to interpret it
Is research or authoring running before the pipeline starts? The research job, using parameters.agentBuildJobId from the create response or asset detail An asset can still be INIT with progress: 0 while its job is running, researching, preparing, or building. Only follow a job when the asset has a job ID; INIT alone does not prove research is running.
What is the current pipeline doing? processingSummary.runStatus, currentStageKey, and activeStages; use detailed progress for the full run processingSummary.progress is a { done, total } stage count. Each active stage can report its own progress percentage and stageSteps: { done, total }. These are different units, not one overall percentage.
What does the coarse asset progress mean? Top-level status and progress This is the asset lifecycle snapshot. A value of 0 does not override a running job or stage. For example, an active texture stage at 5% with 1/16 steps can coexist with coarse progress of 0.
Can I retrieve the model, textures, or export? media.<representation>.state pending means that representation is not available yet, including during active generation. It is not a job status. Follow media availability and call href when available.
  • Poll the same asset or job every few seconds; back off after transient errors and honor Retry-After on 429. Do not submit another create or Variant just to check progress or recover a timed-out read.
  • A missing processingSummary or a null pipeline run means there is no pipeline detail to display yet. Check the research job when present; do not turn missing detail into a failure or invent a percentage.
  • Display billing pauses and job questions or blockers alongside progress. If automaticRetry.status is pending, running, or paused, continue following the same asset while its retry is unresolved. A stopped or failed job should be reported even if the coarse asset state has not caught up.
  • Treat READY as generation completion, then check media and validation results for the intended simulator. It does not promise that every representation is downloadable or that every validation passed.

POST /api/v1/external/assets/statuses

Body: { "ids": ["id1", "id2"] } with 1 to 500 asset IDs. Every asset must be reachable by the key. Returns 201 with an array of { id, status, export, media, processingSummary, generationAgent, validationOutcome, automaticRetry, billing }.

GET /api/v1/external/assets/{id}/pipeline-runs/current

Returns { assetId, assetStatus, activePipelineRunId, run, billing }. run is null before the first run exists; otherwise read run.status (pending, running, paused, cancel_requested, completed, failed, canceled), run.currentStageKey, and run.stages[] with each stage’s timing, progress, and artifacts.

Palatial may merge, skip, or add stages for a route or request. Treat run.stages[] as authoritative; do not assume a fixed stage count. Settled customer charges live under billing.stages[], keyed by stageKey. Research-job progress is on the job, not in this pipeline response.

DELETE /api/v1/external/assets/{id}/cancel-processing

Stops queued, active, or paused processing and moves the asset to PROCESSING_CANCELED.

  • Accepted while the asset is SUBMITTED, QUEUED, PROCESSING_IMPORT, PROCESSING_EDIT, MESH_GENERATED, TEXTURE_GENERATED, or PROCESSING_PAUSED, and for an INIT research job.
  • On a PROCESSING_FAILED asset with a pending automatic retry, it withdraws the retry.
  • Repeating it for an already canceled asset is safe.
Terminal window
curl -X DELETE "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/cancel-processing" \
-H "x-api-key: YOUR_API_KEY"

Response: 200 { "success": true }. Poll status until PROCESSING_CANCELED. Errors: 400 Asset is not currently processing., 409 HEAVY_JOB_IN_FLIGHT when another operation owns the asset.


Generation uses postpaid stage billing when the response contains billing.mode: "postpaid_stage_v1":

  • Nothing is deducted at submission. Each successfully completed stage is charged once, after it completes.
  • billing.stages[].tokenCost is the final amount charged for a stage. Do not calculate it from a stage count.
  • A same-stage replay does not duplicate a charge. A completed stage with tokenCost: 0 is an explicit no-charge result; a missing stage means no charge has settled yet.
  • Eligible edits (feedback, edited segmentation, effective setting changes) use the 50% rate per stage, rounded down and at least 1 token for a positive amount. Read tokenCost rather than reproducing this.
  • Balances and charges can be fractional for discounted edits.
  • Any displayed amount, including a Mad Max estimate, is an estimate, not a cap.
  • Export downloads are free. The first export of an asset needs a positive balance.

All workspaces in an organization share one wallet.

Starting new generation needs a minimum net balance for its route. This admits the work; it does not reserve or deduct the amount.

Route Minimum balance to start
Diffusion 20 tokens
Parametric Low 40 tokens
Mad Max 80 tokens
CAD → Sim 4 tokens

Which balance an operation needs:

Operation Needs
New create (Image, Text, CAD, batch row, Mad Max job) The route’s start minimum
New Variant The start minimum of the route the Variant runs on, which can differ from its source (for example a Diffusion source repaired as Parametric). A Mad Max Variant needs 80, not its full estimate.
Reprocess, retry, failed-step resume, research-job recovery A balance greater than 0
A Variant’s own retries and continuations after it was admitted A balance greater than 0
Resuming work paused for credit A balance greater than 0 (automatic)
First export A balance greater than 0

mode=diffusion uses 20, mode=parametric with effort=low uses 40, effort=mad_max uses 80, and CAD uses 4. A video build uses the minimum of its route. Flat requests without mode use the minimum of their equivalent route. Nothing is created or charged when a start minimum refuses a request.

A refused start minimum returns 403 with the requirement in tokens.required:

{
"statusCode": 403,
"code": "insufficient_tokens",
"error": "Insufficient tokens",
"message": "Starting a new Variant requires at least 40 tokens; balance is 30 (10 short).",
"tokens": { "required": 40, "balance": 30, "shortfall": 10 },
"admission": { "kind": "new_variant", "rule": "route_floor", "route": "parametric_low" },
"correlationId": "…",
"timestamp": "…",
"path": "/api/v1/external/assets/ASSET_ID/variants"
}

A balance that is not positive returns 403 without tokens.required:

{
"statusCode": 403,
"code": "insufficient_tokens",
"message": "Add tokens to start or resume generation.",
"reason": "insufficient_credits",
"billingMode": "postpaid_stage_v1",
"tokens": { "balance": -2 },
"admission": { "kind": "in_place", "rule": "positive_balance" }
}
  • Branch on tokens.required: add at least tokens.shortfall tokens, then send the request once.
  • admission.kind is new_generation, new_variant, in_place, or continuation; admission.rule is route_floor or positive_balance. Older servers omit admission.
  • A first export refused for balance has the message Add credits before exporting this asset.

A running stage is allowed to finish. If its charge leaves the shared balance at 0 or below, the next stage is held and the asset reports PROCESSING_PAUSED with billing.paused: true, billing.pauseReason: "insufficient_credits", and billing.autoResume: true.

  • When a purchase or subscription credit makes the net balance greater than 0, the same asset resumes automatically from its saved progress. A balance restored to exactly 0 does not resume it.
  • The organization owner receives a billing-hold email.
  • Do not create, confirm, or retry a replacement request, and there is no resume endpoint for API keys. Keep polling the same asset.
  • A Mad Max model build can only be stopped for credit before its model charge. Once that charge is recorded, the build finishes and later stages follow the rule above.
  • Normal checkpoints also report PROCESSING_PAUSED, but with billing.paused: false and awaitingContinue: true. Palatial continues them about once a minute while the balance is above 0.

When the asset is billed by stage, create, submit, Variant, reprocess, status, batch-status, current-run, and job responses include:

{
"billing": {
"mode": "postpaid_stage_v1",
"balance": {
"tokens": -2,
"planTokens": 0,
"purchasedTokens": 0,
"debtTokens": 2,
"ownerType": "organization",
"ownerId": "ORGANIZATION_ID"
},
"paused": true,
"pauseReason": "insufficient_credits",
"autoResume": true,
"canStartGeneration": false,
"stages": [
{ "stageKey": "shape-generation", "tokenCost": 10, "chargedAt": "2026-09-24T12:00:00.000Z" }
],
"warnings": []
}
}
  • stages holds the settled charges of the current attempt. It is not a pending quote or a lifetime total.
  • canStartGeneration follows the admission rule for this asset. For a draft that has not started yet, it is true only when the balance meets that draft’s route start minimum. For work that was already admitted (a running or paused build, a research job, or a Variant child), it is true when the balance is greater than 0.
  • warnings are advisory objects:
    • { "code": "below_start_minimum", "requiredTokens": 40, "balance": 12, "message": "…" } while a draft’s start minimum is not covered.
    • { "code": "low_recommended_balance", "recommendedTokens": 20, "balance": 12, "message": "…" } when work can start but the balance is low. The recommendation is 10 tokens for Diffusion and 20 for Parametric and Mad Max. CAD has none.
  • billing is absent for assets billed under the older prepaid model.

Palatial can combine early work into one stage, and the segmented Diffusion and Parametric routes may skip articulation because shape already built the joints. The current run is authoritative.


One-shot endpoints that create and start an asset in a single call. Image → Sim and Text → Sim build with one of two modes, Diffusion or Parametric, and Parametric has two efforts, Low and Mad Max. Each route honors a different set of build settings, so each has its own parameters object. CAD → Sim starts from your uploaded mesh and has no mode.

Route Request What it does Start minimum
Diffusion mode: "diffusion" Generates a mesh from your photo or description, then segments, textures, and simplifies it. Fastest and cheapest; good for organic shapes. You control structure, mesh density, and textures. 20 tokens
Parametric Low mode: "parametric", effort: "low" (the default effort) Authors a clean parametric model with its own parts. Better for manufactured objects and moving parts. You control joints, face budget, and collision. 40 tokens
Mad Max mode: "parametric", effort: "mad_max" Researches the real product, then authors the model. Decides structure, density, and appearance itself. Takes longer and costs more. 80 tokens
  • mode: "mad_max" is shorthand for mode: "parametric" with effort: "mad_max".
  • The Medium effort is retired: effort: "medium" returns 400 Effort must be one of: low, mad_max.
  • See Billing and admission for how the full cost is charged.

Every Image → Sim and Text → Sim request has the same top-level fields. mode, effort, and product_research choose the route; the build settings for that route go inside parameters.

Field Required Description
name Yes 4 to 50 characters: letters, numbers, spaces, _, -, .. Surrounding whitespace is trimmed.
description Yes What the object is, at most 4,000 characters. Include real dimensions and how it should move.
mode Yes for new integrations diffusion or parametric; mad_max is shorthand.
effort No Parametric only: low (default) or mad_max.
product_research No on (default), specs_only, or off. Only for Mad Max and video builds. See Mad Max.
parameters No The route’s build settings. Omit it for the defaults. Mad Max and video builds take none.
engine No Target simulator: isaac_sim (default), mujoco, or newton. Diffusion and Parametric Low accept several (repeat the field or send an array). Mad Max and video builds accept exactly one.
workspace No Workspace ID. Defaults to the key’s workspace.

The same 4,000-character generation-description limit applies to Dashboard, REST, and MCP requests. A description above the limit returns 400 before asset creation or billing; accepted text is retained without truncation. Agent messages keep their separate 16,000-character limit.

In JSON, parameters is an object. In multipart form data, send it as one form field holding JSON, for example -F 'parameters={"structure":"single_object"}'. Keep JSON types: 50000 and true, not strings. null is not a valid setting value.

With mode, the request is strict. These refusals are 400 and create and charge nothing:

Code Cause
CREATE_PARAMETERS_INVALID A setting the route does not accept, an invalid value, or nonempty parameters on Mad Max or a video build. The message lists the accepted settings.
CREATE_MODE_FIELD_CONFLICT A build setting sent at the top level next to mode, such as shape_model or mesh_quality. Move it into parameters.
CREATE_EFFORT_INVALID effort with mode: "diffusion", or an effort other than mad_max with mode: "mad_max".
CREATE_PRODUCT_RESEARCH_UNSUPPORTED product_research on a route that does not research.
CREATE_MODE_REQUIRED parameters without mode.
PRODUCT_RESEARCH_NEEDS_UPLOADS specs_only or off without a photo or video.

After a timeout with an unknown outcome, list your assets before creating again.

The create response is 201 with the full asset, including id, viewerUrl, and, when billed by stage, billing. generationAgent reports the route that built it; it is response metadata, not a create setting. An accepted create is not a finished model.

PowerShell: create and read the returned ID

Section titled “PowerShell: create and read the returned ID”

Use Invoke-RestMethod to decode the JSON response into an object. With Invoke-WebRequest, decode its .Content once with ConvertFrom-Json instead. The create response is a JSON object; do not serialize it back to a string before reading its fields.

On Dev, asset responses publish the validation-video details as object_data.validationVideo and the video URL as object_data.videoUrl. Older deployments may still return the details as object_data.videourl; that case-only collision with videoUrl prevents PowerShell from decoding the JSON into an object. The existing embedded video payload route, /assets/{id}/embedded/videourl, is unchanged. Stage and Production receive the response-field correction with the matching Backend release.

Set PALATIAL_API_KEY in your environment from your secret manager. For Dev, replace the base URL below with https://dashboard.dev.palatial.cloud/api/v1/external and use a Dev workspace key. The POST starts a billable generation; run it once, then keep the returned asset ID for reads.

Terminal window
$apiBase = 'https://dashboard.palatial.cloud/api/v1/external'
$headers = @{ 'x-api-key' = $env:PALATIAL_API_KEY }
$body = @{
name = 'Storage Box'
description = 'A rigid cardboard storage box, 30 cm wide, with a matte finish.'
mode = 'diffusion'
engine = 'isaac_sim'
} | ConvertTo-Json -Depth 10
$createRequest = @{
Method = 'Post'
Uri = "$apiBase/assets/create/texttosim"
Headers = $headers
ContentType = 'application/json; charset=utf-8'
Body = $body
TimeoutSec = 120
}
$asset = Invoke-RestMethod @createRequest
$assetId = $asset.id
$asset | Select-Object id, name, @{Name='status'; Expression={$_.status.status}}, viewerUrl
# Reads use the root asset ID, not the ID inside the nested status object.
$state = Invoke-RestMethod -Method Get -Uri "$apiBase/assets/$assetId/status" -Headers $headers -TimeoutSec 30
$state | Select-Object status, progress, processingSummary, media
# Mad Max and video creates also return a separate research-job ID.
$jobId = $asset.parameters.agentBuildJobId
if ($jobId) {
$job = Invoke-RestMethod -Method Get -Uri "$apiBase/mad-max/jobs/$jobId" -Headers $headers -TimeoutSec 30
$job | Select-Object id, status, assetStatus, progress, error
}

The create response has a nested status object; the /status response has a flat status string. Follow the progress reading rules when they appear different. If the POST times out before returning an ID, list your assets before creating again; do not automatically replay it. Subsequent polling only needs the GET requests above.

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/create/imagetosim" \
-H "x-api-key: YOUR_API_KEY" \
-F "name=Coffee Mug" \
-F "description=A ceramic coffee mug with a rounded handle, 9 cm tall." \
-F "mode=diffusion" \
-F 'parameters={"structure":"single_object","texture_size":4096}'
Setting Default Values
structure static_parts single_object: one solid rigid mesh. static_parts: separate rigid parts with no joints. articulated_parts: parts joined by joints that rotate or slide, such as doors, drawers, and wheels.
mesh_quality high (medium for single_object) low, medium, high
triangle_count auto minimal, low, medium, high, x_high, auto
mesh_density medium low, medium, high. Used when triangle_count is auto.
decimation strict, 100,000 faces false turns simplification off. Explicit legacy true without a mode or target uses the triangle-count preset.
decimation_mode strict auto for quality-driven simplification, or strict with exactly one target below.
decimation_target_faces 100,000 Maximum face count, 4 to 10,000,000. Strict only.
decimation_target_ratio none Fraction of faces to keep, 0.001 to 0.999. Strict only. Do not send with decimation_target_faces.
texture_size 4096 2048, 4096, 8192
optimize_textures true Downsize maps above texture_max_resolution. Never upscales.
texture_max_resolution 4096 512, 1024, 2048, 4096, 8192
collision_quality automatic; currently medium Omit it, or choose low, medium, high, sdf. See Collision selection.
run_simulation true Run physics validation after the build.
newton_solver mujoco mujoco, style3D. Read only when engine includes newton.
repair_mesh true Close holes and fix bad geometry after generation.

body_type (rigid_bodies, soft_bodies, or mixed_bodies), auto_scale, and replace_glass are still accepted for existing clients. New requests should omit them: Palatial builds rigid bodies by default.

Limits: one photo on file, or 2 to 4 named views (front, left, back, right); do not mix them. Text → Sim needs no photo.

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/create/texttosim" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Storage Cabinet",
"description": "A steel storage cabinet, 90 cm wide, 45 cm deep and 180 cm tall, with two hinged doors.",
"mode": "parametric",
"effort": "low",
"engine": ["isaac_sim", "mujoco"],
"parameters": { "articulation": true, "face_budget": 50000 }
}'
Setting Default Values
articulation false true adds joints to the moving parts. Parametric Low always delivers the model’s own parts, so there is no single-mesh option.
face_budget 100,000 Faces the builder authors the model to, 2,000 to 200,000.
collision_quality automatic; currently medium Omit it, or choose low, medium, high, sdf. See Collision selection.
run_simulation true Run physics validation after the build.
optimize_textures true Optimize the delivered texture files.
newton_solver mujoco mujoco, style3D. Read only when engine includes newton.

The builder authors topology, textures, and scale itself, so Diffusion settings such as structure, mesh_quality, texture_size, and decimation controls are refused here. Put real dimensions in description. replace_glass is still accepted for existing clients; omit it.

Limits: 1 to 50 photos across file and the named views, in any mix. Text → Sim needs no photo.

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/create/imagetosim" \
-H "x-api-key: YOUR_API_KEY" \
-F "name=Storage Cabinet from Photo" \
-F "description=Match the cabinet in these photos, 90 cm wide, 45 cm deep and 180 cm tall." \
-F "mode=parametric" \
-F "effort=mad_max" \
-F "engine=isaac_sim" \
-F "product_research=specs_only"

Mad Max takes no parameters. It decides structure, joints, mesh density, textures, and collision proxies itself; any build setting is refused. The one choice left to you is product_research:

product_research What research does
on (default) Researches the real product on the web: its pages, specifications, and product photos.
specs_only Reads the web for identity and specifications only; the model follows your photos or video. Needs at least one photo or a video.
off Looks nothing up. Needs at least one photo or a video.

Limits:

  • Exactly one engine. Several return 400 Researching builds accept exactly one engine. Send engine as a string.
  • One build takes at most 16 inputs in total: each photo, the video, each reference mesh, and the generated asset specification take one slot. Reserve one slot for the specification, leaving up to 15 photos without other inputs, or 15 − N photos with N reference meshes. Adding a video leaves up to 14 − N photos; see Video. The same budget applies to Dashboard and public API builds, research, submission, retries, and resume.
  • Reference meshes are 3D models used as shape guides, not a promise to preserve the uploaded geometry exactly. The dedicated reference_meshes and reference_mesh fields accept GLB only, at most 20 MiB each. Send up to 7 on the repeated reference_meshes field, or one on the older reference_mesh field (not both). Photos and meshes together must be at most 64 MiB. Use CAD → Sim when the uploaded shape must be kept.
  • Too many inputs return 400 IMAGE_TO_SIM_TOO_MANY_BUILD_INPUTS. Reference-mesh errors: REFERENCE_MESH_ROUTE_UNSUPPORTED (not Mad Max), REFERENCE_MESH_FIELDS_CONFLICT, MAD_MAX_REFERENCE_MESH_COUNT_EXCEEDED, MAD_MAX_REFERENCE_MESH_INVALID, MAD_MAX_REFERENCE_MESH_NAME_INVALID, MAD_MAX_REFERENCE_MESH_DUPLICATE, MAD_MAX_REFERENCE_INPUTS_TOO_LARGE.
  • Not available on CAD → Sim.

Mad Max needs usable product evidence: a real product identity, product page, or photo. A generic description can fail with product_research_evidence_missing on the job before any 3D build starts. The create request already authorizes the build; there is no ordinary confirmation step. The asset stays in INIT while research runs. Follow it with its research job.

Use automatic selection unless your simulator or contact task needs a particular representation. Collision quality changes the collision representation and its detail, not the visible mesh.

Choice Diffusion and Parametric Low Tradeoff
Automatic (recommended) Omit collision_quality. The current default is medium. In the MCP, auto omits it for you. The route’s default; Mad Max chooses proxies itself.
low Delegates convex decomposition to the target simulator instead of supplying authored hulls. Simpler; geometry and cost depend on the simulator.
medium Authored convex hulls at the standard detail preset. The usual balance of detail and cost.
high Authored hulls with a higher detail budget. More hulls can increase generation work and contact computation.
sdf Signed-distance-field collision, with a hull fallback when direct SDF cannot be resolved. Support and behaviour depend on the target simulator.

The raw API does not accept the string auto for collision_quality: sending it returns 400. Leave the field out instead. x_high is still accepted as a legacy hull preset for existing clients; new requests should not use it. Validate contacts in the target simulator for the intended task.

Every create route accepts optional top-level settings for the physics validation run:

Field Values
validation_physics_device cpu or gpu. PGS on Isaac Sim requires cpu.
validation_physics_hz 120, 240, 480, 960
validation_solver TGS, PGS, Newton, CG, SemiImplicit, XPBD, MuJoCo, VBD, Style3D; must be valid for every selected engine
validation_position_iterations 1 to 255, Isaac Sim only
validation_velocity_iterations 0 to 255, Isaac Sim only
validation_solver_parameters Object of scalar values, at most 4,000 characters, MuJoCo and Newton only

Omit them for the simulator defaults. An invalid combination returns 400.

POST /api/v1/external/assets/create/imagetosim

Content-Type: multipart/form-data. Send the request fields as form fields, plus photos or a video:

Field Description
file Product photo (PNG, JPEG, or WebP). Repeat for several photos where the route allows it.
front, left, back, right Named views of the same object
video One product video; see Video
reference_meshes, reference_mesh Optional GLB shape guides. Mad Max only; may accompany a video within the shared input budget.

Photo limits without a video: Diffusion takes one file or 2 to 4 named views; Parametric Low takes 1 to 50 photos; Mad Max takes up to 16 total inputs, including its generated specification (up to 15 photos without other inputs). Empty or non-image files return 400.

Send one product video on the video field, alone or with photos. Mad Max also accepts reference meshes beside the video.

Field Description
video MP4 or MOV, at most 300 MiB (314,572,800 bytes) and 60 seconds
  • Every video build runs Product Research on the clip first. It needs a useful description, and the asset stays in INIT while research runs.
  • The route still follows mode and effort: Diffusion builds from views research picks; Parametric Low and Mad Max can also carry the original clip into the build.
  • With a video, parameters must be empty on every route, because research settles the build. product_research still applies.
  • Diffusion and Parametric Low accept up to 50 photos beside the video; they do not accept reference meshes. Mad Max can combine the video with either reference_mesh or repeated reference_meshes, within its 16-input limit.
  • Count one slot for the video, one for the generated asset specification, one for each mesh and one for each photo. A video with no meshes allows up to 14 photos; one mesh plus a video allows up to 13 photos; seven meshes plus a video allows up to 7 photos. Exceeding 16 total inputs returns 400 IMAGE_TO_SIM_TOO_MANY_BUILD_INPUTS before asset creation or billing. Both mesh fields follow this combined budget.
  • Accepted combinations keep the original video, every photo, and every mesh through research, submission, retries, and resume. Mad Max never silently removes the video or photos to make a combination fit; reduce the supplied inputs if the combined budget is exceeded.
  • Errors: 400 VIDEO_FORMAT_UNSUPPORTED, VIDEO_EMPTY, VIDEO_UNREADABLE, VIDEO_TOO_LONG; 413 VIDEO_TOO_LARGE. A refused request creates and charges nothing.
Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/create/imagetosim" \
-H "x-api-key: YOUR_API_KEY" \
-F "name=Desk Lamp from Video" \
-F "description=A desk lamp with a weighted round base, a hinged two-part arm and a tilting cone shade." \
-F "mode=parametric" \
-F "effort=low" \
-F "engine=mujoco" \
-F "product_research=specs_only"

POST /api/v1/external/assets/create/texttosim

Content-Type: application/json (or multipart to attach Mad Max reference meshes). Send the request fields without photos. Diffusion and Parametric Low first generate a reference image from the description; Mad Max researches the described product. Because there are no photos, Mad Max Text → Sim needs product_research: "on".

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/create/texttosim" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Articulated Storage Cabinet",
"description": "A steel storage cabinet, 90 cm wide, 45 cm deep and 180 cm tall, with two hinged doors and four internal shelves.",
"mode": "parametric",
"effort": "mad_max",
"product_research": "on",
"engine": "isaac_sim"
}'

Save the returned id and poll /status; do not recreate the asset while waiting.

Compatibility for existing clients

Requests without mode are still accepted. The settings sit at the top level and the route comes from shape_model and effort:

Flat request Equivalent route
shape_model omitted, auto, or diffusion Diffusion
shape_model=parametric with effort omitted or low Parametric Low
shape_model=parametric with effort=mad_max, or shape_model=mad_max Mad Max
Flat field Setting in parameters
enable_parts_segmentation=false Diffusion structure: "single_object"
enable_parts_segmentation=true Diffusion structure: "static_parts"
create_articulation=true Diffusion structure: "articulated_parts", Parametric Low articulation: true
decimation_mode=strict with decimation_target_faces Parametric Low face_budget
Other flat fields The setting of the same name

The flat shape is lenient: a setting the chosen route does not use is accepted and has no effect, and product_research is ignored on routes that do not research. Two flat requests are refused because they could never be built:

  • Parametric with decimation_target_ratio: 400 PARAMETRIC_DECIMATION_REQUIRES_FACE_TARGET.
  • Parametric with decimation_target_faces outside 2,000 to 200,000: 400 PARAMETRIC_FACE_BUDGET_UNSUPPORTED.

shape_model=diffusion cannot be combined with effort. New integrations should send mode and parameters.

POST /api/v1/external/assets/create/cadtosim

Content-Type: multipart/form-data. Upload a CAD or mesh file and, optionally, a reference photo. Palatial normalizes the geometry and builds collision and physics; CAD does not run AI shape generation. Omit image to skip texture generation.

File field Required Formats
mesh Yes STEP, STP, IGES, IGS, OBJ, GLB, GLTF, FBX, STL, PLY, USD, USDA, USDC, USDZ
image No PNG, JPEG, WebP reference photo
datasheet No PDF engineering datasheet

JT and SLDPRT files are refused with 400 CAD_SOURCE_FORMAT_UNSUPPORTED.

CAD → Sim has no mode, effort, product_research, or parameters; sending them returns 400. Its settings are top-level fields: the shared name, description (at most 4,000 characters), engine, workspace, and validation settings, the Diffusion settings as flat fields (using enable_parts_segmentation and create_articulation instead of structure; mesh_quality and shape_model do not apply), plus:

Field Default Description
enable_parts_segmentation false Run AI part segmentation on the mesh. Authored parts are kept either way.
create_articulation false Articulated parts; also turns segmentation on.
apply_textures true with an image, otherwise false Generate textures from the reference image. true without an image returns 400.
keep_existing_textures Do not replace textures even if an image is sent.
regenerate_parts false Regenerate parts even if the file already has authored parts.
keep_existing_shape Do not replace shape or parts. Incompatible with regenerate_parts=true.
physics_validation_only false Skip texture and parts work and run the later stages. Incompatible with regenerate_parts=true.
source_up_axis / up_direction x, y, or z. Required for STEP, IGES, and direct meshes; omit for USD.
meters_per_unit / units Scale (units: m, cm, mm, inch, feet). Required for OBJ, GLB, GLTF, FBX, STL, PLY; omit for STEP, IGES, and USD.

Source frames are never inferred:

  • 400 CAD_SOURCE_FRAME_REQUIRED: a STEP or IGES file without an up axis, or a direct mesh without an axis and scale.
  • 400 CAD_SOURCE_FRAME_CONFLICT: frame fields on USD, a scale on STEP or IGES, or conflicting aliases.
  • 400 CAD_AUTHORED_APPEARANCE_UNAVAILABLE: a direct mesh asked to keep its appearance when no embedded texture can be verified.

Decimation: use decimation_mode=auto for quality-driven simplification, or strict with exactly one of decimation_target_faces or decimation_target_ratio. For older clients, decimation=true with triangle_count=auto maps to auto, and fixed presets map to strict targets: minimal 20k, low 50k, medium 100k, high 200k, x_high 500k faces. Without decimation settings, CAD meshes are not simplified.

Textures: texture_size (2048, 4096 default, 8192) is the generated size; optimize_textures (default true) downsizes maps above texture_max_resolution (default 4096) and never upscales. Use texture_max_resolution=8192 for 8K delivery or optimize_textures=false to keep provider-native maps.

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/create/cadtosim" \
-H "x-api-key: YOUR_API_KEY" \
-F "name=Optimized Factory Equipment" \
-F "description=Factory equipment prepared for realtime simulation" \
-F "source_up_axis=y" \
-F "meters_per_unit=1" \
-F "apply_textures=true" \
-F "decimation_mode=strict" \
-F "decimation_target_faces=50000"

A STEP file with no reference photo (no texture generation):

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/create/cadtosim" \
-H "x-api-key: YOUR_API_KEY" \
-F "name=Mounting Bracket" \
-F "description=Aluminum L-bracket with two mounting holes" \
-F "source_up_axis=z"

POST /api/v1/external/assets/batch

Starts 1 to 10 independent Text, Image, or CAD generations in one request. Convert table rows into an items array; CSV, Excel, and Notion files are not accepted directly.

To generate several Variants of one existing source, use Create several Variants at once, not this independent-asset batch route. A single request can create up to eight Variants.

  • Each item has a unique id (1 to 40 letters, numbers, _, or -), a type (text, image, or cad), and an input object that follows the matching single-create contract.
  • Files are named <id>.<field>: Image rows use <id>.file or 2 to 4 of <id>.front, <id>.left, <id>.back, <id>.right; CAD rows use <id>.mesh and optional <id>.image and <id>.datasheet; Mad Max rows may attach reference meshes.
  • Limits: 10 rows, 12 files, 8 MiB per file, 100 MiB in total, and 64 KiB for the items manifest. Video is not accepted in a batch.
  • For Text-only batches, send {"items":[...]} as application/json.
Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/batch" \
-H "x-api-key: YOUR_API_KEY" \
-F 'items=[{"id":"text-low","type":"text","input":{"name":"Storage Cabinet","description":"A steel cabinet with two hinged doors.","mode":"parametric","effort":"low","parameters":{"articulation":true}}},{"id":"mug","type":"image","input":{"name":"Red Coffee Mug","description":"A red ceramic mug matching the photo.","mode":"diffusion"}}]' \

Response: 200 with results in request order and a summary:

{
"results": [
{ "id": "text-low", "type": "text", "status": "created", "assetId": "…", "viewerUrl": "…" },
{ "id": "mug", "type": "image", "status": "failed", "httpStatus": 403, "error": { "code": "insufficient_tokens", "…": "…" } }
],
"summary": { "created": 1, "failed": 1 }
}

An invalid manifest returns 400 before any row starts. Each row is otherwise admitted on its own, and later rows are still attempted. There is no batch ID or batch-level idempotency: after a lost response, list your assets before resending only the rows that did not start.

summary.created counts accepted assets, not completed builds. Keep each result’s assetId and follow its status and progress independently.


Mad Max creates and every video build return the asset with parameters.agentBuildJobId. That job ID differs from the asset ID: use the asset ID for status and downloads, and the job ID to follow research and answer questions. Job routes need the same workspace key that owns the job.

POST /api/v1/external/mad-max/jobs

An alternative to the create endpoints for Mad Max, with built-in idempotency. Response 202.

Field Required Description
clientRequestId Yes Your idempotency key, 1 to 128 of A-Z a-z 0-9 . _ : -. Repeating the same request returns the existing job with replayed: true; reusing it for a different request returns 409 CLIENT_TURN_MISMATCH.
name Yes 4 to 50 characters
description Yes At most 4,000 characters
simulator Yes isaac_sim, mujoco, or newton
images No Up to 15 of { "name": "front.jpg", "inlineData": { "mimeType": "image/jpeg", "data": "<base64>" } }, reserving one build input for the generated specification; PNG, JPEG, or WebP, at most 2 MiB each and 12 MiB in total
product_research No on (default), specs_only, or off

engine, mode, effort, and build settings are not accepted here. The response is { id, assetId, viewerUrl, status, replayed, billing }. Its status is the underlying run status (for example running); read the job for the statuses below.

GET /api/v1/external/mad-max/jobs/{jobId}

Returns id, assetId, viewerUrl, status, assetStatus, revision, billing, and, when relevant, question, references, proposal, quote, completion, progress, recoveries, automaticRetry, and error.

status Meaning
running The accepted request is starting; detailed job state may not be available yet. Keep polling the same job.
researching, preparing, building Working
needs_input A question is waiting for your answer
needs_selection references are waiting for you to pick one
needs_confirmation A proposal is waiting for confirmation
paused Billing pause; pauseReason: "insufficient_credits", resumes after a top-up
blocked Stopped; check recoveries
stopping, ready, failed, cancelled Stopping or finished

Read the job first and use the IDs and revision it offers. Paths are relative to /api/v1/external/mad-max/jobs/{jobId}.

Method and path Body and behavior
GET /references/{artifactId} An offered appearance option as image bytes
POST /select-reference artifactId, optional actionId. 200.
POST /answer askId, expectedRevision, clientMutationId, and exactly one of optionId or answerText (at most 4,000 characters); optional subOptionId with optionId. 202.
POST /confirm Only when a confirmation is owed: proposalId, expectedRevision, clientMutationId, the current completionQuoteId, optional offered preset. 200.
POST /revise proposalId, expectedRevision, clientMutationId, feedback (at most 2,000 characters). 200.
POST /cancel-proposal proposalId, expectedRevision, clientMutationId. 200.
POST /retry-model Only an offered retry_model recovery: actionId, clientMutationId. Starts new paid work. 200.
POST /retry-delivery Only an offered retry_delivery recovery: actionId. 200.
POST /retry-completion Only an offered retry_completion recovery: actionId. 200.
POST /stop No body. Stops research, build, or completion. 200 { id, stopRequested: true }.
  • A stale revision or an unavailable choice returns 409; read the job again before retrying.
  • Reuse a clientMutationId only to retry the same mutation. Never replay a paid retry whose outcome is unknown.
  • The create request normally authorizes the build already; /confirm is not a mandatory step.

Job routes are rate-limited per user, shared with that user’s dashboard Agent activity. The defaults per minute are 12 for create and answer, 6 for confirm, retry-model, and retry-completion, 60 for other controls, and 120 for reads. A limited request returns 429 AGENT_RATE_LIMITED with retryAfterSeconds and a Retry-After header.

Research builds also have a capacity limit: a create can return 429 GENERATION_JOB_CAPACITY or TOO_MANY_RUNS, and nothing is created. If Mad Max is not enabled for your environment, research creates return 503 and create nothing.


POST /api/v1/external/assets/{id}/variants

Content-Type: application/json. Creates and submits a new independent asset from a successful source asset. Describe the difference in feedback; Palatial interprets it and chooses the regeneration work. Requires a workspace key with asset:variant-create; service credentials are refused.

For several Variants of the same source, use Create several Variants at once. You do not need one POST per Variant.

Field Required Default Description
feedback Yes What should differ from the source, at most 2,000 characters
name No Generated from the request 4 to 50 characters. A name already used in the family returns 409 VARIANT_LABEL_TAKEN.
description No Source description At most 4,000 characters
parameters No Source settings Regeneration overrides subject to the source’s supported and locked settings, for example {"collision_quality":"high"}. Explicit values win over settings inferred from feedback. This is not the create parameters object: structure, face_budget, and texture_size are refused with REGENERATION_PARAMETERS_INVALID.

Only these fields are accepted. Stages, quotes, and operation IDs are owned by Palatial; sending from or clientOperationId returns 400.

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/variants" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "feedback": "Make the mug matte blue rubber while preserving its form." }'

The request can take several seconds while Palatial interprets the feedback; set the client timeout accordingly.

Response: 201 with the new asset. Use its id with the normal status, cancel, and download routes.

{
"id": "NEW_ASSET_ID",
"name": "Blue Rubber Mug",
"description": "A simulation-ready mug",
"type": "imagetosimready",
"status": { "status": "SUBMITTED" },
"requestedChange": {
"instruction": "Apply a matte blue rubber finish while preserving the form.",
"changeClasses": ["appearance"]
}
}

Behavior and limits:

  • The source is never overwritten. The new asset joins the source’s Variant family.
  • A new Variant needs the start minimum of its own route: Diffusion 20, Parametric Low 40, Mad Max 80, CAD 4 tokens. A Mad Max Variant needs 80 even when its estimate is lower or higher. See Billing and admission.
  • Feedback can describe appearance, geometry, parts, collision, physics, articulation, or validation changes. Feedback that clearly asks for a different structure sets object_behavior the same way an explicit parameters.object_behavior does.
  • requestedChange reports how the feedback was interpreted. The API does not generate a reference image for a Variant.
  • Mad Max Variants rebuild the model from the feedback, from the shape stage. Build settings are refused, except engine and run_simulation on a source whose simulation was never submitted.
  • CAD geometry cannot be changed through feedback; upload the modified file as a new asset.
HTTP Code When
400 VARIANT_FEEDBACK_REQUIRED, VARIANT_CHANGE_UNCLEAR Blank feedback, or no change could be identified
403 VARIANT_FEATURE_DISABLED Variants are not enabled for this environment
403 insufficient_tokens Below the Variant route’s start minimum
409 VARIANT_SOURCE_NOT_SUCCESSFUL The source failed or was canceled; retry it in place instead
409 VARIANT_SOURCE_UNSUPPORTED The source type or requested stage cannot branch
409 VARIANT_STRUCTURE_UNSUPPORTED A structure change on a Mad Max model, which is always articulated
409 VARIANT_SETTINGS_UNSUPPORTED Build settings on a Mad Max Variant
409 CAD_VARIANT_GEOMETRY_UNSUPPORTED A geometry change on a CAD source
409 VARIANT_NO_EFFECTIVE_CHANGE The request would not change anything
409 VARIANT_LIMIT_REACHED, VARIANT_FAMILY_BUSY, VARIANT_OPERATION_IN_PROGRESS The family is full, or another generation in it is still running
503 VARIANT_CHANGE_CLASSIFICATION_UNAVAILABLE The feedback could not be interpreted right now; nothing was created or charged

Optionally, to retry safely after a timeout, send an Idempotency-Key header (1 to 128 letters, digits, periods, underscores, colons or hyphens) and resend the same body with the same key: you get the same Variant back instead of a second one created and charged. The header is not required, and requests without it work as usual.

POST /api/v1/external/assets/{id}/variant-batches

Creates up to eight independent Variants of one successful source asset from one feedback text. Requires a workspace key with asset:variant-create. Each Variant is an ordinary asset, billed on its own under the normal generation rules.

Field Required Description
feedback Yes What should vary across the Variants, at most 2,000 characters
count No Number of Variants, 1 to 8. When omitted, the server infers the number from the feedback; if it cannot, the request returns VARIANT_BATCH_COUNT_REQUIRED. An explicit count wins.
parameters No Regeneration overrides, as for a single Variant
dryRun No true returns the plan, estimate and a quoteId without creating assets or charging
quoteId No A quoteId from a dry run, valid for 10 minutes, to submit exactly that plan. When omitted, the server plans and prices the submission itself.

Submission (anything without dryRun: true) requires an Idempotency-Key header of 1 to 128 letters, digits, periods, underscores, colons or hyphens. Reusing the key with the same body returns the same batch; a different body returns 409 VARIANT_BATCH_IDEMPOTENCY_CONFLICT.

Preview for free:

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/variant-batches" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "count": 2, "feedback": "Make one matte blue and one glossy green", "dryRun": true }'
{
"dryRun": true,
"sourceAssetId": "ASSET_ID",
"count": 2,
"quoteId": "vb1.…",
"expiresAt": "2026-10-21T01:28:34.135Z",
"totalEstimatedTokens": 6,
"maxCount": 8,
"items": [
{ "index": 0, "feedback": "…matte blue exterior finish…", "estimatedTokens": 3 },
{ "index": 1, "feedback": "…glossy green surface…", "estimatedTokens": 3 }
],
"billing": "Estimates are not a spending cap. …"
}

Submit:

Terminal window
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/variant-batches" \
-H "x-api-key: YOUR_API_KEY" \
-H "Idempotency-Key: blue-green-batch-1" \
-H "Content-Type: application/json" \
-d '{ "count": 2, "feedback": "Make one matte blue and one glossy green" }'

Response: 201 with the batch. Poll it with GET /api/v1/external/assets/{id}/variant-batches/{batchId} (requires asset:read), and use each item’s assetId with the normal status and download routes.

{
"id": "BATCH_ID",
"sourceAssetId": "ASSET_ID",
"status": "queued",
"counts": { "queued": 2, "running": 0, "completed": 0, "failed": 0, "paused": 0, "canceled": 0 },
"items": [
{ "index": 0, "feedback": "…", "assetId": "VARIANT_ASSET_ID", "status": "queued", "estimatedTokens": 3 }
],
"totalEstimatedTokens": 6
}

Batch status is queued, running, paused, completed, partially_failed or failed. Item status is queued, running, paused, completed, failed or canceled; an item in REVIEW counts as completed for the batch, so read the item’s asset status before downloading.

HTTP Code When
400 VARIANT_BATCH_COUNT_INVALID count outside 1 to the server maximum
400 VARIANT_BATCH_COUNT_REQUIRED No count, and the feedback does not state an unambiguous number
400 VARIANT_BATCH_IDEMPOTENCY_KEY_REQUIRED Submission without a valid Idempotency-Key
409 VARIANT_BATCH_IDEMPOTENCY_CONFLICT The key belongs to a different batch request
409 VARIANT_BATCH_QUOTE_EXPIRED, VARIANT_BATCH_QUOTE_INVALID, VARIANT_BATCH_QUOTE_ALREADY_USED The quoteId cannot be used; request a new dry run
409 VARIANT_BATCH_QUOTE_CHANGED, VARIANT_BATCH_SOURCE_CHANGED The body or the source changed since the quote

Single-Variant source and billing errors above also apply.


POST /api/v1/external/assets/{id}/reprocess

Re-runs an existing asset from a pipeline stage, in place: the asset ID and type stay the same. Use it to regenerate one step, such as shape or texture. To keep the original, create a Variant instead. Reprocess needs a balance greater than 0.

Field Required Description
from By request Stage to re-run from: shape-generation, parts-gen, texture, collision-preview, physics-predictions, articulation, validation-playback. Required unless you send physics_validation_only or both keep flags, or retry a research job.
mode No step (default) stops after the stage; auto runs the downstream stages.
stopAfter No Stage to stop after. Defaults to from in step mode.
sourceRunId No Prior run whose results seed this run
feedback No Instruction for the regenerated stage, at most 4,000 characters
parameters No Regeneration setting overrides, as for Variants
useEditedSegmentation No Rebuild from the asset’s edited segmentation
physics_validation_only No true keeps textures, shape, and parts and re-runs physics through validation (mode defaults to auto).
keep_existing_textures, keep_existing_shape No Both true is the same as physics_validation_only.
destination No overwrite only. variant returns 400.
clientOperationId For quoted requests Stable operation ID shared by quote and execution: 1–128 letters, digits, ., _, :, or -, starting with a letter or digit.
quoteId For quoted requests The returned quote.quoteId, sent with the same clientOperationId and unchanged request fields.
regenerationQuoteId No Compatibility alias for a quote; prefer quoteId. If both are sent, they must match. Mad Max in-place changes require quoteId.

POST /api/v1/external/assets/{id}/reprocess/quote

Mad Max in-place changes require a quote first. Send the intended reprocess fields plus a clientOperationId to /reprocess/quote, without quote fields. This returns 201 with quote.quoteId and does not start generation. Then send the same fields and operation ID to /reprocess, adding that quoteId:

Terminal window
# 1. Quote the change
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/reprocess/quote" \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{ "clientOperationId": "handle-edit-1", "from": "shape-generation", "mode": "auto", "feedback": "Make the handle thicker." }'
# 2. Execute using quote.quoteId from the response
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/reprocess" \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{ "clientOperationId": "handle-edit-1", "from": "shape-generation", "mode": "auto", "feedback": "Make the handle thicker.", "quoteId": "RETURNED_QUOTE_ID" }'

The quote route needs the same workspace edit access and asset:submit permission as execution. A stage is required, either from or a physics-only flag below. If the intended change or source has changed, obtain a new quote before executing. Preserve the original operation ID and input after an uncertain response.

Ordinary pipeline-stage reruns can still be submitted directly without quote fields or an operation ID:

Terminal window
# Regenerate texture only and pause after it
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/reprocess" \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{ "from": "texture", "mode": "step" }'
# Keep textures and shape; re-run physics and validation
curl -X POST "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/reprocess" \
-H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{ "physics_validation_only": true }'

Research-job retry: for an asset backed by a research job, an empty body {} runs the job’s offered recovery (delivery, model, or completion). It returns { id, recovery, accepted, actionId }. No recovery available returns 409 GENERATION_JOB_NOT_RETRYABLE; a job paused for credit returns 409 GENERATION_JOB_PAUSED.

Response: 201 with the reprocess result (including run) and, when billed by stage, billing. Then poll status or pipeline-runs/current until READY, PROCESSING_PAUSED, PROCESSING_FAILED, or PROCESSING_CANCELED.

HTTP When
400 Invalid stage, mode, or request; from missing; REGENERATION_PARAMETERS_INVALID; missing paired operation ID or quote (REPROCESS_OPERATION_ID_REQUIRED, REPROCESS_QUOTE_REQUIRED); conflicting quote fields (QUOTE_ID_CONFLICT)
403 No submit access, or a balance that is not positive (insufficient_tokens)
409 QUOTE_REQUIRED (Mad Max in-place change without its quote), REPROCESS_SOURCE_INCOMPLETE (the source run did not complete the stages before from, for example after a cancel), REPROCESS_SOURCE_RUN_REQUIRED, EDITED_SEGMENTATION_INPUT_MISSING, VARIANT_FAMILY_BUSY, or the asset is still processing

Failed builds are often retried automatically from where they stopped; check automaticRetry before reprocessing a failed asset.


POST /api/v1/external/assets/generate-image

Generates a reference image without creating an asset.

Field Description
prompt Required, at most 1,000 characters
articulationType Optional, for example single_object
sourceImageMode Optional: identity or preserve
sourceImageBase64, sourceMimeType Required with preserve; the image to keep, at most 20 MB

Response: 201. The configured preview route returns either a sheet { mode: "sheet", imageBase64, mimeType } or several images { mode, images: [{ imageBase64, mimeType, index }], timing, warning }; check the shape instead of assuming one image.


The following API-key endpoints are awaiting backend release. Confirm that they have been deployed in your integration environment before using them.

Method Path What you get
GET /api/v1/external/library/assets/{sourceAssetId}/download JSON containing a short-lived archive URL; optional engine=isaac_sim, mujoco, or newton
POST /api/v1/external/library/assets/{sourceAssetId}/copy A new private workspace asset; required JSON body { "workspace": "WORKSPACE_ID" }
GET /api/v1/external/library/copies/{newAssetId}/status Copy progress for the new destination asset

These endpoints accept user API keys and public Library sources. Downloads require asset:export and edit access to the key’s workspace. Copies require asset:create and edit access to the destination within the key’s scope. Progress requires asset:read and read access to the destination. Internal-service keys are rejected. Ordinary workspace asset routes retain their workspace restrictions.

A copy response with 201 does not establish readiness. Poll the returned new asset ID until both phase is complete and projectStatus is READY. Copy requests are not idempotent; inspect the destination before replaying a request whose outcome is unknown.

See Library API for request and response examples, download format selection, public browsing and preview routes, errors, and polling behavior.


Paths are relative to /api/v1/external/assets/{id} unless shown otherwise.

Method Path What you get
GET /media/export SimReady export ZIP. Normally 307 to the file (use curl -L), with a streamed fallback. Free; the first export needs a positive balance, and an existing export stays downloadable during a billing pause. 409 MEDIA_NOT_READY while the output is pending.
GET /media/mesh { downloadUrl } for the shape GLB
GET /media/texture { downloadUrl } for the textured GLB, not a standalone texture image. ?final_viewer=true selects the legacy final-viewer representation; it is unsupported on newer routes
GET /media/collisions { collisions: [{ filename, downloadUrl, size }] }
GET /media/parts { downloadUrl } for the segmented parts GLB
GET /media/articulated_meshes { downloadUrl, urdf } for the articulated GLB and optional URDF
GET /media/validation-report { downloadUrl } for the physics validation JSON
GET /media/image { downloadUrl } for the source or preview image
POST /api/v1/external/assets/media/images Batch thumbnails: body { "ids": ["…"] } with 1 to 500 IDs; returns an ID-keyed URL map

Mesh and texture return signed URLs, not file bytes; fetch them promptly, as they expire. Export normally redirects to the package. Outputs differ by generation route: an authored USD package can be available without separate shape or textured GLBs. Do not infer texture availability from an asset name, its source model, or READY.

Terminal window
curl -L -o export.zip "https://dashboard.palatial.cloud/api/v1/external/assets/ASSET_ID/media/export" \
-H "x-api-key: YOUR_API_KEY"

The media manifest and specific media errors below are available in Production. Older deployments can still return an undifferentiated 404 for pending, unsupported, or missing media. Successful response shapes remain unchanged.

Asset detail, status, and batch-status responses include a media object for the default mesh, texture, and export representations:

{
"media": {
"mesh": { "state": "available", "retryable": false, "href": "/api/v1/external/assets/ASSET_ID/media/mesh" },
"texture": { "state": "unavailable", "code": "MEDIA_NOT_SUPPORTED", "retryable": false },
"export": { "state": "pending", "code": "MEDIA_NOT_READY", "retryable": true }
}
}
  • available: the backing object exists. Call href with your API key; it is an API path, not a signed storage URL. Existing access and balance rules still apply.
  • pending: keep polling the same asset. It includes queued, running, and paused generation or packaging; a pause can require action before progress resumes.
  • unavailable: this route does not produce that representation, or generation ended without it. Inspect code; repeating the same media request will not create it.
  • missing: the asset expects an artifact but the backing object is absent. Report the asset ID and correlation ID to support.
  • unknown: storage could not be checked. This is a retryable failure, not evidence that the artifact is absent; wait and poll.

The manifest covers final_viewer=false, the default. It does not promise the optional final_viewer=true representation or availability for other media routes. Availability is a snapshot; the download endpoint checks again.

For mesh, texture, and export requests, branch on these error codes:

HTTP code retryable Action
409 MEDIA_NOT_READY true Poll status; request again after the representation becomes available.
422 MEDIA_NOT_SUPPORTED false Use another available representation, often the export ZIP. Do not poll this media type.
409 MEDIA_GENERATION_FAILED false Generation failed or was canceled without this output. Inspect status; do not blindly retry downloads or generation.
404 MEDIA_ARTIFACT_MISSING false The expected stored output is missing. Contact support.
503 MEDIA_STORAGE_UNAVAILABLE true Wait and retry status or the download; storage could not be checked.
404 ASSET_NOT_FOUND — The service could not find the asset. API-key scope checks may instead return 403 Asset scope denied, including for an unknown ID.

Media errors use the standard error body, with retryable added. These specific statuses replace the former generic media 404; successful download response shapes are unchanged. Other media routes retain their existing contracts.

GET /api/v1/external/assets/{id}/process-file/{process}

Checks whether a stage output is ready and returns its download URLs. ?runId= selects a specific run; otherwise the current run is used.

process Output
shape-generation Base mesh
parts-gen Part geometry, when emitted separately
texture Textured mesh
collision-preview Collision geometry
articulation Articulated mesh; can include the MJCF at articulation/mjcf/model.xml
physics-predictions Physics data
validation-playback Validation artifacts
simulator-asset Completed simulator package
download-package Completed export package

simulator-asset and download-package are output names, not valid reprocess stages.

state Meaning
pending Not ready; poll again
found Ready; use files[].downloadUrl
skipped No separate artifact: the stage does not apply, or its output is part of the final model
error The stage failed; see reason

The final export ZIP uses the simulator’s package name, such as MuJoCo_assets_<id>.zip or IsaacSim_assets_<id>.zip.


Paths are relative to /api/v1/external.

Method Path Description
GET /workspaces Your workspaces (a workspace key sees its own)
GET /workspaces/{id} Workspace details, including tokenBalance (tokens, planTokens, purchasedTokens, debtTokens, ownerType, ownerId) from the wallet used for admission
GET /workspaces/{id}/members Workspace members
PATCH /workspaces/{id}/deduct-tokens Legacy compatibility call: body { "assetId": "…" } marks that export ready and returns the balance. It does not debit tokens. New clients use the export route directly.
Method Path Description
GET /api/v1/external/users A JSON array with your user profile
GET /api/v1/external/users/{id} Your profile; other user IDs return 403

/api/v1/external/assets/{id}/embedded/{purpose}

Reads or writes the supported physics and videourl payloads stored with an asset; this is not a general JSON store. ?runId= selects a run.

Method Description
GET Read the payload
POST Create or replace: { "data": { … } }. 201.
PATCH Replace the payload: { "data": { … } }. No deep merge.
DELETE Remove the payload

import json, os, time, requests
HOST = "https://dashboard.palatial.cloud"
H = {"x-api-key": os.environ["PALATIAL_API_KEY"]}
with open("product.jpg", "rb") as f:
resp = requests.post(
f"{HOST}/api/v1/external/assets/create/imagetosim",
headers=H,
files={"file": f},
data={
"name": "Storage Bin",
"description": "A plastic storage bin with a hinged lid, 40 cm wide.",
"mode": "diffusion",
"parameters": json.dumps({"structure": "static_parts"}),
},
timeout=120,
)
if resp.status_code == 403 and resp.json().get("code") == "insufficient_tokens":
raise SystemExit(f"Add tokens: {resp.json().get('tokens')}")
resp.raise_for_status()
asset = resp.json()
asset_id = asset["id"]
job_id = asset.get("parameters", {}).get("agentBuildJobId")
deadline = time.monotonic() + 4 * 60 * 60 # client policy, not an API guarantee
while time.monotonic() < deadline:
st = requests.get(f"{HOST}/api/v1/external/assets/{asset_id}/status", headers=H, timeout=30)
st.raise_for_status()
st = st.json()
# Older environments do not expose the media manifest until their Backend release.
if "media" not in st:
if st["status"] == "READY":
break
media = {"state": "pending", "retryable": True}
else:
media = st["media"]["export"]
if media["state"] == "available":
break
if not media["retryable"]:
raise RuntimeError(f"Export unavailable: {media.get('code', media['state'])}")
if st["status"] in ("PROCESSING_FAILED", "PROCESSING_CANCELED"):
raise RuntimeError(st.get("displayText", st["status"]))
if job_id:
job = requests.get(f"{HOST}/api/v1/external/mad-max/jobs/{job_id}", headers=H, timeout=30).json()
if job["status"] in ("needs_input", "needs_selection", "needs_confirmation", "blocked", "failed", "cancelled"):
raise RuntimeError(f"Job {job_id} needs handling: {job['status']}")
time.sleep(10)
else:
raise TimeoutError(f"Export still pending: {asset_id}")
export = requests.get(
f"{HOST}/api/v1/external/assets/{asset_id}/media/export",
headers=H, allow_redirects=True, timeout=300,
)
export.raise_for_status()
with open(f"export_{asset_id}.zip", "wb") as out:
out.write(export.content)

A billing pause (billing.paused) is not an error: keep polling and it resumes after a top-up.

{
"status": "READY",
"progress": 100,
"displayText": "Ready",
"generationAgent": "diffusion",
"export": { "status": "READY", "size": 4902028 },
"media": {
"mesh": { "state": "available", "retryable": false, "href": "/api/v1/external/assets/ASSET_ID/media/mesh" },
"texture": { "state": "available", "retryable": false, "href": "/api/v1/external/assets/ASSET_ID/media/texture" },
"export": { "state": "available", "retryable": false, "href": "/api/v1/external/assets/ASSET_ID/media/export" }
},
"validationOutcome": {
"delivery": "delivered",
"validation": "passed",
"exportAvailable": true,
"simulators": [{ "simulator": "IsaacSim", "result": "passed", "tests": { "total": 4, "failed": 0 } }]
}
}

For API keys, workspace credits, or rate limits, open the Palatial dashboard or contact Palatial.

Still need help?

Our team reviews every request and will get back to you as soon as possible.

Submit a request

Tell us what you're trying to do and where it went wrong. Include the asset link if you have one.