Assets, jobs and delivery
Upload media, preserve task identity and recover work without duplicate submissions.
Import a file in two steps
First call upload_create with the original filename, exact byte count, SHA-256 digest, source, license and rightsConfirmed: true. The result includes uploadUrl and method: PUT. Upload the raw bytes to that returned path on the same runtime origin with the same Bearer key. The completed upload returns an asset record; use its id in later operations. Remote runtimes cannot read a path on your computer.
| Field | Type / requirement |
|---|---|
| name | string · Original filename. |
| bytes | positive integer · Exact file length. |
| sha256 | string · Digest of the same bytes you will upload. |
| source / license | strings · Accurate provenance and usage rights. |
| rightsConfirmed | true · Only for media you have permission to use. |
Node.js upload example
Append this to the API guide’s callTool helper. Replace ./product.png with your own file. The local upload route accepts up to 100 MiB; a selected model may impose smaller limits, dimensions or duration requirements. Validate those separately before preparing generation.
import { readFile } from "node:fs/promises";
import { createHash } from "node:crypto";
const bytes = await readFile("./product.png");
const intent = await callTool("upload_create", {
name: "product.png",
bytes: bytes.byteLength,
sha256: createHash("sha256").update(bytes).digest("hex"),
source: "My product photography",
license: "Owned by me",
rightsConfirmed: true,
});
const uploadUrl = new URL(intent.uploadUrl, base);
if (uploadUrl.origin !== new URL(base).origin) throw new Error("Unexpected upload origin");
const response = await fetch(uploadUrl, {
method: "PUT",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/octet-stream" },
body: bytes,
});
const body = await response.json();
if (!response.ok || body.error) throw new Error(body.error?.code || `HTTP ${response.status}`);
console.log(body.result.id);Submit once, then observe
Prepare model generation with generation_prepare, then pass its exact validated operation to job_submit. Keep preparedRequestId when present. Store a stable idempotencyKey before submission and reuse it only with the same input when retrying. A quoteId is optional in this runtime; quote_create can produce an estimate when requested. Request controls or selecting a model do not authorize a purchase. Honor the requested task and any explicit spending limit.
{ "type": "job", "id": "<job-id-returned-by-submission>" }Job states and recovery
| State | Next action |
|---|---|
| queued / running | Poll record_get with the same job ID. Back off between reads; do not submit again. |
| completed | Inspect outputs and retrieve the workspace result when applicable. |
| failed | Read the error. job_resume is only for a known provider request eligible for recovery. |
| submission_unknown | Stop automatic resubmission and resolve the original provider request or settlement. |
| cancelled | Check the final task and billing records; upstream cancellation is not a refund guarantee. |
Edit and deliver the result
Read workspace_snapshot before editing. Submit workspace_commit with projectId, the current baseRevisionId and a stable idempotencyKey. On REVISION_CONFLICT, read the snapshot again and reapply the intended edit. workspace_commit queues a local render; it does not buy new generated footage. Use workspace_result for progress and final delivery, including its workspace link. If the MCP host exposes workspace_export, use it to save the completed files again without rendering or generating. HTTP clients can refresh delivery links through workspace_result; workspace_export is a host-side MCP helper, not an HTTP tool endpoint.