Run a job through one workflow
Use the Jobs endpoint when Glossi should orchestrate model upload, project creation, rendering, and result state for you.
The Jobs endpoint combines the entire workflow into fewer steps. Instead of managing each step individually, you create a single job that orchestrates everything.
Why Use Jobs?
| Traditional Flow (8+ steps) | Jobs Flow (4 steps) |
|---|---|
| Create model → Upload → Confirm → Poll status → Create project → Render → Poll render | Create job → Upload → Confirm → Poll until done |
Step 1: Create a Job
Endpoint:
POST https://api.glossi.io/api/v1/jobsHeaders:
| Header | Value |
|---|---|
X-API-Key | Your API key |
Content-Type | application/json |
Body (without rendering):
{
"models": [
{ "fileName": "chair.glb", "fileType": "glb" },
{ "fileName": "table.glb", "fileType": "glb" }
],
"templateId": "your-template-uuid",
"createProjects": true
}Body (with rendering):
{
"models": [
{ "fileName": "chair.glb", "fileType": "glb" }
],
"templateId": "your-template-uuid",
"renderSettings": {
"renderBookmarks": true,
"imageQuality": 1
}
}Response:
{
"jobId": "job-uuid",
"status": "AWAITING_UPLOADS",
"models": [
{
"id": "model-1-uuid",
"fileName": "chair.glb",
"uploadUrl": "https://s3.../signed-url-1",
"uploadKey": "workspaces/.../model.glb"
},
{
"id": "model-2-uuid",
"fileName": "table.glb",
"uploadUrl": "https://s3.../signed-url-2",
"uploadKey": "workspaces/.../model.glb"
}
]
}Step 2: Upload Files to S3
Upload each file directly to its signed URL. This request goes directly to S3, not to Glossi's API.
Endpoint:
PUT <uploadUrl from Step 1 response>Headers:
| Header | Value |
|---|---|
Content-Type | model/gltf-binary (for GLB files) |
Body: Your binary file data
Note Don't include your API key for this request - the signed URL already contains authentication.
A successful upload returns an empty response with status 200 OK.
Repeat this for each model in your job.
Step 3: Confirm Uploads
After uploading all files, tell the job that uploads are complete. This sets the file paths and advances the job to processing.
Endpoint:
POST https://api.glossi.io/api/v1/jobs/{jobId}/confirm-uploadsHeaders:
| Header | Value |
|---|---|
X-API-Key | Your API key |
Body: None required
Response:
{
"jobId": "job-uuid",
"status": "PROCESSING",
"modelsConfirmed": 2
}Step 4: Poll for Completion
Poll this endpoint to check status. Polling is what moves the job through its phases — each status check advances the workflow to the next step when the previous one has finished, so keep polling until you reach a terminal status.
Endpoint:
GET https://api.glossi.io/api/v1/jobs/{jobId}Headers:
| Header | Value |
|---|---|
X-API-Key | Your API key |
Response (in progress):
{
"jobId": "job-uuid",
"status": "PROCESSING",
"progress": {
"modelsUploaded": 2,
"modelsReady": 1,
"modelsTotal": 2,
"projectsCreated": 0,
"projectsTotal": 2,
"rendersComplete": 0,
"rendersTotal": 0
}
}Response (complete):
{
"jobId": "job-uuid",
"status": "COMPLETE",
"batchProject": {
"id": "batch-uuid",
"title": "API Job - 2026-07-28"
},
"progress": {
"modelsUploaded": 2,
"modelsReady": 2,
"modelsTotal": 2,
"projectsCreated": 2,
"projectsTotal": 2,
"rendersComplete": 0,
"rendersTotal": 0
},
"results": [
{
"model": { "id": "model-1-uuid", "name": "chair.glb" },
"project": { "id": "project-1-uuid", "name": "chair.glb" }
},
{
"model": { "id": "model-2-uuid", "name": "table.glb" },
"project": { "id": "project-2-uuid", "name": "table.glb" }
}
]
}Note Project names in
resultsare derived from each model's file name. When renders were requested, each result also includes arendersarray of{ id, imageUrl, fileType }entries.
Job Status Flow
AWAITING_UPLOADS → PROCESSING → CREATING_PROJECTS → RENDERING → COMPLETE
↓ ↓ ↓
FAILED FAILED FAILED| Status | Description |
|---|---|
AWAITING_UPLOADS | Waiting for files to be uploaded and confirmed |
PROCESSING | Models are being processed |
CREATING_PROJECTS | Projects are being created |
RENDERING | Renders in progress (if rendering requested) |
COMPLETE | All done |
FAILED | Something went wrong (check error field) |
Job Options
| Field | Type | Default | Description |
|---|---|---|---|
models | array | Required | Models to upload (fileName + fileType). Maximum 50 per job |
templateId | string | - | Template to use for projects (required if rendering) |
createProjects | boolean | true | Create projects after models are ready |
renderSettings | object | - | Render settings. Include renderBookmarks: true or renderShots: true to trigger rendering |
webhookUrl | string | - | URL to receive completion notification |
Tip To render images, include
"renderSettings": { "renderBookmarks": true }. To skip rendering, omitrenderSettingsentirely.
BatchProject
When you upload multiple models with a templateId, the resulting projects are automatically grouped into a BatchProject. The batchProject field in the response contains the ID you can use to manage them together. Jobs with a single model, or without a template, are not grouped and won't include this field.
Complete Example with cURL
# 1. Create job
JOB_RESPONSE=$(curl -s -X POST https://api.glossi.io/api/v1/jobs \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"models": [
{ "fileName": "chair.glb", "fileType": "glb" }
],
"templateId": "your-template-uuid",
"createProjects": true
}')
JOB_ID=$(echo $JOB_RESPONSE | jq -r '.jobId')
UPLOAD_URL=$(echo $JOB_RESPONSE | jq -r '.models[0].uploadUrl')
# 2. Upload file to S3
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: model/gltf-binary" \
--data-binary @chair.glb
# 3. Confirm uploads
curl -X POST "https://api.glossi.io/api/v1/jobs/$JOB_ID/confirm-uploads" \
-H "X-API-Key: $API_KEY"
# 4. Poll for completion
while true; do
STATUS_RESPONSE=$(curl -s "https://api.glossi.io/api/v1/jobs/$JOB_ID" \
-H "X-API-Key: $API_KEY")
STATUS=$(echo $STATUS_RESPONSE | jq -r '.status')
echo "Status: $STATUS"
if [ "$STATUS" = "COMPLETE" ] || [ "$STATUS" = "FAILED" ]; then
echo "Final response:"
echo $STATUS_RESPONSE | jq
break
fi
sleep 5
doneUsing Webhooks with Jobs
You can provide a webhookUrl when creating the job to receive a notification when it completes or fails:
{
"models": [
{ "fileName": "chair.glb", "fileType": "glb" }
],
"templateId": "your-template-uuid",
"webhookUrl": "https://your-app.com/webhooks/glossi"
}Important Webhooks reduce polling frequency but do not replace polling. A job's state only advances when its status is checked, so you must keep polling
GET /jobs/{jobId}(a slow interval like every 30-60 seconds is fine) even when using awebhookUrl. Treat the webhook as a low-latency signal and the polled status as the source of truth.
The per-job webhookUrl notification is different from your workspace webhook configuration:
- It is not signed — there is no
X-Glossi-Signatureheader to verify. Use an unguessable URL if you rely on it. - The payload is flat, not wrapped in the standard envelope:
{
"event": "job.complete",
"jobId": "job-uuid",
"status": "COMPLETE",
"results": [ ... ]
}On failure, event is "job.failed" and an error string replaces results. If you also have a workspace webhook subscribed to job.complete / job.failed, you'll receive both: the signed workspace webhook (see Webhooks) and this unsigned per-job notification.
Build with the Glossi API
Authenticate a workspace integration, choose the right orchestration surface, and run a retry-safe first request.
Control every API step
Create models, confirm uploads, build projects, start renders, inspect status, retry failures, and retrieve outputs through individual endpoints.