{"components":{"schemas":{"AssetAnimationMetadata":{"additionalProperties":false,"properties":{"duration":{"type":["number","null"]},"index":{"type":"number"},"name":{"type":"string"},"trackCount":{"description":"glTF animation channels; absent on older collected records.","type":"number"}},"required":["index","duration"],"type":"object"},"AssetBoundsMetadata":{"additionalProperties":false,"description":"Axis-aligned bounds in the default pose, not bounds over an animation.","properties":{"approximate":{"description":"True when skinning or morph targets make the default-pose estimate uncertain.","type":"boolean"},"center":{"$ref":"#/components/schemas/AssetVector3"},"dimensions":{"$ref":"#/components/schemas/AssetVector3"},"max":{"$ref":"#/components/schemas/AssetVector3"},"min":{"$ref":"#/components/schemas/AssetVector3"},"originOffset":{"$ref":"#/components/schemas/AssetVector3","description":"Translation needed to put the bounds center at the origin."},"pose":{"const":"default","type":"string"}},"required":["approximate","center","dimensions","max","min","originOffset","pose"],"type":"object"},"AssetCompanionMetadata":{"additionalProperties":false,"description":"Compact complexity totals for a companion model or LOD.","properties":{"asset":{"$ref":"#/components/schemas/AssetEmbeddedMetadata","description":"Omitted when identical to the entrypoint's embedded asset information."},"collectorVersion":{"description":"Revision of the collector that measured these facts.","type":"number"},"geometry":{"$ref":"#/components/schemas/AssetGeometryMetadata"}},"required":["collectorVersion","geometry"],"type":"object"},"AssetEmbeddedMetadata":{"additionalProperties":false,"properties":{"attribution":{"additionalProperties":false,"description":"Claims declared by the file, not verified licenses or ownership.","properties":{"author":{"type":"string"},"license":{"type":"string"},"source":{"type":"string"},"title":{"type":"string"}},"type":"object"},"copyright":{"type":"string"},"generator":{"type":"string"},"minVersion":{"type":"string"},"version":{"type":"string"}},"required":["version","attribution"],"type":"object"},"AssetGeometryMetadata":{"additionalProperties":false,"properties":{"meshes":{"type":"number"},"primitives":{"type":"number"},"triangles":{"description":"Triangles only; points/lines contribute zero. Unknown accessor counts give null.","type":["number","null"]},"vertices":{"description":"Unique POSITION accessor elements per mesh primitive; scene totals count placements.","type":["number","null"]}},"required":["meshes","primitives","vertices","triangles"],"type":"object"},"AssetImageMetadata":{"additionalProperties":false,"properties":{"encodedBytes":{"description":"Encoded image payload bytes; absent on older records.","type":["number","null"]},"estimatedRgba8Bytes":{"type":["number","null"]},"height":{"type":["number","null"]},"index":{"type":"number"},"mimeType":{"type":["string","null"]},"width":{"type":["number","null"]}},"required":["index","mimeType","width","height","estimatedRgba8Bytes"],"type":"object"},"AssetLegacyBoundsMetadata":{"additionalProperties":false,"deprecated":"Historical records used static to mean default pose, not absence of animation.","properties":{"accuracy":{"enum":["static","approximate"],"type":"string"},"center":{"$ref":"#/components/schemas/AssetVector3"},"dimensions":{"$ref":"#/components/schemas/AssetVector3"},"max":{"$ref":"#/components/schemas/AssetVector3"},"min":{"$ref":"#/components/schemas/AssetVector3"},"originOffset":{"$ref":"#/components/schemas/AssetVector3","description":"Translation needed to put the bounds center at the origin."}},"required":["accuracy","center","dimensions","max","min","originOffset"],"type":"object"},"AssetLoadingMetadata":{"additionalProperties":false,"properties":{"dependencies":{"description":"Declared URIs, never fetched automatically by the collector.","items":{"type":"string"},"type":"array"},"mechanisms":{"items":{"type":"string"},"type":"array"},"meshLodLevels":{"description":"Maximum declared levels per resource, including the embedded base level.","type":"number"},"progressive":{"type":"boolean"},"textureLodLevels":{"type":"number"}},"required":["progressive","mechanisms","meshLodLevels","textureLodLevels","dependencies"],"type":"object"},"AssetMetadataCollectionState":{"enum":["pending","running","complete","partial","failed","unsupported"],"type":"string"},"AssetMetadataDetailResponse":{"additionalProperties":false,"properties":{"collected_at":{"type":["string","null"]},"collector_version":{"type":"number"},"data":{"anyOf":[{"$ref":"#/components/schemas/GltfAssetMetadata"},{"$ref":"#/components/schemas/AssetTechnicalMetadata"},{"type":"null"}]},"status":{"$ref":"#/components/schemas/AssetMetadataCollectionState"},"summary":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummary"},{"type":"null"}]},"verification":{"enum":["provisional","verified"],"type":"string"}},"required":["collected_at","collector_version","data","status","summary","verification"],"type":"object"},"AssetMetadataSummary":{"additionalProperties":false,"properties":{"animationCount":{"type":"number"},"dimensions":{"anyOf":[{"$ref":"#/components/schemas/AssetVector3"},{"type":"null"}],"description":"Only the declared default scene is summarized; never silently select scene zero."},"meshLodLevels":{"type":"number"},"progressive":{"type":"boolean"},"sceneCount":{"type":"number"},"status":{"description":"Partial means some requested measurements could not be established.","enum":["complete","partial"],"type":"string"},"textureLodLevels":{"type":"number"},"triangles":{"type":["number","null"]}},"required":["animationCount","dimensions","meshLodLevels","progressive","sceneCount","status","textureLodLevels","triangles"],"type":"object"},"AssetMetadataSummaryResponse":{"additionalProperties":false,"properties":{"collected_at":{"type":["string","null"]},"collector_version":{"type":"number"},"status":{"$ref":"#/components/schemas/AssetMetadataCollectionState"},"summary":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummary"},{"type":"null"}]},"verification":{"enum":["provisional","verified"],"type":"string"}},"required":["status","verification","collector_version","collected_at","summary"],"type":"object"},"AssetResourceUsage":{"additionalProperties":false,"description":"Facts measurable from glTF JSON. Binary/dependency inspection augments these later.","properties":{"geometryAssumption":{"const":"Unique dense mesh accessors, no renderer overhead","type":"string"},"geometryBytesEstimate":{"type":["number","null"]},"meshCompression":{"items":{"type":"string"},"type":"array"},"nodeCount":{"type":"number"}},"required":["nodeCount","meshCompression","geometryBytesEstimate","geometryAssumption"],"type":"object"},"AssetSceneMetadata":{"additionalProperties":false,"properties":{"bounds":{"anyOf":[{"$ref":"#/components/schemas/AssetBoundsMetadata"},{"$ref":"#/components/schemas/AssetLegacyBoundsMetadata"},{"type":"null"}],"description":"Default pose, node transforms applied. Null if any geometry bounds are unknown."},"geometry":{"$ref":"#/components/schemas/AssetGeometryMetadata"},"index":{"type":"number"},"name":{"type":"string"},"rootCount":{"type":"number"}},"required":["index","rootCount","geometry","bounds"],"type":"object"},"AssetTechnicalMetadata":{"additionalProperties":false,"properties":{"animationCount":{"type":"number"},"animations":{"items":{"$ref":"#/components/schemas/AssetAnimationMetadata"},"type":"array"},"asset":{"$ref":"#/components/schemas/AssetEmbeddedMetadata"},"collectorVersion":{"description":"Revision of the collector that measured these facts.","type":"number"},"companionAttributions":{"description":"Distinct companion attribution grouped by file-manifest indices. Other companion models inherit the entrypoint's asset information.","items":{"additionalProperties":false,"properties":{"asset":{"$ref":"#/components/schemas/AssetEmbeddedMetadata"},"fileIndices":{"items":{"type":"number"},"type":"array"}},"required":["asset","fileIndices"],"type":"object"},"type":"array"},"defaultSceneIndex":{"type":["number","null"]},"entrypoint":{"type":"string"},"extensionsRequired":{"items":{"type":"string"},"type":"array"},"extensionsUsed":{"items":{"type":"string"},"type":"array"},"files":{"items":{"additionalProperties":false,"properties":{"path":{"type":"string"},"sha256":{"type":"string"},"size":{"type":"number"}},"required":["path","size","sha256"],"type":"object"},"type":"array"},"fingerprint":{"description":"SHA-256 of the canonical entrypoint + sorted path/size/SHA-256 manifest.","type":"string"},"fingerprintComplete":{"description":"False if declared dependencies could not all be resolved from supplied files.","type":"boolean"},"geometry":{"$ref":"#/components/schemas/AssetGeometryMetadata"},"gpuMemoryEstimate":{"additionalProperties":false,"properties":{"assumption":{"const":"RGBA8 base level, no mipmaps; actual GPU formats may differ","type":"string"},"bytes":{"type":["number","null"]}},"required":["bytes","assumption"],"type":"object"},"imageCount":{"type":"number"},"images":{"items":{"$ref":"#/components/schemas/AssetImageMetadata"},"type":"array"},"issues":{"items":{"type":"string"},"type":"array"},"jointCount":{"type":"number"},"loading":{"$ref":"#/components/schemas/AssetLoadingMetadata"},"lodSummary":{"description":"Companion model ranges grouped by declared progressive level (1 is the first external level). Null means no declared level could be established. Shared files count once per level; ranges describe individual files, not the complexity of a fully assembled scene. Unknown measurements stay unknown.","items":{"additionalProperties":false,"properties":{"bytes":{"type":"number"},"fileCount":{"type":"number"},"level":{"type":["number","null"]},"triangles":{"additionalProperties":false,"properties":{"max":{"type":["number","null"]},"min":{"type":["number","null"]},"unknownCount":{"type":"number"}},"required":["min","max","unknownCount"],"type":"object"},"vertices":{"additionalProperties":false,"properties":{"max":{"type":["number","null"]},"min":{"type":["number","null"]},"unknownCount":{"type":"number"}},"required":["min","max","unknownCount"],"type":"object"}},"required":["level","fileCount","bytes","vertices","triangles"],"type":"object"},"type":"array"},"materialCount":{"type":"number"},"materialVariantCount":{"type":"number"},"morphTargetCount":{"type":"number"},"relatedModels":{"description":"Companion/LOD complexity, never summed into the entrypoint's geometry. Historical records may contain a full description for each companion.","items":{"additionalProperties":false,"properties":{"metadata":{"anyOf":[{"$ref":"#/components/schemas/AssetCompanionMetadata"},{"$ref":"#/components/schemas/GltfAssetMetadata"}]},"path":{"type":"string"}},"required":["path","metadata"],"type":"object"},"type":"array"},"resourceUsage":{"$ref":"#/components/schemas/AssetResourceUsage","description":"Absent on older collected records."},"sceneCount":{"type":"number"},"scenes":{"items":{"$ref":"#/components/schemas/AssetSceneMetadata"},"type":"array"},"schemaVersion":{"const":1,"description":"Metadata structure version.","type":"number"},"size":{"type":"number"},"skinCount":{"type":"number"},"status":{"description":"Partial means some requested measurements could not be established.","enum":["complete","partial"],"type":"string"},"textureCount":{"type":"number"},"textureEncodedBytes":{"description":"Sum over unique image definitions, excluding companion LOD files.","type":["number","null"]},"unit":{"const":"meter","description":"glTF coordinates use meters; this is a format convention, not a scale verification.","type":"string"}},"required":["animationCount","animations","asset","collectorVersion","defaultSceneIndex","entrypoint","extensionsRequired","extensionsUsed","files","fingerprint","fingerprintComplete","geometry","gpuMemoryEstimate","imageCount","images","issues","jointCount","loading","materialCount","materialVariantCount","morphTargetCount","relatedModels","sceneCount","scenes","schemaVersion","size","skinCount","status","textureCount","unit"],"type":"object"},"AssetVector3":{"items":{"type":"number"},"maxItems":3,"minItems":3,"type":"array"},"ContentFormat":{"description":"File format — e.g. `glb`, `usdz`, `gltf`, `ply`/`splat`/`spz`/`ksplat`/`sog` (Gaussian splats), `exr` (HDRI source), `pmrem` (engine-ready HDRI KTX2).","enum":["glb","gltf","usd","usda","usdz","obj","fbx","vrm","stl","ply","splat","spz","ksplat","sog","sogs","textures","html","exr","hdr","pmrem"],"type":"string"},"ContentLabel":{"additionalProperties":false,"description":"A named pointer label (e.g. \"latest\", \"main\") with a resolved URL.","properties":{"displayName":{"description":"Human-readable display name","type":"string"},"label":{"description":"Version label, for example \"latest\".","type":"string"},"url":{"anyOf":[{"type":"string"},{"enum":[null,"hidden"],"type":["null","string"]}],"description":"Resolved CDN URL. Null if unavailable, \"hidden\" if paywalled."}},"required":["label","displayName","url"],"type":"object"},"ContentListItem":{"additionalProperties":false,"properties":{"availability":{"description":"Collection membership can remain visible to editors when the source is unavailable.","enum":["available","unavailable"],"type":"string"},"content_formats":{"description":"Available content formats for this content item (e.g. [\"glb\", \"textures\"])","items":{"$ref":"#/components/schemas/ContentFormat"},"type":"array"},"content_type":{"anyOf":[{"$ref":"#/components/schemas/ContentType"},{"type":"null"}]},"created_at":{"type":"string"},"deleted_at":{"description":"When the item was moved to the trash (only present when trash=true)","type":["string","null"]},"deleted_by_name":{"description":"Display name of the user who deleted this item (only present when trash=true)","type":["string","null"]},"description":{"type":["string","null"]},"edit_url":{"description":"Page for editing this item in the cloud UI. Present for everything the caller can see through this endpoint, since it is already scoped to their org. Null only when the server has no configured frontend origin.","type":["string","null"]},"identifier":{"type":"string"},"is_public":{"description":"Whether this content is publicly readable via the public discovery API","type":"boolean"},"item_type":{"const":"content","type":"string"},"password_protected":{"description":"Whether the content is password-protected (requires commercial license)","type":"boolean"},"size":{"description":"Bytes in the latest primary artifact, including companion files; projects include their current document and attached files. Null when unavailable.","type":["number","null"]},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"technical_metadata":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummaryResponse"},{"$ref":"#/components/schemas/AssetMetadataDetailResponse"},{"type":"null"}]},"thumbnail_url":{"description":"Resolved CDN URL for the thumbnail image","type":["string","null"]},"title":{"type":["string","null"]},"updated_at":{"type":"string"},"updated_by":{"description":"Identity associated with the latest recorded activity, when known.","type":["string","null"]},"updated_by_name":{"description":"Display name for that identity, when available.","type":["string","null"]},"url":{"description":"Resolved CDN URL for the asset. When `content_format` filter is set, points to the matching artifact's URL (e.g. PMREM `.ktx2` for HDRI); otherwise the latest upload's URL. Null when no completed job/output is available.","type":["string","null"]},"version_count":{"description":"Number of upload versions, or the latest saved project revision number.","type":"number"},"view_url":{"description":"Standalone viewer page. Null for deployments (they ARE a website — use `url`) and for items with no artifact to view yet.","type":["string","null"]}},"required":["content_formats","content_type","created_at","description","identifier","is_public","password_protected","tags","thumbnail_url","title","updated_at","url","version_count"],"type":"object"},"ContentListResponse":{"additionalProperties":false,"properties":{"deleted":{"type":"number"},"has_more":{"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/ContentListItem"},"type":"array"},"limit":{"type":"number"},"page":{"type":"number"},"pages_count":{"type":"number"},"total":{"type":"number"}},"required":["deleted","has_more","items","limit","page","pages_count","total"],"type":"object"},"ContentType":{"description":"Semantic content type — what kind of asset this is (3D model, material, deployed website, HDRI environment).","enum":["3d-asset","material","deployment","hdri"],"type":"string"},"ContentVariant":{"anyOf":[{"additionalProperties":false,"properties":{"cdn_key":{"anyOf":[{"type":"string"},{"enum":[null,"hidden"],"type":["null","string"]}],"description":"CDN storage key. Null if not yet complete, \"hidden\" if paywalled."},"content_format":{"description":"File format","type":["string","null"]},"content_type":{"description":"Semantic content type","type":["string","null"]},"created_at":{"description":"ISO 8601 timestamp","type":"string"},"error":{"description":"Error message if the job failed","type":["string","null"]},"height":{"description":"User-facing pixel height. See `width`.","type":["number","null"]},"identifier":{"description":"Job identifier","type":"string"},"is_progressive":{"const":true,"description":"This variant supports progressive/streaming loading","type":"boolean"},"labels":{"description":"Named pointer labels with resolved URLs","items":{"$ref":"#/components/schemas/ContentLabel"},"type":"array"},"merge":{"description":"True for the experimental merged-mesh optimization variant.","type":"boolean"},"progress":{"description":"Progress percentage (0-100)","type":"number"},"progressive_size":{"description":"Total size in bytes of all LOD-0 chunks (initial download)","type":"number"},"size":{"description":"Output size in bytes","type":["number","null"]},"status":{"$ref":"#/components/schemas/JobStatus","description":"Current job status"},"status_message":{"description":"Human-readable status message","type":"string"},"technical_metadata":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummaryResponse"},{"$ref":"#/components/schemas/AssetMetadataDetailResponse"},{"type":"null"}]},"type":{"$ref":"#/components/schemas/JobType","description":"Job type: \"optimization\", \"deployment\", \"ai-material\", etc."},"updated_at":{"description":"ISO 8601 timestamp","type":"string"},"url":{"anyOf":[{"type":"string"},{"enum":[null,"hidden"],"type":["null","string"]}],"description":"Resolved CDN URL for the output file. Null if not yet complete. Website URL for deployments."},"usecase":{"description":"Optimization use case. Null for non-optimization variants or the default optimization.","enum":["product","world",null],"type":["string","null"]},"width":{"description":"Image width in pixels. Derived image variants report the source image's logical resolution, which may differ from the dimensions of the downloadable file. Null for non-image variants.","type":["number","null"]}},"required":["cdn_key","content_format","content_type","created_at","height","identifier","is_progressive","labels","progressive_size","size","status","type","updated_at","url","width"],"type":"object"},{"additionalProperties":false,"properties":{"cdn_key":{"anyOf":[{"type":"string"},{"enum":[null,"hidden"],"type":["null","string"]}],"description":"CDN storage key. Null if not yet complete, \"hidden\" if paywalled."},"content_format":{"description":"File format","type":["string","null"]},"content_type":{"description":"Semantic content type","type":["string","null"]},"created_at":{"description":"ISO 8601 timestamp","type":"string"},"error":{"description":"Error message if the job failed","type":["string","null"]},"height":{"description":"User-facing pixel height. See `width`.","type":["number","null"]},"identifier":{"description":"Job identifier","type":"string"},"is_progressive":{"const":false,"description":"This variant does not support progressive loading","type":"boolean"},"labels":{"description":"Named pointer labels with resolved URLs","items":{"$ref":"#/components/schemas/ContentLabel"},"type":"array"},"merge":{"description":"True for the experimental merged-mesh optimization variant.","type":"boolean"},"progress":{"description":"Progress percentage (0-100)","type":"number"},"size":{"description":"Output size in bytes","type":["number","null"]},"status":{"$ref":"#/components/schemas/JobStatus","description":"Current job status"},"status_message":{"description":"Human-readable status message","type":"string"},"technical_metadata":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummaryResponse"},{"$ref":"#/components/schemas/AssetMetadataDetailResponse"},{"type":"null"}]},"type":{"$ref":"#/components/schemas/JobType","description":"Job type: \"optimization\", \"deployment\", \"ai-material\", etc."},"updated_at":{"description":"ISO 8601 timestamp","type":"string"},"url":{"anyOf":[{"type":"string"},{"enum":[null,"hidden"],"type":["null","string"]}],"description":"Resolved CDN URL for the output file. Null if not yet complete. Website URL for deployments."},"usecase":{"description":"Optimization use case. Null for non-optimization variants or the default optimization.","enum":["product","world",null],"type":["string","null"]},"width":{"description":"Image width in pixels. Derived image variants report the source image's logical resolution, which may differ from the dimensions of the downloadable file. Null for non-image variants.","type":["number","null"]}},"required":["cdn_key","content_format","content_type","created_at","height","identifier","is_progressive","labels","size","status","type","updated_at","url","width"],"type":"object"}],"description":"A derived variant produced from an upload (optimization, deployment, etc)."},"ContentVersion":{"additionalProperties":false,"description":"A single version of the content, created by an upload or processing job.","properties":{"cdn_key":{"anyOf":[{"type":"string"},{"enum":[null,"hidden"],"type":["null","string"]}],"description":"CDN storage key. Null if upload not yet complete, \"hidden\" if paywalled."},"content_format":{"description":"File format (e.g. \"glb\", \"gltf\", \"fbx\")","type":["string","null"]},"content_type":{"description":"Semantic content type (e.g. \"3d-asset\", \"material\", \"deployment\")","type":["string","null"]},"created_at":{"description":"ISO 8601 timestamp","type":"string"},"error":{"description":"Error message if the job failed","type":["string","null"]},"height":{"description":"User-facing pixel height. See `width`.","type":["number","null"]},"identifier":{"description":"Job identifier of the upload","type":"string"},"labels":{"description":"Named pointer labels (e.g. \"latest\") with resolved URLs","items":{"$ref":"#/components/schemas/ContentLabel"},"type":"array"},"metadata":{"$ref":"#/components/schemas/JobMetadata","description":"Where this version came from — the change that produced it, and what ran it.\n\nOnly present when whoever created it supplied provenance, so treat it as optional everywhere. Visible to your team only; it is never part of a public listing."},"name":{"description":"Original filename","type":"string"},"preparing_optimizations":{"description":"Optimization jobs are being prepared for this version.","type":"boolean"},"progress":{"description":"Progress percentage (0-100)","type":"number"},"size":{"description":"File size in bytes","type":["number","null"]},"status":{"$ref":"#/components/schemas/JobStatus","description":"Current job status"},"status_message":{"description":"Human-readable status message","type":"string"},"technical_metadata":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummaryResponse"},{"$ref":"#/components/schemas/AssetMetadataDetailResponse"},{"type":"null"}]},"type":{"$ref":"#/components/schemas/JobType","description":"Job that produced this version. Older servers may omit this field."},"updated_at":{"description":"ISO 8601 timestamp","type":"string"},"url":{"anyOf":[{"type":"string"},{"enum":[null,"hidden"],"type":["null","string"]}],"description":"Resolved CDN URL for the uploaded file. Null if upload not yet complete."},"user":{"description":"Username of the uploader","type":"string"},"variants":{"description":"Derived variants (optimizations, deployments, AI generations)","items":{"$ref":"#/components/schemas/ContentVariant"},"type":"array"},"width":{"description":"Image width in pixels. Null for non-image content. Derived image variants report the source image's logical resolution, which may differ from the dimensions of the downloadable file.","type":["number","null"]}},"required":["cdn_key","content_format","content_type","created_at","height","identifier","labels","name","size","status","updated_at","url","user","variants","width"],"type":"object"},"DeleteContentResponse":{"additionalProperties":false,"description":"Response from moving a content item to Trash. Deletion immediately frees total storage capacity but does not refund monthly usage.","properties":{"identifier":{"type":"string"},"message":{"type":"string"}},"required":["identifier","message"],"type":"object"},"EnvironmentImageRef":{"anyOf":[{"type":"null"},{"const":"studio","type":"string"},{"additionalProperties":false,"properties":{"content_id":{"type":"string"},"url":{"type":"string"}},"required":["content_id"],"type":"object"}],"description":"Reference to an environment image used for scene lighting.\n\n- `null` — use the viewer's built-in environment.\n- `\"studio\"` — use the viewer's studio lighting preset.\n- `{ content_id, url? }` — use a specific HDRI content item. The server   resolves this to the latest PMREM artifact URL at read time. On write,   only `content_id` is required."},"GetContentDetailResponse":{"additionalProperties":false,"description":"Response for an uploaded item from GET /v1/content/:identifier","properties":{"colors_hex":{"description":"Dominant colors from AI analysis, hex format (e.g. `[\"#a83232\", \"#2e2e2e\"]`).","items":{"type":"string"},"type":"array"},"content_type":{"anyOf":[{"$ref":"#/components/schemas/ContentType"},{"type":"null"}],"description":"Semantic content type derived from the first version (e.g. \"3d-asset\", \"material\", \"deployment\")"},"copyrights":{"description":"Copyright strings collected from all job outputs","items":{"type":"string"},"type":"array"},"created_at":{"description":"ISO 8601 timestamp","type":"string"},"created_by":{"description":"Username of the content creator","type":["string","null"]},"description":{"description":"User-editable description","type":["string","null"]},"identifier":{"description":"Unique content identifier","type":"string"},"is_public":{"description":"Whether this content is publicly readable via the public discovery API. When true, anyone (authenticated or not) can fetch it.","type":"boolean"},"item_type":{"const":"content","description":"Uploaded content; omitted by older servers.","type":"string"},"moderation_status":{"description":"Content moderation status","enum":["ok","blocked"],"type":"string"},"name":{"description":"Original filename at first upload","type":"string"},"org":{"description":"Organization identifier","type":"string"},"password_protected":{"description":"Whether a password is required to view this content","type":"boolean"},"presentation":{"anyOf":[{"$ref":"#/components/schemas/PresentationV1"},{"type":"null"}],"description":"Viewer presentation settings (lighting, tone mapping, background). `null` when no custom presentation is configured."},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"description":"User-assigned tags"},"thumbnail_url":{"description":"Resolved CDN URL for the thumbnail image","type":["string","null"]},"title":{"description":"User-editable title","type":["string","null"]},"updated_at":{"description":"ISO 8601 timestamp — latest across content and all jobs","type":"string"},"versions":{"description":"Upload versions, newest first","items":{"$ref":"#/components/schemas/ContentVersion"},"type":"array"}},"required":["identifier","created_at","updated_at","created_by","org","name","title","description","thumbnail_url","content_type","copyrights","colors_hex","tags","password_protected","is_public","moderation_status","presentation","versions"],"type":"object"},"GetContentRequestQuery":{"additionalProperties":false,"properties":{"include_metadata":{"enum":["none","summary","full"],"type":"string"}},"type":"object"},"GetContentResponse":{"$ref":"#/components/schemas/GetContentDetailResponse"},"GltfAssetMetadata":{"additionalProperties":false,"properties":{"animationCount":{"type":"number"},"animations":{"items":{"$ref":"#/components/schemas/AssetAnimationMetadata"},"type":"array"},"asset":{"$ref":"#/components/schemas/AssetEmbeddedMetadata"},"collectorVersion":{"description":"Revision of the collector that measured these facts.","type":"number"},"defaultSceneIndex":{"type":["number","null"]},"extensionsRequired":{"items":{"type":"string"},"type":"array"},"extensionsUsed":{"items":{"type":"string"},"type":"array"},"geometry":{"$ref":"#/components/schemas/AssetGeometryMetadata"},"imageCount":{"type":"number"},"issues":{"items":{"type":"string"},"type":"array"},"jointCount":{"type":"number"},"loading":{"$ref":"#/components/schemas/AssetLoadingMetadata"},"materialCount":{"type":"number"},"materialVariantCount":{"type":"number"},"morphTargetCount":{"type":"number"},"resourceUsage":{"$ref":"#/components/schemas/AssetResourceUsage","description":"Absent on older collected records."},"sceneCount":{"type":"number"},"scenes":{"items":{"$ref":"#/components/schemas/AssetSceneMetadata"},"type":"array"},"schemaVersion":{"const":1,"description":"Metadata structure version.","type":"number"},"skinCount":{"type":"number"},"status":{"description":"Partial means some requested measurements could not be established.","enum":["complete","partial"],"type":"string"},"textureCount":{"type":"number"},"unit":{"const":"meter","description":"glTF coordinates use meters; this is a format convention, not a scale verification.","type":"string"}},"required":["schemaVersion","collectorVersion","status","issues","asset","sceneCount","defaultSceneIndex","scenes","geometry","materialCount","animationCount","animations","skinCount","jointCount","morphTargetCount","textureCount","imageCount","materialVariantCount","extensionsUsed","extensionsRequired","loading","unit"],"type":"object"},"JobMetadata":{"additionalProperties":{"description":"Your own keys."},"description":"Provenance for a job — what produced it. Optional on job creation; shown on the version row in the Cloud UI so an asset answers \"where did this come from?\".\n\nDeliberately NOT git-shaped. `source` names whatever system the input came from and `runner` whatever executed it; git is one `source.type` among many, and a DCC export or a hosted runner fits the same slots. The generic fields (`label`, `author`, `message`, `url`) are what the UI renders, so a producer nobody has written yet still displays something useful.","properties":{"client":{"additionalProperties":false,"description":"What talked to the API.","properties":{"name":{"type":"string"},"version":{"type":"string"}},"type":"object"},"runner":{"additionalProperties":false,"description":"What EXECUTED the job — a CI runner, a workstation, a service.","properties":{"actor":{"description":"Who or what set it off.","type":"string"},"event":{"description":"`\"push\"`, `\"pull_request\"`, `\"manual\"`, `\"schedule\"`, …","type":"string"},"id":{"description":"Run id within that system.","type":"string"},"type":{"description":"`\"github-actions\"`, `\"gitlab-ci\"`, `\"local\"`, … — open on purpose.","type":"string"},"url":{"description":"Link to the run.","type":"string"}},"type":"object"},"source":{"additionalProperties":{"description":"System-specific extras (branch, tag, repository, project, scene, …)."},"description":"Where the INPUT came from.","properties":{"author":{"description":"Display name of whoever authored the change. Email addresses are rejected.","type":"string"},"id":{"description":"Stable id within that system: a commit sha, an asset guid, a document id.","type":"string"},"label":{"description":"Short label for display when the specific fields don't tell the story.","type":"string"},"message":{"description":"One short line: a commit subject, an export note. Max 200 characters.","type":"string"},"type":{"description":"`\"git\"`, `\"unity\"`, `\"blender\"`, … — open on purpose.","type":"string"},"url":{"description":"Link back to it — the commit, the asset page, the document.","type":"string"}},"type":"object"}},"type":"object"},"JobStatus":{"enum":["new","pending","running","completed","failed","canceled","stalled"],"type":"string"},"JobType":{"anyOf":[{"type":"string"},{"enum":["upload","conversion","optimization","deployment","ai-material","ai-3d-asset","hdri-compress","splat-compress"],"type":"string"}]},"ListContentRequestQuery":{"additionalProperties":false,"properties":{"collection_id":{"description":"Filter by collection — returns only items belonging to the specified collection, ordered by sort_order","type":"string"},"content_format":{"$ref":"#/components/schemas/ContentFormat","description":"Filter by output content format (e.g. \"glb\", \"textures\"). When set, only items with a matching output are returned, and artifact data comes from that output."},"content_type":{"$ref":"#/components/schemas/ContentType"},"include_metadata":{"enum":["none","summary","full"],"type":"string"},"is_public":{"description":"When true, returns only publicly discoverable items","type":"boolean"},"limit":{"description":"Maximum items per page (default 20, max 100)","type":"number"},"offset":{"description":"Number of items to skip (alternative to page-based pagination)","type":"number"},"page":{"description":"1-based page number (default 1)","type":"number"},"search":{"description":"Full-text search across name, title, description, tags, and identifier","type":"string"},"sort_by":{"description":"Sort the full matching library before pagination (default updated_at).","enum":["name","size","updated_at","created_at"],"type":"string"},"sort_direction":{"description":"Sort direction; defaults to asc for name, desc otherwise. Unknown sizes sort last.","enum":["asc","desc"],"type":"string"},"tags":{"description":"Filter by tags (comma-separated)","type":"string"},"trash":{"description":"When true, returns items in the trash instead of active items","type":"boolean"},"types":{"description":"Filter by job types (comma-separated, e.g. \"upload,optimization,deployment\")","type":"string"}},"type":"object"},"ListPublicContentQuery":{"additionalProperties":false,"description":"Query parameters for GET /v1/public/content","properties":{"content_format":{"$ref":"#/components/schemas/ContentFormat","description":"Filter by content format"},"content_type":{"$ref":"#/components/schemas/ContentType","description":"Filter by content type"},"cursor":{"description":"Opaque cursor from a previous response. When omitted, returns the first page.","type":"string"},"include_metadata":{"enum":["none","summary","full"],"type":"string"},"include_protected":{"description":"Include password-protected items in the listing. Default: false (excluded). Opt-in for the niche \"teaser\" pattern where the owner wants the metadata publicly discoverable but viewing requires a password.","type":"boolean"},"limit":{"description":"Page size (default 20, max 100)","type":"number"},"org_id":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"description":"Filter to content owned by one or more Needle Cloud teams. Pass a single team id, or an array of ids to include items from any of the listed teams. Team ids are shown on the team's details page at `https://cloud.needle.tools/team/<id>`."},"search":{"description":"Substring match against identifier, title, description, and tags.","type":"string"}},"type":"object"},"ListPublicContentResponse":{"additionalProperties":false,"description":"Response from GET /v1/public/content","properties":{"items":{"items":{"$ref":"#/components/schemas/PublicContentItem"},"type":"array"},"next_cursor":{"description":"Cursor to fetch the next page; omitted when there are no more items.","type":"string"}},"required":["items"],"type":"object"},"ModelUnit":{"description":"Source-unit choice for a model. Defaults to `\"auto\"`.","enum":["auto","mm","cm","m","km"],"type":"string"},"MultipartAbortRequestBody":{"$ref":"#/components/schemas/MultipartUploadReference","description":"Abort a multipart upload and release its incomplete parts."},"MultipartAbortResponse":{"additionalProperties":false,"description":"Successful multipart abort.","properties":{"ok":{"const":true,"type":"boolean"}},"required":["ok"],"type":"object"},"MultipartCompleteRequestBody":{"additionalProperties":false,"description":"Complete a multipart upload using the ETag returned by each successful part PUT.","properties":{"key":{"type":"string"},"parts":{"items":{"additionalProperties":false,"properties":{"etag":{"type":"string"},"part_number":{"type":"number"}},"required":["part_number","etag"],"type":"object"},"type":"array"},"upload_id":{"type":"string"}},"required":["key","parts","upload_id"],"type":"object"},"MultipartCompleteResponse":{"additionalProperties":false,"description":"Response of `multipart/complete`.","properties":{"key":{"type":"string"},"ok":{"const":true,"type":"boolean"}},"required":["ok","key"],"type":"object"},"MultipartCreateResponse":{"additionalProperties":false,"description":"Response of multipart/create: retain both values for subsequent multipart operations.","properties":{"key":{"type":"string"},"upload_id":{"type":"string"}},"required":["upload_id","key"],"type":"object"},"MultipartPartUrlsRequestBody":{"additionalProperties":false,"description":"Request URLs for one-based part numbers in a previously created multipart upload.","properties":{"key":{"type":"string"},"part_numbers":{"items":{"type":"number"},"type":"array"},"upload_id":{"type":"string"}},"required":["key","part_numbers","upload_id"],"type":"object"},"MultipartPartUrlsResponse":{"additionalProperties":false,"description":"Response of `multipart/part-urls`: a presigned UploadPart url per requested part.","properties":{"urls":{"items":{"additionalProperties":false,"properties":{"part_number":{"type":"number"},"url":{"type":"string"}},"required":["part_number","url"],"type":"object"},"type":"array"}},"required":["urls"],"type":"object"},"MultipartUploadReference":{"additionalProperties":false,"description":"Identifiers returned by multipart/create. Pass both unchanged to later multipart calls.","properties":{"key":{"type":"string"},"upload_id":{"type":"string"}},"required":["upload_id","key"],"type":"object"},"PresentationHdriV1":{"additionalProperties":false,"description":"Presentation for HDRI content items. Carries metadata about how the HDRI should be used when consumed as the `skybox_image` of another content item's presentation — the backend resolver pulls these values and attaches them onto the consumer's resolved `skybox_image` ref, so a 3D-asset doesn't need to know or repeat them.\n\nDeliberately small for now (scene-geometry fields only). Tone mapping etc. stay off this type — HDRIs viewed standalone use the viewer's defaults.","properties":{"environment_height":{"description":"Height parameter for the engine's `GroundProjectedEnv` — the elevation the HDRI's source camera was at when the photo was taken. A larger value magnifies the downward part of the image (closer perspective); smaller flattens it. NOT a vertical ceiling for models. Default `5`. Range 0.1..50.","type":"number"},"environment_radius":{"description":"Radius of the visible HDRI projection dome (the sphere the HDRI is projected onto) in scene units (metres). Used by the engine's `GroundProjectedEnv` component. Default `30`. Range 0.5..1000.","type":"number"},"placement_radius":{"description":"Radius of the usable placement space around the origin where consumer models sit comfortably (the viewer's auto-fit clamp). Must be ≤ `environment_radius`. Default `15`. Range 0.5..1000.","type":"number"},"type":{"const":"hdri","type":"string"},"v":{"const":1,"type":"number"}},"required":["v","type"],"type":"object"},"PresentationV1":{"anyOf":[{"$ref":"#/components/schemas/PresentationViewerV1"},{"$ref":"#/components/schemas/PresentationHdriV1"}],"description":"Presentation settings — discriminated union on `type`.\n\n- `\"default\"` — for 3D-asset content items that USE environments.\n- `\"hdri\"`    — for HDRI content items, carrying metadata about how the                 HDRI should be used by consumer presentations.\n\nThe discriminator lets each variant carry only the fields that make sense for its content type, so a 3D asset's presentation doesn't have unused HDRI-only knobs and vice-versa."},"PresentationViewerV1":{"additionalProperties":false,"description":"Viewer presentation settings for a content item.\n\nControls how the asset appears when opened in the viewer — lighting, background, and tone mapping. All fields are optional; omitted fields fall back to the viewer's defaults.\n\n`v` is the schema version (always `1` for now). `type` is `\"default\"` — the content item's default presentation.","properties":{"background_blurriness":{"description":"Blurriness of the background image (0 = sharp, 1 = fully blurred).","type":"number"},"background_color":{"description":"Background color (`#RRGGBB`) when skybox is hidden.","type":["string","null"]},"background_intensity":{"description":"Intensity multiplier for the background image (0–1).","type":"number"},"background_rotation_y":{"description":"Y-axis rotation of the background image in degrees (0–360).","type":"number"},"environment_image":{"$ref":"#/components/schemas/EnvironmentImageRef","description":"Scene lighting environment."},"environment_intensity":{"description":"Intensity multiplier for the environment lighting (0.01–2).","type":"number"},"environment_rotation_y":{"description":"Y-axis rotation of the environment lighting in degrees (0–360).","type":"number"},"ground_distance":{"description":"Vertical offset (in scaled-world metres) applied AFTER the auto-fit + `model_scale` composition. Positive lifts the model off the ground; negative sinks it into the ground. Range -50..50, default 0. Only meaningful when `ground_projection` is true.","type":"number"},"ground_projection":{"description":"When `true`, project the background image as a ground plane under the model so it sits on the environment instead of floating against the skybox. Only meaningful when a background image is set.","type":"boolean"},"model_scale":{"description":"Uniform scale multiplier applied on top of the viewer's auto-fit when ground projection is on. 1 = auto-fit only (model fits the envelope as computed by the viewer); <1 shrinks the model further; >1 grows it beyond the envelope. Clamped to 0.01..100. Only meaningful when `ground_projection` is true.","type":"number"},"model_unit":{"$ref":"#/components/schemas/ModelUnit","description":"Source-unit choice for the model:\n- `\"auto\"` (default) — viewer auto-fits to the placement space.\n- `\"mm\" | \"cm\" | \"m\" | \"km\"` — declares the literal source unit; the   viewer applies the factor and renders the model at its TRUE   real-world size relative to the placement space (no auto-fit   clamp). `model_scale` is still applied on top for fine-tuning."},"skybox_image":{"$ref":"#/components/schemas/SkyboxImageRef","description":"Visible background / skybox. Use `\"\"` to hide."},"tone_mapping":{"$ref":"#/components/schemas/ToneMapping","description":"Tone-mapping algorithm. `\"default\"` keeps the viewer's built-in setting."},"tone_mapping_exposure":{"description":"Tone-mapping exposure multiplier (clamped to 0.1–4.0).","type":"number"},"type":{"const":"default","type":"string"},"v":{"const":1,"type":"number"}},"required":["v","type"],"type":"object"},"PublicContentDetail":{"additionalProperties":false,"description":"Full detail for a public content item — adds version information to the list shape.","properties":{"content_formats":{"description":"All formats available across versions (e.g. `[\"glb\"]` or `[\"glb\", \"textures\"]`).","items":{"$ref":"#/components/schemas/ContentFormat"},"type":"array"},"content_type":{"anyOf":[{"$ref":"#/components/schemas/ContentType"},{"type":"null"}],"description":"Semantic content type (e.g. `material`, `3d-asset`, `deployment`)."},"created_at":{"description":"ISO 8601 timestamp.","type":"string"},"description":{"type":["string","null"]},"embed_url":{"description":"Iframe-friendly embed URL. Drop into an `<iframe src=\"…\">` to embed the asset on a page. Omitted for `content_type === \"deployment\"` (use `url` instead).","type":["string","null"]},"identifier":{"type":"string"},"is_password_protected":{"description":"Whether this content requires a password to be viewed.","type":"boolean"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"technical_metadata":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummaryResponse"},{"$ref":"#/components/schemas/AssetMetadataDetailResponse"},{"type":"null"}]},"thumbnail_url":{"description":"URL for the thumbnail image, when one is available.","type":["string","null"]},"title":{"description":"Display name.","type":"string"},"updated_at":{"description":"ISO 8601 timestamp.","type":"string"},"url":{"description":"Linkable URL for this content. For deployments this is the live website URL; for other content types it points at the asset's entrypoint file. This is the URL to *reference* / link to. It is NOT a \"download the whole asset\" URL — progressive formats (glTF, USD, …) only fetch the entrypoint here; dependent files are resolved relatively by the consuming runtime.","type":["string","null"]},"versions":{"items":{"$ref":"#/components/schemas/PublicContentVersion"},"type":"array"},"view_url":{"description":"Standalone viewer URL. Open in a browser to view the asset interactively. Omitted for `content_type === \"deployment\"` (use `url` instead).","type":["string","null"]}},"required":["content_formats","content_type","created_at","description","identifier","is_password_protected","tags","thumbnail_url","title","updated_at","url","versions"],"type":"object"},"PublicContentItem":{"additionalProperties":false,"description":"A single content item in a public discovery listing.","properties":{"content_formats":{"description":"All formats available across versions (e.g. `[\"glb\"]` or `[\"glb\", \"textures\"]`).","items":{"$ref":"#/components/schemas/ContentFormat"},"type":"array"},"content_type":{"anyOf":[{"$ref":"#/components/schemas/ContentType"},{"type":"null"}],"description":"Semantic content type (e.g. `material`, `3d-asset`, `deployment`)."},"created_at":{"description":"ISO 8601 timestamp.","type":"string"},"description":{"type":["string","null"]},"embed_url":{"description":"Iframe-friendly embed URL. Drop into an `<iframe src=\"…\">` to embed the asset on a page. Omitted for `content_type === \"deployment\"` (use `url` instead).","type":["string","null"]},"identifier":{"type":"string"},"is_password_protected":{"description":"Whether this content requires a password to be viewed.","type":"boolean"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}]},"technical_metadata":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummaryResponse"},{"$ref":"#/components/schemas/AssetMetadataDetailResponse"},{"type":"null"}]},"thumbnail_url":{"description":"URL for the thumbnail image, when one is available.","type":["string","null"]},"title":{"description":"Display name.","type":"string"},"updated_at":{"description":"ISO 8601 timestamp.","type":"string"},"url":{"description":"Linkable URL for this content. For deployments this is the live website URL; for other content types it points at the asset's entrypoint file. This is the URL to *reference* / link to. It is NOT a \"download the whole asset\" URL — progressive formats (glTF, USD, …) only fetch the entrypoint here; dependent files are resolved relatively by the consuming runtime.","type":["string","null"]},"view_url":{"description":"Standalone viewer URL. Open in a browser to view the asset interactively. Omitted for `content_type === \"deployment\"` (use `url` instead).","type":["string","null"]}},"required":["identifier","title","description","tags","content_type","content_formats","thumbnail_url","created_at","updated_at","is_password_protected","url"],"type":"object"},"PublicContentVersion":{"additionalProperties":false,"description":"One version of a public content item. Each version exposes a single entrypoint URL — relative file references (e.g. `./model.bin`) are resolved automatically when fetched.","properties":{"content_format":{"anyOf":[{"$ref":"#/components/schemas/ContentFormat"},{"type":"null"}],"description":"File format (e.g. `glb`, `gltf`)."},"content_type":{"anyOf":[{"$ref":"#/components/schemas/ContentType"},{"type":"null"}],"description":"Semantic content type for this version (may differ from the item-level type for derived variants)."},"created_at":{"description":"ISO 8601 timestamp.","type":"string"},"entrypoint":{"description":"Filename of the main entrypoint within the version.","type":"string"},"height":{"description":"Pixel height. See `width`.","type":["number","null"]},"identifier":{"description":"Stable identifier for this version.","type":"string"},"md5":{"description":"Base64-encoded MD5 of the entrypoint file. Use for integrity / cache keys.","type":["string","null"]},"size":{"description":"Total size of the version in bytes.","type":["number","null"]},"technical_metadata":{"anyOf":[{"$ref":"#/components/schemas/AssetMetadataSummaryResponse"},{"$ref":"#/components/schemas/AssetMetadataDetailResponse"},{"type":"null"}]},"updated_at":{"description":"ISO 8601 timestamp — last time this version was touched.","type":"string"},"url":{"description":"URL to the entrypoint file — what consumers should load.","type":"string"},"width":{"description":"Pixel width for image content (HDRI / PNG / JPEG / WebP / KTX2). Null otherwise.","type":["number","null"]}},"required":["identifier","created_at","updated_at","entrypoint","url","size","md5","content_type","content_format","width","height"],"type":"object"},"RequestPresignedUrlRequestBody":{"additionalProperties":false,"properties":{"content-encoding":{"type":"string"},"content-md5":{"description":"Content md5 hash of the file to be uploaded","type":"string"},"content-type":{"description":"Content type of the file to be uploaded","type":"string"},"entrypoint":{"description":"The entrypoint for this whole upload. If multiple files are being uploaded this is always the same file since it's stored only once","type":"string"},"filename":{"type":"string"},"output_index":{"description":"Index of the registered output containing this file, starting at 0. Defaults to 0.","type":"number"}},"required":["filename","entrypoint","content-type","content-md5"],"type":"object"},"RequestPutUrlResponse":{"additionalProperties":false,"properties":{"path":{"type":"string"},"presigned_url":{"description":"Presigned url for uploading","type":"string"}},"required":["presigned_url"],"type":"object"},"RestoreContentResponse":{"additionalProperties":false,"description":"Response from restoring a content item from Trash. Restore requires enough available total storage for the item's files.","properties":{"identifier":{"type":"string"},"org":{"type":"string"},"restored":{"type":"boolean"}},"required":["identifier","org","restored"],"type":"object"},"SkyboxImageRef":{"anyOf":[{"type":"null"},{"const":"studio","type":"string"},{"additionalProperties":false,"properties":{"content_id":{"type":"string"},"environment_height":{"type":"number"},"environment_radius":{"type":"number"},"placement_radius":{"type":"number"},"url":{"type":"string"}},"required":["content_id"],"type":"object"},{"const":"","type":"string"}],"description":"Reference to a skybox (visible background) image.\n\nSame as  {@link  EnvironmentImageRef } , plus `\"\"` to explicitly hide the skybox (shows a solid background color instead).\n\nWhen `{ content_id }` points at an HDRI whose own presentation is `type: \"hdri\"`, the server-side resolver also attaches `environment_radius` / `placement_radius` on read. Consumers use these to size the ground projection without having to fetch the HDRI separately. Both fields are populated only on resolved reads; clients ignore them on write (the values live on the HDRI's own presentation, not the consumer's)."},"ToneMapping":{"description":"Tone-mapping mode. `\"default\"` means the viewer uses its built-in setting.","enum":["default","linear","agx"],"type":"string"},"UpdateContentRequestBody":{"additionalProperties":false,"description":"Request body for PATCH /v1/content/:identifier","properties":{"description":{"description":"Content description (max 512 chars)","type":"string"},"is_public":{"description":"When true, the content is discoverable and readable through the public content API without authentication.","type":"boolean"},"password":{"description":"Set or clear password protection (null = remove). Requires commercial license.","type":["string","null"]},"presentation":{"anyOf":[{"$ref":"#/components/schemas/PresentationV1"},{"type":"null"}],"description":"Viewer presentation settings (lighting, tone mapping, background). Pass `null` to clear; omit to leave unchanged."},"remove_thumbnail":{"description":"Thumbnail upload request — server returns presigned URL in response","type":"boolean"},"tags":{"description":"Comma-separated tags (max 512 chars total, each tag < 50 chars, max 40 tags)","type":["string","null"]},"title":{"description":"Content title (max 255 chars). An empty string clears it.","type":"string"},"upload_thumbnail":{"additionalProperties":false,"properties":{"content_md5":{"type":"string"},"content_size":{"type":"number"},"content_type":{"type":"string"},"name":{"type":"string"}},"required":["name","content_size","content_type","content_md5"],"type":"object"}},"type":"object"},"UpdateContentResponse":{"additionalProperties":false,"description":"Response from PATCH /v1/content/:identifier","properties":{"content_id":{"type":"string"},"upload_thumbnail":{"additionalProperties":false,"properties":{"content_md5":{"type":"string"},"content_size":{"type":"number"},"content_type":{"type":"string"},"put_url":{"type":"string"}},"required":["put_url","content_size","content_md5","content_type"],"type":"object"}},"required":["content_id"],"type":"object"},"UploadBeginRequestBody":{"additionalProperties":false,"properties":{"optimizations":{"description":"Whether the upload should receive the standard post-upload processing jobs (asset optimization and related derived formats). Defaults to true when absent. Pass false to store the files exactly as uploaded.","type":"boolean"},"outputs":{"items":{"$ref":"#/components/schemas/UploadOutput"},"type":"array"},"verify":{"description":"Opt in to best-effort storage verification. Defaults to false.","type":"boolean"}},"required":["outputs"],"type":"object"},"UploadBeginResponse":{"anyOf":[{"additionalProperties":false,"properties":{"content_id":{"description":"the view this upload belongs to","type":"string"},"done_url":{"type":"string"},"id":{"description":"id for this upload","type":"string"},"job_type":{"enum":["upload","conversion","deployment"],"type":"string"},"request_put_url":{"description":"Call to get a presigned url for uploading","type":"string"},"type":{"const":"start","type":"string"}},"required":["content_id","done_url","id","job_type","request_put_url","type"],"type":"object"},{"additionalProperties":false,"properties":{"content_id":{"description":"the view this upload belongs to","type":"string"},"edit_url":{"description":"Link to the asset's management page on Needle Cloud — open it in a browser to rename, share, or configure the asset. Use this when presenting \"the Needle Cloud page\" to a user; `url` points at the asset itself.","type":"string"},"id":{"description":"id for the existing upload","type":"string"},"job_type":{"enum":["upload","conversion","deployment"],"type":"string"},"type":{"const":"exists","type":"string"},"url":{"type":["string","null"]}},"required":["content_id","id","job_type","type","url"],"type":"object"}]},"UploadDoneRequestResponse":{"additionalProperties":false,"properties":{"content_id":{"type":"string"},"edit_url":{"description":"Link to the asset's management page on Needle Cloud — open it in a browser to rename, share, or configure the asset. Use this when presenting \"the Needle Cloud page\" to a user; `url` points at the asset itself.","type":"string"},"id":{"type":"string"},"type":{"$ref":"#/components/schemas/JobType","description":"Type is a string that can be one of the following values:    \"upload\" or \"optimization\""},"url":{"description":"This is the cdn or pages URL to the entrypoint file","type":["string","null"]}},"required":["content_id","id","type"],"type":"object"},"UploadFile":{"additionalProperties":false,"description":"Files registered for direct upload. content_md5 is the base64-encoded MD5 of the exact bytes to transfer.","properties":{"content_length":{"type":"number"},"content_md5":{"type":"string"},"content_type":{"type":"string"},"filename":{"type":"string"}},"required":["filename","content_length","content_type","content_md5"],"type":"object"},"UploadOutput":{"additionalProperties":false,"description":"An uploaded asset and its companion files.","properties":{"content_format":{"$ref":"#/components/schemas/ContentFormat","description":"Format of the content (e.g. \"glb\", \"textures\"). Optional for upload begin — server infers."},"content_type":{"$ref":"#/components/schemas/ContentType","description":"Semantic content type (e.g. \"material\", \"upload\"). Optional for upload begin — server infers."},"entrypoint":{"description":"Main file for this output","type":"string"},"files":{"items":{"$ref":"#/components/schemas/UploadFile"},"type":"array"}},"required":["entrypoint","files"],"type":"object"}},"securitySchemes":{"accessToken":{"bearerFormat":"Access Token (nc_*)","description":"Account or team access token. Create one with the permissions required by the operation, then send Authorization: Bearer YOUR_ACCESS_TOKEN. A team token selects its issuing team. Keep it secret; do not put it in URLs or browser-side application code.","scheme":"bearer","type":"http"},"logtoOAuth":{"description":"OAuth 2.0 access token for applications registered with Needle sign-in. Send it as Authorization: Bearer YOUR_ACCESS_TOKEN. Content reads require the read scope. For scripts and integrations, an account or team access token is the simplest starting point.","flows":{"authorizationCode":{"authorizationUrl":"https://auth.needle.tools/oidc/auth","scopes":{"delete":"Delete content (can be restored).","download":"Download files.","read":"Read content, jobs, and collections accessible to the user.","upload":"Upload new files.","write":"Create and update content metadata."},"tokenUrl":"https://auth.needle.tools/oidc/token"}},"type":"oauth2"}}},"info":{"contact":{"name":"Needle Tools","url":"https://needle.tools"},"description":"# Introduction\n\nDiscover public assets and read, upload, update, delete or restore your team's assets through the Needle Cloud REST API. This is the Cloud HTTP reference; the [Needle Engine API](https://engine.needle.tools/docs/api/latest) documents the engine runtime, not these endpoints. Download the [OpenAPI document](https://cloud.needle.tools/api/openapi.json) or browse [the interactive reference](https://cloud.needle.tools/api/docs).\n\n## Authentication\n\nPublic Content endpoints require no authentication. Team content reads require **read**, uploads require **upload**, metadata updates and restores require **write**, and team deletion requires **delete** (or **all**). Use a token with the permissions needed by your operation. Create a token from your [account](https://cloud.needle.tools/account) or team settings, and send it as `Authorization: Bearer YOUR_ACCESS_TOKEN`. Requests are restricted to the team associated with that token; a token does not grant access to other teams. Keep tokens out of URLs, client-side code and source control.\n\nInteractive applications can also use a Needle OAuth access token. For an OAuth session, use `x-needle-cloud-org` to select a team you belong to. A long-lived access token already selects its issuing team and does not need that header.\n\n## Query your team's assets\n\n`GET /v1/content` supports search, content type/format filters, tags, collections, and page- or offset-based pagination. Read `items`, `total` and `has_more` from the response; this endpoint does not use the public discovery cursor.\n\n```sh\ncurl 'https://cloud.needle.tools/api/v1/content?content_type=3d-asset&include_metadata=summary&limit=20' \\\n  -H \"Authorization: Bearer $NEEDLE_CLOUD_TOKEN\"\n```\n\nFor details and versions, call `GET /v1/content/{identifier}`. Each original upload and optimized variant has its own technical metadata. Request `include_metadata=summary` for compact measurements or `include_metadata=full` for the full collected facts. The default is `none` to keep responses small. A pending or failed collection is not evidence that the asset has no animations, extensions or geometry.\n\nExtension lists describe the selected file: an original glTF may use only core features while its optimized variants declare Draco, BasisU or progressive loading. LOD summaries contain per-level file counts, bytes and vertex/triangle ranges; they are separate from the main asset's geometry. `technical_metadata` describes the asset; `metadata` on an upload version describes its provenance.\n\nA missing or invalid token returns **401**. A token without read permission returns **403**. A detail lookup outside the token's team returns **404**.\n\n## JavaScript and TypeScript\n\nThe [needle-cloud npm package](https://www.npmjs.com/package/needle-cloud) provides typed helpers for these endpoints:\n\n```js\nimport { listContent, getContent } from 'needle-cloud';\n\nconst authToken = process.env.NEEDLE_CLOUD_TOKEN;\nif (!authToken) throw new Error('Set NEEDLE_CLOUD_TOKEN');\nconst page = await listContent({\n  authToken,\n  content_type: '3d-asset',\n  include_metadata: 'summary',\n});\nif (page.items.length) {\n  const asset = await getContent(page.items[0].identifier, {\n    authToken,\n    include_metadata: 'full',\n  });\n}\n```\n\nThe SDK uses the Needle Cloud API automatically. The OpenAPI document can also be used directly with HTTP clients and API tooling.\n\n## Upload, update, delete and restore\n\nUploads are a three-step protocol, not a multipart/form-data form:\n\n1. POST `/v1/upload/request` with file registration JSON and its required headers.\n2. If the response type is `start`, POST `/v1/upload/put` for each file and PUT its bytes to the returned URL. For large files, use the multipart operations instead.\n3. POST `/v1/upload/done` with the upload `id` header after all files succeed. If the first response type is `exists`, skip transfers and completion: Cloud has reused the source. Matching stored optimizations are reused as well.\n\nA single-file Node.js example (raw, uncompressed GLB):\n\n```js\nimport { readFile } from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nconst token = process.env.NEEDLE_CLOUD_TOKEN;\nif (!token) throw new Error('Set NEEDLE_CLOUD_TOKEN to a team token with upload permission');\nconst bytes = await readFile('./model.glb');\nconst md5 = createHash('md5').update(bytes).digest('base64');\nconst type = 'model/gltf-binary';\nasync function post(path, body, headers = {}) {\n  const r = await fetch('https://cloud.needle.tools/api' + path, {\n    method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', ...headers },\n    ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n  });\n  if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);\n  return r.json();\n}\nconst begin = await post('/v1/upload/request', { outputs: [{ entrypoint: 'model.glb', files: [{\n  filename: 'model.glb', content_length: bytes.length, content_type: type, content_md5: md5,\n}] }] }, { filename: 'model.glb', 'total-files': '1', 'total-files-size': String(bytes.length), triggered_by: 'cli', type: 'upload' });\nlet receipt = begin;\nif (begin.type === 'start') {\n  const target = await post('/v1/upload/put', { filename: 'model.glb', entrypoint: 'model.glb', 'content-type': type, 'content-md5': md5 }, { job_id: begin.id, filesize: String(bytes.length) });\n  const uploaded = await fetch(target.presigned_url, { method: 'PUT', body: bytes, headers: { 'Content-Type': type, 'Content-MD5': md5 } });\n  if (!uploaded.ok) throw new Error(`File upload failed: ${uploaded.status}`);\n  receipt = await post('/v1/upload/done', undefined, { id: begin.id });\n}\nconsole.log(receipt.content_id);\n```\n\nThe receipt confirms upload, not finished optimization. Request `getContent` to inspect available versions. To upload a new version of an existing asset, supply its identifier as `content_id` to beginUpload.\n\nMetadata uses `PATCH /v1/content/{identifier}` with a JSON body such as `{\"title\":\"Wooden crate\",\"tags\":\"wood,crate\"}`. File replacement uses a new upload instead. `DELETE /v1/content/{identifier}` moves content to Trash; `POST /v1/content/{identifier}/restore` restores it. See each operation for request schemas, permissions and examples. The [CLI reference](https://engine.needle.tools/docs/cloud/cli) offers higher-level upload and content commands.\n\n## Pagination\n\nFor Team Content, start with page=1 and a fixed limit. To fetch the next page, keep your filters and increment page. For example:\n\n```http\nGET /api/v1/content?content_type=3d-asset&limit=20&page=2\n```\n\nSend the same Authorization header on every request. You can instead pass offset=20&limit=20; offset takes precedence over page. Use total and pages_count to plan pagination. Stop when items is empty or has_more is false; an additional empty page can occur at the end. Results may change while uploads or edits are in progress.\n\nPublic Content uses cursors instead. Start without cursor, then pass the returned next_cursor with the same filters and limit:\n\n```http\nGET /api/v1/public/content?content_type=3d-asset&limit=20&cursor=RETURNED_CURSOR\n```\n\nReplace RETURNED_CURSOR with the URL-encoded next_cursor value. Stop when next_cursor is absent. Public content responses do not contain the team pagination fields.\n\n## Reading responses and errors\n\nThe operations below include example success responses. Example identifiers and file URLs are illustrative. A technical_metadata object with status pending and summary null means collection has not finished; it does not mean the asset lacks geometry or animation. Metadata can also be null when unavailable, and is omitted unless requested.\n\nMost successful responses use application/json; cancelUpload returns text. Error responses can contain message text or upload-admission JSON; do not assume every response is JSON or insert an error body as HTML. Check the HTTP status first. For example, with the same authToken as above:\n\n```js\nconst response = await fetch(\n  'https://cloud.needle.tools/api/v1/content?limit=20',\n  { headers: { Authorization: `Bearer ${authToken}` } },\n);\nif (!response.ok) {\n  throw new Error(`HTTP ${response.status}: ${await response.text()}`);\n}\nconst page = await response.json();\n```\n\nFor 400, correct the query parameters. For 401, check the token and expiry; for 403, check the permission required by the operation. A 404 means the requested item is missing or inaccessible. For 500, retry with a delay; contact support if it persists. Gateways may return other error formats, so treat the status as authoritative and the body as diagnostic text.\n\nLast updated on November 1, 2026.","title":"Needle Cloud API","version":"1.0.0","x-last-updated":"2026-11-01T13:47:54.783Z"},"openapi":"3.1.0","paths":{"/v1/content":{"get":{"description":"Lists content accessible to the authenticated team, including private assets. Filter by search, type, format, tags or collection. Uses page/offset pagination; offset takes precedence over page when both are supplied. The response includes items, total, pages_count and has_more.","operationId":"listContent","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Technical metadata expansion for the selected files. Default none; summary is compact, while full may substantially increase response size.","in":"query","name":"include_metadata","required":false,"schema":{"default":"none","enum":["none","summary","full"],"type":"string"}},{"description":"Search identifiers, names, titles, descriptions and tags.","in":"query","name":"search","required":false,"schema":{"type":"string"}},{"description":"Filter by asset type.","in":"query","name":"content_type","required":false,"schema":{"$ref":"#/components/schemas/ContentType"}},{"description":"Filter by an available output format. Returned asset URLs select the matching format.","in":"query","name":"content_format","required":false,"schema":{"$ref":"#/components/schemas/ContentFormat"}},{"description":"Comma-separated tags.","in":"query","name":"tags","required":false,"schema":{"type":"string"}},{"description":"Comma-separated processing types, for example upload,optimization,deployment.","in":"query","name":"types","required":false,"schema":{"type":"string"}},{"description":"Return items in this collection, in collection order.","in":"query","name":"collection_id","required":false,"schema":{"type":"string"}},{"description":"When true, include only publicly discoverable items from your team.","in":"query","name":"is_public","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"When true, list items in the trash instead of active content.","in":"query","name":"trash","required":false,"schema":{"default":false,"type":"boolean"}},{"description":"One-based page number. Ignored when offset is supplied.","in":"query","name":"page","required":false,"schema":{"default":1,"minimum":1,"type":"integer"}},{"description":"Maximum number of items per page.","in":"query","name":"limit","required":false,"schema":{"default":20,"maximum":100,"minimum":1,"type":"integer"}},{"description":"Number of items to skip; takes precedence over page.","in":"query","name":"offset","required":false,"schema":{"minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":{"deleted":0,"has_more":false,"items":[{"content_formats":["glb"],"content_type":"3d-asset","created_at":"2026-10-23T08:00:00.000Z","description":"A chair for an interior scene.","identifier":"0123456789abcdef0123456789abcdef","is_public":false,"password_protected":false,"tags":["furniture"],"technical_metadata":{"collected_at":null,"collector_version":4,"status":"pending","summary":null,"verification":"provisional"},"thumbnail_url":null,"title":"Studio chair","updated_at":"2026-10-23T08:00:00.000Z","url":"https://example.com/models/chair/model.glb","version_count":1}],"limit":20,"page":1,"pages_count":1,"total":1},"schema":{"$ref":"#/components/schemas/ContentListResponse"}}},"description":"Content accessible to your team."},"400":{"content":{"text/html":{"example":"include_metadata must be none, summary, or full","schema":{"type":"string"}}},"description":"Invalid query parameters."},"401":{"content":{"text/html":{"example":"Unauthorized","schema":{"type":"string"}}},"description":"A valid access token is required."},"403":{"content":{"text/html":{"example":"Forbidden: missing permission","schema":{"type":"string"}}},"description":"The token does not grant read permission."},"500":{"content":{"text/html":{"example":"Failed to list content","schema":{"type":"string"}}},"description":"The request could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["read"]}],"summary":"List your team's content","tags":["Team Content"]}},"/v1/content/{identifier}":{"delete":{"description":"Move the content and its associated versions to Trash. Requires delete permission for team content, or write permission for personal content; an all grant also permits it. The alternative OAuth requirements below correspond to these different account contexts, not interchangeable permissions for team deletion. Total storage capacity is released, but monthly upload usage is not refunded. Content is retained in Trash for 31 days unless already permanently removed. Use restoreContent to recover it when enough total storage is available.","operationId":"deleteContent","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Content identifier returned as content_id by upload operations.","in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"identifier":"ASSET_ID","message":"Content deleted successfully"},"schema":{"$ref":"#/components/schemas/DeleteContentResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["delete"]},{"logtoOAuth":["write"]}],"summary":"Move an asset to Trash","tags":["Team Content"]},"get":{"description":"Returns an uploaded item with its original versions, derived variants, URLs, processing state and requested technical metadata. A missing or inaccessible item returns 404.","operationId":"getContent","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Stable identifier of the content item.","in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"description":"Technical metadata expansion for the selected files. Default none; summary is compact, while full may substantially increase response size.","in":"query","name":"include_metadata","required":false,"schema":{"default":"none","enum":["none","summary","full"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"colors_hex":[],"content_type":"3d-asset","copyrights":[],"created_at":"2026-10-23T08:00:00.000Z","created_by":"Example creator","description":"A chair for an interior scene.","identifier":"0123456789abcdef0123456789abcdef","is_public":false,"item_type":"content","moderation_status":"ok","name":"model.glb","org":"example-team","password_protected":false,"presentation":null,"tags":["furniture"],"thumbnail_url":null,"title":"Studio chair","updated_at":"2026-10-23T08:00:00.000Z","versions":[{"cdn_key":"example-version","content_format":"glb","content_type":"3d-asset","created_at":"2026-10-23T08:00:00.000Z","height":null,"identifier":"fedcba9876543210fedcba9876543210","labels":[],"name":"model.glb","size":1048576,"status":"completed","technical_metadata":{"collected_at":null,"collector_version":4,"status":"pending","summary":null,"verification":"provisional"},"type":"upload","updated_at":"2026-10-23T08:00:00.000Z","url":"https://example.com/models/chair/model.glb","user":"Example creator","variants":[],"width":null}]},"schema":{"$ref":"#/components/schemas/GetContentResponse"}}},"description":"Content accessible to your team."},"400":{"content":{"text/html":{"example":"include_metadata must be none, summary, or full","schema":{"type":"string"}}},"description":"Invalid query parameters."},"401":{"content":{"text/html":{"example":"Unauthorized","schema":{"type":"string"}}},"description":"A valid access token is required."},"403":{"content":{"text/html":{"example":"Forbidden: missing permission","schema":{"type":"string"}}},"description":"The token does not grant read permission."},"404":{"content":{"text/html":{"example":"Not found","schema":{"type":"string"}}},"description":"No matching content is accessible to this team."},"500":{"content":{"text/html":{"example":"Failed to get content detail","schema":{"type":"string"}}},"description":"The request could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["read"]}],"summary":"Get content and its versions","tags":["Team Content"]},"patch":{"description":"Update only the properties supplied. Empty title or description clears the value; tags replace the existing comma-separated list, and null clears tags. Setting a password requires a commercial license; null or an empty password removes protection. Presentation may be null to reset viewer settings. To replace the model file, begin a new upload using this content identifier instead. A thumbnail request returns a put_url: upload JPEG, PNG or WebP bytes to it, up to 5 MiB, using the returned content type and base64 MD5. This PATCH accepts JSON, not file bytes or multipart/form-data.","operationId":"updateContent","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Content identifier returned as content_id by upload operations.","in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"description":"Updated asset description","tags":"wood,crate","title":"Wooden crate"},"schema":{"$ref":"#/components/schemas/UpdateContentRequestBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"content_id":"ASSET_ID"},"schema":{"$ref":"#/components/schemas/UpdateContentResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["write"]}],"summary":"Update asset metadata and viewer settings","tags":["Team Content"]}},"/v1/content/{identifier}/restore":{"post":{"description":"Restore content and versions moved to Trash with it. Requires write permission and enough available total storage. Previously deleted individual versions are not restored. A 400 response means the content is not in Trash, 402 means insufficient storage, and 404 means it is missing or inaccessible. No request body is required.","operationId":"restoreContent","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Content identifier returned as content_id by upload operations.","in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"identifier":"ASSET_ID","org":"TEAM_ID","restored":true},"schema":{"$ref":"#/components/schemas/RestoreContentResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"402":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Insufficient storage or plan allowance."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["write"]}],"summary":"Restore an asset from Trash","tags":["Team Content"]}},"/v1/public/content":{"get":{"description":"Returns a paginated list of publicly available content items. Anonymous — no auth required.\n\nPagination is cursor-based: when `next_cursor` is present in the response, pass it back as `cursor` to fetch the next page.\n\nRequested technical metadata is null for password-protected items. Requesting metadata does not unlock an item.","operationId":"listPublicContent","parameters":[{"description":"Omitted by default. Request compact summary or full technical metadata; full expansion may produce large responses.","in":"query","name":"include_metadata","required":false,"schema":{"default":"none","enum":["none","summary","full"],"type":"string"}},{"description":"Filter to a single semantic content type.","in":"query","name":"content_type","required":false,"schema":{"$ref":"#/components/schemas/ContentType"}},{"description":"Filter to a single file format (e.g. `glb`, `usdz`).","in":"query","name":"content_format","required":false,"schema":{"$ref":"#/components/schemas/ContentFormat"}},{"description":"Filter to content owned by one or more Needle Cloud teams. Pass a single team ID or comma-separated IDs. Find a team ID on its details page at https://cloud.needle.tools/team/<id>.","in":"query","name":"org_id","required":false,"schema":{"type":"string"}},{"description":"Substring/exact match against identifier, name, title, description, and tags.","in":"query","name":"search","required":false,"schema":{"type":"string"}},{"description":"Page size. Defaults to 20, max 100.","in":"query","name":"limit","required":false,"schema":{"default":20,"maximum":100,"minimum":1,"type":"integer"}},{"description":"Opaque cursor returned by a previous response. Omit for the first page.","in":"query","name":"cursor","required":false,"schema":{"type":"string"}},{"description":"If `true`, also include password-protected items (which a consumer would need to unlock separately). Default `false`.","in":"query","name":"include_protected","required":false,"schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"example":{"items":[{"content_formats":["glb"],"content_type":"3d-asset","created_at":"2026-10-23T08:00:00.000Z","description":"A chair for an interior scene.","identifier":"0123456789abcdef0123456789abcdef","is_password_protected":false,"tags":["furniture"],"technical_metadata":{"collected_at":null,"collector_version":4,"status":"pending","summary":null,"verification":"provisional"},"thumbnail_url":null,"title":"Studio chair","updated_at":"2026-10-23T08:00:00.000Z","url":"https://example.com/models/chair/model.glb"}]},"schema":{"$ref":"#/components/schemas/ListPublicContentResponse"}}},"description":"Page of public content items."},"400":{"content":{"text/html":{"example":"include_metadata must be none, summary, or full","schema":{"type":"string"}}},"description":"Invalid query parameters."},"500":{"content":{"text/html":{"example":"Failed to list public content","schema":{"type":"string"}}},"description":"The request could not be completed. Try again later."}},"summary":"List public content items","tags":["Public Content"]}},"/v1/public/content/{identifier}":{"get":{"description":"Returns the full detail (versions, URLs, password-protected flag) for one publicly available content item.\n\nAnonymous — no auth required.\n\nRequested technical metadata is null for password-protected items. Requesting metadata does not unlock an item.","operationId":"getPublicContent","parameters":[{"description":"Omitted by default. Request compact summary or full technical metadata; full expansion may produce large responses.","in":"query","name":"include_metadata","required":false,"schema":{"default":"none","enum":["none","summary","full"],"type":"string"}},{"description":"The content item's stable identifier.","in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"content_formats":["glb"],"content_type":"3d-asset","created_at":"2026-10-23T08:00:00.000Z","description":"A chair for an interior scene.","identifier":"0123456789abcdef0123456789abcdef","is_password_protected":false,"tags":["furniture"],"technical_metadata":{"collected_at":null,"collector_version":4,"status":"pending","summary":null,"verification":"provisional"},"thumbnail_url":null,"title":"Studio chair","updated_at":"2026-10-23T08:00:00.000Z","url":"https://example.com/models/chair/model.glb","versions":[{"content_format":"glb","content_type":"3d-asset","created_at":"2026-10-23T08:00:00.000Z","entrypoint":"model.glb","height":null,"identifier":"fedcba9876543210fedcba9876543210","md5":null,"size":1048576,"technical_metadata":{"collected_at":null,"collector_version":4,"status":"pending","summary":null,"verification":"provisional"},"updated_at":"2026-10-23T08:00:00.000Z","url":"https://example.com/models/chair/model.glb","width":null}]},"schema":{"$ref":"#/components/schemas/PublicContentDetail"}}},"description":"The public content item with full version detail."},"400":{"content":{"text/html":{"example":"include_metadata must be none, summary, or full","schema":{"type":"string"}}},"description":"Invalid query parameters."},"404":{"content":{"text/html":{"example":"Not found","schema":{"type":"string"}}},"description":"No publicly available item with this identifier."},"500":{"content":{"text/html":{"example":"Failed to get public content","schema":{"type":"string"}}},"description":"The request could not be completed. Try again later."}},"summary":"Get a single public content item","tags":["Public Content"]}},"/v1/upload/cancel":{"post":{"description":"Cancel the overall upload using its id header. Completed uploads cannot be canceled. Abort incomplete multipart transfers separately with abortMultipartUpload.","operationId":"cancelUpload","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Upload id returned by beginUpload.","in":"header","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"text/html":{"example":"upload canceled","schema":{"type":"string"}}},"description":"Upload canceled."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"409":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Upload completion conflicts with its current state or file verification."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["upload"]}],"summary":"Cancel an active upload","tags":["Uploads"]}},"/v1/upload/done":{"post":{"description":"Call only after all registered files have been successfully transferred. Send the upload id in the id header, not a JSON body. Standard optimization is scheduled unless beginUpload disabled it; completion confirms the source upload, not finished optimization. Read getContent for current versions and processing status. Do not call this for a type=exists response.","operationId":"completeUpload","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Upload id returned by beginUpload.","in":"header","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadDoneRequestResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"402":{"content":{"application/json":{"example":{"error":"Upload limit reached"},"schema":{"additionalProperties":true,"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Insufficient storage or plan allowance."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"409":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Upload completion conflicts with its current state or file verification."},"410":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Upload expired or is no longer active. Start a new upload."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["upload"]}],"summary":"Complete an upload","tags":["Uploads"]}},"/v1/upload/multipart/abort":{"post":{"description":"Release the incomplete parts for this file. Use cancelUpload separately to cancel the overall Cloud upload.","operationId":"abortMultipartUpload","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Upload id returned by beginUpload.","in":"header","name":"job_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"key":"RETURNED_KEY","upload_id":"MULTIPART_ID"},"schema":{"$ref":"#/components/schemas/MultipartAbortRequestBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartAbortResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["upload"]}],"summary":"Abort a multipart file transfer","tags":["Uploads"]}},"/v1/upload/multipart/complete":{"post":{"description":"Submit each uploaded part number and its ETag, in ascending part-number order. This assembles one file; it does not complete the overall Cloud upload. After every registered file succeeds, call completeUpload.","operationId":"completeMultipartUpload","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Upload id returned by beginUpload.","in":"header","name":"job_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"key":"RETURNED_KEY","parts":[{"etag":"\"RETURNED_ETAG\"","part_number":1}],"upload_id":"MULTIPART_ID"},"schema":{"$ref":"#/components/schemas/MultipartCompleteRequestBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartCompleteResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"402":{"content":{"application/json":{"example":{"error":"Upload limit reached"},"schema":{"additionalProperties":true,"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Insufficient storage or plan allowance."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"410":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Upload expired or is no longer active. Start a new upload."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["upload"]}],"summary":"Finish a multipart file transfer","tags":["Uploads"]}},"/v1/upload/multipart/create":{"post":{"description":"Large-file alternative to requestUploadUrl. Use the same registered file details and headers. Save upload_id and key exactly as returned, then request part URLs. This operation accepts JSON, not multipart/form-data.","operationId":"createMultipartUpload","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Upload id returned by beginUpload.","in":"header","name":"job_id","required":true,"schema":{"type":"string"}},{"description":"Byte length of this file, matching its registered content_length.","in":"header","name":"filesize","required":true,"schema":{"type":"string"}},{"description":"Optional integer percentage from 0 through 100.","in":"header","name":"progress","required":false,"schema":{"type":"string"}},{"description":"Optional upload status text.","in":"header","name":"status_message","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"content-md5":"BASE64_MD5_OF_FILE","content-type":"model/gltf-binary","entrypoint":"model.glb","filename":"model.glb"},"schema":{"$ref":"#/components/schemas/RequestPresignedUrlRequestBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"key":"RETURNED_KEY","upload_id":"MULTIPART_ID"},"schema":{"$ref":"#/components/schemas/MultipartCreateResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"402":{"content":{"application/json":{"example":{"error":"Upload limit reached"},"schema":{"additionalProperties":true,"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Insufficient storage or plan allowance."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"410":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Upload expired or is no longer active. Start a new upload."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["upload"]}],"summary":"Start a multipart file transfer","tags":["Uploads"]}},"/v1/upload/multipart/part-urls":{"post":{"description":"Request between 1 and 10000 one-based part numbers, each from 1 through 10000. PUT each raw part to its returned URL; do not send the Cloud bearer token. Use parts of at least 5 MiB except the final part, and no more than 10000 parts. Send Content-MD5 as the base64 MD5 of that part for integrity checking. Retain the ETag response header for each successful PUT, including any quotes. Use completeMultipartUpload after all parts succeed.","operationId":"getMultipartPartUrls","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Upload id returned by beginUpload.","in":"header","name":"job_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"key":"RETURNED_KEY","part_numbers":[1,2],"upload_id":"MULTIPART_ID"},"schema":{"$ref":"#/components/schemas/MultipartPartUrlsRequestBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultipartPartUrlsResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"402":{"content":{"application/json":{"example":{"error":"Upload limit reached"},"schema":{"additionalProperties":true,"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Insufficient storage or plan allowance."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"410":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Upload expired or is no longer active. Start a new upload."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["upload"]}],"summary":"Get multipart part upload URLs","tags":["Uploads"]}},"/v1/upload/put":{"post":{"description":"Request a URL for one previously registered file. The filename, size, MIME type and base64 MD5 must match its beginUpload registration. PUT the raw file bytes to presigned_url with Content-Type and Content-MD5 matching the request, plus Content-Encoding if supplied. Do not send your Cloud bearer token to that URL. The storage PUT is separate from this JSON operation. After all files are uploaded, call completeUpload.","operationId":"requestUploadUrl","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Upload id returned by beginUpload.","in":"header","name":"job_id","required":true,"schema":{"type":"string"}},{"description":"Byte length of this file, matching its registered content_length.","in":"header","name":"filesize","required":true,"schema":{"type":"string"}},{"description":"Optional integer percentage from 0 through 100.","in":"header","name":"progress","required":false,"schema":{"type":"string"}},{"description":"Optional upload status text.","in":"header","name":"status_message","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"content-md5":"BASE64_MD5_OF_FILE","content-type":"model/gltf-binary","entrypoint":"model.glb","filename":"model.glb"},"schema":{"$ref":"#/components/schemas/RequestPresignedUrlRequestBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"presigned_url":"https://example.com/signed-upload-url"},"schema":{"$ref":"#/components/schemas/RequestPutUrlResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"402":{"content":{"application/json":{"example":{"error":"Upload limit reached"},"schema":{"additionalProperties":true,"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Insufficient storage or plan allowance."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"410":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Upload expired or is no longer active. Start a new upload."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["upload"]}],"summary":"Get a direct file upload URL","tags":["Uploads"]}},"/v1/upload/request":{"post":{"description":"Register file names, byte lengths, MIME types and base64 MD5 hashes before transferring bytes. Requires a team-scoped token with upload permission; personal-account uploads are not accepted. JSON only: do not send multipart/form-data. For type=start, request file PUT URLs, upload every file and call completeUpload. For type=exists, Cloud reused an existing upload: do not transfer bytes or call completeUpload. Matching stored optimization results are reused. Repeating a request can still create a new source version; pass content_id or name with an existing identifier to keep updating the same asset. optimizations defaults to true; processing continues asynchronously after upload completion.","operationId":"beginUpload","parameters":[{"description":"For OAuth sessions, select a team you belong to. Not needed with a long-lived access token, which already selects its issuing team.","in":"header","name":"x-needle-cloud-org","required":false,"schema":{"type":"string"}},{"description":"Number of registered files, from 1 through 10000.","in":"header","name":"total-files","required":true,"schema":{"type":"string"}},{"description":"Total byte length of registered files; a positive integer encoded as text.","in":"header","name":"total-files-size","required":true,"schema":{"type":"string"}},{"description":"URL-encoded primary filename, for example model.glb.","in":"header","name":"filename","required":true,"schema":{"type":"string"}},{"description":"Use cli for scripts or web for browser clients.","in":"header","name":"triggered_by","required":true,"schema":{"enum":["cli","web"],"type":"string"}},{"description":"Upload kind. Use upload for standalone assets.","in":"header","name":"type","required":false,"schema":{"default":"upload","enum":["upload","deployment","conversion"],"type":"string"}},{"description":"URL-encoded asset name or an existing content identifier to update.","in":"header","name":"name","required":false,"schema":{"type":"string"}},{"description":"Existing content identifier to append a source version to.","in":"header","name":"content_id","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"optimizations":true,"outputs":[{"entrypoint":"model.glb","files":[{"content_length":1024,"content_md5":"BASE64_MD5_OF_FILE","content_type":"model/gltf-binary","filename":"model.glb"}]}]},"schema":{"$ref":"#/components/schemas/UploadBeginRequestBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"examples":{"exists":{"value":{"content_id":"ASSET_ID","id":"UPLOAD_ID","job_type":"upload","type":"exists","url":"https://example.com/model.glb"}},"start":{"value":{"content_id":"ASSET_ID","done_url":"https://cloud.needle.tools/api/v1/upload/done","id":"UPLOAD_ID","job_type":"upload","request_put_url":"https://cloud.needle.tools/api/v1/upload/put","type":"start"}}},"schema":{"$ref":"#/components/schemas/UploadBeginResponse"}}},"description":"Operation completed."},"400":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Invalid request, file registration, or upload identifiers."},"401":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing or invalid authentication."},"402":{"content":{"application/json":{"example":{"error":"Upload limit reached"},"schema":{"additionalProperties":true,"type":"object"}},"text/html":{"schema":{"type":"string"}}},"description":"Insufficient storage or plan allowance."},"403":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Missing permission or operation not permitted for this content."},"404":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Content or upload not found in your team."},"410":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"Upload expired or is no longer active. Start a new upload."},"500":{"content":{"text/html":{"schema":{"type":"string"}}},"description":"The operation could not be completed."}},"security":[{"accessToken":[]},{"logtoOAuth":["upload"]}],"summary":"Begin an asset upload","tags":["Uploads"]}}},"servers":[{"url":"https://cloud.needle.tools/api","description":"This deployment"}],"tags":[{"description":"Anonymous discovery of content that has been published publicly. No authentication required.","name":"Public Content"},{"description":"Read, update, move to Trash and restore content in your team. Each operation lists its required permissions.","name":"Team Content"},{"description":"Authenticated file registration, direct single-PUT or multipart transfer, completion and cancellation. File bytes go to returned upload URLs, not the JSON endpoints.","name":"Uploads"}]}