{
  "openapi": "3.0.3",
  "info": {
    "title": "artlabs Studio Partner API",
    "version": "2026-09-23",
    "description": "Supported GLB-to-VTO partner workflow, not the complete Studio API. The core workflow was verified with a partner credential on 2026-09-23. Schemas describe the documented subset; responses may contain additional fields. Examples do not authorize mutations. Use caller-assigned IDs and credentials. No preset-detail or automation-detail access is required."
  },
  "servers": [
    {
      "url": "https://studio-api.artlabs.ai",
      "description": "Production; every documented path includes /v1."
    }
  ],
  "security": [
    {
      "studioJwt": []
    }
  ],
  "paths": {
    "/v1/objects": {
      "post": {
        "operationId": "createObject",
        "summary": "Create a partner-owned object",
        "description": "Save response.id as objectId. Reconcile a timeout before retrying; project name uniqueness may apply.",
        "responses": {
          "201": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedObject"
                },
                "example": {
                  "id": 1000,
                  "name": "PARTNER-SHOE-001",
                  "state": "Generated"
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateObjectRequest"
              },
              "example": {
                "data": {
                  "name": "PARTNER-SHOE-001",
                  "project": 100
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "findObjects",
        "summary": "Find an object before creating or retrying",
        "description": "Read-only. Results are limited by account access. Verify project/name before reusing an object.",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObjectListResponse"
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "filters[project][id][$eq]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Assigned project ID."
          },
          {
            "name": "filters[name][$eq]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Exact object name."
          },
          {
            "name": "pagination[pageSize]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Requested result count."
          }
        ]
      }
    },
    "/v1/object-files": {
      "post": {
        "operationId": "uploadObjectFile",
        "summary": "Upload a GLB and attach it to an object",
        "description": "Save response.data.id as inputObjectFileId. data is a JSON string; files.file is the binary. Let the client generate the multipart boundary.",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObjectFileResponse"
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": {
                    "type": "string",
                    "description": "JSON string with object (numeric objectId), format GLB, and source import.",
                    "example": "{\"object\":1000,\"format\":\"GLB\",\"source\":\"import\"}"
                  },
                  "files.file": {
                    "type": "string",
                    "format": "binary",
                    "description": "GLB binary."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "data",
                  "files.file"
                ]
              },
              "encoding": {
                "data": {
                  "contentType": "text/plain"
                },
                "files.file": {
                  "contentType": "model/gltf-binary"
                }
              }
            }
          }
        }
      }
    },
    "/v1/object-files/{id}/run-automation": {
      "post": {
        "operationId": "startPreset",
        "summary": "Start the assigned preset on an input object-file",
        "description": "Path id is inputObjectFileId. No data wrapper. Save response.id as automationId; poll the input object-file. Lost responses must be reconciled before retry.",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StartedAutomation"
                },
                "example": {
                  "id": 4000,
                  "state": "queued"
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Returned Studio ID, not a media ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartPresetRequest"
              },
              "example": {
                "preset": 300
              }
            }
          }
        }
      }
    },
    "/v1/object-files/{id}": {
      "get": {
        "operationId": "getObjectFile",
        "summary": "Poll processing status on the input object-file",
        "description": "Read response.data.attributes.metadata.automation; match its id to automationId. Missing state or queued/provisioning/running means wait. finished means inspect output models. errored/cancelled means stop. automating=false is not success by itself. Recommended polling: 10 seconds with a caller-defined deadline.",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObjectFileResponse"
                },
                "example": {
                  "data": {
                    "id": 2000,
                    "attributes": {
                      "automating": true,
                      "metadata": {
                        "automation": {
                          "id": 4000,
                          "state": "running"
                        }
                      }
                    }
                  },
                  "meta": {}
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Returned Studio ID, not a media ID."
          },
          {
            "name": "populate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "file"
              ]
            },
            "description": "Optional file relation; use file to also inspect the binary."
          }
        ]
      }
    },
    "/v1/objects/{id}/models": {
      "get": {
        "operationId": "listObjectModels",
        "summary": "Find the VTO output from the completed run",
        "description": "Path id is objectId. Plain array. Require automated=true and matching metadata.automation.id. For grouped models select mergedInfo[assigned outputVariant] and require fileUrl. Otherwise require GLB_VTO, matching compression and file.url. Use the selected object-file id, not fileId. Wait for uploads after finished; do not choose by array order.",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Model"
                  }
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Returned Studio ID, not a media ID."
          }
        ]
      }
    },
    "/v1/objects/{id}/publish-to-product": {
      "post": {
        "operationId": "publishVtoProduct",
        "summary": "Publish a selected VTO output to a product",
        "description": "Path id is objectId. No data wrapper. objectFiles.vto is outputObjectFileId. Same SKU + organization replaces the product model and preserves existing name/description/category. Not idempotent: reconcile ambiguous failures before retry. Save response.id as productId and verify with getObjectPublication and searchProducts.",
        "responses": {
          "201": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Returned Studio ID, not a media ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishRequest"
              },
              "example": {
                "objectFiles": {
                  "vto": 2001
                },
                "product": {
                  "name": "Partner Shoe 001",
                  "SKU": "PARTNER-SHOE-001",
                  "organization": "YOUR_PLATFORM_ORGANIZATION_ID",
                  "category": "YOUR_PLATFORM_CATEGORY_ID"
                }
              }
            }
          }
        }
      }
    },
    "/v1/objects/{id}": {
      "get": {
        "operationId": "getObjectPublication",
        "summary": "Verify the published VTO object-file reference",
        "description": "Path id is objectId. With nested population, require response.data.attributes.published_object_files.vto.data.id to match outputObjectFileId and state Published. The enclosing published_object_files.id is not a model ID. A 200 with data={} is not success. Publication references may update after the POST response.",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObjectResponse"
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Returned Studio ID, not a media ID."
          },
          {
            "name": "populate[published_object_files][populate]",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "*"
              ]
            },
            "description": "Set to * to populate VTO and AR references."
          }
        ]
      }
    },
    "/v1/products/search": {
      "get": {
        "operationId": "searchProducts",
        "summary": "Verify the live Platform product or check SKU collisions",
        "description": "Read-only. Search can match names as well as SKUs. Select the exact SKU and organization. Verify the current model has a file captioned vto; SDK availability is a separate check.",
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Product"
                  }
                }
              }
            }
          },
          "default": {
            "description": "Error; use HTTP status and inspect the body. No automatic mutation retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "searchText",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "SKU to search for."
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "studioJwt": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Studio user JWT supplied by artlabs; keep server-side. Separate from the Platform SDK credential."
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "properties": {
          "data": {
            "nullable": true
          },
          "error": {
            "type": "object",
            "properties": {
              "status": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "description": "Typical Strapi error. Custom controllers can return different details."
      },
      "AutomationMetadata": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "state": {
            "type": "string",
            "enum": [
              "queued",
              "provisioning",
              "running",
              "finished",
              "errored",
              "cancelled"
            ]
          }
        },
        "description": "Match id to the submitted run. state may be absent initially; other preset/operation fields may be present."
      },
      "Media": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "url": {
            "type": "string"
          },
          "caption": {
            "type": "string",
            "nullable": true
          },
          "ext": {
            "type": "string"
          }
        },
        "nullable": true
      },
      "ObjectFileAttributes": {
        "type": "object",
        "properties": {
          "format": {
            "type": "string"
          },
          "compression": {
            "type": "string",
            "nullable": true
          },
          "automating": {
            "type": "boolean",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "properties": {
              "automation": {
                "$ref": "#/components/schemas/AutomationMetadata"
              }
            },
            "nullable": true
          },
          "file": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/MediaEntity"
              }
            }
          }
        }
      },
      "MediaEntity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "attributes": {
            "$ref": "#/components/schemas/Media"
          }
        },
        "required": [
          "id",
          "attributes"
        ],
        "nullable": true
      },
      "ObjectFile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "attributes": {
            "$ref": "#/components/schemas/ObjectFileAttributes"
          }
        },
        "required": [
          "id",
          "attributes"
        ],
        "nullable": true
      },
      "ObjectFileResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ObjectFile"
          },
          "meta": {
            "type": "object",
            "properties": {}
          }
        },
        "required": [
          "data"
        ]
      },
      "Variant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "fileId": {
            "type": "integer"
          },
          "fileUrl": {
            "type": "string",
            "nullable": true
          },
          "compression": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "id"
        ]
      },
      "Model": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "automated": {
            "type": "boolean",
            "nullable": true
          },
          "format": {
            "type": "string"
          },
          "compression": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "properties": {
              "automation": {
                "$ref": "#/components/schemas/AutomationMetadata"
              }
            },
            "nullable": true
          },
          "file": {
            "$ref": "#/components/schemas/Media"
          },
          "mergedInfo": {
            "type": "object",
            "properties": {
              "GLB_VTO": {
                "$ref": "#/components/schemas/Variant"
              },
              "GLB_VTO_DRACO": {
                "$ref": "#/components/schemas/Variant"
              }
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "PublishedFiles": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Publication component ID; never use as an object-file ID."
          },
          "vto": {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/ObjectFile"
              }
            }
          },
          "ar": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ObjectFile"
                }
              }
            }
          }
        },
        "nullable": true
      },
      "ObjectEntity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "attributes": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "state": {
                "type": "string"
              },
              "published_product": {
                "type": "object",
                "properties": {},
                "nullable": true
              },
              "published_object_files": {
                "$ref": "#/components/schemas/PublishedFiles"
              }
            }
          }
        },
        "description": "An inaccessible or missing object can be returned as an empty object."
      },
      "ObjectResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ObjectEntity"
          },
          "meta": {
            "type": "object",
            "properties": {}
          }
        },
        "required": [
          "data"
        ]
      },
      "ObjectListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ObjectEntity"
            }
          },
          "meta": {
            "type": "object",
            "properties": {}
          }
        },
        "required": [
          "data"
        ]
      },
      "CreateObjectRequest": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1
              },
              "project": {
                "type": "integer"
              }
            },
            "additionalProperties": false,
            "required": [
              "name",
              "project"
            ]
          }
        },
        "additionalProperties": false,
        "required": [
          "data"
        ]
      },
      "CreatedObject": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "state": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "StartPresetRequest": {
        "type": "object",
        "properties": {
          "preset": {
            "type": "integer"
          }
        },
        "additionalProperties": false,
        "required": [
          "preset"
        ]
      },
      "StartedAutomation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "state": {
            "type": "string",
            "enum": [
              "queued",
              "provisioning",
              "running",
              "finished",
              "errored",
              "cancelled"
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "PublishRequest": {
        "type": "object",
        "properties": {
          "objectFiles": {
            "type": "object",
            "properties": {
              "vto": {
                "type": "integer",
                "description": "Selected output object-file ID."
              }
            },
            "additionalProperties": false,
            "required": [
              "vto"
            ]
          },
          "product": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1
              },
              "SKU": {
                "type": "string",
                "minLength": 1
              },
              "organization": {
                "type": "string",
                "minLength": 1
              },
              "category": {
                "type": "string",
                "minLength": 1
              },
              "description": {
                "type": "string",
                "minLength": 1
              }
            },
            "additionalProperties": false,
            "required": [
              "name",
              "SKU",
              "organization",
              "category"
            ]
          }
        },
        "additionalProperties": false,
        "required": [
          "objectFiles",
          "product"
        ]
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "SKU": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "category": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  }
                },
                "required": [
                  "id"
                ]
              }
            ]
          },
          "organization": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  }
                },
                "required": [
                  "id"
                ]
              }
            ]
          },
          "model": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Media"
                    }
                  }
                },
                "required": [
                  "id"
                ]
              }
            ]
          },
          "published_at": {
            "type": "string",
            "nullable": true
          }
        },
        "required": [
          "id",
          "SKU"
        ]
      }
    }
  }
}
