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.
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.
API key created in the Marso Studio dashboard. The value has the form
mxr_live_<entropy> and is sent as a Bearer token in the Authorization
header.
Request body for POST /batch/uploads. Describe 5 to 200 files you want to
upload; the response returns a presigned upload URL for each one.
Upload URLs in the same order as the request.
Response from POST /batch/uploads. Contains the upload batch ID and the
presigned upload URL for each file.
The ID that groups the created pending assets. Poll
GET /batch/uploads/{upload_batch_id} for aggregate readiness. If
this field is absent, poll each asset individually.
Either a batch-level validation error (returned as the standard
Error with code INVALID_REQUEST) or one or more per-file
validation failures. Per-file failures are returned as a
BatchUploadErrorResponse that lists every invalid file so you can
fix them all at once. The batch is all-or-nothing: if any file fails,
nothing is created.
Per-file error codes: UNSUPPORTED_ASSET_FORMAT,
INVALID_CONTENT_TYPE, FILE_TOO_LARGE, INVALID_REQUEST.
The API key is missing, invalid, or revoked. Possible error codes:
API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, or UNAUTHORIZED.
Rate limit exceeded (RATE_LIMITED). The response includes a Retry-After
header with the number of seconds to wait before retrying, plus the
X-RateLimit-* headers. Those headers are also present on successful (2xx)
responses so a client can pace itself.
POST /api/public/v1/batch/uploads HTTP/1.1
Host: api.marso.app/
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 68
{
"files": [
{
"filename": "text",
"content_type": "text",
"size_bytes": 1
}
]
}{
"upload_batch_id": "text",
"files": [
{
"asset_id": "text",
"upload_url": "https://example.com",
"headers": {
"ANY_ADDITIONAL_PROPERTY": "text"
}
}
]
}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.
API key created in the Marso Studio dashboard. The value has the form
mxr_live_<entropy> and is sent as a Bearer token in the Authorization
header.
Upload batch status.
Response from GET /batch/uploads/{upload_batch_id}. Contains the
aggregate state of the batch and the validation status of each file.
Aggregate state of an upload batch.
Possible enum values:
UPLOADING: at least one file is still being validated.COMPLETED: every file in the batch isready.PARTIALLY_COMPLETED: the batch has a mix ofreadyandrejectedfiles; this is a terminal state.FAILED: every file in the batch was rejected.
Time represented as milliseconds since the Unix epoch.
Time represented as milliseconds since the Unix epoch, or null when the
field has not been set.
The API key is missing, invalid, or revoked. Possible error codes:
API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, or UNAUTHORIZED.
The batch does not exist or does not belong to your account (EXECUTION_NOT_FOUND).
Rate limit exceeded (RATE_LIMITED). The response includes a Retry-After
header with the number of seconds to wait before retrying, plus the
X-RateLimit-* headers. Those headers are also present on successful (2xx)
responses so a client can pace itself.
GET /api/public/v1/batch/uploads/{upload_batch_id} HTTP/1.1
Host: api.marso.app/
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"state": "UPLOADING",
"files": [
{
"file_name": "text",
"status": "pending"
}
],
"created_at": 1,
"completed_at": null
}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.
API key created in the Marso Studio dashboard. The value has the form
mxr_live_<entropy> and is sent as a Bearer token in the Authorization
header.
Request body for POST /uploads. Describes the one file you want to
upload.
The name of the file, including its extension. The extension selects
the asset type and must be one of .usd, .glb, .jpg, .jpeg, or
.png.
The MIME type of the file. Optional: it is inferred from the file extension when omitted. When present it must match the extension.
The exact size of the file in bytes. The uploaded byte count must match this value. Must be at least 1, and no more than 5 MB for an image or 100 MB for a mesh.
The upload URL was created.
Presigned upload URL and metadata returned by POST /uploads. There is no
upload batch ID: a single upload is polled with
GET /uploads/{asset_id}.
The ID of the pending asset created for this file. Use it to poll the
upload and, once the status is ready, to submit the asset for PBR
generation.
Presigned PUT URL. Upload the raw file bytes to this URL using the
headers provided in headers.
The request is invalid. Possible error codes:
UNSUPPORTED_ASSET_FORMAT: the file extension is not one of.usd,.glb,.jpg,.jpeg, or.png.INVALID_CONTENT_TYPE: thecontent_typedoes not match the file extension.FILE_TOO_LARGE:size_bytesis outside the allowed range for the file type.INVALID_REQUEST: the body is malformed or the file name is missing.
The API key is missing, invalid, or revoked. Possible error codes:
API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, or UNAUTHORIZED.
Rate limit exceeded (RATE_LIMITED). The response includes a Retry-After
header with the number of seconds to wait before retrying, plus the
X-RateLimit-* headers. Those headers are also present on successful (2xx)
responses so a client can pace itself.
POST /api/public/v1/uploads HTTP/1.1
Host: api.marso.app/
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 56
{
"filename": "text",
"content_type": "text",
"size_bytes": 1
}{
"asset_id": "text",
"upload_url": "https://example.com",
"headers": {
"ANY_ADDITIONAL_PROPERTY": "text"
}
}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.
API key created in the Marso Studio dashboard. The value has the form
mxr_live_<entropy> and is sent as a Bearer token in the Authorization
header.
Unique identifier of the asset.
The status of the upload.
The validation status of one file uploaded with POST /uploads.
The ID of the asset created for this file.
The file name as provided in the upload request.
The validation status of this file.
Possible enum values:
pending: the file has not been validated yet.ready: the file passed validation and can be used for PBR generation.rejected: the file failed validation. The file must be uploaded again.
Time represented as milliseconds since the Unix epoch.
The asset ID is malformed (MALFORMED_UUID).
The API key is missing, invalid, or revoked. Possible error codes:
API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, or UNAUTHORIZED.
The asset does not exist or does not belong to the authenticated
account (ASSET_NOT_FOUND).
Rate limit exceeded (RATE_LIMITED). The response includes a Retry-After
header with the number of seconds to wait before retrying, plus the
X-RateLimit-* headers. Those headers are also present on successful (2xx)
responses so a client can pace itself.
GET /api/public/v1/uploads/{asset_id} HTTP/1.1
Host: api.marso.app/
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"asset_id": "text",
"file_name": "text",
"status": "pending",
"created_at": 1
}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.
API key created in the Marso Studio dashboard. The value has the form
mxr_live_<entropy> and is sent as a Bearer token in the Authorization
header.
Page size. Defaults to 25 and has a maximum of 25. Other values use the default.
What the number counts depends on which part of the listing you are in.
While groups are still being returned it counts PROJECTS: one page carries
up to 25 projects and every PBR asset each of them produced, so a single
page can hold many more than 25 entries. Once the groups are exhausted it
counts assets, and one page carries up to 25 entries in ungrouped_assets.
25Opaque pagination cursor returned by the previous page as next_cursor.
Omit on the first request.
Which assets to return. pbr is the only supported value today and is
the default, so you can omit this.
pbrPossible values: How to group the response. projects is the only supported value today
and is the default, so you can omit this.
projectsPossible values: A page of your PBR assets grouped by project.
Response from GET /assets: your PBR assets grouped by the project that
produced them, then the ones belonging to no project.
data and ungrouped_assets form ONE ordered sequence under a single
cursor. Every group first, then the ungrouped assets. Just follow
pagination.next_cursor until has_more is false.
A cursor is a position in that one sequence, and what it points at differs either side of the boundary between the two parts. Treat it as opaque and short-lived: echo the one you were just given, and start a fresh traversal rather than storing a cursor and replaying it later.
How the response is grouped, echoing the request.
Invalid cursor, or an unsupported type / group_by value
(INVALID_REQUEST). The message names the parameter at fault and the
value that IS supported.
The API key is missing, invalid, or revoked. Possible error codes:
API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, or UNAUTHORIZED.
The account does not have the plan feature required by this operation
(PLAN_FEATURE_REQUIRED).
Rate limit exceeded (RATE_LIMITED). The response includes a Retry-After
header with the number of seconds to wait before retrying, plus the
X-RateLimit-* headers. Those headers are also present on successful (2xx)
responses so a client can pace itself.
GET /api/public/v1/assets HTTP/1.1
Host: api.marso.app/
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"group_by": "projects",
"data": [
{
"group_id": "text",
"name": "text",
"thumbnail_url": "https://example.com",
"thumbnail_url_expires_at": 1,
"assets": [
{
"id": "text",
"name": "text",
"file_name": "text",
"thumbnail_url": "https://example.com",
"thumbnail_url_expires_at": 1,
"model_version": "text",
"enriched_status": "not_started",
"enrichment": {
"schema_version": "text",
"prompt_version": "text",
"model": "text",
"display_name": "text",
"description_short": "text",
"description_long": "text",
"category": "text",
"subcategory": "text",
"detail": "text",
"search_aliases": [
"text"
],
"tags": [
"text"
],
"materials": [
"text"
],
"style_period": "text",
"region": "text",
"setting": "text",
"conditions": [
"text"
],
"cleanliness": [
"text"
],
"placements": [
"text"
],
"needs_review": true
}
}
]
}
],
"ungrouped_assets": [
{
"id": "text",
"name": "text",
"file_name": "text",
"thumbnail_url": "https://example.com",
"thumbnail_url_expires_at": 1,
"model_version": "text",
"enriched_status": "not_started",
"enrichment": {
"schema_version": "text",
"prompt_version": "text",
"model": "text",
"display_name": "text",
"description_short": "text",
"description_long": "text",
"category": "text",
"subcategory": "text",
"detail": "text",
"search_aliases": [
"text"
],
"tags": [
"text"
],
"materials": [
"text"
],
"style_period": "text",
"region": "text",
"setting": "text",
"conditions": [
"text"
],
"cleanliness": [
"text"
],
"placements": [
"text"
],
"needs_review": true
}
}
],
"errors": [
{
"asset_id": "text",
"file_name": "text",
"code": "THUMBNAIL_URL_UNAVAILABLE",
"message": "text"
}
],
"pagination": {
"next_cursor": "text",
"has_more": true
}
}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 call changes nothing and is safe to retry. 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.
API key created in the Marso Studio dashboard. The value has the form
mxr_live_<entropy> and is sent as a Bearer token in the Authorization
header.
Request body for POST /assets/download. The asset IDs you want download
URLs for.
Asset IDs to get download URLs for. Repeated IDs collapse to a single entry. Every ID must be a well-formed asset ID. One malformed value rejects the whole request.
Download URLs for the resolved assets, plus a reason for each ID that could not be resolved.
Response from POST /assets/download. Together assets and errors
account for every distinct asset ID submitted.
asset_ids was missing or empty, contained a malformed asset ID
(MALFORMED_UUID), or exceeded the maximum length
(INVALID_REQUEST).
The API key is missing, invalid, or revoked. Possible error codes:
API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, or UNAUTHORIZED.
The account does not have the plan feature required by this operation
(PLAN_FEATURE_REQUIRED).
Rate limit exceeded (RATE_LIMITED). The response includes a Retry-After
header with the number of seconds to wait before retrying, plus the
X-RateLimit-* headers. Those headers are also present on successful (2xx)
responses so a client can pace itself.
POST /api/public/v1/assets/download HTTP/1.1
Host: api.marso.app/
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 54
{
"asset_ids": [
"123e4567-e89b-12d3-a456-426614174000"
]
}{
"assets": [
{
"asset_id": "text",
"type": "text",
"file_name": "text",
"files": [
{
"asset_id": "text",
"role": "source",
"pass": "text",
"type": "text",
"size_bytes": 1,
"download_url": "https://example.com",
"expires_at": 1
}
]
}
],
"errors": [
{
"asset_id": "text",
"file_name": "text",
"code": "ASSET_NOT_FOUND",
"message": "text"
}
]
}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.
API key created in the Marso Studio dashboard. The value has the form
mxr_live_<entropy> and is sent as a Bearer token in the Authorization
header.
Unique identifier of the asset.
Request a specific downloadable representation. usdz is available for PBR material assets that produced a USDZ component.
Asset metadata and presigned download URL, or PBR material detail.
The asset ID is malformed (MALFORMED_UUID).
The API key is missing, invalid, or revoked. Possible error codes:
API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, or UNAUTHORIZED.
The account does not have the plan feature required by this operation
(PLAN_FEATURE_REQUIRED).
Asset not found or not owned by the authenticated account (ASSET_NOT_FOUND).
The requested format is not available for this asset (FORMAT_NOT_AVAILABLE).
Rate limit exceeded (RATE_LIMITED). The response includes a Retry-After
header with the number of seconds to wait before retrying, plus the
X-RateLimit-* headers. Those headers are also present on successful (2xx)
responses so a client can pace itself.
GET /api/public/v1/assets/{asset_id} HTTP/1.1
Host: api.marso.app/
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"asset_id": "text",
"name": "text",
"type": "text",
"size_bytes": 1,
"created_at": 1,
"download_url": "https://example.com",
"expires_at": 1
}Deletes an asset owned by the authenticated account. For a PBR material, this operation deletes every material slot, physical component, and enrichment commit object from the same PBR execution. The operation returns ASSET_IN_USE without deleting data when an active execution uses a component or when another PBR execution shares a source or target component.
API key created in the Marso Studio dashboard. The value has the form
mxr_live_<entropy> and is sent as a Bearer token in the Authorization
header.
Unique identifier of the asset.
Deletion acknowledged.
Response from DELETE /assets/{asset_id}.
Unique identifier of the asset that was deleted.
Whether the asset was deleted. If the asset was already deleted or
did not exist, this may be false.
The asset ID is malformed (MALFORMED_UUID).
The API key is missing, invalid, or revoked. Possible error codes:
API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, or UNAUTHORIZED.
Asset not found or not owned by the authenticated account (ASSET_NOT_FOUND).
An active execution uses a component, or another PBR execution shares a source or target component (ASSET_IN_USE).
Rate limit exceeded (RATE_LIMITED). The response includes a Retry-After
header with the number of seconds to wait before retrying, plus the
X-RateLimit-* headers. Those headers are also present on successful (2xx)
responses so a client can pace itself.
DELETE /api/public/v1/assets/{asset_id} HTTP/1.1
Host: api.marso.app/
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"asset_id": "text",
"deleted": true
}Last updated
