Assets
Manage 3D models, textures, and graphics. Upload, organize, and retrieve assets with signed download URLs.
List Assets
GET/assets
// Get all assets (optionally filter by type)
const response = await fetch(BASE_URL + '/assets?type=3d', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
// A 3D model imported from an online library (see Online 3D Libraries) also
// carries its provenance and readiness score:
// externalSource: { provider: "polyhaven", providerName: "Poly Haven",
// providerAssetId, name, sourceUrl, author, license, licenseId, licenseUrl,
// commercialUseAllowed, attributionRequired, attribution, importedAt, … }
// readinessScore: 84, readinessLevel: "ready" | "needs-work" | "not-ready"
// All three are null for uploaded models, and absent on a deployment older
// than API 3.16.Upload Asset
POST/assets
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'Chair Model');
formData.append('type', '3d');
formData.append('category', 'furniture');
const response = await fetch(BASE_URL + '/assets', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken },
body: formData
});
const result = await response.json();
// Size limits (per file): SVG 10 MB; everything else 50 MB.
//
// SVGs are cleaned before they are stored: scripts, event handlers, links to
// external files/websites, external stylesheets and animation are removed;
// the artwork itself is kept. What was removed comes back as plain-language
// notices on success (always [] for non-SVG files):
// { success: true, asset: { … }, notices: ["Removed 1 script — graphics can't run code."] }
// A rejected file returns HTTP 400 with a stable code and a readable reason:
// { success: false, code: "file_too_large", error: "This SVG is too large (12.5 MB). SVG graphics can be up to 10 MB. …" }
// code: file_too_large | invalid_content | unsupported_type | empty_file
// (An older deployment omits notices and code — treat missing as [] / unknown.)Get Asset
GET/assets/:assetId
const response = await fetch(BASE_URL + '/assets/asset_123', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});Update Asset
PUT/assets/:assetId
const response = await fetch(BASE_URL + '/assets/asset_123', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Renamed Asset', category: 'new-category' })
});Delete Asset
DELETE/assets/:assetId
const response = await fetch(BASE_URL + '/assets/asset_123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Download Asset (binary or signed URL)
GET/assets/:assetId/download
// Returns the RAW FILE, not JSON — do not call response.json().
const response = await fetch(BASE_URL + '/assets/asset_123/download', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const bytes = await response.arrayBuffer();
// Content-Type: application/octet-stream
// Content-Disposition: attachment; filename="model.glb"; filename*=UTF-8''model.glb
// For an asset that came from a template, pass the project so the server can
// fall back to the template owner's copy:
// GET /assets/asset_123/download?projectId=YOUR_PROJECT_ID
//
// NOTE: by default this endpoint streams the file through the API. Very large
// models can exceed the platform's 150s request ceiling.
// OPT-IN: ?mode=signed-url returns JSON instead, and the browser fetches the
// bytes straight from storage — no Edge buffering, so no 150s ceiling.
// The default stays the raw file: that response is a published contract and
// switching it would break every existing consumer.
const meta = await fetch(
BASE_URL + '/assets/asset_123/download?mode=signed-url',
{ headers: { 'Authorization': 'Bearer ' + accessToken } }
).then(r => r.json());
// { success: true, signedUrl: "https://…", fileName: "model.glb",
// downloadFileName: "Lounge Chair.glb", variant: "model", disposition: "inline" }
const bytes2 = await (await fetch(meta.signedUrl)).arrayBuffer();
// &disposition=attachment — the signed URL DOWNLOADS (Content-Disposition:
// attachment) under downloadFileName: the asset's display name + the file's
// extension. Use it for a "Download" link a person clicks.
// GET /assets/asset_123/download?mode=signed-url&disposition=attachment
// &variant=original — the ORIGINAL upload of a 3D model that was uploaded as
// OBJ / FBX / STL / DAE / GLTF / ZIP and stored as GLB (the asset list marks
// these with hasSourceFile: true). Requires mode=signed-url.
// GET /assets/asset_123/download?mode=signed-url&variant=original&disposition=attachment
// → 404 { success: false, code: "no_original" } when there is no original.
// Check the response echoes variant: "original" — a deployment older than
// API 3.15 ignores the parameter and signs the GLB instead.
// An older deployment ignores ?mode= and returns the binary, so feature-detect
// on the Content-Type rather than assuming JSON.Continue reading
Online 3D LibrariesSearch Poly Haven, Sketchfab and Smithsonian Open Access and import a model as a normal 3D asset — license gate, background import jobs, provenance and Configurator Readiness.MaterialsCreate and manage PBR materials and their texture maps.CategoriesOrganize assets and materials into categories for filtering and bulk material blocks.Team ManagementInvite, update, and remove team members on an account.