Populate the library
Import models from URLs, upload props and decals, and bring in PBR materials from texture files through the API.
The rendering workflow starts from a model, but a workspace library holds more than models: props to dress scenes, decals to apply to surfaces, and materials to swap onto products. Each has its own API surface, and they all follow the same shape as models: create a record and get a signed upload URL, PUT the file, confirm, then poll status until it is ready.
Everything on this page is also available to AI agents through the Glossi MCP connector; the same server code backs both.
Import a model from a URL
When a model already lives at a public URL (a CDN, a signed cloud-storage link, a supplier's download page), Glossi can fetch it for you instead of you streaming the bytes through your integration.
POST https://api.glossi.io/api/v1/models/importcurl -X POST https://api.glossi.io/api/v1/models/import \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{"url": "https://cdn.example.com/models/chair.glb", "name": "Chair"}'Response (202 Accepted):
{
"modelId": "abc123-uuid",
"name": "Chair",
"fileType": "glb",
"status": "UPLOADING"
}| Field | Required | Description |
|---|---|---|
url | Yes | Public http(s) URL. Private network addresses are refused, and every redirect is checked the same way |
name | No | Library name. Defaults to the file name in the URL |
fileType | No | File extension. Needed when the URL path has no extension, which signed links often lack |
The download and confirm run in the background. Poll POST /models/status as in the complete workflow: the model moves UPLOADING → PENDING → ANALYZING → READY. If the download fails, the model is left FAILED and its status entry carries an error field with the reason. Files are capped at 1 GB.
Upload props
Props are 3D files that dress a scene: furniture, plants, packaging. They accept the same formats as models and go through the same converter.
Create props and get upload URLs
POST https://api.glossi.io/api/v1/propscurl -X POST https://api.glossi.io/api/v1/props \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"props": [
{ "fileName": "wooden_stool.glb", "collection": "Furniture" },
{ "fileName": "fern.fbx", "collection": "Plants" }
]
}'Response (201 Created):
{
"props": [
{
"propId": "prop-uuid-1",
"name": "Wooden Stool",
"uploadUrl": "https://s3.amazonaws.com/...",
"uploadKey": "workspaces/.../PROPS/prop-uuid-1/model.glb"
},
{
"propId": "prop-uuid-2",
"name": "Fern",
"uploadUrl": "https://s3.amazonaws.com/...",
"uploadKey": "workspaces/.../PROPS/prop-uuid-2/model.fbx"
}
]
}Up to 50 props per request. collection is a name, not an id: the collection is created in your workspace the first time you use it and matched by name after that. name is optional and defaults to a cleaned-up file name.
Upload and confirm
PUT each file to its uploadUrl within one hour, then confirm each prop:
curl -X PUT "https://s3.amazonaws.com/..." --data-binary @wooden_stool.glb
curl -X POST https://api.glossi.io/api/v1/props/prop-uuid-1/confirm \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx"Wait until ready
curl -X POST https://api.glossi.io/api/v1/props/status \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{"propIds": ["prop-uuid-1", "prop-uuid-2"]}'{
"props": [
{ "propId": "prop-uuid-1", "name": "Wooden Stool", "status": "READY", "ready": true },
{ "propId": "prop-uuid-2", "name": "Fern", "status": "ANALYZING", "ready": false }
],
"allReady": false,
"notFound": []
}Poll every few seconds until allReady is true. GET /props lists the library (filter by status, name, or collectionId) and GET /props/{propId} returns one prop.
Upload decals
Decals are images (logos, labels, graphics) applied to model surfaces. They need no conversion, so they are ready as soon as you confirm.
Create decals and get upload URLs
POST https://api.glossi.io/api/v1/decalscurl -X POST https://api.glossi.io/api/v1/decals \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{"decals": [{ "fileName": "brand_logo.png", "collection": "Logos" }]}'Response (201 Created):
{
"decals": [
{
"decalId": "decal-uuid",
"name": "Brand Logo",
"uploadUrl": "https://s3.amazonaws.com/...",
"uploadKey": "workspaces/.../DECALS/decal-uuid/source.png",
"contentType": "image/png"
}
]
}Accepted formats: png, jpg, jpeg, webp, gif, svg, tif, tiff. Up to 50 per request.
Upload and confirm
The upload must send the returned
contentType. The signed URL is bound to it, so a PUT without that exactContent-Typeheader is refused by storage.
curl -X PUT "https://s3.amazonaws.com/..." \
-H "Content-Type: image/png" \
--data-binary @brand_logo.png
curl -X POST https://api.glossi.io/api/v1/decals/decal-uuid/confirm \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx"{ "decalId": "decal-uuid", "status": "READY" }Confirm reads the image's dimensions and marks it ready. POST /decals/status, GET /decals, and GET /decals/{decalId} mirror the prop endpoints.
Import materials
A material is a set of texture images, one per PBR channel: diffuse, normal, roughness, metallic, depth, and opacity. You supply the channels you have. Channels you leave out stay empty unless you ask Glossi to generate them when you confirm. To create a material from a description instead, see Generate a material from a description or a photo.
Plan a folder import (optional)
If you have a folder of textures named by convention (Wood_Oak_BaseColor.png, Wood_Oak_Normal.png, ...), this endpoint groups them into materials for you. Nothing is uploaded. For packs that arrive as .zip files, unzip each into its own folder first and include those paths. (In Claude.ai and Claude Desktop, the upload_materials panel opens zips for you.)
curl -X POST https://api.glossi.io/api/v1/materials/plan-import \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{"paths": ["Wood_Oak_BaseColor.png", "Wood_Oak_Normal.png", "Wood_Oak_AO.png", "Leather_ORM.png"]}'{
"materials": [
{
"name": "Wood Oak",
"textures": { "diffuse": "Wood_Oak_BaseColor.png", "normal": "Wood_Oak_Normal.png" }
}
],
"ignored": ["Wood_Oak_AO.png"],
"packed": ["Leather_ORM.png"],
"unmatched": [],
"skipped": [],
"duplicates": []
}ignored lists maps Glossi has no slot for (ambient occlusion, specular, emissive). packed lists combined maps such as ORM, which need to be exported as separate channels. unmatched lists images whose channel could not be told from the name; pass "unmatchedAsDiffuse": true to treat those as single-texture materials.
Create a material and get one upload URL per channel
POST https://api.glossi.io/api/v1/materialscurl -X POST https://api.glossi.io/api/v1/materials \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Wood Oak",
"textures": [
{ "channel": "diffuse", "fileType": "png" },
{ "channel": "normal", "fileType": "png" }
],
"collection": "Woods"
}'Response (201 Created):
{
"materialId": "custom_1757930400000_k3j9x2a",
"name": "Wood Oak",
"collectionId": "collection-uuid",
"uploads": [
{ "channel": "diffuse.png", "uploadUrl": "https://s3.amazonaws.com/..." },
{ "channel": "normal.png", "uploadUrl": "https://s3.amazonaws.com/..." }
],
"uploadedTypes": ["diffuse.png", "normal.png"]
}Texture formats: png, jpg, jpeg, exr. Keep name, uploadedTypes, and collectionId from this response: confirm needs them.
Upload, confirm, and wait
curl -X PUT "https://s3.amazonaws.com/..." --data-binary @Wood_Oak_BaseColor.png
curl -X PUT "https://s3.amazonaws.com/..." --data-binary @Wood_Oak_Normal.png
curl -X POST https://api.glossi.io/api/v1/materials/custom_1757930400000_k3j9x2a/confirm \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{"name": "Wood Oak", "uploadedTypes": ["diffuse.png", "normal.png"], "collectionId": "collection-uuid"}'{ "materialId": "custom_1757930400000_k3j9x2a", "status": "processing" }Optional fields on confirm:
| Field | Default | Description |
|---|---|---|
generateMissingMaps | false | Generate the channels you did not upload. Needs a diffuse. Uses your workspace's usage allowance once for the material, and is not charged if the build fails. |
upscaleReference | false | Upscale your diffuse before processing. Needs a diffuse, and a paid plan. |
materialType | none | What the material is: fabric, metal, wood, plastic, leather, or ceramic. Generated maps follow it, so plastic or wood never comes back metallic. Leave it out if none fits. |
hasOpacity | none | false when the material has no see-through or cut-out areas: no opacity map is generated and the material stays opaque. true for lace, mesh, perforation or glass when you did not upload an opacity map. Leave it out to let Glossi decide from the image. |
materialType and hasOpacity only apply with generateMissingMaps, and only shape the maps Glossi generates. Maps you upload are used as they are.
Without them, importing never uses your plan's usage. If no usage is left, generateMissingMaps answers 402 with USAGE_LIMIT_REACHED; upscaleReference on a free plan answers 403 with PLAN_LIMIT_REACHED.
Processing runs in the background and usually takes one to two minutes. Poll for it:
curl -X POST https://api.glossi.io/api/v1/materials/status \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{"materialIds": ["custom_1757930400000_k3j9x2a"]}'{
"materials": [{ "materialId": "custom_1757930400000_k3j9x2a", "status": "ready" }],
"allReady": true
}status is processing, ready, or failed (with an error field). Once ready, the material can be used in material swaps and by the Studio.
Generate a material from a description or a photo
Describe a surface, give a photo of a real one, or both, and Glossi creates a tileable PBR material, with base colour, normal, roughness, metallic, and height maps, and adds it to your library. With a photo, the material reproduces that surface and your description only adjusts it ("warmer", "more worn").
curl -X POST https://api.glossi.io/api/v1/materials/generate \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 00000000-0000-4000-8000-000000000000" \
-d '{"prompt": "worn tan saddle leather with visible pores", "materialType": "fabric", "collection": "Leathers"}'Response (202 Accepted):
{ "materialId": "ai_1757930400000_k3j9x2a", "status": "processing", "collectionId": "collection-uuid" }| Field | Description |
|---|---|
prompt | The surface to create, up to 1000 characters. Optional when you send a photo. |
referenceImageUrl | A public link to a photo of the surface: PNG, JPEG, or WEBP, up to 25 MB. |
referenceKey | A photo you uploaded (see below). Send this or referenceImageUrl, not both. |
name | Name in the library. Defaults to a short name taken from the prompt. |
materialType | fabric, metal, wood, plastic, leather, or ceramic, to tune how shiny it is. Leave it out to let Glossi decide. |
collection | Collection name. Created if it does not exist. |
Send a prompt, a photo, or both.
To use a photo on your computer, get an upload URL first, upload the photo with the Content-Type it returns, then pass referenceKey:
curl -X POST https://api.glossi.io/api/v1/materials/generate/reference-upload \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{"fileType": "jpg"}'{
"referenceKey": "AI_EDIT_UPLOADS/.../reference.jpg",
"uploadUrl": "https://s3.amazonaws.com/...",
"contentType": "image/jpeg"
}curl -X PUT "https://s3.amazonaws.com/..." -H "Content-Type: image/jpeg" --data-binary @leather.jpgUploaded photos are kept for 7 days.
Generating a material uses your workspace's usage allowance, and a generation that fails is not charged. If no usage is left the request answers 402 with USAGE_LIMIT_REACHED; a photo that cannot be used answers 400 before anything is charged. It usually takes a few minutes; poll POST /materials/status as for an import. Send an Idempotency-Key so a retried request does not generate a second material.
Find materials
GET /materials lists the workspace's materials newest first. Add ?search= for a fuzzy match on name, keywords, and colour (the same search the Studio assistant uses):
curl "https://api.glossi.io/api/v1/materials?search=brushed%20steel" \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx"Manage what is in the library
Every kind supports rename and delete, and props, decals, and materials can be moved between collections. The shape is the same across kinds.
Rename or move to a collection
PATCH https://api.glossi.io/api/v1/props/{propId}
PATCH https://api.glossi.io/api/v1/decals/{decalId}
PATCH https://api.glossi.io/api/v1/materials/{materialId}
PATCH https://api.glossi.io/api/v1/models/{modelId}curl -X PATCH https://api.glossi.io/api/v1/props/prop-uuid-1 \
-H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{"name": "Oak Stool", "collection": "Furniture"}'| Field | Description |
|---|---|
name | New library name |
collection | Collection name, created if it does not exist yet. Send null to take the item out of its collection |
collectionId | A collection id instead of a name. Ignored when collection is given |
Models accept name only. The response is the updated item, in the same shape the GET endpoint returns.
Delete
DELETE https://api.glossi.io/api/v1/props/{propId}
DELETE https://api.glossi.io/api/v1/decals/{decalId}
DELETE https://api.glossi.io/api/v1/materials/{materialId}
DELETE https://api.glossi.io/api/v1/models/{modelId}Deletion is permanent and removes the stored files too. Deleting a material also drops it from any surface, colorway, or showroom price list that referenced it. Deleting a model removes it from the projects that use it, so check GET /models/{modelId} first if that matters.
{ "deleted": true, "propId": "prop-uuid-1" }Download a model file
GET https://api.glossi.io/api/v1/models/{modelId}/download?format=glb|usdz|sourceRedirects to a one-hour signed URL for the converted GLB (default), the USDZ derivative, or the original upload. See the Models API for details.
Limits and errors
- Batches: 50 items per create request, 100 ids per status request.
- Signed upload URLs expire after one hour.
- Files: 1 GB per model or prop.
403 STORAGE_LIMIT_REACHEDwhen the workspace's storage allowance is used up (props and models).400 VALIDATION_ERRORfor a malformed request; the message says which item and why.404 NOT_FOUNDwhen an id does not exist in your workspace.
All creation requests accept an Idempotency-Key header, as described in Getting Started.