# Complete Workflow

{% stepper %}
{% step %}

## Upload a 3D Model

Uploading a model is a two-step process: create the model record to get a signed upload URL, then upload the file directly to storage.

### Create the Model Record

Request:

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

Headers:

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

Body:

```json
{
  "models": [
    {
      "name": "My Product",
      "fileName": "product.glb",
      "fileType": "glb"
    }
  ]
}
```

Example with cURL:

```bash
curl -X POST https://api.glossi.app/api/v1/models \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "models": [
      {
        "name": "My Product",
        "fileName": "product.glb",
        "fileType": "glb"
      }
    ]
  }'
```

Response:

```json
{
  "models": [
    {
      "modelId": "abc123-uuid",
      "name": "My Product",
      "uploadUrl": "https://s3.amazonaws.com/...",
      "uploadKey": "workspaces/.../model.glb"
    }
  ]
}
```

Save these values from the response:

* `modelId` → used in later steps
* `uploadUrl` → upload your file to this URL

{% hint style="info" %}
The `uploadUrl` expires after 1 hour. If uploading many files, complete the upload within this time window.
{% endhint %}

### Upload the File to S3

Upload your 3D file directly to the signed URL (this request goes to S3, not Glossi's API).

Request:

```
PUT <uploadUrl from previous response>
```

Headers:

| Header         | Value                               |
| -------------- | ----------------------------------- |
| `Content-Type` | `model/gltf-binary` (for GLB files) |

Body: Your binary file data

Example with cURL:

```bash
curl -X PUT "<uploadUrl>" \
  -H "Content-Type: model/gltf-binary" \
  --data-binary @/path/to/your/model.glb
```

In Postman:

1. Set method to PUT
2. Paste the `uploadUrl` as the URL
3. Add header `Content-Type: model/gltf-binary`
4. Body → binary → choose file → Send

{% hint style="warning" %}
Do not 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`.

### Confirm the Upload

After uploading to S3, finalize the model so the file path is saved and the model is marked ready for processing.

Request:

```
POST https://api.glossi.app/api/v1/models/{modelId}/confirm
```

Headers:

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

Body:

```json
{
  "fileType": "glb"
}
```

Example with cURL:

```bash
curl -X POST https://api.glossi.app/api/v1/models/abc123-uuid/confirm \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"fileType": "glb"}'
```

Response:

```json
{
  "modelId": "abc123-uuid",
  "name": "My Product",
  "status": "PENDING",
  "filePath": "workspaces/.../model.glb"
}
```

The upload is confirmed; the model will now be processed before it can be used in a project.
{% endstep %}

{% step %}

### Wait for Model Processing (Important!)

After confirming the upload, poll the model status until it's ready.

Request:

```
POST https://api.glossi.app/api/v1/models/status
```

Headers:

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

Body:

```json
{
  "modelIds": ["abc123-uuid"]
}
```

Example with cURL:

```bash
curl -X POST https://api.glossi.app/api/v1/models/status \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"modelIds": ["abc123-uuid"]}'
```

Response:

```json
{
  "models": [
    {
      "modelId": "abc123-uuid",
      "name": "My Product",
      "status": "READY",
      "ready": true
    }
  ],
  "allReady": true,
  "notFound": []
}
```

Key fields:

* `ready`: true when the model can be used in a project
* `allReady`: true when ALL models in the request are ready
* `status`: processing status (`PENDING`, `ANALYZING`, `READY`, etc.)

Polling example (bash):

```bash
# Simple polling loop
while true; do
  RESPONSE=$(curl -s -X POST https://api.glossi.app/api/v1/models/status \
    -H "X-API-Key: $API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"modelIds": ["abc123-uuid"]}')

  ALL_READY=$(echo $RESPONSE | jq -r '.allReady')

  if [ "$ALL_READY" = "true" ]; then
    echo "Model ready! Proceeding to create project..."
    break
  fi

  echo "Model still processing..."
  sleep 5
done
```

{% hint style="info" %}
Models typically take 30 seconds to a few minutes to process, depending on complexity.
{% endhint %}
{% endstep %}

{% step %}

### Finding Available Templates

Templates define lighting, camera angles, and background for renders.

Request:

```
GET https://api.glossi.app/api/v1/templates
```

Headers:

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

Example with cURL:

```bash
curl https://api.glossi.app/api/v1/templates \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx"
```

Response:

```json
{
  "templates": [
    {
      "templateId": "abc123-template",
      "name": "Studio Light",
      "description": "Clean studio lighting setup"
    },
    {
      "templateId": "def456-template",
      "name": "Outdoor Scene",
      "description": "Natural outdoor lighting"
    }
  ]
}
```

Save the `templateId` for the template you want to use when creating a project.

Filtering examples:

```bash
# Only public templates
curl "https://api.glossi.app/api/v1/templates?internal=false" \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx"

# Only your workspace templates
curl "https://api.glossi.app/api/v1/templates?public=false" \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx"
```

{% endstep %}

{% step %}

## Create a Project

A project combines your 3D model with a template (lighting, camera angles, background). Ensure your model is `READY` before creating a project.

Request:

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

Headers:

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

Body:

```json
{
  "name": "Product Photoshoot",
  "modelIds": ["abc123-uuid"],
  "templateId": "your-template-uuid"
}
```

Example with cURL:

```bash
curl -X POST https://api.glossi.app/api/v1/projects \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Product Photoshoot",
    "modelIds": ["abc123-uuid"],
    "templateId": "template-uuid"
  }'
```

Response:

```json
{
  "projects": [
    {
      "projectId": "project-uuid",
      "name": "Product Photoshoot",
      "status": "PENDING",
      "modelIds": ["abc123-uuid"],
      "templateId": "template-uuid"
    }
  ]
}
```

Save the `projectId` — you'll use it to start a render.

Creating multiple projects at once:

```json
{
  "modelIds": ["model-1-uuid", "model-2-uuid", "model-3-uuid"],
  "templateId": "template-uuid",
  "createPerModel": true
}
```

This creates one project per model.
{% endstep %}

{% step %}

## Start a Render

Use the `projectId` from the previous step to generate images/videos.

Request:

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

Headers:

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

Body example:

```json
{
  "projectIds": ["project-uuid"],
  "settings": {
    "renderBookmarks": true,
    "renderShots": true,
    "renderVariants": true,
    "imageQuality": 1,
    "videoQuality": 1
  }
}
```

Response:

```json
{
  "jobs": [
    {
      "jobId": "job-uuid",
      "status": "QUEUED",
      "projectId": "project-uuid"
    }
  ],
  "totalProjects": 1
}
```

Save the `jobId` — you'll use it to check render status.

Rendering multiple projects:

```json
{
  "projectIds": ["project-1", "project-2", "project-3"],
  "settings": {
    "renderBookmarks": true,
    "imageQuality": 1
  }
}
```

### Render Settings Explained

| Setting           | Default | Options                                            |
| ----------------- | ------- | -------------------------------------------------- |
| `renderBookmarks` | `true`  | Render all saved camera angles (images)            |
| `renderShots`     | `true`  | Render all video shots                             |
| `renderVariants`  | `true`  | Render all material/color variants                 |
| `imageQuality`    | `1`     | `0`=720p, `1`=1080p, `2`=4K                        |
| `videoQuality`    | `1`     | `0`=Fastest (MP4), `1`=Balanced, `2`=Best (ProRes) |
| `videoResolution` | `1`     | `0`=720p, `1`=1080p, `2`=4K                        |

Example — render images only at 4K:

```bash
curl -X POST https://api.glossi.app/api/v1/renders \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "projectIds": ["project-uuid"],
    "settings": {
      "renderBookmarks": true,
      "renderShots": false,
      "imageQuality": 2
    }
  }'
```

{% endstep %}

{% step %}

## Check Render Status

Poll the job status with the `jobId`.

Request:

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

Headers:

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

Example with cURL:

```bash
curl https://api.glossi.app/api/v1/renders/job-uuid \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx"
```

Response (in progress):

```json
{
  "jobId": "job-uuid",
  "status": "RENDERING",
  "projectId": "project-uuid"
}
```

Response (complete):

```json
{
  "jobId": "job-uuid",
  "status": "COMPLETED",
  "projectId": "project-uuid",
  "renders": [
    {
      "id": "render-uuid-1",
      "name": "Front View - Default",
      "filePath": "workspaces/.../render.png",
      "fileType": "PNG",
      "isVideo": false
    },
    {
      "id": "render-uuid-2",
      "name": "360 Spin - Default",
      "filePath": "workspaces/.../video.mp4",
      "fileType": "MP4",
      "isVideo": true
    }
  ]
}
```

Key fields in renders:

* `name`: Display name of the render
* `filePath`: S3 path to the file (use to copy/download)
* `fileType`: Format of the file (`PNG`, `MP4`, `PRORES`, etc.)
* `isVideo`: `true` for videos, `false` for images

The `renders` array contains all images and videos generated for the job (bookmarks, shots, variants).

### Job Statuses

| Status      | Meaning                                     |
| ----------- | ------------------------------------------- |
| `QUEUED`    | Waiting to start                            |
| `RENDERING` | Currently processing                        |
| `COMPLETED` | Finished successfully                       |
| `FAILED`    | Something went wrong (check `errorMessage`) |

### Retrying Failed Renders

Request:

```
POST https://api.glossi.app/api/v1/renders/{jobId}/retry
```

Headers:

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

Example with cURL:

```bash
curl -X POST https://api.glossi.app/api/v1/renders/job-uuid/retry \
  -H "X-API-Key: glsi_xxxxxxxxxx_xxxxx"
```

Response:

```json
{
  "jobId": "job-uuid",
  "status": "QUEUED",
  "projectId": "project-uuid",
  "batchId": "new-batch-uuid"
}
```

Only jobs with status `FAILED` can be retried.
{% endstep %}

{% step %}

## Set Up Webhooks (Optional)

Instead of polling, receive notifications for important events.

Full documentation: See the Webhooks Guide at ./API\_WEBHOOKS.md

### Available Events

| Event             | Description                                    |
| ----------------- | ---------------------------------------------- |
| `model.processed` | Model upload processing completed successfully |
| `model.failed`    | Model processing failed                        |
| `project.created` | A project was created                          |
| `job.complete`    | A job workflow completed successfully          |
| `job.failed`      | A job workflow failed                          |
| `render.complete` | A render completed successfully                |
| `render.failed`   | A render failed                                |

### Configure Your Webhook

Request:

```
PUT https://api.glossi.app/api/v1/webhooks
```

Body:

```json
{
  "url": "https://your-app.com/webhooks/glossi",
  "events": [
    "model.processed",
    "model.failed",
    "project.created",
    "job.complete",
    "job.failed",
    "render.complete",
    "render.failed"
  ],
  "enabled": true
}
```

Response:

```json
{
  "id": "webhook-uuid",
  "url": "https://your-app.com/webhooks/glossi",
  "secret": "your-webhook-secret",
  "events": ["model.processed", "job.complete", "render.complete", ...],
  "enabled": true
}
```

Save the `secret` — you'll use it to verify webhook signatures.

### Webhook Payloads

<details>

<summary>Model processed payload</summary>

```json
{
  "event": "model.processed",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "modelId": "model-uuid",
    "name": "Chair Model",
    "status": "READY",
    "glbFilePath": "https://s3.../file.glb",
    "thumbnailUrl": "https://s3.../file.jpg"
  }
}
```

</details>

<details>

<summary>Job complete payload</summary>

```json
{
  "event": "job.complete",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "jobId": "job-uuid",
    "status": "COMPLETE",
    "results": [
      {
        "model": { "id": "model-uuid", "name": "Chair" },
        "project": { "id": "project-uuid", "name": "Chair Project" }
      }
    ]
  }
}
```

</details>

<details>

<summary>Render complete payload</summary>

```json
{
  "event": "render.complete",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "jobId": "render-job-uuid",
    "status": "COMPLETED",
    "projectId": "project-uuid",
    "renderIds": ["render-uuid-1", "render-uuid-2"]
  }
}
```

</details>

### Verify Webhook Signatures

Always verify the `X-Glossi-Signature` header to ensure the request originates from Glossi:

```javascript
const crypto = require("crypto")

function verifyWebhook(payload, signature, secret) {
  const expected = crypto.createHmac("sha256", secret).update(JSON.stringify(payload)).digest("hex")

  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}
```

{% endstep %}
{% endstepper %}

{% hint style="info" %}
Next steps:

* Webhooks Guide: ./API\_WEBHOOKS.md
* Simple Workflow: ./API\_SIMPLE\_WORKFLOW\.md
* Getting Started: ./API\_GETTING\_STARTED.md
  {% endhint %}


---

# 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/complete-workflow.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.
