GlossiDocs
Open Glossi
API

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 renderCreate job → Upload → Confirm → Poll until done

Step 1: Create a Job

Endpoint:

POST https://api.glossi.io/api/v1/jobs

Headers:

HeaderValue
X-API-KeyYour API key
Content-Typeapplication/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:

HeaderValue
Content-Typemodel/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-uploads

Headers:

HeaderValue
X-API-KeyYour 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:

HeaderValue
X-API-KeyYour 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 results are derived from each model's file name. When renders were requested, each result also includes a renders array of { id, imageUrl, fileType } entries.


Job Status Flow

AWAITING_UPLOADS → PROCESSING → CREATING_PROJECTS → RENDERING → COMPLETE
                       ↓              ↓                 ↓
                    FAILED         FAILED            FAILED
StatusDescription
AWAITING_UPLOADSWaiting for files to be uploaded and confirmed
PROCESSINGModels are being processed
CREATING_PROJECTSProjects are being created
RENDERINGRenders in progress (if rendering requested)
COMPLETEAll done
FAILEDSomething went wrong (check error field)

Job Options

FieldTypeDefaultDescription
modelsarrayRequiredModels to upload (fileName + fileType). Maximum 50 per job
templateIdstring-Template to use for projects (required if rendering)
createProjectsbooleantrueCreate projects after models are ready
renderSettingsobject-Render settings. Include renderBookmarks: true or renderShots: true to trigger rendering
webhookUrlstring-URL to receive completion notification

Tip To render images, include "renderSettings": { "renderBookmarks": true }. To skip rendering, omit renderSettings entirely.


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
done

Using 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 a webhookUrl. 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-Signature header 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.

On this page