{"openapi":"3.1.0","info":{"title":"TechArt Utils API","version":"1.0.0","description":"REST API for TechArt Utils workspaces, assets, uploads and server-side conversions. Authenticate with a Firebase ID token (`Authorization: Bearer <idToken>`) or a workspace API key (`Authorization: Bearer tau_…`)."},"servers":[{"url":"https://0.0.0.0:8080/api/v1"}],"tags":[{"name":"Meta","description":"Service health and the machine-readable API description."},{"name":"Account","description":"The signed-in user and available plans."},{"name":"Workspaces","description":"Workspaces own all saved data. Every user has a personal workspace and can create team workspaces."},{"name":"Members","description":"Membership, roles and email invites. Roles: viewer < editor < admin < owner."},{"name":"Folders","description":"Organize assets into a folder tree."},{"name":"Assets","description":"Files saved in a workspace (models, textures, shaders…) and their versions."},{"name":"Uploads","description":"Direct-to-storage uploads using presigned URLs, including multipart for large files."},{"name":"Comments","description":"Discussion on assets, optionally pinned to a point on a model or image."},{"name":"Sharing","description":"Read-only links that open an asset without an account."},{"name":"Activity","description":"The workspace audit log."},{"name":"API keys","description":"Workspace-scoped keys for scripts, CI and DCC plugins."},{"name":"Jobs","description":"Server-side conversions such as FBX export. Currently unavailable."},{"name":"Billing","description":"Plans and subscriptions (Stripe). Only owners can change the plan."}],"paths":{"/health":{"get":{"operationId":"health","tags":["Meta"],"summary":"Health check","security":[],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"version":{"type":"string"},"time":{"type":"string"},"storage":{"type":"boolean"}},"required":["ok","version","time","storage"],"additionalProperties":false}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApi","tags":["Meta"],"summary":"OpenAPI document","description":"The OpenAPI 3.1 description of this API. Import it into Postman, Insomnia or a client generator.","security":[],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}}}}}},"/me":{"get":{"operationId":"getMe","tags":["Account"],"summary":"Get the current user","description":"Returns the signed-in user and every workspace they belong to. On first call this also creates the user record and their personal workspace.","security":[{"firebaseIdToken":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"uid":{"type":"string"},"email":{"type":["string","null"]},"displayName":{"type":["string","null"]},"photoURL":{"type":["string","null"]}},"required":["uid","email","displayName","photoURL"],"additionalProperties":false},"workspaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["personal","team"]},"plan":{"type":"string","enum":["free","pro","studio"]},"icon":{"anyOf":[{"type":"string","maxLength":200000,"pattern":"^data:image\\/(png|jpeg|webp|gif);base64,[A-Za-z0-9+/=]+$"},{"type":"null"}],"description":"Workspace icon as an image data URL, or null for the default letter tile"},"role":{"type":"string","enum":["viewer","editor","admin","owner"]}},"required":["id","name","type","plan","icon","role"],"additionalProperties":false}},"lastWorkspaceId":{"type":["string","null"]},"storageEnabled":{"type":"boolean","description":"False when the server has no object storage configured; uploads and downloads are unavailable"},"emailEnabled":{"type":"boolean","description":"True when the server emails invites; otherwise invite links must be shared manually"}},"required":["user","workspaces","lastWorkspaceId","storageEnabled","emailEnabled"],"additionalProperties":false}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateMe","tags":["Account"],"summary":"Update the current user","security":[{"firebaseIdToken":[]}],"parameters":[],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"type":"string","minLength":1,"maxLength":80},"lastWorkspaceId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}}}}}}},"/plans":{"get":{"operationId":"listPlans","tags":["Account"],"summary":"List plans and their limits","security":[],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"limits":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"number"}},"features":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["id","label","limits","features"],"additionalProperties":false}}}}}}}},"/workspaces":{"post":{"operationId":"createWorkspace","tags":["Workspaces"],"summary":"Create a team workspace","description":"Creates a new team workspace with the caller as owner. Workspaces start on the Free plan; each person may create 1 free team workspace (besides their personal one), so another needs an existing one upgraded or deleted first (`402 quota_exceeded`).","security":[{"firebaseIdToken":[]}],"parameters":[],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["personal","team"]},"ownerUid":{"type":"string"},"plan":{"type":"string","enum":["free","pro","studio"]},"engineProfile":{"type":"string","enum":["unity","unreal","godot","threejs"]},"storageBucket":{"type":"string","description":"R2 bucket that stores this workspace's objects"},"storageBytesUsed":{"type":"integer","minimum":0,"maximum":9007199254740991},"icon":{"anyOf":[{"type":"string","maxLength":200000,"pattern":"^data:image\\/(png|jpeg|webp|gif);base64,[A-Za-z0-9+/=]+$"},{"type":"null"}],"description":"Workspace icon as an image data URL, or null for the default letter tile"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"role":{"type":"string","enum":["viewer","editor","admin","owner"]},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"limits":{"type":"object","properties":{"storageBytes":{"type":"number"},"maxFileBytes":{"type":"number"},"workerMinutesPerMonth":{"type":"number"},"members":{"type":"number"},"apiKeys":{"type":"number"}},"required":["storageBytes","maxFileBytes","workerMinutesPerMonth","members","apiKeys"],"additionalProperties":false},"features":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}},"workerMinutesUsed":{"type":"number"}},"required":["id","name","type","ownerUid","plan","engineProfile","storageBucket","storageBytesUsed","icon","createdAt","role","memberCount","limits","features","workerMinutesUsed"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"A plan limit was reached or the feature is not in the plan. `details` has `plan` plus `resource`, `limit`, `used`, `over` (already above the limit, e.g. after a downgrade) or `feature`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"engineProfile":{"default":"unity","type":"string","enum":["unity","unreal","godot","threejs"]}},"required":["name"]}}}}},"get":{"operationId":"listWorkspaces","tags":["Workspaces"],"summary":"List your workspaces","security":[{"firebaseIdToken":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["personal","team"]},"plan":{"type":"string","enum":["free","pro","studio"]},"icon":{"anyOf":[{"type":"string","maxLength":200000,"pattern":"^data:image\\/(png|jpeg|webp|gif);base64,[A-Za-z0-9+/=]+$"},{"type":"null"}],"description":"Workspace icon as an image data URL, or null for the default letter tile"},"role":{"type":"string","enum":["viewer","editor","admin","owner"]}},"required":["id","name","type","plan","icon","role"],"additionalProperties":false}}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}":{"delete":{"operationId":"deleteWorkspace","tags":["Workspaces"],"summary":"Delete a workspace","description":"Permanently deletes a team workspace, all of its metadata and every stored object. Personal workspaces cannot be deleted. Owner only.\n\n**Required role:** `owner`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"getWorkspace","tags":["Workspaces"],"summary":"Get a workspace","description":"Includes plan limits, features and current usage.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["personal","team"]},"ownerUid":{"type":"string"},"plan":{"type":"string","enum":["free","pro","studio"]},"engineProfile":{"type":"string","enum":["unity","unreal","godot","threejs"]},"storageBucket":{"type":"string","description":"R2 bucket that stores this workspace's objects"},"storageBytesUsed":{"type":"integer","minimum":0,"maximum":9007199254740991},"icon":{"anyOf":[{"type":"string","maxLength":200000,"pattern":"^data:image\\/(png|jpeg|webp|gif);base64,[A-Za-z0-9+/=]+$"},{"type":"null"}],"description":"Workspace icon as an image data URL, or null for the default letter tile"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"role":{"type":"string","enum":["viewer","editor","admin","owner"]},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"limits":{"type":"object","properties":{"storageBytes":{"type":"number"},"maxFileBytes":{"type":"number"},"workerMinutesPerMonth":{"type":"number"},"members":{"type":"number"},"apiKeys":{"type":"number"}},"required":["storageBytes","maxFileBytes","workerMinutesPerMonth","members","apiKeys"],"additionalProperties":false},"features":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}},"workerMinutesUsed":{"type":"number"}},"required":["id","name","type","ownerUid","plan","engineProfile","storageBucket","storageBytesUsed","icon","createdAt","role","memberCount","limits","features","workerMinutesUsed"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateWorkspace","tags":["Workspaces"],"summary":"Update workspace settings","description":"**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["personal","team"]},"ownerUid":{"type":"string"},"plan":{"type":"string","enum":["free","pro","studio"]},"engineProfile":{"type":"string","enum":["unity","unreal","godot","threejs"]},"storageBucket":{"type":"string","description":"R2 bucket that stores this workspace's objects"},"storageBytesUsed":{"type":"integer","minimum":0,"maximum":9007199254740991},"icon":{"anyOf":[{"type":"string","maxLength":200000,"pattern":"^data:image\\/(png|jpeg|webp|gif);base64,[A-Za-z0-9+/=]+$"},{"type":"null"}],"description":"Workspace icon as an image data URL, or null for the default letter tile"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"role":{"type":"string","enum":["viewer","editor","admin","owner"]},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"limits":{"type":"object","properties":{"storageBytes":{"type":"number"},"maxFileBytes":{"type":"number"},"workerMinutesPerMonth":{"type":"number"},"members":{"type":"number"},"apiKeys":{"type":"number"}},"required":["storageBytes","maxFileBytes","workerMinutesPerMonth","members","apiKeys"],"additionalProperties":false},"features":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}},"workerMinutesUsed":{"type":"number"}},"required":["id","name","type","ownerUid","plan","engineProfile","storageBucket","storageBytesUsed","icon","createdAt","role","memberCount","limits","features","workerMinutesUsed"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"engineProfile":{"type":"string","enum":["unity","unreal","godot","threejs"]},"icon":{"description":"Image data URL, or null to reset to the default letter tile","anyOf":[{"type":"string","maxLength":200000,"pattern":"^data:image\\/(png|jpeg|webp|gif);base64,[A-Za-z0-9+/=]+$"},{"type":"null"}]}}}}}}}},"/workspaces/{workspaceId}/members":{"get":{"operationId":"listMembers","tags":["Members"],"summary":"List workspace members","description":"**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"uid":{"type":"string"},"role":{"type":"string","enum":["viewer","editor","admin","owner"]},"email":{"type":["string","null"]},"displayName":{"type":["string","null"]},"joinedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["uid","role","email","displayName","joinedAt"],"additionalProperties":false}}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/members/{uid}":{"delete":{"operationId":"removeMember","tags":["Members"],"summary":"Remove a member (or leave)","description":"Admins can remove editors and viewers; owners can remove anyone except the workspace creator. Any member can remove themselves (leave).\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"uid","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":128}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateMember","tags":["Members"],"summary":"Change a member’s role","description":"Admins can change roles between viewer and editor. Only owners can grant or revoke admin/owner. Granting owner does not remove the existing owner.\n\n**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"uid","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":128}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"uid":{"type":"string"},"role":{"type":"string","enum":["viewer","editor","admin","owner"]},"email":{"type":["string","null"]},"displayName":{"type":["string","null"]},"joinedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["uid","role","email","displayName","joinedAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["viewer","editor","admin","owner"]}},"required":["role"]}}}}}},"/invites/accept":{"post":{"operationId":"acceptInvite","tags":["Members"],"summary":"Accept an invite","description":"Joins the workspace referenced by the invite token. The signed-in email must match the invited email.","security":[{"firebaseIdToken":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["personal","team"]},"plan":{"type":"string","enum":["free","pro","studio"]},"icon":{"anyOf":[{"type":"string","maxLength":200000,"pattern":"^data:image\\/(png|jpeg|webp|gif);base64,[A-Za-z0-9+/=]+$"},{"type":"null"}],"description":"Workspace icon as an image data URL, or null for the default letter tile"},"role":{"type":"string","enum":["viewer","editor","admin","owner"]}},"required":["id","name","type","plan","icon","role"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":10,"maxLength":400}},"required":["token"]}}}}}},"/workspaces/{workspaceId}/invites":{"post":{"operationId":"createInvite","tags":["Members"],"summary":"Invite someone by email","description":"Creates an invite valid for 7 days and returns an accept link. When the server has email configured (Resend), the link is also emailed to the invitee and `emailSent` is true; otherwise share the link yourself. The invitee must sign in with the invited email address. Only owners can invite admins.\n\n**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"email":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"role":{"type":"string","enum":["viewer","editor","admin"]},"expiresAt":{"type":"string","description":"ISO-8601 timestamp"},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"acceptUrl":{"type":"string","description":"Single-use link; shown only once"},"emailSent":{"type":"boolean","description":"True when the invite was emailed to the invitee"}},"required":["id","email","role","expiresAt","createdBy","createdAt","acceptUrl","emailSent"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"A plan limit was reached or the feature is not in the plan. `details` has `plan` plus `resource`, `limit`, `used`, `over` (already above the limit, e.g. after a downgrade) or `feature`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"role":{"default":"editor","type":"string","enum":["viewer","editor","admin"]}},"required":["email"]}}}}},"get":{"operationId":"listInvites","tags":["Members"],"summary":"List pending invites","description":"**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"email":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"role":{"type":"string","enum":["viewer","editor","admin"]},"expiresAt":{"type":"string","description":"ISO-8601 timestamp"},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","email","role","expiresAt","createdBy","createdAt"],"additionalProperties":false}}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/invites/{inviteId}":{"delete":{"operationId":"revokeInvite","tags":["Members"],"summary":"Revoke an invite","description":"**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"inviteId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/folders":{"post":{"operationId":"createFolder","tags":["Folders"],"summary":"Create a folder","description":"**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"parentId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","parentId","createdAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"parentId":{"default":null,"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]}},"required":["name"]}}}}},"get":{"operationId":"listFolders","tags":["Folders"],"summary":"List folders","description":"Returns every folder in the workspace as a flat list; build the tree from `parentId`.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"parentId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","parentId","createdAt"],"additionalProperties":false}}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/folders/{folderId}":{"delete":{"operationId":"deleteFolder","tags":["Folders"],"summary":"Delete an empty folder","description":"**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"folderId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateFolder","tags":["Folders"],"summary":"Rename or move a folder","description":"**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"folderId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"parentId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","parentId","createdAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"parentId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]}}}}}}}},"/workspaces/{workspaceId}/uploads/{assetId}/abort":{"post":{"operationId":"abortUpload","tags":["Uploads"],"summary":"Abort an upload","description":"Cancels the in-progress upload. A brand-new asset is deleted; an existing asset keeps its current version.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uploadId":{"type":"string"}}}}}}}},"/workspaces/{workspaceId}/uploads/{assetId}/complete":{"post":{"operationId":"completeUpload","tags":["Uploads"],"summary":"Complete an upload","description":"Verifies the uploaded object (size must match the declared `sizeBytes`), marks the asset ready and counts it against storage. For multipart uploads pass `uploadId` and every part’s `ETag`.\n\nDocuments that reference other assets (`.scene`, `.shadergraph`, `.shadersketch`) are read and checked: every referenced asset must be ready in this workspace, and pinned versions must exist. If not, the response is `422` with `details.problems` and the upload is cancelled (a new asset is removed; an existing one keeps its current version). On success the asset’s `refIds` lists the referenced assets.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"filename":{"type":"string"},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"format":{"type":"string"},"contentType":{"type":"string"},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"tags":{"maxItems":32,"type":"array","items":{"type":"string","maxLength":40}},"status":{"type":"string","enum":["pending","ready","failed"]},"sizeBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"contentHash":{"type":["string","null"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"hasThumbnail":{"type":"boolean"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Unresolved comments"},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}},"additionalProperties":false},"refIds":{"description":"Assets this document references (scenes, shader graphs, sketches); set by the server on upload","type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","filename","kind","format","contentType","folderId","tags","status","sizeBytes","contentHash","version","hasThumbnail","commentCount","meta","createdBy","createdAt","updatedAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"A plan limit was reached or the feature is not in the plan. `details` has `plan` plus `resource`, `limit`, `used`, `over` (already above the limit, e.g. after a downgrade) or `feature`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A document references assets that are missing, not ready or in another workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uploadId":{"type":"string"},"parts":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","minimum":1,"maximum":9007199254740991},"etag":{"type":"string","minLength":1}},"required":["partNumber","etag"]}}}}}}}}},"/workspaces/{workspaceId}/assets/{assetId}/thumbnail":{"post":{"operationId":"createThumbnailUpload","tags":["Assets"],"summary":"Get a thumbnail upload URL","description":"Returns a presigned PUT URL for the current version’s thumbnail (max 2 MB). After uploading, PATCH the asset with `hasThumbnail: true`.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"uploadUrl":{"type":"string"},"headers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["uploadUrl","headers"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contentType":{"default":"image/webp","type":"string","enum":["image/webp","image/png","image/jpeg"]},"sizeBytes":{"type":"integer","exclusiveMinimum":0,"maximum":2097152}},"required":["sizeBytes"]}}}}}},"/workspaces/{workspaceId}/uploads":{"post":{"operationId":"createUpload","tags":["Uploads"],"summary":"Start an upload","description":"Creates a pending asset (or a new version of `assetId`) and returns where to upload the bytes. Files go directly to object storage; they never pass through this API.\n\n- Files under 100 MB use `mode: \"single\"`: PUT the file to `uploadUrl` with the returned `headers`.\n- Larger files use `mode: \"multipart\"`: request part URLs from `/parts`, PUT each 64 MB part, keep each response's `ETag`.\n\nThen call `/complete`. Pending uploads that are never completed are deleted after 24 hours.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","const":"single"},"asset":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"filename":{"type":"string"},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"format":{"type":"string"},"contentType":{"type":"string"},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"tags":{"maxItems":32,"type":"array","items":{"type":"string","maxLength":40}},"status":{"type":"string","enum":["pending","ready","failed"]},"sizeBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"contentHash":{"type":["string","null"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"hasThumbnail":{"type":"boolean"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Unresolved comments"},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}},"additionalProperties":false},"refIds":{"description":"Assets this document references (scenes, shader graphs, sketches); set by the server on upload","type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","filename","kind","format","contentType","folderId","tags","status","sizeBytes","contentHash","version","hasThumbnail","commentCount","meta","createdBy","createdAt","updatedAt"],"additionalProperties":false},"uploadUrl":{"type":"string","description":"Presigned PUT URL"},"headers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Headers that must be sent with the PUT"}},"required":["mode","asset","uploadUrl","headers"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","const":"multipart"},"asset":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"filename":{"type":"string"},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"format":{"type":"string"},"contentType":{"type":"string"},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"tags":{"maxItems":32,"type":"array","items":{"type":"string","maxLength":40}},"status":{"type":"string","enum":["pending","ready","failed"]},"sizeBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"contentHash":{"type":["string","null"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"hasThumbnail":{"type":"boolean"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Unresolved comments"},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}},"additionalProperties":false},"refIds":{"description":"Assets this document references (scenes, shader graphs, sketches); set by the server on upload","type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","filename","kind","format","contentType","folderId","tags","status","sizeBytes","contentHash","version","hasThumbnail","commentCount","meta","createdBy","createdAt","updatedAt"],"additionalProperties":false},"uploadId":{"type":"string"},"partSize":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"partCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["mode","asset","uploadId","partSize","partCount"],"additionalProperties":false}]}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"A plan limit was reached or the feature is not in the plan. `details` has `plan` plus `resource`, `limit`, `used`, `over` (already above the limit, e.g. after a downgrade) or `feature`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255},"name":{"type":"string","minLength":1,"maxLength":200},"sizeBytes":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"contentType":{"type":"string","maxLength":120},"contentHash":{"description":"Hex SHA-256 of the file","anyOf":[{"type":"string","pattern":"^[a-f0-9]{64}$"},{"type":"null"}]},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"tags":{"maxItems":32,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}}},"assetId":{"description":"Upload a new version of this asset instead of creating one","type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"required":["filename","sizeBytes"]}}}}}},"/workspaces/{workspaceId}/uploads/{assetId}/parts":{"post":{"operationId":"createUploadParts","tags":["Uploads"],"summary":"Get multipart part URLs","description":"Returns presigned PUT URLs for the requested part numbers (1-based). Request in batches of up to 1000.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"urls":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"url":{"type":"string"}},"required":["partNumber","url"],"additionalProperties":false}}},"required":["urls"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"uploadId":{"type":"string","minLength":1},"partNumbers":{"minItems":1,"maxItems":1000,"type":"array","items":{"type":"integer","minimum":1,"maximum":10000}}},"required":["uploadId","partNumbers"]}}}}}},"/workspaces/{workspaceId}/assets/{assetId}":{"delete":{"operationId":"deleteAsset","tags":["Assets"],"summary":"Delete an asset","description":"Deletes the asset, all of its versions and derived files. Storage usage is released immediately.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"getAsset","tags":["Assets"],"summary":"Get an asset","description":"**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"filename":{"type":"string"},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"format":{"type":"string"},"contentType":{"type":"string"},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"tags":{"maxItems":32,"type":"array","items":{"type":"string","maxLength":40}},"status":{"type":"string","enum":["pending","ready","failed"]},"sizeBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"contentHash":{"type":["string","null"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"hasThumbnail":{"type":"boolean"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Unresolved comments"},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}},"additionalProperties":false},"refIds":{"description":"Assets this document references (scenes, shader graphs, sketches); set by the server on upload","type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","filename","kind","format","contentType","folderId","tags","status","sizeBytes","contentHash","version","hasThumbnail","commentCount","meta","createdBy","createdAt","updatedAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateAsset","tags":["Assets"],"summary":"Update asset metadata","description":"Rename, retag, move to a folder, or update tool-computed metadata (e.g. triangle counts).\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"filename":{"type":"string"},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"format":{"type":"string"},"contentType":{"type":"string"},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"tags":{"maxItems":32,"type":"array","items":{"type":"string","maxLength":40}},"status":{"type":"string","enum":["pending","ready","failed"]},"sizeBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"contentHash":{"type":["string","null"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"hasThumbnail":{"type":"boolean"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Unresolved comments"},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}},"additionalProperties":false},"refIds":{"description":"Assets this document references (scenes, shader graphs, sketches); set by the server on upload","type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","filename","kind","format","contentType","folderId","tags","status","sizeBytes","contentHash","version","hasThumbnail","commentCount","meta","createdBy","createdAt","updatedAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"tags":{"maxItems":32,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}}},"hasThumbnail":{"type":"boolean"}}}}}}}},"/workspaces/{workspaceId}/assets/{assetId}/bundle":{"get":{"operationId":"getAssetBundle","tags":["Assets"],"summary":"Open a document with its dependencies","description":"For a scene, shader graph or sketch: the parsed document, plus a presigned URL for every asset it references, at the right version (the newest for `follow: \"latest\"`, the given one for `follow: \"pinned\"`). For a scene that is the model and each material binding’s shader and library textures, so the Model Viewer opens a scene with this one request.\nReferences to deleted assets or versions come back with `missing: true`. URLs are valid for 10 minutes. Other assets return no dependencies. `version` opens an older version of the document.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"version","in":"query","required":false,"schema":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"asset":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"filename":{"type":"string"},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"format":{"type":"string"},"contentType":{"type":"string"},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"tags":{"maxItems":32,"type":"array","items":{"type":"string","maxLength":40}},"status":{"type":"string","enum":["pending","ready","failed"]},"sizeBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"contentHash":{"type":["string","null"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"hasThumbnail":{"type":"boolean"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Unresolved comments"},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}},"additionalProperties":false},"refIds":{"description":"Assets this document references (scenes, shader graphs, sketches); set by the server on upload","type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","filename","kind","format","contentType","folderId","tags","status","sizeBytes","contentHash","version","hasThumbnail","commentCount","meta","createdBy","createdAt","updatedAt"],"additionalProperties":false},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"The document version returned"},"documentUrl":{"type":"string"},"document":{"description":"The parsed JSON document (null for assets without references)"},"dependencies":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"object","properties":{"assetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"follow":{"type":"string","enum":["latest","pinned"]}},"required":["assetId","version","follow"],"additionalProperties":false},"asset":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"filename":{"type":"string"},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"The version `url` serves"}},"required":["id","name","kind","filename","version"],"additionalProperties":false},"url":{"description":"Presigned download URL for that version","type":"string"},"missing":{"description":"The asset or the pinned version no longer exists","type":"boolean","const":true}},"required":["ref"],"additionalProperties":false}},"expiresAt":{"type":"string"}},"required":["asset","version","documentUrl","document","dependencies","expiresAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A document references assets that are missing, not ready or in another workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/assets/{assetId}/download":{"get":{"operationId":"getAssetDownloadUrl","tags":["Assets"],"summary":"Get a download URL","description":"Returns a presigned URL valid for 10 minutes. Pass `redirect=true` to receive a 302 redirect instead (handy for curl -L). `variant=thumbnail` returns the 256px WebP thumbnail.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"variant","in":"query","required":false,"schema":{"default":"original","type":"string","enum":["original","thumbnail"]}},{"name":"version","in":"query","required":false,"schema":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},{"name":"redirect","in":"query","required":false,"schema":{"default":"false","type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"expiresAt":{"type":"string"}},"required":["url","expiresAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/assets/{assetId}/used-by":{"get":{"operationId":"listAssetUsedBy","tags":["Assets"],"summary":"List what uses an asset","description":"Scenes, shader graphs and sketches whose latest version references this asset, most recently updated first (up to 50). Deleting the asset is still allowed; those documents then show it as missing.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"assets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"filename":{"type":"string"}},"required":["id","name","kind","filename"],"additionalProperties":false}}},"required":["assets"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/assets/{assetId}/versions":{"get":{"operationId":"listAssetVersions","tags":["Assets"],"summary":"List asset versions","description":"**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"version":{"type":"number"},"filename":{"type":"string"},"sizeBytes":{"type":"number"},"contentHash":{"type":["string","null"]},"createdBy":{"type":"string"},"createdAt":{"type":"string"}},"required":["version","filename","sizeBytes","contentHash","createdBy","createdAt"],"additionalProperties":false}}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/assets":{"get":{"operationId":"listAssets","tags":["Assets"],"summary":"List assets","description":"Lists ready assets, newest first. Filter by `kind` and `folderId` (`root` for the top level). `q` does a case-insensitive name prefix search across all folders. Paginate with `cursor` (the `nextCursor` from the previous page).\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]}},{"name":"folderId","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"string","const":"root"}]}},{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":100}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"assets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":200},"filename":{"type":"string"},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"format":{"type":"string"},"contentType":{"type":"string"},"folderId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"tags":{"maxItems":32,"type":"array","items":{"type":"string","maxLength":40}},"status":{"type":"string","enum":["pending","ready","failed"]},"sizeBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"contentHash":{"type":["string","null"]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"hasThumbnail":{"type":"boolean"},"commentCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Unresolved comments"},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}},"additionalProperties":false},"refIds":{"description":"Assets this document references (scenes, shader graphs, sketches); set by the server on upload","type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","name","filename","kind","format","contentType","folderId","tags","status","sizeBytes","contentHash","version","hasThumbnail","commentCount","meta","createdBy","createdAt","updatedAt"],"additionalProperties":false}},"nextCursor":{"type":["string","null"]}},"required":["assets","nextCursor"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/assets/{assetId}/comments":{"post":{"operationId":"createComment","tags":["Comments"],"summary":"Comment on an asset","description":"Any member can comment, including viewers.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"assetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"body":{"type":"string","minLength":1,"maxLength":4000},"anchor":{"anyOf":[{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"3d"},"position":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},"normal":{"anyOf":[{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},{"type":"null"}]},"camera":{"anyOf":[{"type":"object","properties":{"position":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},"target":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3}},"required":["position","target"],"additionalProperties":false},{"type":"null"}]}},"required":["kind","position","normal","camera"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"2d"},"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1}},"required":["kind","x","y"],"additionalProperties":false}]},{"type":"null"}]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"resolved":{"type":"boolean"},"authorId":{"type":"string"},"authorName":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"editedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]}},"required":["id","assetId","body","anchor","version","resolved","authorId","authorName","createdAt","editedAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":4000},"anchor":{"default":null,"anyOf":[{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"3d"},"position":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},"normal":{"anyOf":[{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},{"type":"null"}]},"camera":{"anyOf":[{"type":"object","properties":{"position":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},"target":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3}},"required":["position","target"]},{"type":"null"}]}},"required":["kind","position","normal","camera"]},{"type":"object","properties":{"kind":{"type":"string","const":"2d"},"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1}},"required":["kind","x","y"]}]},{"type":"null"}]}},"required":["body"]}}}}},"get":{"operationId":"listComments","tags":["Comments"],"summary":"List comments on an asset","description":"Oldest first. Comments can be pinned to a point on a model (`anchor.kind = \"3d\"`) or an image (`\"2d\"`).\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"assetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"body":{"type":"string","minLength":1,"maxLength":4000},"anchor":{"anyOf":[{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"3d"},"position":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},"normal":{"anyOf":[{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},{"type":"null"}]},"camera":{"anyOf":[{"type":"object","properties":{"position":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},"target":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3}},"required":["position","target"],"additionalProperties":false},{"type":"null"}]}},"required":["kind","position","normal","camera"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"2d"},"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1}},"required":["kind","x","y"],"additionalProperties":false}]},{"type":"null"}]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"resolved":{"type":"boolean"},"authorId":{"type":"string"},"authorName":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"editedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]}},"required":["id","assetId","body","anchor","version","resolved","authorId","authorName","createdAt","editedAt"],"additionalProperties":false}}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/assets/{assetId}/comments/{commentId}":{"delete":{"operationId":"deleteComment","tags":["Comments"],"summary":"Delete a comment","description":"The author or a workspace admin can delete a comment.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateComment","tags":["Comments"],"summary":"Edit or resolve a comment","description":"Only the author can change the text. The author or any editor can resolve and reopen.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"assetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"body":{"type":"string","minLength":1,"maxLength":4000},"anchor":{"anyOf":[{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"3d"},"position":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},"normal":{"anyOf":[{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},{"type":"null"}]},"camera":{"anyOf":[{"type":"object","properties":{"position":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3},"target":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3}},"required":["position","target"],"additionalProperties":false},{"type":"null"}]}},"required":["kind","position","normal","camera"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","const":"2d"},"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1}},"required":["kind","x","y"],"additionalProperties":false}]},{"type":"null"}]},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"resolved":{"type":"boolean"},"authorId":{"type":"string"},"authorName":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"editedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]}},"required":["id","assetId","body","anchor","version","resolved","authorId","authorName","createdAt","editedAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":4000},"resolved":{"type":"boolean"}}}}}}}},"/workspaces/{workspaceId}/share-links":{"post":{"operationId":"createShareLink","tags":["Sharing"],"summary":"Create a share link","description":"Creates a read-only link that opens an asset without an account: in the matching viewer, with a download button.\nAnyone with the URL can open it until it expires or is revoked. `version` pins the current version; omit it to always serve the latest.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Short id for managing the link (not the secret token)"},"assetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"version":{"anyOf":[{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},{"type":"null"}]},"url":{"type":"string","description":"Public URL that opens the asset without an account"},"expiresAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"viewCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"lastViewedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","assetId","version","url","expiresAt","viewCount","lastViewedAt","createdBy","createdAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"A plan limit was reached or the feature is not in the plan. `details` has `plan` plus `resource`, `limit`, `used`, `over` (already above the limit, e.g. after a downgrade) or `feature`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"assetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"pinVersion":{"default":false,"type":"boolean"},"expiresInDays":{"default":7,"anyOf":[{"type":"integer","minimum":1,"maximum":365},{"type":"null"}]}},"required":["assetId"]}}}}},"get":{"operationId":"listShareLinks","tags":["Sharing"],"summary":"List share links","description":"Share links in the workspace, newest first; pass `assetId` for one asset’s links.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"assetId","in":"query","required":false,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Short id for managing the link (not the secret token)"},"assetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"version":{"anyOf":[{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},{"type":"null"}]},"url":{"type":"string","description":"Public URL that opens the asset without an account"},"expiresAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"viewCount":{"type":"integer","minimum":0,"maximum":9007199254740991},"lastViewedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","assetId","version","url","expiresAt","viewCount","lastViewedAt","createdBy","createdAt"],"additionalProperties":false}}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/share/{token}":{"get":{"operationId":"getSharedAsset","tags":["Sharing"],"summary":"Open a share link","description":"Public: no authentication. Returns the shared asset’s details and a short-lived download URL.","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"workspaceName":{"type":"string"},"asset":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"filename":{"type":"string"},"kind":{"type":"string","enum":["model","texture","animation","shader","hdri","scene","other"]},"format":{"type":"string"},"sizeBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"meta":{"type":"object","properties":{"triCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vertCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"meshCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"materialCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"boneCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"clipCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"width":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"height":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"channels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bitDepth":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"colorSpace":{"type":"string"},"modelName":{"type":"string","maxLength":200},"shaderCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"mode":{"type":"string","enum":["pbr","unlit"]},"parameterCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"textureSlots":{"maxItems":64,"type":"array","items":{"type":"string","maxLength":128}}},"additionalProperties":false},"version":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["name","filename","kind","format","sizeBytes","meta","version"],"additionalProperties":false},"downloadUrl":{"type":"string","description":"Presigned URL, valid for a few minutes"},"embeddable":{"type":"boolean","description":"Whether the workspace's plan allows embedding this link on other websites"},"thumbnailUrl":{"type":["string","null"]},"expiresAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]}},"required":["workspaceName","asset","downloadUrl","embeddable","thumbnailUrl","expiresAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/share-links/{linkId}":{"delete":{"operationId":"revokeShareLink","tags":["Sharing"],"summary":"Revoke a share link","description":"**Required role:** `editor`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"linkId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{12}$"}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/activity":{"get":{"operationId":"listActivity","tags":["Activity"],"summary":"Workspace activity","description":"The audit log: uploads, deletes, member and sharing changes, newest first. Page with `before` (the last entry’s `createdAt`). Entries are kept for a year.\n\n**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"before","in":"query","required":false,"schema":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["asset.created","asset.version","asset.renamed","asset.moved","asset.deleted","folder.created","folder.renamed","folder.deleted","member.invited","member.joined","member.role","member.removed","invite.revoked","workspace.updated","apikey.created","apikey.revoked","share.created","share.revoked","comment.created","plan.changed"]}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"type":{"type":"string","enum":["asset.created","asset.version","asset.renamed","asset.moved","asset.deleted","folder.created","folder.renamed","folder.deleted","member.invited","member.joined","member.role","member.removed","invite.revoked","workspace.updated","apikey.created","apikey.revoked","share.created","share.revoked","comment.created","plan.changed"]},"actorId":{"type":"string","description":"User uid, `apikey:<id>`, or `system`"},"actorName":{"type":"string"},"target":{"type":"object","properties":{"kind":{"type":"string","enum":["asset","folder","member","invite","workspace","apikey","share","comment"]},"id":{"type":"string"},"name":{"type":"string"}},"required":["kind","id","name"],"additionalProperties":false},"details":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":["string","number","boolean","null"]}},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","type","actorId","actorName","target","details","createdAt"],"additionalProperties":false}},"next":{"type":["string","null"]}},"required":["entries","next"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/api-keys":{"post":{"operationId":"createApiKey","tags":["API keys"],"summary":"Create an API key","description":"Creates a workspace-scoped key for scripts and pipelines. The full key is returned **only once**; store it securely. `read` keys act as a viewer; `write` keys act as an editor. Keys cannot manage members, invites or other keys.\n\n**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"prefix":{"type":"string","description":"First characters of the key, for identification"},"scopes":{"type":"array","items":{"type":"string","enum":["read","write"]}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"lastUsedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"key":{"type":"string","description":"The secret key, shown once"}},"required":["id","name","prefix","scopes","createdBy","createdAt","lastUsedAt","key"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"A plan limit was reached or the feature is not in the plan. `details` has `plan` plus `resource`, `limit`, `used`, `over` (already above the limit, e.g. after a downgrade) or `feature`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"scopes":{"default":["read"],"minItems":1,"type":"array","items":{"type":"string","enum":["read","write"]}}},"required":["name"]}}}}},"get":{"operationId":"listApiKeys","tags":["API keys"],"summary":"List API keys","description":"**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"name":{"type":"string","minLength":1,"maxLength":80},"prefix":{"type":"string","description":"First characters of the key, for identification"},"scopes":{"type":"array","items":{"type":"string","enum":["read","write"]}},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"lastUsedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]}},"required":["id","name","prefix","scopes","createdBy","createdAt","lastUsedAt"],"additionalProperties":false}}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/api-keys/{keyId}":{"delete":{"operationId":"revokeApiKey","tags":["API keys"],"summary":"Revoke an API key","description":"**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"keyId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{12}$"}}],"responses":{"204":{"description":"No content"},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/jobs":{"post":{"operationId":"createJob","tags":["Jobs"],"summary":"Queue a server-side conversion","description":"**Currently unavailable** (returns 503) while server-side conversion is switched off.\n\nQueues a conversion that cannot run in the browser, such as **FBX export**. The input must be a ready model asset in the workspace.\nThe result is saved as a new asset; poll `GET /jobs/{jobId}` until `status` is `succeeded` (then read `outputAssetId`) or `failed`.\n\nEngine profiles set axis and unit conventions: `unity` (Y-up, meters), `unreal` (Z-up, centimeters), `godot` (Y-up, meters).\nWorker time counts against the plan’s monthly worker minutes.\n\n**Required role:** `editor`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"202":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"type":{"type":"string","enum":["convert-model"]},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"inputAssetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"params":{"type":"object","properties":{"targetFormat":{"type":"string","enum":["fbx","glb","gltf","obj"]},"engineProfile":{"type":"string","enum":["unity","unreal","godot","threejs"]},"scale":{"default":1,"type":"number","exclusiveMinimum":0},"stripAnimations":{"default":false,"type":"boolean"}},"required":["targetFormat","engineProfile","scale","stripAnimations"],"additionalProperties":false},"outputAssetId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"error":{"type":["string","null"]},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","type","status","inputAssetId","params","outputAssetId","error","createdBy","createdAt","updatedAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"A plan limit was reached or the feature is not in the plan. `details` has `plan` plus `resource`, `limit`, `used`, `over` (already above the limit, e.g. after a downgrade) or `feature`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A backing service (storage, worker) is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","const":"convert-model"},"inputAssetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"params":{"type":"object","properties":{"targetFormat":{"type":"string","enum":["fbx","glb","gltf","obj"]},"engineProfile":{"type":"string","enum":["unity","unreal","godot","threejs"]},"scale":{"default":1,"type":"number","exclusiveMinimum":0},"stripAnimations":{"default":false,"type":"boolean"}},"required":["targetFormat","engineProfile"]}},"required":["type","inputAssetId","params"]}}}}},"get":{"operationId":"listJobs","tags":["Jobs"],"summary":"List recent jobs","description":"**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"limit","in":"query","required":false,"schema":{"default":25,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"type":{"type":"string","enum":["convert-model"]},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"inputAssetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"params":{"type":"object","properties":{"targetFormat":{"type":"string","enum":["fbx","glb","gltf","obj"]},"engineProfile":{"type":"string","enum":["unity","unreal","godot","threejs"]},"scale":{"default":1,"type":"number","exclusiveMinimum":0},"stripAnimations":{"default":false,"type":"boolean"}},"required":["targetFormat","engineProfile","scale","stripAnimations"],"additionalProperties":false},"outputAssetId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"error":{"type":["string","null"]},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","type","status","inputAssetId","params","outputAssetId","error","createdBy","createdAt","updatedAt"],"additionalProperties":false}}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/jobs/{jobId}":{"get":{"operationId":"getJob","tags":["Jobs"],"summary":"Get a job","description":"**Required role:** `viewer`","security":[{"firebaseIdToken":[]},{"apiKey":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}},{"name":"jobId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"type":{"type":"string","enum":["convert-model"]},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"inputAssetId":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},"params":{"type":"object","properties":{"targetFormat":{"type":"string","enum":["fbx","glb","gltf","obj"]},"engineProfile":{"type":"string","enum":["unity","unreal","godot","threejs"]},"scale":{"default":1,"type":"number","exclusiveMinimum":0},"stripAnimations":{"default":false,"type":"boolean"}},"required":["targetFormat","engineProfile","scale","stripAnimations"],"additionalProperties":false},"outputAssetId":{"anyOf":[{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"},{"type":"null"}]},"error":{"type":["string","null"]},"createdBy":{"type":"string"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["id","type","status","inputAssetId","params","outputAssetId","error","createdBy","createdAt","updatedAt"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/billing/portal":{"post":{"operationId":"createBillingPortal","tags":["Billing"],"summary":"Manage billing","description":"Returns a Stripe Billing Portal URL to change plan, update the card, see invoices or cancel.\n\n**Required role:** `owner`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A backing service (storage, worker) is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/workspaces/{workspaceId}/billing/checkout":{"post":{"operationId":"createCheckout","tags":["Billing"],"summary":"Start an upgrade","description":"Returns a Stripe Checkout URL for the plan. The plan changes when Stripe confirms the payment (via the webhook).\n\n**Required role:** `owner`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state (e.g. upload already completed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A backing service (storage, worker) is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["pro","studio"]}},"required":["plan"]}}}}}},"/workspaces/{workspaceId}/billing":{"get":{"operationId":"getBilling","tags":["Billing"],"summary":"Plan, subscription and usage","description":"**Required role:** `admin`","security":[{"firebaseIdToken":[]}],"parameters":[{"name":"workspaceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,128}$"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"configured":{"type":"boolean"},"plan":{"type":"string","enum":["free","pro","studio"]},"status":{"type":"string","enum":["none","active","trialing","past_due","canceled","incomplete","unpaid"]},"currentPeriodEnd":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"cancelAtPeriodEnd":{"type":"boolean"},"usage":{"type":"object","properties":{"storageBytes":{"type":"number"},"workerMinutes":{"type":"number"},"members":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"apiKeys":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["storageBytes","workerMinutes","members","apiKeys"],"additionalProperties":false}},"required":["configured","plan","status","currentPeriodEnd","cancelAtPeriodEnd","usage"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but the role or API key scope does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The workspace or resource does not exist, or you are not a member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/billing/plans":{"get":{"operationId":"getPlanCatalog","tags":["Billing"],"summary":"Plans and prices","description":"Every plan with its limits, features and monthly price (from Stripe, cached for 10 minutes). Prices are null when billing is off or Stripe could not be reached.","security":[],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"configured":{"type":"boolean"},"plans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["free","pro","studio"]},"label":{"type":"string"},"limits":{"type":"object","properties":{"storageBytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"maxFileBytes":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"workerMinutesPerMonth":{"type":"number","minimum":0},"members":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"apiKeys":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["storageBytes","maxFileBytes","workerMinutesPerMonth","members","apiKeys"],"additionalProperties":false},"features":{"type":"object","properties":{"apiAccess":{"type":"boolean"},"serverConversion":{"type":"boolean"},"batchPipelines":{"type":"boolean"},"shareLinks":{"type":"boolean"},"embeds":{"type":"boolean"}},"required":["apiAccess","serverConversion","batchPipelines","shareLinks","embeds"],"additionalProperties":false},"price":{"anyOf":[{"type":"object","properties":{"amount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Minor units, e.g. cents"},"currency":{"type":"string"},"interval":{"type":"string","enum":["day","week","month","year"]},"intervalCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["amount","currency","interval","intervalCount"],"additionalProperties":false},{"type":"null"}],"description":"Null for Free, or when the price could not be loaded"}},"required":["id","label","limits","features","price"],"additionalProperties":false}}},"required":["configured","plans"],"additionalProperties":false}}}}}}},"/billing/webhook":{"post":{"operationId":"stripeWebhook","tags":["Billing"],"summary":"Stripe webhook","description":"Receives Stripe events (signed with `STRIPE_WEBHOOK_SECRET`). Subscribe to `checkout.session.completed` and `customer.subscription.*`. Events only identify the subscription: its current state is always refetched from Stripe, so out-of-order and duplicate deliveries are harmless.","security":[],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean"}},"required":["received"],"additionalProperties":false}}}},"400":{"description":"The request body, query or path parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials, or the ID token / API key is invalid or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A backing service (storage, worker) is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"firebaseIdToken":{"type":"http","scheme":"bearer","bearerFormat":"Firebase ID token (JWT)"},"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"Workspace API key (tau_…)"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}}}}}}}}