# Simple Workflow (Jobs)

## Why Use Jobs?

| Complete 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 |

{% stepper %}
{% step %}

### Create a Job

**Endpoint:**

```
POST https://api.glossi.app/api/v1/jobs
```

**Headers:**

| Header         | Value              |
| -------------- | ------------------ |
| `X-API-Key`    | Your API key       |
| `Content-Type` | `application/json` |

**Body (without rendering):**

```json
{
  "models": [
    { "fileName": "chair.glb", "fileType": "glb" },
    { "fileName": "table.glb", "fileType": "glb" }
  ],
  "templateId": "your-template-uuid",
  "createProjects": true
}
```

**Body (with rendering):**

```json
{
  "models": [
    { "fileName": "chair.glb", "fileType": "glb" }
  ],
  "templateId": "your-template-uuid",
  "renderSettings": {
    "renderBookmarks": true,
    "imageQuality": 1
  }
}
```

**Response:**

```json
{
  "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"
    }
  ]
}
```

{% endstep %}

{% step %}

### 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

{% hint style="info" %}
Don't include your API key for this request — the signed URL already contains authentication.
{% endhint %}

A successful upload returns an empty response with status `200 OK`.

Repeat this for each model in your job.
{% endstep %}

{% step %}

### 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.app/api/v1/jobs/{jobId}/confirm-uploads
```

**Headers:**

| Header      | Value        |
| ----------- | ------------ |
| `X-API-Key` | Your API key |

**Body:** None required

**Response:**

```json
{
  "jobId": "job-uuid",
  "status": "PROCESSING",
  "modelsConfirmed": 2
}
```

{% endstep %}

{% step %}

### Poll for Completion

The job automatically progresses through each phase. Poll this endpoint to check status.

**Endpoint:**

```
GET https://api.glossi.app/api/v1/jobs/{jobId}
```

**Headers:**

| Header      | Value        |
| ----------- | ------------ |
| `X-API-Key` | Your API key |

**Response (in progress):**

```json
{
  "jobId": "job-uuid",
  "status": "PROCESSING",
  "progress": {
    "modelsUploaded": 2,
    "modelsReady": 1,
    "modelsTotal": 2,
    "projectsCreated": 0,
    "projectsTotal": 2,
    "rendersComplete": 0,
    "rendersTotal": 0
  }
}
```

**Response (complete):**

```json
{
  "jobId": "job-uuid",
  "status": "COMPLETE",
  "batchProject": {
    "id": "batch-uuid",
    "title": "API Job - 2025-02-10"
  },
  "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 Project" }
    },
    {
      "model": { "id": "model-2-uuid", "name": "table.glb" },
      "project": { "id": "project-2-uuid", "name": "Table Project" }
    }
  ]
}
```

{% endstep %}
{% endstepper %}

## 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)                                                       |
| `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                                                       |

{% hint style="info" %}
To render images, include `"renderSettings": { "renderBookmarks": true }`. To skip rendering, omit `renderSettings` entirely.
{% endhint %}

## BatchProject

When you upload multiple models, they're automatically grouped into a BatchProject. The `batchProject` field in the response contains the ID you can use to manage them together.

## Complete Example with cURL

{% stepper %}
{% step %}

### Create job (cURL)

{% code title="create-job.sh" %}

```bash
JOB_RESPONSE=$(curl -s -X POST https://api.glossi.app/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')
```

{% endcode %}
{% endstep %}

{% step %}

### Upload file to S3 (cURL)

{% code title="upload-to-s3.sh" %}

```bash
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: model/gltf-binary" \
  --data-binary @chair.glb
```

{% endcode %}
{% endstep %}

{% step %}

### Confirm uploads (cURL)

{% code title="confirm-uploads.sh" %}

```bash
curl -X POST "https://api.glossi.app/api/v1/jobs/$JOB_ID/confirm-uploads" \
  -H "X-API-Key: $API_KEY"
```

{% endcode %}
{% endstep %}

{% step %}

### Poll for completion (cURL)

{% code title="poll-status.sh" %}

```bash
while true; do
  STATUS_RESPONSE=$(curl -s "https://api.glossi.app/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
```

{% endcode %}
{% endstep %}
{% endstepper %}

## Using Webhooks with Jobs

Instead of polling, you can provide a `webhookUrl` when creating the job to receive notifications:

```json
{
  "models": [
    { "fileName": "chair.glb", "fileType": "glb" }
  ],
  "templateId": "your-template-uuid",
  "webhookUrl": "https://your-app.com/webhooks/glossi"
}
```

When the job completes (or fails), Glossi will POST to your webhook URL. See [Webhooks](broken://pages/c3ff11d01ce7a6bea4fe5d8203b36fce41572f53) for details on payload formats and signature verification.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.glossi.io/glossi-api/simple-workflow-jobs.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
