> For the complete documentation index, see [llms.txt](https://docs.m-xr.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.m-xr.com/marso-studio-api/api/assets.md).

# Assets

Upload source files and manage generated assets.

Assets represent the files you upload and the material outputs the API generates. Use these endpoints to upload source files, list assets, fetch metadata and download URLs, and delete assets you no longer need.

## Start uploading a batch of files.

> Creates an upload batch for 5 to 200 files at once. For each file you\
> provide the filename, size, and optional content type; the response\
> returns a presigned upload URL and the exact headers you must send with\
> the PUT.\
> \
> Upload the raw bytes directly to each URL, then poll\
> \`GET /batch/uploads/{upload\_batch\_id}\` until every file is \`ready\`.\
> \
> Files are validated after upload. A file that fails validation becomes\
> \`rejected\` rather than \`ready\`. The upload endpoint itself only rejects\
> obvious request-level problems, such as an unsupported file type or a\
> size mismatch. Images must be 4096×4096 pixels or smaller.<br>

```json
{"openapi":"3.1.0","info":{"title":"Marso Studio API","version":"0.0.1"},"tags":[{"name":"assets","description":"Assets represent the files you upload and the material outputs the API\ngenerates. Use these endpoints to upload source files, list assets, fetch\nmetadata and download URLs, and delete assets you no longer need.\n"}],"servers":[{"url":"https://api.marso.ai/api/public/v1","description":"Production server."}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key minted from the Marso Studio dashboard. The value has the form\n`mxr_live_<entropy>` and is sent as a Bearer token in the `Authorization`\nheader.\n"}},"schemas":{"BatchUploadRequest":{"type":"object","required":["files"],"description":"Request body for `POST /batch/uploads`. Describe 5 to 200 files you want to\nupload; the response returns a presigned upload URL for each one.\n","properties":{"files":{"type":"array","minItems":5,"maxItems":200,"description":"The files to upload. Must contain between 5 and 200 items.\n","items":{"$ref":"#/components/schemas/BatchUploadFileRequest"}}}},"BatchUploadFileRequest":{"type":"object","required":["filename","size_bytes"],"description":"Metadata for a single file in a batch upload request.\n","properties":{"filename":{"type":"string","description":"The file name. The extension determines the asset type and must be in\nthe allowed list. Allowed extensions: `.usd` and `.glb` for\nmeshes; `.jpg`, `.jpeg`, and `.png` for images.\n"},"content_type":{"type":"string","description":"The file's content type. Optional; inferred from the extension when\nomitted. When provided, it must match the extension's allowed\ncontent type.\n"},"size_bytes":{"type":"integer","format":"int64","description":"The exact byte length of the file. Used as the upload's\n`Content-Length` and enforced as a hard size cap. Allowed byte limits\nvary by format; images are additionally validated to be\n4096×4096 pixels or smaller after upload.\n"}}},"BatchUploadResponse":{"type":"object","required":["files"],"description":"Response from `POST /batch/uploads`. Contains the upload batch ID and the\npresigned upload URL for each file.\n","properties":{"upload_batch_id":{"type":"string","description":"The ID that groups the created pending assets. Poll\n`GET /batch/uploads/{upload_batch_id}` for aggregate readiness. May be\nabsent if the grouping could not be recorded; in that case, fall back\nto polling each asset individually.\n"},"files":{"type":"array","description":"Presigned upload URLs and asset IDs, index-aligned with the request.\n","items":{"$ref":"#/components/schemas/BatchUploadFileResponse"}}}},"BatchUploadFileResponse":{"type":"object","required":["asset_id","upload_url"],"description":"Presigned upload URL and metadata for one file in a batch upload response.\n","properties":{"asset_id":{"type":"string","description":"The ID of the pending asset created for this file.\n"},"upload_url":{"type":"string","format":"uri","description":"Presigned S3 PUT URL. Upload the raw file bytes to this URL using the\nheaders provided in `headers`.\n"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Every header the client must send verbatim with the PUT request. This\nincludes `Content-Type`, `Content-Length`, and any `x-amz-meta-*`\nmetadata headers. All headers are bound into the signature, so omitting\nor altering any of them causes S3 to reject the upload.\n"}}},"BatchUploadErrorResponse":{"type":"object","required":["errors"],"description":"Returned when one or more files in an upload request fail validation.\nLists every failed file so you can fix them all at once. The batch\nis all-or-nothing: if any file fails, nothing is created.\n","properties":{"errors":{"type":"array","description":"Per-file validation failures.\n","items":{"$ref":"#/components/schemas/BatchUploadFileError"}}}},"BatchUploadFileError":{"type":"object","required":["index","filename","code","message"],"description":"One validation failure from a `POST /batch/uploads` request.\n","properties":{"index":{"type":"integer","description":"Zero-based position of the file in the upload request.\n"},"filename":{"type":"string","description":"The offending file name. Empty if the fault was a missing file name.\n"},"code":{"type":"string","description":"Machine-readable reason for the rejection. Possible values include\n`UNSUPPORTED_ASSET_FORMAT`, `INVALID_CONTENT_TYPE`, `FILE_TOO_LARGE`,\nand `INVALID_REQUEST`.\n"},"message":{"type":"string","description":"Human-readable explanation of why this file was rejected.\n"}}},"Error":{"type":"object","required":["error"],"description":"Canonical error envelope. The error payload is nested under the `error`\nkey.\n","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","description":"The body of an error response. The `code` is a stable value clients can\nbranch on; the `message` is a human-readable explanation.\n","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable description of the error.\n"},"details":{"type":["object","null"],"description":"Additional structured information about the error, or `null` if no\nextra details are available.\n","additionalProperties":true}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. The enum enumerates every code that can be\nreturned by this API, including infrastructure-level 5xx codes, so\ngenerated clients can decode any `error.code` they receive.\n\nPossible enum values:\n- `INVALID_REQUEST`: the request body or a query parameter failed validation.\n- `MALFORMED_UUID`: a path parameter that should be a UUID is not one.\n- `UNAUTHORIZED`: the request could not be authenticated.\n- `API_KEY_MISSING`: no API key was supplied.\n- `API_KEY_INVALID`: the supplied API key does not match any known key.\n- `API_KEY_REVOKED`: the supplied API key has been revoked.\n- `PLAN_FEATURE_REQUIRED`: the account plan does not include a required feature.\n- `INSUFFICIENT_CREDITS`: the account does not have enough credits for this request.\n- `QUOTA_EXCEEDED`: a usage quota for the account has been exceeded.\n- `FILE_TOO_LARGE`: an uploaded or referenced file exceeds the allowed size.\n- `INVALID_CONTENT_TYPE`: a file's content type does not match its declared or expected type.\n- `UNSUPPORTED_ASSET_FORMAT`: a file's extension is not one of the supported asset formats.\n- `FORMAT_NOT_AVAILABLE`: the requested downloadable format does not exist for this asset.\n- `ASSET_NOT_FOUND`: the asset does not exist or does not belong to the account.\n- `ASSET_IN_USE`: the asset is referenced by an active execution and cannot be deleted.\n- `BATCH_TOO_LARGE`: the batch contains more items than the endpoint allows.\n- `CONCURRENT_EXECUTION_LIMIT_EXCEEDED`: the account has reached its limit on simultaneous submissions.\n- `PBR_FAILURE_CAP_EXCEEDED`: the account has too many recent failed PBR runs; new submissions are temporarily blocked.\n- `EXECUTION_NOT_FOUND`: the execution or batch does not exist or does not belong to the account.\n- `EXECUTION_IN_PROGRESS`: the request conflicts with an execution that is still running.\n- `RATE_LIMITED`: the API key's rate limit was exceeded.\n- `SERVICE_UNAVAILABLE`: a dependent service is temporarily unavailable; retry later.\n- `MAINTENANCE_MODE`: the API is temporarily unavailable for maintenance.\n- `INTERNAL_SERVER_ERROR`: an unexpected server error occurred.\n- `WORKFLOW_CREATION_FAILED`: the platform could not start the underlying workflow.\n- `WORKFLOW_API_ERROR`: the platform's workflow system returned an unexpected error.\n","enum":["INVALID_REQUEST","MALFORMED_UUID","UNAUTHORIZED","API_KEY_MISSING","API_KEY_INVALID","API_KEY_REVOKED","PLAN_FEATURE_REQUIRED","INSUFFICIENT_CREDITS","QUOTA_EXCEEDED","FILE_TOO_LARGE","INVALID_CONTENT_TYPE","UNSUPPORTED_ASSET_FORMAT","FORMAT_NOT_AVAILABLE","ASSET_NOT_FOUND","ASSET_IN_USE","BATCH_TOO_LARGE","CONCURRENT_EXECUTION_LIMIT_EXCEEDED","PBR_FAILURE_CAP_EXCEEDED","EXECUTION_NOT_FOUND","EXECUTION_IN_PROGRESS","RATE_LIMITED","SERVICE_UNAVAILABLE","MAINTENANCE_MODE","INTERNAL_SERVER_ERROR","WORKFLOW_CREATION_FAILED","WORKFLOW_API_ERROR"]}},"responses":{"Unauthorized":{"description":"The API key is missing, invalid, or revoked. Possible error codes:\n`API_KEY_MISSING`, `API_KEY_INVALID`, `API_KEY_REVOKED`, or `UNAUTHORIZED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded (`RATE_LIMITED`). The response includes a `Retry-After`\nheader with the number of seconds to wait before retrying, plus the\n`X-RateLimit-*` headers. Those headers are also present on successful (2xx)\nresponses so a client can pace itself.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request burst capacity for the API key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current burst window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the burst capacity is fully restored.\n","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/batch/uploads":{"post":{"operationId":"createBatchUploads","tags":["assets"],"summary":"Start uploading a batch of files.","description":"Creates an upload batch for 5 to 200 files at once. For each file you\nprovide the filename, size, and optional content type; the response\nreturns a presigned upload URL and the exact headers you must send with\nthe PUT.\n\nUpload the raw bytes directly to each URL, then poll\n`GET /batch/uploads/{upload_batch_id}` until every file is `ready`.\n\nFiles are validated after upload. A file that fails validation becomes\n`rejected` rather than `ready`. The upload endpoint itself only rejects\nobvious request-level problems, such as an unsupported file type or a\nsize mismatch. Images must be 4096×4096 pixels or smaller.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUploadRequest"}}}},"responses":{"201":{"description":"Upload URLs minted (index-aligned with the request).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUploadResponse"}}}},"400":{"description":"Either a batch-level validation error (returned as the standard\n`Error` with code `INVALID_REQUEST`) or one or more per-file\nvalidation failures. Per-file failures are returned as a\n`BatchUploadErrorResponse` that lists every invalid file so you can\nfix them all at once. The batch is all-or-nothing: if any file fails,\nnothing is created.\n\nPer-file error codes: `UNSUPPORTED_ASSET_FORMAT`,\n`INVALID_CONTENT_TYPE`, `FILE_TOO_LARGE`, `INVALID_REQUEST`.\n","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/BatchUploadErrorResponse"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Retrieve an upload batch.

> Returns the state of an upload batch created with \`POST /batch/uploads\`,\
> including the validation status of each file. Poll this endpoint until\
> \`state\` reaches a terminal value (\`COMPLETED\`, \`PARTIALLY\_COMPLETED\`, or\
> \`FAILED\`), then use the ready asset IDs to submit a PBR batch.<br>

```json
{"openapi":"3.1.0","info":{"title":"Marso Studio API","version":"0.0.1"},"tags":[{"name":"assets","description":"Assets represent the files you upload and the material outputs the API\ngenerates. Use these endpoints to upload source files, list assets, fetch\nmetadata and download URLs, and delete assets you no longer need.\n"}],"servers":[{"url":"https://api.marso.ai/api/public/v1","description":"Production server."}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key minted from the Marso Studio dashboard. The value has the form\n`mxr_live_<entropy>` and is sent as a Bearer token in the `Authorization`\nheader.\n"}},"schemas":{"BatchUploadStatusResponse":{"type":"object","required":["state","files","created_at"],"description":"Response from `GET /batch/uploads/{upload_batch_id}`. Contains the\naggregate state of the batch and the validation status of each file.\n","properties":{"state":{"$ref":"#/components/schemas/BatchUploadState"},"files":{"type":"array","description":"Per-file validation status, index-aligned with the upload request.\n","items":{"$ref":"#/components/schemas/BatchUploadStatusFile"}},"created_at":{"$ref":"#/components/schemas/EpochMillis"},"completed_at":{"$ref":"#/components/schemas/NullableEpochMillis"}}},"BatchUploadState":{"type":"string","description":"Aggregate state of an upload batch.\n\nPossible enum values:\n- `UPLOADING`: at least one file is still being validated.\n- `COMPLETED`: every file in the batch is `ready`.\n- `PARTIALLY_COMPLETED`: the batch has a mix of `ready` and `rejected`\n  files; this is a terminal state.\n- `FAILED`: every file in the batch was rejected.\n","enum":["UPLOADING","COMPLETED","PARTIALLY_COMPLETED","FAILED"]},"BatchUploadStatusFile":{"type":"object","required":["file_name","status"],"description":"Validation status of one file in an upload batch.\n","properties":{"file_name":{"type":"string","description":"The file name as provided in the upload request.\n"},"status":{"type":"string","enum":["pending","ready","rejected"],"description":"The validation status of this file.\n\nPossible enum values:\n- `pending`: the file has not been validated yet.\n- `ready`: the file passed validation and can be used for PBR generation.\n- `rejected`: the file failed validation. The file must be re-uploaded.\n"}}},"EpochMillis":{"type":"integer","format":"int64","description":"Time represented as milliseconds since the Unix epoch.\n"},"NullableEpochMillis":{"type":["integer","null"],"format":"int64","description":"Time represented as milliseconds since the Unix epoch, or `null` when the\nfield has not been set.\n"},"Error":{"type":"object","required":["error"],"description":"Canonical error envelope. The error payload is nested under the `error`\nkey.\n","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","description":"The body of an error response. The `code` is a stable value clients can\nbranch on; the `message` is a human-readable explanation.\n","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable description of the error.\n"},"details":{"type":["object","null"],"description":"Additional structured information about the error, or `null` if no\nextra details are available.\n","additionalProperties":true}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. The enum enumerates every code that can be\nreturned by this API, including infrastructure-level 5xx codes, so\ngenerated clients can decode any `error.code` they receive.\n\nPossible enum values:\n- `INVALID_REQUEST`: the request body or a query parameter failed validation.\n- `MALFORMED_UUID`: a path parameter that should be a UUID is not one.\n- `UNAUTHORIZED`: the request could not be authenticated.\n- `API_KEY_MISSING`: no API key was supplied.\n- `API_KEY_INVALID`: the supplied API key does not match any known key.\n- `API_KEY_REVOKED`: the supplied API key has been revoked.\n- `PLAN_FEATURE_REQUIRED`: the account plan does not include a required feature.\n- `INSUFFICIENT_CREDITS`: the account does not have enough credits for this request.\n- `QUOTA_EXCEEDED`: a usage quota for the account has been exceeded.\n- `FILE_TOO_LARGE`: an uploaded or referenced file exceeds the allowed size.\n- `INVALID_CONTENT_TYPE`: a file's content type does not match its declared or expected type.\n- `UNSUPPORTED_ASSET_FORMAT`: a file's extension is not one of the supported asset formats.\n- `FORMAT_NOT_AVAILABLE`: the requested downloadable format does not exist for this asset.\n- `ASSET_NOT_FOUND`: the asset does not exist or does not belong to the account.\n- `ASSET_IN_USE`: the asset is referenced by an active execution and cannot be deleted.\n- `BATCH_TOO_LARGE`: the batch contains more items than the endpoint allows.\n- `CONCURRENT_EXECUTION_LIMIT_EXCEEDED`: the account has reached its limit on simultaneous submissions.\n- `PBR_FAILURE_CAP_EXCEEDED`: the account has too many recent failed PBR runs; new submissions are temporarily blocked.\n- `EXECUTION_NOT_FOUND`: the execution or batch does not exist or does not belong to the account.\n- `EXECUTION_IN_PROGRESS`: the request conflicts with an execution that is still running.\n- `RATE_LIMITED`: the API key's rate limit was exceeded.\n- `SERVICE_UNAVAILABLE`: a dependent service is temporarily unavailable; retry later.\n- `MAINTENANCE_MODE`: the API is temporarily unavailable for maintenance.\n- `INTERNAL_SERVER_ERROR`: an unexpected server error occurred.\n- `WORKFLOW_CREATION_FAILED`: the platform could not start the underlying workflow.\n- `WORKFLOW_API_ERROR`: the platform's workflow system returned an unexpected error.\n","enum":["INVALID_REQUEST","MALFORMED_UUID","UNAUTHORIZED","API_KEY_MISSING","API_KEY_INVALID","API_KEY_REVOKED","PLAN_FEATURE_REQUIRED","INSUFFICIENT_CREDITS","QUOTA_EXCEEDED","FILE_TOO_LARGE","INVALID_CONTENT_TYPE","UNSUPPORTED_ASSET_FORMAT","FORMAT_NOT_AVAILABLE","ASSET_NOT_FOUND","ASSET_IN_USE","BATCH_TOO_LARGE","CONCURRENT_EXECUTION_LIMIT_EXCEEDED","PBR_FAILURE_CAP_EXCEEDED","EXECUTION_NOT_FOUND","EXECUTION_IN_PROGRESS","RATE_LIMITED","SERVICE_UNAVAILABLE","MAINTENANCE_MODE","INTERNAL_SERVER_ERROR","WORKFLOW_CREATION_FAILED","WORKFLOW_API_ERROR"]}},"responses":{"Unauthorized":{"description":"The API key is missing, invalid, or revoked. Possible error codes:\n`API_KEY_MISSING`, `API_KEY_INVALID`, `API_KEY_REVOKED`, or `UNAUTHORIZED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BatchNotFound":{"description":"The batch does not exist or does not belong to your account (`EXECUTION_NOT_FOUND`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded (`RATE_LIMITED`). The response includes a `Retry-After`\nheader with the number of seconds to wait before retrying, plus the\n`X-RateLimit-*` headers. Those headers are also present on successful (2xx)\nresponses so a client can pace itself.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request burst capacity for the API key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current burst window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the burst capacity is fully restored.\n","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/batch/uploads/{upload_batch_id}":{"get":{"operationId":"getUploadBatch","tags":["assets"],"summary":"Retrieve an upload batch.","description":"Returns the state of an upload batch created with `POST /batch/uploads`,\nincluding the validation status of each file. Poll this endpoint until\n`state` reaches a terminal value (`COMPLETED`, `PARTIALLY_COMPLETED`, or\n`FAILED`), then use the ready asset IDs to submit a PBR batch.\n","parameters":[{"name":"upload_batch_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Upload batch status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUploadStatusResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/BatchNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Start uploading a single file.

> Creates an upload for one file. You provide the file name, size, and\
> optional content type; the response returns a presigned upload URL and the\
> exact headers you must send with the PUT.\
> \
> Upload the raw bytes directly to that URL, then poll\
> \`GET /uploads/{asset\_id}\` until the status is \`ready\`.\
> \
> The file is validated after upload. A file that fails validation becomes\
> \`rejected\` rather than \`ready\`. This endpoint itself only rejects\
> request-level problems, such as an unsupported file type or a size over\
> the limit. Images must be 4096 by 4096 pixels or smaller.\
> \
> Use \`POST /batch/uploads\` instead when you have 5 or more files to upload\
> at once.<br>

```json
{"openapi":"3.1.0","info":{"title":"Marso Studio API","version":"0.0.1"},"tags":[{"name":"assets","description":"Assets represent the files you upload and the material outputs the API\ngenerates. Use these endpoints to upload source files, list assets, fetch\nmetadata and download URLs, and delete assets you no longer need.\n"}],"servers":[{"url":"https://api.marso.ai/api/public/v1","description":"Production server."}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key minted from the Marso Studio dashboard. The value has the form\n`mxr_live_<entropy>` and is sent as a Bearer token in the `Authorization`\nheader.\n"}},"schemas":{"UploadRequest":{"type":"object","required":["filename","size_bytes"],"description":"Request body for `POST /uploads`. Describes the one file you want to\nupload.\n","properties":{"filename":{"type":"string","description":"The name of the file, including its extension. The extension selects\nthe asset type and must be one of `.usd`, `.glb`, `.jpg`, `.jpeg`, or\n`.png`.\n"},"content_type":{"type":"string","description":"The MIME type of the file. Optional: it is inferred from the file\nextension when omitted. When present it must match the extension.\n"},"size_bytes":{"type":"integer","format":"int64","description":"The exact size of the file in bytes. It is signed into the upload URL\nas `Content-Length`, so the bytes you PUT must match it exactly. Must\nbe at least 1, and no more than 5 MB for an image or 100 MB for a mesh.\n"}}},"UploadResponse":{"type":"object","required":["asset_id","upload_url"],"description":"Presigned upload URL and metadata returned by `POST /uploads`. There is no\nupload batch ID: a single upload is polled with\n`GET /uploads/{asset_id}`.\n","properties":{"asset_id":{"type":"string","description":"The ID of the pending asset created for this file. Use it to poll the\nupload and, once the status is `ready`, to submit the asset for PBR\ngeneration.\n"},"upload_url":{"type":"string","format":"uri","description":"Presigned S3 PUT URL. Upload the raw file bytes to this URL using the\nheaders provided in `headers`.\n"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Every header you must send verbatim with the PUT request. This includes\n`Content-Type`, `Content-Length`, and any `x-amz-meta-*` metadata\nheaders. All headers are bound into the signature, so omitting or\naltering any of them causes S3 to reject the upload.\n"}}},"Error":{"type":"object","required":["error"],"description":"Canonical error envelope. The error payload is nested under the `error`\nkey.\n","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","description":"The body of an error response. The `code` is a stable value clients can\nbranch on; the `message` is a human-readable explanation.\n","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable description of the error.\n"},"details":{"type":["object","null"],"description":"Additional structured information about the error, or `null` if no\nextra details are available.\n","additionalProperties":true}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. The enum enumerates every code that can be\nreturned by this API, including infrastructure-level 5xx codes, so\ngenerated clients can decode any `error.code` they receive.\n\nPossible enum values:\n- `INVALID_REQUEST`: the request body or a query parameter failed validation.\n- `MALFORMED_UUID`: a path parameter that should be a UUID is not one.\n- `UNAUTHORIZED`: the request could not be authenticated.\n- `API_KEY_MISSING`: no API key was supplied.\n- `API_KEY_INVALID`: the supplied API key does not match any known key.\n- `API_KEY_REVOKED`: the supplied API key has been revoked.\n- `PLAN_FEATURE_REQUIRED`: the account plan does not include a required feature.\n- `INSUFFICIENT_CREDITS`: the account does not have enough credits for this request.\n- `QUOTA_EXCEEDED`: a usage quota for the account has been exceeded.\n- `FILE_TOO_LARGE`: an uploaded or referenced file exceeds the allowed size.\n- `INVALID_CONTENT_TYPE`: a file's content type does not match its declared or expected type.\n- `UNSUPPORTED_ASSET_FORMAT`: a file's extension is not one of the supported asset formats.\n- `FORMAT_NOT_AVAILABLE`: the requested downloadable format does not exist for this asset.\n- `ASSET_NOT_FOUND`: the asset does not exist or does not belong to the account.\n- `ASSET_IN_USE`: the asset is referenced by an active execution and cannot be deleted.\n- `BATCH_TOO_LARGE`: the batch contains more items than the endpoint allows.\n- `CONCURRENT_EXECUTION_LIMIT_EXCEEDED`: the account has reached its limit on simultaneous submissions.\n- `PBR_FAILURE_CAP_EXCEEDED`: the account has too many recent failed PBR runs; new submissions are temporarily blocked.\n- `EXECUTION_NOT_FOUND`: the execution or batch does not exist or does not belong to the account.\n- `EXECUTION_IN_PROGRESS`: the request conflicts with an execution that is still running.\n- `RATE_LIMITED`: the API key's rate limit was exceeded.\n- `SERVICE_UNAVAILABLE`: a dependent service is temporarily unavailable; retry later.\n- `MAINTENANCE_MODE`: the API is temporarily unavailable for maintenance.\n- `INTERNAL_SERVER_ERROR`: an unexpected server error occurred.\n- `WORKFLOW_CREATION_FAILED`: the platform could not start the underlying workflow.\n- `WORKFLOW_API_ERROR`: the platform's workflow system returned an unexpected error.\n","enum":["INVALID_REQUEST","MALFORMED_UUID","UNAUTHORIZED","API_KEY_MISSING","API_KEY_INVALID","API_KEY_REVOKED","PLAN_FEATURE_REQUIRED","INSUFFICIENT_CREDITS","QUOTA_EXCEEDED","FILE_TOO_LARGE","INVALID_CONTENT_TYPE","UNSUPPORTED_ASSET_FORMAT","FORMAT_NOT_AVAILABLE","ASSET_NOT_FOUND","ASSET_IN_USE","BATCH_TOO_LARGE","CONCURRENT_EXECUTION_LIMIT_EXCEEDED","PBR_FAILURE_CAP_EXCEEDED","EXECUTION_NOT_FOUND","EXECUTION_IN_PROGRESS","RATE_LIMITED","SERVICE_UNAVAILABLE","MAINTENANCE_MODE","INTERNAL_SERVER_ERROR","WORKFLOW_CREATION_FAILED","WORKFLOW_API_ERROR"]}},"responses":{"Unauthorized":{"description":"The API key is missing, invalid, or revoked. Possible error codes:\n`API_KEY_MISSING`, `API_KEY_INVALID`, `API_KEY_REVOKED`, or `UNAUTHORIZED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded (`RATE_LIMITED`). The response includes a `Retry-After`\nheader with the number of seconds to wait before retrying, plus the\n`X-RateLimit-*` headers. Those headers are also present on successful (2xx)\nresponses so a client can pace itself.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request burst capacity for the API key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current burst window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the burst capacity is fully restored.\n","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/uploads":{"post":{"operationId":"createUpload","tags":["assets"],"summary":"Start uploading a single file.","description":"Creates an upload for one file. You provide the file name, size, and\noptional content type; the response returns a presigned upload URL and the\nexact headers you must send with the PUT.\n\nUpload the raw bytes directly to that URL, then poll\n`GET /uploads/{asset_id}` until the status is `ready`.\n\nThe file is validated after upload. A file that fails validation becomes\n`rejected` rather than `ready`. This endpoint itself only rejects\nrequest-level problems, such as an unsupported file type or a size over\nthe limit. Images must be 4096 by 4096 pixels or smaller.\n\nUse `POST /batch/uploads` instead when you have 5 or more files to upload\nat once.\n","requestBody":{"required":true,"description":"The file to upload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRequest"}}}},"responses":{"201":{"description":"The upload URL was created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}},"400":{"description":"The request is invalid. Possible error codes:\n\n- `UNSUPPORTED_ASSET_FORMAT`: the file extension is not one of `.usd`,\n  `.glb`, `.jpg`, `.jpeg`, or `.png`.\n- `INVALID_CONTENT_TYPE`: the `content_type` does not match the file\n  extension.\n- `FILE_TOO_LARGE`: `size_bytes` is outside the allowed range for the\n  file type.\n- `INVALID_REQUEST`: the body is malformed or the file name is missing.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Retrieve the status of a single upload.

> Returns the validation status of one file uploaded with \`POST /uploads\`.\
> Poll this endpoint until the status is \`ready\`, then submit the asset ID\
> for PBR generation.<br>

```json
{"openapi":"3.1.0","info":{"title":"Marso Studio API","version":"0.0.1"},"tags":[{"name":"assets","description":"Assets represent the files you upload and the material outputs the API\ngenerates. Use these endpoints to upload source files, list assets, fetch\nmetadata and download URLs, and delete assets you no longer need.\n"}],"servers":[{"url":"https://api.marso.ai/api/public/v1","description":"Production server."}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key minted from the Marso Studio dashboard. The value has the form\n`mxr_live_<entropy>` and is sent as a Bearer token in the `Authorization`\nheader.\n"}},"parameters":{"AssetId":{"name":"asset_id","in":"path","required":true,"description":"Unique identifier of the asset.","schema":{"type":"string","format":"uuid"}}},"schemas":{"UploadStatusResponse":{"type":"object","required":["asset_id","file_name","status","created_at"],"description":"The validation status of one file uploaded with `POST /uploads`.\n","properties":{"asset_id":{"type":"string","description":"The ID of the asset created for this file.\n"},"file_name":{"type":"string","description":"The file name as provided in the upload request.\n"},"status":{"type":"string","enum":["pending","ready","rejected"],"description":"The validation status of this file.\n\nPossible enum values:\n- `pending`: the file has not been validated yet.\n- `ready`: the file passed validation and can be used for PBR generation.\n- `rejected`: the file failed validation. The file must be uploaded again.\n"},"created_at":{"$ref":"#/components/schemas/EpochMillis"}}},"EpochMillis":{"type":"integer","format":"int64","description":"Time represented as milliseconds since the Unix epoch.\n"},"Error":{"type":"object","required":["error"],"description":"Canonical error envelope. The error payload is nested under the `error`\nkey.\n","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","description":"The body of an error response. The `code` is a stable value clients can\nbranch on; the `message` is a human-readable explanation.\n","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable description of the error.\n"},"details":{"type":["object","null"],"description":"Additional structured information about the error, or `null` if no\nextra details are available.\n","additionalProperties":true}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. The enum enumerates every code that can be\nreturned by this API, including infrastructure-level 5xx codes, so\ngenerated clients can decode any `error.code` they receive.\n\nPossible enum values:\n- `INVALID_REQUEST`: the request body or a query parameter failed validation.\n- `MALFORMED_UUID`: a path parameter that should be a UUID is not one.\n- `UNAUTHORIZED`: the request could not be authenticated.\n- `API_KEY_MISSING`: no API key was supplied.\n- `API_KEY_INVALID`: the supplied API key does not match any known key.\n- `API_KEY_REVOKED`: the supplied API key has been revoked.\n- `PLAN_FEATURE_REQUIRED`: the account plan does not include a required feature.\n- `INSUFFICIENT_CREDITS`: the account does not have enough credits for this request.\n- `QUOTA_EXCEEDED`: a usage quota for the account has been exceeded.\n- `FILE_TOO_LARGE`: an uploaded or referenced file exceeds the allowed size.\n- `INVALID_CONTENT_TYPE`: a file's content type does not match its declared or expected type.\n- `UNSUPPORTED_ASSET_FORMAT`: a file's extension is not one of the supported asset formats.\n- `FORMAT_NOT_AVAILABLE`: the requested downloadable format does not exist for this asset.\n- `ASSET_NOT_FOUND`: the asset does not exist or does not belong to the account.\n- `ASSET_IN_USE`: the asset is referenced by an active execution and cannot be deleted.\n- `BATCH_TOO_LARGE`: the batch contains more items than the endpoint allows.\n- `CONCURRENT_EXECUTION_LIMIT_EXCEEDED`: the account has reached its limit on simultaneous submissions.\n- `PBR_FAILURE_CAP_EXCEEDED`: the account has too many recent failed PBR runs; new submissions are temporarily blocked.\n- `EXECUTION_NOT_FOUND`: the execution or batch does not exist or does not belong to the account.\n- `EXECUTION_IN_PROGRESS`: the request conflicts with an execution that is still running.\n- `RATE_LIMITED`: the API key's rate limit was exceeded.\n- `SERVICE_UNAVAILABLE`: a dependent service is temporarily unavailable; retry later.\n- `MAINTENANCE_MODE`: the API is temporarily unavailable for maintenance.\n- `INTERNAL_SERVER_ERROR`: an unexpected server error occurred.\n- `WORKFLOW_CREATION_FAILED`: the platform could not start the underlying workflow.\n- `WORKFLOW_API_ERROR`: the platform's workflow system returned an unexpected error.\n","enum":["INVALID_REQUEST","MALFORMED_UUID","UNAUTHORIZED","API_KEY_MISSING","API_KEY_INVALID","API_KEY_REVOKED","PLAN_FEATURE_REQUIRED","INSUFFICIENT_CREDITS","QUOTA_EXCEEDED","FILE_TOO_LARGE","INVALID_CONTENT_TYPE","UNSUPPORTED_ASSET_FORMAT","FORMAT_NOT_AVAILABLE","ASSET_NOT_FOUND","ASSET_IN_USE","BATCH_TOO_LARGE","CONCURRENT_EXECUTION_LIMIT_EXCEEDED","PBR_FAILURE_CAP_EXCEEDED","EXECUTION_NOT_FOUND","EXECUTION_IN_PROGRESS","RATE_LIMITED","SERVICE_UNAVAILABLE","MAINTENANCE_MODE","INTERNAL_SERVER_ERROR","WORKFLOW_CREATION_FAILED","WORKFLOW_API_ERROR"]}},"responses":{"Unauthorized":{"description":"The API key is missing, invalid, or revoked. Possible error codes:\n`API_KEY_MISSING`, `API_KEY_INVALID`, `API_KEY_REVOKED`, or `UNAUTHORIZED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded (`RATE_LIMITED`). The response includes a `Retry-After`\nheader with the number of seconds to wait before retrying, plus the\n`X-RateLimit-*` headers. Those headers are also present on successful (2xx)\nresponses so a client can pace itself.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request burst capacity for the API key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current burst window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the burst capacity is fully restored.\n","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/uploads/{asset_id}":{"get":{"operationId":"getUpload","tags":["assets"],"summary":"Retrieve the status of a single upload.","description":"Returns the validation status of one file uploaded with `POST /uploads`.\nPoll this endpoint until the status is `ready`, then submit the asset ID\nfor PBR generation.\n","parameters":[{"$ref":"#/components/parameters/AssetId"}],"responses":{"200":{"description":"The status of the upload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadStatusResponse"}}}},"400":{"description":"The asset ID is malformed (`MALFORMED_UUID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"The asset does not exist or does not belong to the authenticated\naccount (`ASSET_NOT_FOUND`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List your PBR assets, grouped by project.

> Returns your PBR assets grouped by the project that produced them.\
> \
> Only PBR assets are returned, not the image or 3D model you uploaded to\
> produce them. Assets created through this API belong to no project and\
> arrive in \`ungrouped\_assets\`.\
> \
> Each entry's \`id\` can be passed straight to \`POST /assets/download\` to get\
> download URLs for its 3D model and all of its texture maps, and its\
> \`file\_name\` is the source 3D model's name.\
> \
> \`type\` and \`group\_by\` each have one supported value today (\`pbr\` and\
> \`projects\`) and both default to it, so a bare request returns the grouped\
> listing. Any other value is rejected rather than ignored.<br>

```json
{"openapi":"3.1.0","info":{"title":"Marso Studio API","version":"0.0.1"},"tags":[{"name":"assets","description":"Assets represent the files you upload and the material outputs the API\ngenerates. Use these endpoints to upload source files, list assets, fetch\nmetadata and download URLs, and delete assets you no longer need.\n"}],"servers":[{"url":"https://api.marso.ai/api/public/v1","description":"Production server."}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key minted from the Marso Studio dashboard. The value has the form\n`mxr_live_<entropy>` and is sent as a Bearer token in the `Authorization`\nheader.\n"}},"parameters":{"Cursor":{"name":"cursor","in":"query","required":false,"description":"Opaque pagination cursor returned by the previous page as `next_cursor`.\nOmit on the first request.\n","schema":{"type":"string"}}},"schemas":{"PublicGroupedAssetListResponse":{"type":"object","required":["group_by","data","pagination"],"description":"Response from `GET /assets`: your PBR assets grouped by the project that\nproduced them, then the ones belonging to no project.\n\n`data` and `ungrouped_assets` form ONE ordered sequence under a single\ncursor. Every group first, then the ungrouped assets. Just follow\n`pagination.next_cursor` until `has_more` is false.\n\nA cursor is a position in that one sequence, and what it points at differs\neither side of the boundary between the two parts. Treat it as opaque and\nshort-lived: echo the one you were just given, and start a fresh traversal\nrather than storing a cursor and replaying it later.\n","properties":{"group_by":{"type":"string","enum":["projects"],"description":"How the response is grouped, echoing the request.\n"},"data":{"type":"array","description":"Project groups, newest project first.\n","items":{"$ref":"#/components/schemas/PublicAssetGroupItem"}},"ungrouped_assets":{"type":"array","description":"PBR assets that belong to no project, which is where every asset\ncreated through this API arrives. Absent while the response is still\npaging through groups.\n","items":{"$ref":"#/components/schemas/PublicGroupedAssetItem"}},"errors":{"type":"array","description":"Entries on this page that could be served only partially. The entry itself is\nstill returned, in `data` or `ungrouped_assets`, with the affected field empty.\nAbsent when everything on the page resolved.\n\nThis exists so a partial result is visible rather than silent. An entry whose\nthumbnail URL could not be signed arrives with an empty `thumbnail_url` and a\nrow here, which is what lets you tell \"this material has no preview\" from \"we\ncould not give you its preview\". One such entry never costs you the page.\n","items":{"$ref":"#/components/schemas/PublicAssetErrorItem"}},"pagination":{"$ref":"#/components/schemas/PublicPagination"}}},"PublicAssetGroupItem":{"type":"object","required":["group_id","assets"],"description":"One project and the PBR assets it produced.\n","properties":{"group_id":{"type":"string","description":"Identifier of the project.\n"},"name":{"type":"string","description":"Name of the project.\n"},"thumbnail_url":{"type":"string","format":"uri","description":"Presigned URL for the project's thumbnail. Valid for about 1 minute.\nAbsent when the project has no thumbnail resolved yet.\n"},"thumbnail_url_expires_at":{"$ref":"#/components/schemas/EpochMillis"},"assets":{"type":"array","description":"The project's PBR assets. This list is complete: every asset the project\nholds is included, however long the derivation history behind it.\n","items":{"$ref":"#/components/schemas/PublicGroupedAssetItem"}}}},"EpochMillis":{"type":"integer","format":"int64","description":"Time represented as milliseconds since the Unix epoch.\n"},"PublicGroupedAssetItem":{"type":"object","required":["id","name","model_version"],"description":"One PBR asset in a grouped listing.\n","properties":{"id":{"type":"string","description":"Identifier of the PBR asset. Pass this to `POST /assets/download` to get\ndownload URLs for the 3D model and all of its texture maps.\n"},"name":{"type":"string","description":"The display name from the asset library. If the library has no stored\nname, this is the source file name without its extension.\n"},"file_name":{"type":"string","description":"File name of the SOURCE 3D MODEL this PBR asset was generated from.\n"},"thumbnail_url":{"type":"string","format":"uri","description":"Presigned URL for the PBR asset's thumbnail. Valid for about 1 minute.\n"},"thumbnail_url_expires_at":{"$ref":"#/components/schemas/EpochMillis"},"model_version":{"type":"string","description":"The img2mat model that produced this PBR asset, for example\n`M-XR I2M v1.2`. A PBR asset created before the platform recorded the\nmodel reports `M-XR I2M v1.0`, the first release. Always present.\n"}}},"PublicAssetErrorItem":{"type":"object","required":["asset_id","code","message"],"description":"One entry a listing could serve only partially.\n","properties":{"asset_id":{"type":"string","description":"The ID of the entry this error belongs to. It matches the `id` of an entry in\nthe same response, so the two can be joined.\n"},"file_name":{"type":"string","description":"That entry's file name, so the error is legible without looking the ID up.\nAbsent only when the entry carries no name of its own.\n"},"code":{"type":"string","enum":["THUMBNAIL_URL_UNAVAILABLE"],"description":"Possible enum values:\n\n`THUMBNAIL_URL_UNAVAILABLE`: the entry's thumbnail URL could not be signed.\nThe entry is present with an empty `thumbnail_url`, and everything else about\nit is complete. Worth retrying.\n"},"message":{"type":"string","description":"Human-readable explanation.\n"}}},"PublicPagination":{"type":"object","required":["has_more"],"description":"Cursor pagination envelope.\n","properties":{"next_cursor":{"type":"string","description":"Opaque cursor for the next page. Absent when the result set is\nexhausted. Echo it back verbatim as `cursor`.\n"},"has_more":{"type":"boolean","description":"Whether another page follows. Always agrees with the presence of\n`next_cursor`.\n"}}},"Error":{"type":"object","required":["error"],"description":"Canonical error envelope. The error payload is nested under the `error`\nkey.\n","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","description":"The body of an error response. The `code` is a stable value clients can\nbranch on; the `message` is a human-readable explanation.\n","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable description of the error.\n"},"details":{"type":["object","null"],"description":"Additional structured information about the error, or `null` if no\nextra details are available.\n","additionalProperties":true}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. The enum enumerates every code that can be\nreturned by this API, including infrastructure-level 5xx codes, so\ngenerated clients can decode any `error.code` they receive.\n\nPossible enum values:\n- `INVALID_REQUEST`: the request body or a query parameter failed validation.\n- `MALFORMED_UUID`: a path parameter that should be a UUID is not one.\n- `UNAUTHORIZED`: the request could not be authenticated.\n- `API_KEY_MISSING`: no API key was supplied.\n- `API_KEY_INVALID`: the supplied API key does not match any known key.\n- `API_KEY_REVOKED`: the supplied API key has been revoked.\n- `PLAN_FEATURE_REQUIRED`: the account plan does not include a required feature.\n- `INSUFFICIENT_CREDITS`: the account does not have enough credits for this request.\n- `QUOTA_EXCEEDED`: a usage quota for the account has been exceeded.\n- `FILE_TOO_LARGE`: an uploaded or referenced file exceeds the allowed size.\n- `INVALID_CONTENT_TYPE`: a file's content type does not match its declared or expected type.\n- `UNSUPPORTED_ASSET_FORMAT`: a file's extension is not one of the supported asset formats.\n- `FORMAT_NOT_AVAILABLE`: the requested downloadable format does not exist for this asset.\n- `ASSET_NOT_FOUND`: the asset does not exist or does not belong to the account.\n- `ASSET_IN_USE`: the asset is referenced by an active execution and cannot be deleted.\n- `BATCH_TOO_LARGE`: the batch contains more items than the endpoint allows.\n- `CONCURRENT_EXECUTION_LIMIT_EXCEEDED`: the account has reached its limit on simultaneous submissions.\n- `PBR_FAILURE_CAP_EXCEEDED`: the account has too many recent failed PBR runs; new submissions are temporarily blocked.\n- `EXECUTION_NOT_FOUND`: the execution or batch does not exist or does not belong to the account.\n- `EXECUTION_IN_PROGRESS`: the request conflicts with an execution that is still running.\n- `RATE_LIMITED`: the API key's rate limit was exceeded.\n- `SERVICE_UNAVAILABLE`: a dependent service is temporarily unavailable; retry later.\n- `MAINTENANCE_MODE`: the API is temporarily unavailable for maintenance.\n- `INTERNAL_SERVER_ERROR`: an unexpected server error occurred.\n- `WORKFLOW_CREATION_FAILED`: the platform could not start the underlying workflow.\n- `WORKFLOW_API_ERROR`: the platform's workflow system returned an unexpected error.\n","enum":["INVALID_REQUEST","MALFORMED_UUID","UNAUTHORIZED","API_KEY_MISSING","API_KEY_INVALID","API_KEY_REVOKED","PLAN_FEATURE_REQUIRED","INSUFFICIENT_CREDITS","QUOTA_EXCEEDED","FILE_TOO_LARGE","INVALID_CONTENT_TYPE","UNSUPPORTED_ASSET_FORMAT","FORMAT_NOT_AVAILABLE","ASSET_NOT_FOUND","ASSET_IN_USE","BATCH_TOO_LARGE","CONCURRENT_EXECUTION_LIMIT_EXCEEDED","PBR_FAILURE_CAP_EXCEEDED","EXECUTION_NOT_FOUND","EXECUTION_IN_PROGRESS","RATE_LIMITED","SERVICE_UNAVAILABLE","MAINTENANCE_MODE","INTERNAL_SERVER_ERROR","WORKFLOW_CREATION_FAILED","WORKFLOW_API_ERROR"]},"PlanFeatureRequiredError":{"type":"object","required":["error"],"description":"The error envelope for a missing subscription feature.","properties":{"error":{"type":"object","required":["code","message","details"],"description":"The missing-feature error body.","properties":{"code":{"type":"string","const":"PLAN_FEATURE_REQUIRED","description":"Stable error code for a missing subscription feature."},"message":{"type":"string","description":"Human-readable description of the error."},"details":{"$ref":"#/components/schemas/PlanFeatureRequiredErrorDetails"}}}}},"PlanFeatureRequiredErrorDetails":{"type":"object","required":["feature","upgrade_plan_codes"],"description":"The missing feature and the active plans that provide it.","properties":{"feature":{"$ref":"#/components/schemas/SubscriptionFeature"},"upgrade_plan_codes":{"type":"array","description":"Active plans available for purchase that provide the missing\nfeature. The array uses plan class and opaque code order.\n","items":{"$ref":"#/components/schemas/SubscriptionPlanCode"}}}},"SubscriptionFeature":{"type":"string","description":"Product capability available to the account.\n\nPossible enum values:\n- `commercial_license`: commercial-use rights.\n- `api_pbr`: PBR generation through the Public Platform API.\n- `api_asset_download`: asset downloads through the Public Platform API.\n- `custom_polycount`: custom polygon counts for 3D generation.\n- `image_edit`: image editing.\n- `multiview`: multiview generation.\n","enum":["commercial_license","api_pbr","api_asset_download","custom_polycount","image_edit","multiview"]},"SubscriptionPlanCode":{"type":"string","pattern":"^plan_[A-Za-z0-9]{7,64}$","description":"Opaque and immutable code for a subscription plan. Use the plan\ncatalogue for its current display name. The code must start with\n`plan_`, followed by 7 to 64 ASCII letters or digits.\n"}},"responses":{"Unauthorized":{"description":"The API key is missing, invalid, or revoked. Possible error codes:\n`API_KEY_MISSING`, `API_KEY_INVALID`, `API_KEY_REVOKED`, or `UNAUTHORIZED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PlanFeatureRequired":{"description":"The account does not have the plan feature required by this operation\n(`PLAN_FEATURE_REQUIRED`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanFeatureRequiredError"}}}},"RateLimited":{"description":"Rate limit exceeded (`RATE_LIMITED`). The response includes a `Retry-After`\nheader with the number of seconds to wait before retrying, plus the\n`X-RateLimit-*` headers. Those headers are also present on successful (2xx)\nresponses so a client can pace itself.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request burst capacity for the API key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current burst window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the burst capacity is fully restored.\n","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/assets":{"get":{"operationId":"listAssets","tags":["assets"],"summary":"List your PBR assets, grouped by project.","description":"Returns your PBR assets grouped by the project that produced them.\n\nOnly PBR assets are returned, not the image or 3D model you uploaded to\nproduce them. Assets created through this API belong to no project and\narrive in `ungrouped_assets`.\n\nEach entry's `id` can be passed straight to `POST /assets/download` to get\ndownload URLs for its 3D model and all of its texture maps, and its\n`file_name` is the source 3D model's name.\n\n`type` and `group_by` each have one supported value today (`pbr` and\n`projects`) and both default to it, so a bare request returns the grouped\nlisting. Any other value is rejected rather than ignored.\n","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size. Defaults to 25 and is clamped to a maximum of 25. Non-positive\nor non-numeric values fall back to the default. This endpoint does not use\nthe 50/200 defaults the other list endpoints do, because a page here is\nlarger than the number suggests.\n\nWhat the number counts depends on which part of the listing you are in.\nWhile groups are still being returned it counts PROJECTS: one page carries\nup to 25 projects and every PBR asset each of them produced, so a single\npage can hold many more than 25 entries. Once the groups are exhausted it\ncounts assets, and one page carries up to 25 entries in `ungrouped_assets`.\n","schema":{"type":"integer","minimum":1,"default":25}},{"$ref":"#/components/parameters/Cursor"},{"name":"type","in":"query","required":false,"description":"Which assets to return. `pbr` is the only supported value today and is\nthe default, so you can omit this.\n","schema":{"type":"string","enum":["pbr"],"default":"pbr"}},{"name":"group_by","in":"query","required":false,"description":"How to group the response. `projects` is the only supported value today\nand is the default, so you can omit this.\n","schema":{"type":"string","enum":["projects"],"default":"projects"}}],"responses":{"200":{"description":"A page of your PBR assets grouped by project.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicGroupedAssetListResponse"}}}},"400":{"description":"Invalid cursor, or an unsupported `type` / `group_by` value\n(`INVALID_REQUEST`). The message names the parameter at fault and the\nvalue that IS supported.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/PlanFeatureRequired"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Get download URLs for many assets.

> Returns presigned download URLs for each asset ID you submit, so you can\
> collect a whole set of assets in one call instead of one\
> \`GET /assets/{asset\_id}\` per asset. URLs are valid for about 2 minutes, so\
> start each download as soon as you get the response.\
> \
> Each entry lists \*\*every file\*\* for the ID you submitted. A PBR material\
> resolves to its 3D model plus all of its texture maps (and its USDZ where\
> one exists), so a single request gives you everything needed to use the\
> material. A plain asset resolves to one file.\
> \
> The IDs travel in the request body, which is why this is a \`POST\`: it\
> removes the URL-length limit on how many you can ask for. The call\
> changes nothing and is safe to retry, but responses are not cacheable.\
> \
> Unrecognised IDs do not fail the call. Every ID you submit comes back in\
> exactly one of \`assets\` (resolved, with a URL) or \`errors\` (with a\
> reason), so a single stale ID never costs you the rest of the batch. An\
> asset that belongs to another account reports \`ASSET\_NOT\_FOUND\`, the same\
> as one that does not exist.\
> \
> Repeated IDs collapse to a single entry, keeping the order you sent them\
> in.<br>

```json
{"openapi":"3.1.0","info":{"title":"Marso Studio API","version":"0.0.1"},"tags":[{"name":"assets","description":"Assets represent the files you upload and the material outputs the API\ngenerates. Use these endpoints to upload source files, list assets, fetch\nmetadata and download URLs, and delete assets you no longer need.\n"}],"servers":[{"url":"https://api.marso.ai/api/public/v1","description":"Production server."}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key minted from the Marso Studio dashboard. The value has the form\n`mxr_live_<entropy>` and is sent as a Bearer token in the `Authorization`\nheader.\n"}},"schemas":{"PublicAssetBulkDownloadRequest":{"type":"object","required":["asset_ids"],"description":"Request body for `POST /assets/download`. The asset IDs you want download\nURLs for.\n","properties":{"asset_ids":{"type":"array","minItems":1,"maxItems":100,"description":"Asset IDs to mint download URLs for. Repeated IDs collapse to a single\nentry. Every ID must be a well-formed asset ID. One malformed value\nrejects the whole request.\n","items":{"type":"string","format":"uuid"}}}},"PublicAssetBulkDownloadResponse":{"type":"object","required":["assets","errors"],"description":"Response from `POST /assets/download`. Together `assets` and `errors`\naccount for every distinct asset ID submitted.\n","properties":{"assets":{"type":"array","description":"Resolved assets with download URLs, in the order the IDs were\nsubmitted (after duplicates are collapsed).\n","items":{"$ref":"#/components/schemas/PublicAssetBulkDownloadItem"}},"errors":{"type":"array","description":"One entry per submitted ID that could not be resolved. Empty when\nevery ID resolved.\n","items":{"$ref":"#/components/schemas/PublicAssetBulkDownloadError"}}}},"PublicAssetBulkDownloadItem":{"type":"object","required":["asset_id","files"],"description":"One resolved asset ID and every file it resolves to. A PBR material\nresolves to its 3D model plus all of its texture maps (and its USDZ where\none exists), so one request collects a whole material rather than one\nrequest per file. A plain asset resolves to a single file.\n","properties":{"asset_id":{"type":"string","description":"The submitted asset ID this entry belongs to.\n"},"type":{"type":"string","description":"Type of the requested asset.\n"},"file_name":{"type":"string","description":"Name for the entry. For a PBR material this is the source 3D model's\nfile name.\n"},"files":{"type":"array","minItems":1,"description":"Every downloadable file for this entry, each with its own presigned\nURL. Never empty. An ID that resolves to no file is reported in\n`errors` instead.\n","items":{"$ref":"#/components/schemas/PublicAssetBulkDownloadFile"}}}},"PublicAssetBulkDownloadFile":{"type":"object","required":["asset_id","role","download_url","expires_at"],"description":"One downloadable object within a resolved entry.\n","properties":{"asset_id":{"type":"string","description":"Unique identifier of this file's asset.\n"},"role":{"type":"string","enum":["source","texture","usdz","asset"],"description":"The part this file plays in its entry. A role never tells you the file's\nkind; read the sibling `type` field for that.\n\nPossible enum values:\n- `source`: the asset the PBR material was generated from: a 3D mesh for\n  a mesh batch, an image for an image batch.\n- `texture`: a PBR map. The `pass` field names which one.\n- `usdz`: the USDZ variant of the model.\n- `asset`: a plain (non-PBR) asset, which yields exactly one file.\n"},"pass":{"type":"string","description":"For `texture` files, which PBR map this is (for example `albedo`,\n`metallic`, `roughness`, `normal`).\n"},"type":{"type":"string","description":"Physical type of this file's asset.\n"},"size_bytes":{"type":"integer","format":"int64","description":"Size of this file in bytes.\n"},"download_url":{"type":"string","format":"uri","description":"Presigned URL for downloading this file. Valid for about 2 minutes.\n"},"expires_at":{"$ref":"#/components/schemas/EpochMillis"}}},"EpochMillis":{"type":"integer","format":"int64","description":"Time represented as milliseconds since the Unix epoch.\n"},"PublicAssetBulkDownloadError":{"type":"object","required":["asset_id","code","message"],"description":"One asset ID that produced no download URL.\n","properties":{"asset_id":{"type":"string","description":"The submitted asset ID this failure belongs to.\n"},"file_name":{"type":"string","description":"The asset's own file name, so the failure is legible without looking the ID\nup. Present when the asset resolved far enough to have a name, and absent\nfor `ASSET_NOT_FOUND`, where nothing was resolved.\n"},"code":{"type":"string","enum":["ASSET_NOT_FOUND","DOWNLOAD_URL_UNAVAILABLE"],"description":"`ASSET_NOT_FOUND`: no such asset, or it belongs to another account.\nThe two are reported identically on purpose.\n`DOWNLOAD_URL_UNAVAILABLE`: the asset is yours but a URL could not be\nminted for it this time. Worth retrying.\n"},"message":{"type":"string","description":"Human-readable explanation.\n"}}},"Error":{"type":"object","required":["error"],"description":"Canonical error envelope. The error payload is nested under the `error`\nkey.\n","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","description":"The body of an error response. The `code` is a stable value clients can\nbranch on; the `message` is a human-readable explanation.\n","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable description of the error.\n"},"details":{"type":["object","null"],"description":"Additional structured information about the error, or `null` if no\nextra details are available.\n","additionalProperties":true}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. The enum enumerates every code that can be\nreturned by this API, including infrastructure-level 5xx codes, so\ngenerated clients can decode any `error.code` they receive.\n\nPossible enum values:\n- `INVALID_REQUEST`: the request body or a query parameter failed validation.\n- `MALFORMED_UUID`: a path parameter that should be a UUID is not one.\n- `UNAUTHORIZED`: the request could not be authenticated.\n- `API_KEY_MISSING`: no API key was supplied.\n- `API_KEY_INVALID`: the supplied API key does not match any known key.\n- `API_KEY_REVOKED`: the supplied API key has been revoked.\n- `PLAN_FEATURE_REQUIRED`: the account plan does not include a required feature.\n- `INSUFFICIENT_CREDITS`: the account does not have enough credits for this request.\n- `QUOTA_EXCEEDED`: a usage quota for the account has been exceeded.\n- `FILE_TOO_LARGE`: an uploaded or referenced file exceeds the allowed size.\n- `INVALID_CONTENT_TYPE`: a file's content type does not match its declared or expected type.\n- `UNSUPPORTED_ASSET_FORMAT`: a file's extension is not one of the supported asset formats.\n- `FORMAT_NOT_AVAILABLE`: the requested downloadable format does not exist for this asset.\n- `ASSET_NOT_FOUND`: the asset does not exist or does not belong to the account.\n- `ASSET_IN_USE`: the asset is referenced by an active execution and cannot be deleted.\n- `BATCH_TOO_LARGE`: the batch contains more items than the endpoint allows.\n- `CONCURRENT_EXECUTION_LIMIT_EXCEEDED`: the account has reached its limit on simultaneous submissions.\n- `PBR_FAILURE_CAP_EXCEEDED`: the account has too many recent failed PBR runs; new submissions are temporarily blocked.\n- `EXECUTION_NOT_FOUND`: the execution or batch does not exist or does not belong to the account.\n- `EXECUTION_IN_PROGRESS`: the request conflicts with an execution that is still running.\n- `RATE_LIMITED`: the API key's rate limit was exceeded.\n- `SERVICE_UNAVAILABLE`: a dependent service is temporarily unavailable; retry later.\n- `MAINTENANCE_MODE`: the API is temporarily unavailable for maintenance.\n- `INTERNAL_SERVER_ERROR`: an unexpected server error occurred.\n- `WORKFLOW_CREATION_FAILED`: the platform could not start the underlying workflow.\n- `WORKFLOW_API_ERROR`: the platform's workflow system returned an unexpected error.\n","enum":["INVALID_REQUEST","MALFORMED_UUID","UNAUTHORIZED","API_KEY_MISSING","API_KEY_INVALID","API_KEY_REVOKED","PLAN_FEATURE_REQUIRED","INSUFFICIENT_CREDITS","QUOTA_EXCEEDED","FILE_TOO_LARGE","INVALID_CONTENT_TYPE","UNSUPPORTED_ASSET_FORMAT","FORMAT_NOT_AVAILABLE","ASSET_NOT_FOUND","ASSET_IN_USE","BATCH_TOO_LARGE","CONCURRENT_EXECUTION_LIMIT_EXCEEDED","PBR_FAILURE_CAP_EXCEEDED","EXECUTION_NOT_FOUND","EXECUTION_IN_PROGRESS","RATE_LIMITED","SERVICE_UNAVAILABLE","MAINTENANCE_MODE","INTERNAL_SERVER_ERROR","WORKFLOW_CREATION_FAILED","WORKFLOW_API_ERROR"]},"PlanFeatureRequiredError":{"type":"object","required":["error"],"description":"The error envelope for a missing subscription feature.","properties":{"error":{"type":"object","required":["code","message","details"],"description":"The missing-feature error body.","properties":{"code":{"type":"string","const":"PLAN_FEATURE_REQUIRED","description":"Stable error code for a missing subscription feature."},"message":{"type":"string","description":"Human-readable description of the error."},"details":{"$ref":"#/components/schemas/PlanFeatureRequiredErrorDetails"}}}}},"PlanFeatureRequiredErrorDetails":{"type":"object","required":["feature","upgrade_plan_codes"],"description":"The missing feature and the active plans that provide it.","properties":{"feature":{"$ref":"#/components/schemas/SubscriptionFeature"},"upgrade_plan_codes":{"type":"array","description":"Active plans available for purchase that provide the missing\nfeature. The array uses plan class and opaque code order.\n","items":{"$ref":"#/components/schemas/SubscriptionPlanCode"}}}},"SubscriptionFeature":{"type":"string","description":"Product capability available to the account.\n\nPossible enum values:\n- `commercial_license`: commercial-use rights.\n- `api_pbr`: PBR generation through the Public Platform API.\n- `api_asset_download`: asset downloads through the Public Platform API.\n- `custom_polycount`: custom polygon counts for 3D generation.\n- `image_edit`: image editing.\n- `multiview`: multiview generation.\n","enum":["commercial_license","api_pbr","api_asset_download","custom_polycount","image_edit","multiview"]},"SubscriptionPlanCode":{"type":"string","pattern":"^plan_[A-Za-z0-9]{7,64}$","description":"Opaque and immutable code for a subscription plan. Use the plan\ncatalogue for its current display name. The code must start with\n`plan_`, followed by 7 to 64 ASCII letters or digits.\n"}},"responses":{"Unauthorized":{"description":"The API key is missing, invalid, or revoked. Possible error codes:\n`API_KEY_MISSING`, `API_KEY_INVALID`, `API_KEY_REVOKED`, or `UNAUTHORIZED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PlanFeatureRequired":{"description":"The account does not have the plan feature required by this operation\n(`PLAN_FEATURE_REQUIRED`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanFeatureRequiredError"}}}},"RateLimited":{"description":"Rate limit exceeded (`RATE_LIMITED`). The response includes a `Retry-After`\nheader with the number of seconds to wait before retrying, plus the\n`X-RateLimit-*` headers. Those headers are also present on successful (2xx)\nresponses so a client can pace itself.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request burst capacity for the API key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current burst window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the burst capacity is fully restored.\n","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/assets/download":{"post":{"operationId":"bulkDownloadAssets","tags":["assets"],"summary":"Get download URLs for many assets.","description":"Returns presigned download URLs for each asset ID you submit, so you can\ncollect a whole set of assets in one call instead of one\n`GET /assets/{asset_id}` per asset. URLs are valid for about 2 minutes, so\nstart each download as soon as you get the response.\n\nEach entry lists **every file** for the ID you submitted. A PBR material\nresolves to its 3D model plus all of its texture maps (and its USDZ where\none exists), so a single request gives you everything needed to use the\nmaterial. A plain asset resolves to one file.\n\nThe IDs travel in the request body, which is why this is a `POST`: it\nremoves the URL-length limit on how many you can ask for. The call\nchanges nothing and is safe to retry, but responses are not cacheable.\n\nUnrecognised IDs do not fail the call. Every ID you submit comes back in\nexactly one of `assets` (resolved, with a URL) or `errors` (with a\nreason), so a single stale ID never costs you the rest of the batch. An\nasset that belongs to another account reports `ASSET_NOT_FOUND`, the same\nas one that does not exist.\n\nRepeated IDs collapse to a single entry, keeping the order you sent them\nin.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAssetBulkDownloadRequest"}}}},"responses":{"200":{"description":"Download URLs for the resolved assets, plus a reason for each ID that\ncould not be resolved.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAssetBulkDownloadResponse"}}}},"400":{"description":"`asset_ids` was missing or empty, contained a malformed asset ID\n(`MALFORMED_UUID`), or exceeded the maximum length\n(`INVALID_REQUEST`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/PlanFeatureRequired"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Retrieve an asset.

> Returns the metadata for a single asset, plus a fresh presigned download\
> URL valid for about 1 minute. For PBR material assets, the response\
> includes the generated mesh, material, and texture components.<br>

```json
{"openapi":"3.1.0","info":{"title":"Marso Studio API","version":"0.0.1"},"tags":[{"name":"assets","description":"Assets represent the files you upload and the material outputs the API\ngenerates. Use these endpoints to upload source files, list assets, fetch\nmetadata and download URLs, and delete assets you no longer need.\n"}],"servers":[{"url":"https://api.marso.ai/api/public/v1","description":"Production server."}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key minted from the Marso Studio dashboard. The value has the form\n`mxr_live_<entropy>` and is sent as a Bearer token in the `Authorization`\nheader.\n"}},"parameters":{"AssetId":{"name":"asset_id","in":"path","required":true,"description":"Unique identifier of the asset.","schema":{"type":"string","format":"uuid"}}},"schemas":{"PublicAssetDetailResponse":{"type":"object","required":["asset_id","name","type","size_bytes","created_at","download_url","expires_at"],"description":"Response from `GET /assets/{asset_id}` for a regular asset. Contains the\nasset metadata and a presigned download URL.\n","properties":{"asset_id":{"type":"string","description":"Unique identifier of the asset.\n"},"name":{"type":"string","description":"The display name from the asset library. If the library has no stored\nname, this is the file name without its extension.\n"},"type":{"type":"string","description":"Physical type of the asset.\n"},"size_bytes":{"type":"integer","format":"int64","description":"Size of the asset in bytes.\n"},"created_at":{"$ref":"#/components/schemas/EpochMillis"},"download_url":{"type":"string","format":"uri","description":"Presigned URL for downloading the asset. Valid for about 1 minute.\n"},"expires_at":{"$ref":"#/components/schemas/EpochMillis"}}},"EpochMillis":{"type":"integer","format":"int64","description":"Time represented as milliseconds since the Unix epoch.\n"},"PublicPBRMaterialDetailResponse":{"type":"object","required":["asset_id","name","type","created_at","url","source_execution_id","model_version","material","source","textures"],"description":"Response from `GET /assets/{asset_id}` for a PBR material asset.\nContains the material metadata, the asset the material was generated from,\nand the generated texture components.\n","properties":{"asset_id":{"type":"string","description":"Unique identifier of the PBR material asset.\n"},"name":{"type":"string","description":"The display name from the asset library. If the library has no stored\nname, this is the source file name without its extension.\n"},"type":{"type":"string","enum":["PBR_MATERIAL"],"description":"Always `PBR_MATERIAL` for this response type.\n"},"created_at":{"$ref":"#/components/schemas/EpochMillis"},"url":{"type":"string","format":"uri","description":"Presigned URL for downloading the main PBR material asset.\n"},"thumbnail_url":{"type":"string","format":"uri","description":"Presigned URL for a thumbnail preview of the result, if available.\n"},"thumbnail_url_expires_at":{"$ref":"#/components/schemas/EpochMillis"},"source_execution_id":{"type":"string","description":"ID of the execution that produced this PBR material result.\n"},"model_version":{"type":"string","description":"The img2mat model that produced this PBR asset, for example\n`M-XR I2M v1.2`. A PBR asset created before the platform recorded the\nmodel reports `M-XR I2M v1.0`, the first release. Always present, and\nthe same value `GET /assets` reports for this asset.\n"},"material":{"$ref":"#/components/schemas/PublicPBRMaterialInfo"},"source":{"allOf":[{"$ref":"#/components/schemas/PublicPBRMaterialAsset"}],"description":"The asset this PBR material was generated from: a 3D mesh for a mesh\nbatch, an image for an image batch. Read its `type` rather than assuming\nthe material came from a mesh.\n"},"textures":{"type":"object","description":"Map from texture map name to the generated texture asset.\n","additionalProperties":{"$ref":"#/components/schemas/PublicPBRMaterialAsset"}},"usdz":{"$ref":"#/components/schemas/PublicPBRMaterialAsset"},"available_formats":{"type":"array","description":"Additional downloadable formats available for this PBR material\nresult. Currently only `usdz` is returned, and only if the result\nproduced a USDZ component.\n","items":{"type":"string","enum":["usdz"]}}}},"PublicPBRMaterialInfo":{"type":"object","required":["slot_name","gltf_material_index"],"description":"Metadata about the material within a PBR material result.\n","properties":{"slot_name":{"type":"string","description":"The material slot name in the source asset. `default` for a\nsingle-material mesh and for every image-sourced material.\n"},"display_name":{"type":"string","description":"Human-readable name of the material.\n"},"gltf_material_index":{"type":["integer","null"],"description":"Zero-based glTF material index. `null` for legacy single-material\nresults.\n"}}},"PublicPBRMaterialAsset":{"type":"object","required":["asset_id","download_url","expires_at"],"description":"One component of a PBR material result: the asset it was generated from, a\ntexture map, or a USDZ bundle. The component is itself an asset with a\npresigned download URL.\n","properties":{"asset_id":{"type":"string","description":"Unique identifier of the component asset.\n"},"type":{"type":"string","description":"Physical type of the component asset.\n"},"download_url":{"type":"string","format":"uri","description":"Presigned URL for downloading the component. Valid until `expires_at`.\n"},"expires_at":{"$ref":"#/components/schemas/EpochMillis"}}},"Error":{"type":"object","required":["error"],"description":"Canonical error envelope. The error payload is nested under the `error`\nkey.\n","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","description":"The body of an error response. The `code` is a stable value clients can\nbranch on; the `message` is a human-readable explanation.\n","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable description of the error.\n"},"details":{"type":["object","null"],"description":"Additional structured information about the error, or `null` if no\nextra details are available.\n","additionalProperties":true}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. The enum enumerates every code that can be\nreturned by this API, including infrastructure-level 5xx codes, so\ngenerated clients can decode any `error.code` they receive.\n\nPossible enum values:\n- `INVALID_REQUEST`: the request body or a query parameter failed validation.\n- `MALFORMED_UUID`: a path parameter that should be a UUID is not one.\n- `UNAUTHORIZED`: the request could not be authenticated.\n- `API_KEY_MISSING`: no API key was supplied.\n- `API_KEY_INVALID`: the supplied API key does not match any known key.\n- `API_KEY_REVOKED`: the supplied API key has been revoked.\n- `PLAN_FEATURE_REQUIRED`: the account plan does not include a required feature.\n- `INSUFFICIENT_CREDITS`: the account does not have enough credits for this request.\n- `QUOTA_EXCEEDED`: a usage quota for the account has been exceeded.\n- `FILE_TOO_LARGE`: an uploaded or referenced file exceeds the allowed size.\n- `INVALID_CONTENT_TYPE`: a file's content type does not match its declared or expected type.\n- `UNSUPPORTED_ASSET_FORMAT`: a file's extension is not one of the supported asset formats.\n- `FORMAT_NOT_AVAILABLE`: the requested downloadable format does not exist for this asset.\n- `ASSET_NOT_FOUND`: the asset does not exist or does not belong to the account.\n- `ASSET_IN_USE`: the asset is referenced by an active execution and cannot be deleted.\n- `BATCH_TOO_LARGE`: the batch contains more items than the endpoint allows.\n- `CONCURRENT_EXECUTION_LIMIT_EXCEEDED`: the account has reached its limit on simultaneous submissions.\n- `PBR_FAILURE_CAP_EXCEEDED`: the account has too many recent failed PBR runs; new submissions are temporarily blocked.\n- `EXECUTION_NOT_FOUND`: the execution or batch does not exist or does not belong to the account.\n- `EXECUTION_IN_PROGRESS`: the request conflicts with an execution that is still running.\n- `RATE_LIMITED`: the API key's rate limit was exceeded.\n- `SERVICE_UNAVAILABLE`: a dependent service is temporarily unavailable; retry later.\n- `MAINTENANCE_MODE`: the API is temporarily unavailable for maintenance.\n- `INTERNAL_SERVER_ERROR`: an unexpected server error occurred.\n- `WORKFLOW_CREATION_FAILED`: the platform could not start the underlying workflow.\n- `WORKFLOW_API_ERROR`: the platform's workflow system returned an unexpected error.\n","enum":["INVALID_REQUEST","MALFORMED_UUID","UNAUTHORIZED","API_KEY_MISSING","API_KEY_INVALID","API_KEY_REVOKED","PLAN_FEATURE_REQUIRED","INSUFFICIENT_CREDITS","QUOTA_EXCEEDED","FILE_TOO_LARGE","INVALID_CONTENT_TYPE","UNSUPPORTED_ASSET_FORMAT","FORMAT_NOT_AVAILABLE","ASSET_NOT_FOUND","ASSET_IN_USE","BATCH_TOO_LARGE","CONCURRENT_EXECUTION_LIMIT_EXCEEDED","PBR_FAILURE_CAP_EXCEEDED","EXECUTION_NOT_FOUND","EXECUTION_IN_PROGRESS","RATE_LIMITED","SERVICE_UNAVAILABLE","MAINTENANCE_MODE","INTERNAL_SERVER_ERROR","WORKFLOW_CREATION_FAILED","WORKFLOW_API_ERROR"]},"PlanFeatureRequiredError":{"type":"object","required":["error"],"description":"The error envelope for a missing subscription feature.","properties":{"error":{"type":"object","required":["code","message","details"],"description":"The missing-feature error body.","properties":{"code":{"type":"string","const":"PLAN_FEATURE_REQUIRED","description":"Stable error code for a missing subscription feature."},"message":{"type":"string","description":"Human-readable description of the error."},"details":{"$ref":"#/components/schemas/PlanFeatureRequiredErrorDetails"}}}}},"PlanFeatureRequiredErrorDetails":{"type":"object","required":["feature","upgrade_plan_codes"],"description":"The missing feature and the active plans that provide it.","properties":{"feature":{"$ref":"#/components/schemas/SubscriptionFeature"},"upgrade_plan_codes":{"type":"array","description":"Active plans available for purchase that provide the missing\nfeature. The array uses plan class and opaque code order.\n","items":{"$ref":"#/components/schemas/SubscriptionPlanCode"}}}},"SubscriptionFeature":{"type":"string","description":"Product capability available to the account.\n\nPossible enum values:\n- `commercial_license`: commercial-use rights.\n- `api_pbr`: PBR generation through the Public Platform API.\n- `api_asset_download`: asset downloads through the Public Platform API.\n- `custom_polycount`: custom polygon counts for 3D generation.\n- `image_edit`: image editing.\n- `multiview`: multiview generation.\n","enum":["commercial_license","api_pbr","api_asset_download","custom_polycount","image_edit","multiview"]},"SubscriptionPlanCode":{"type":"string","pattern":"^plan_[A-Za-z0-9]{7,64}$","description":"Opaque and immutable code for a subscription plan. Use the plan\ncatalogue for its current display name. The code must start with\n`plan_`, followed by 7 to 64 ASCII letters or digits.\n"}},"responses":{"Unauthorized":{"description":"The API key is missing, invalid, or revoked. Possible error codes:\n`API_KEY_MISSING`, `API_KEY_INVALID`, `API_KEY_REVOKED`, or `UNAUTHORIZED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PlanFeatureRequired":{"description":"The account does not have the plan feature required by this operation\n(`PLAN_FEATURE_REQUIRED`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanFeatureRequiredError"}}}},"RateLimited":{"description":"Rate limit exceeded (`RATE_LIMITED`). The response includes a `Retry-After`\nheader with the number of seconds to wait before retrying, plus the\n`X-RateLimit-*` headers. Those headers are also present on successful (2xx)\nresponses so a client can pace itself.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request burst capacity for the API key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current burst window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the burst capacity is fully restored.\n","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/assets/{asset_id}":{"get":{"operationId":"getAsset","tags":["assets"],"summary":"Retrieve an asset.","description":"Returns the metadata for a single asset, plus a fresh presigned download\nURL valid for about 1 minute. For PBR material assets, the response\nincludes the generated mesh, material, and texture components.\n","parameters":[{"$ref":"#/components/parameters/AssetId"},{"name":"format","in":"query","required":false,"description":"Request a specific downloadable representation. `usdz` is available for PBR material assets that produced a USDZ component.","schema":{"type":"string","enum":["usdz"]}}],"responses":{"200":{"description":"Asset metadata and presigned download URL, or PBR material detail.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PublicAssetDetailResponse"},{"$ref":"#/components/schemas/PublicPBRMaterialDetailResponse"}]}}}},"400":{"description":"The asset ID is malformed (`MALFORMED_UUID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/PlanFeatureRequired"},"404":{"description":"Asset not found or not owned by the authenticated account (`ASSET_NOT_FOUND`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The requested format is not available for this asset (`FORMAT_NOT_AVAILABLE`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Delete an asset.

> Deletes an asset owned by the authenticated account. Assets that are referenced by an\
> active execution cannot be deleted.<br>

```json
{"openapi":"3.1.0","info":{"title":"Marso Studio API","version":"0.0.1"},"tags":[{"name":"assets","description":"Assets represent the files you upload and the material outputs the API\ngenerates. Use these endpoints to upload source files, list assets, fetch\nmetadata and download URLs, and delete assets you no longer need.\n"}],"servers":[{"url":"https://api.marso.ai/api/public/v1","description":"Production server."}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key minted from the Marso Studio dashboard. The value has the form\n`mxr_live_<entropy>` and is sent as a Bearer token in the `Authorization`\nheader.\n"}},"parameters":{"AssetId":{"name":"asset_id","in":"path","required":true,"description":"Unique identifier of the asset.","schema":{"type":"string","format":"uuid"}}},"schemas":{"PublicAssetDeleteResponse":{"type":"object","required":["asset_id","deleted"],"description":"Response from `DELETE /assets/{asset_id}`.\n","properties":{"asset_id":{"type":"string","description":"Unique identifier of the asset that was deleted.\n"},"deleted":{"type":"boolean","description":"Whether the asset was deleted. If the asset was already deleted or\ndid not exist, this may be `false`.\n"}}},"Error":{"type":"object","required":["error"],"description":"Canonical error envelope. The error payload is nested under the `error`\nkey.\n","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}},"ErrorBody":{"type":"object","description":"The body of an error response. The `code` is a stable value clients can\nbranch on; the `message` is a human-readable explanation.\n","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable description of the error.\n"},"details":{"type":["object","null"],"description":"Additional structured information about the error, or `null` if no\nextra details are available.\n","additionalProperties":true}}},"ErrorCode":{"type":"string","description":"Machine-readable error code. The enum enumerates every code that can be\nreturned by this API, including infrastructure-level 5xx codes, so\ngenerated clients can decode any `error.code` they receive.\n\nPossible enum values:\n- `INVALID_REQUEST`: the request body or a query parameter failed validation.\n- `MALFORMED_UUID`: a path parameter that should be a UUID is not one.\n- `UNAUTHORIZED`: the request could not be authenticated.\n- `API_KEY_MISSING`: no API key was supplied.\n- `API_KEY_INVALID`: the supplied API key does not match any known key.\n- `API_KEY_REVOKED`: the supplied API key has been revoked.\n- `PLAN_FEATURE_REQUIRED`: the account plan does not include a required feature.\n- `INSUFFICIENT_CREDITS`: the account does not have enough credits for this request.\n- `QUOTA_EXCEEDED`: a usage quota for the account has been exceeded.\n- `FILE_TOO_LARGE`: an uploaded or referenced file exceeds the allowed size.\n- `INVALID_CONTENT_TYPE`: a file's content type does not match its declared or expected type.\n- `UNSUPPORTED_ASSET_FORMAT`: a file's extension is not one of the supported asset formats.\n- `FORMAT_NOT_AVAILABLE`: the requested downloadable format does not exist for this asset.\n- `ASSET_NOT_FOUND`: the asset does not exist or does not belong to the account.\n- `ASSET_IN_USE`: the asset is referenced by an active execution and cannot be deleted.\n- `BATCH_TOO_LARGE`: the batch contains more items than the endpoint allows.\n- `CONCURRENT_EXECUTION_LIMIT_EXCEEDED`: the account has reached its limit on simultaneous submissions.\n- `PBR_FAILURE_CAP_EXCEEDED`: the account has too many recent failed PBR runs; new submissions are temporarily blocked.\n- `EXECUTION_NOT_FOUND`: the execution or batch does not exist or does not belong to the account.\n- `EXECUTION_IN_PROGRESS`: the request conflicts with an execution that is still running.\n- `RATE_LIMITED`: the API key's rate limit was exceeded.\n- `SERVICE_UNAVAILABLE`: a dependent service is temporarily unavailable; retry later.\n- `MAINTENANCE_MODE`: the API is temporarily unavailable for maintenance.\n- `INTERNAL_SERVER_ERROR`: an unexpected server error occurred.\n- `WORKFLOW_CREATION_FAILED`: the platform could not start the underlying workflow.\n- `WORKFLOW_API_ERROR`: the platform's workflow system returned an unexpected error.\n","enum":["INVALID_REQUEST","MALFORMED_UUID","UNAUTHORIZED","API_KEY_MISSING","API_KEY_INVALID","API_KEY_REVOKED","PLAN_FEATURE_REQUIRED","INSUFFICIENT_CREDITS","QUOTA_EXCEEDED","FILE_TOO_LARGE","INVALID_CONTENT_TYPE","UNSUPPORTED_ASSET_FORMAT","FORMAT_NOT_AVAILABLE","ASSET_NOT_FOUND","ASSET_IN_USE","BATCH_TOO_LARGE","CONCURRENT_EXECUTION_LIMIT_EXCEEDED","PBR_FAILURE_CAP_EXCEEDED","EXECUTION_NOT_FOUND","EXECUTION_IN_PROGRESS","RATE_LIMITED","SERVICE_UNAVAILABLE","MAINTENANCE_MODE","INTERNAL_SERVER_ERROR","WORKFLOW_CREATION_FAILED","WORKFLOW_API_ERROR"]}},"responses":{"Unauthorized":{"description":"The API key is missing, invalid, or revoked. Possible error codes:\n`API_KEY_MISSING`, `API_KEY_INVALID`, `API_KEY_REVOKED`, or `UNAUTHORIZED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded (`RATE_LIMITED`). The response includes a `Retry-After`\nheader with the number of seconds to wait before retrying, plus the\n`X-RateLimit-*` headers. Those headers are also present on successful (2xx)\nresponses so a client can pace itself.\n","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request burst capacity for the API key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current burst window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix epoch seconds when the burst capacity is fully restored.\n","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/assets/{asset_id}":{"delete":{"operationId":"deleteAsset","tags":["assets"],"summary":"Delete an asset.","description":"Deletes an asset owned by the authenticated account. Assets that are referenced by an\nactive execution cannot be deleted.\n","parameters":[{"$ref":"#/components/parameters/AssetId"}],"responses":{"200":{"description":"Deletion acknowledged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAssetDeleteResponse"}}}},"400":{"description":"The asset ID is malformed (`MALFORMED_UUID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Asset not found or not owned by the authenticated account (`ASSET_NOT_FOUND`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The asset is referenced by an active execution (`ASSET_IN_USE`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.m-xr.com/marso-studio-api/api/assets.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
