> ## Documentation Index
> Fetch the complete documentation index at: https://docs.platform.decart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Queue API

> Asynchronous job-based video processing on Android

The Queue API is the primary way to process videos with the Android SDK. Submit jobs that are processed asynchronously, then poll for status or observe progress as a Kotlin Flow.

<Note>
  All video processing (video editing, restyling, motion control) uses the Queue API. For realtime camera transformation, use the [Realtime API](/sdks/android-realtime).
</Note>

## Overview

The Queue API provides five methods:

| Method               | Description                                    |
| -------------------- | ---------------------------------------------- |
| `submit()`           | Submit a job and get a job ID immediately      |
| `status()`           | Check the status of a submitted job            |
| `result()`           | Download the completed video as `ByteArray`    |
| `submitAndPoll()`    | Submit and auto-poll until completion          |
| `submitAndObserve()` | Submit and return a `Flow` of progress updates |

## Quick Start

```kotlin theme={null}
import ai.decart.sdk.DecartClient
import ai.decart.sdk.DecartClientConfig
import ai.decart.sdk.VideoModels
import ai.decart.sdk.queue.FileInput
import ai.decart.sdk.queue.QueueJobResult
import ai.decart.sdk.queue.VideoEditInput

val client = DecartClient(context, DecartClientConfig(apiKey = "your-api-key"))

val input = VideoEditInput(
    prompt = "Cinematic color grade, soft contrast",
    data = FileInput.fromUri(videoUri),
)

when (val result = client.queue.submitAndPoll(VideoModels.LUCY_2_1, input)) {
    is QueueJobResult.Completed -> {
        val output = java.io.File(context.cacheDir, "output.mp4")
        output.writeBytes(result.data)
    }
    is QueueJobResult.Failed -> {
        Log.e("Decart", "Job failed: ${result.error}")
    }
    else -> Unit
}

client.release()
```

## Submit a Job

Submit a video processing job and receive a job ID immediately:

```kotlin theme={null}
val input = VideoEditInput(
    prompt = "Transform to anime style",
    data = FileInput.fromUri(videoUri),
)

val job = client.queue.submit(VideoModels.LUCY_2_1, input)

println("Job ID: ${job.jobId}")
println("Status: ${job.status}")  // PENDING
```

**Returns:** `JobSubmitResponse`

* `jobId: String` - Unique job identifier
* `status: JobStatus` - Initial status (`PENDING`)

***

## Check Job Status

Poll the status of a submitted job:

```kotlin theme={null}
val status = client.queue.status(job.jobId)
println("Status: ${status.status}")  // PENDING, PROCESSING, COMPLETED, or FAILED
```

**Status Values:**

| Status       | Description                            |
| ------------ | -------------------------------------- |
| `PENDING`    | Job is queued, waiting to be processed |
| `PROCESSING` | Job is currently being processed       |
| `COMPLETED`  | Job finished successfully              |
| `FAILED`     | Job failed or timed out                |

***

## Get Job Result

Download the completed video as raw bytes:

```kotlin theme={null}
val status = client.queue.status(job.jobId)

if (status.status == JobStatus.COMPLETED) {
    val videoBytes = client.queue.result(job.jobId)

    val output = java.io.File(context.cacheDir, "output.mp4")
    output.writeBytes(videoBytes)
}
```

**Returns:** `ByteArray` - The output video (MP4).

***

## Submit and Poll

The easiest way to use the Queue API. Submit a job and automatically poll until completion:

```kotlin theme={null}
val result = client.queue.submitAndPoll(
    model = VideoModels.LUCY_2_1,
    input = VideoEditInput(
        prompt = "Cinematic color grade",
        data = FileInput.fromUri(videoUri),
    ),
    onStatusChange = { status ->
        Log.d("Decart", "Status: ${status.status}")
    }
)

when (result) {
    is QueueJobResult.Completed -> {
        val output = java.io.File(context.cacheDir, "output.mp4")
        output.writeBytes(result.data)
    }
    is QueueJobResult.Failed -> {
        Log.e("Decart", "Failed: ${result.error}")
    }
    else -> Unit
}
```

**Parameters:**

* `model` (required) - Video model from `VideoModels`
* `input` (required) - Typed input (e.g., `VideoEditInput`, `TextToVideoInput`)
* `onStatusChange` (optional) - Callback invoked on each status change

<Note>
  `submitAndPoll` does not throw on job failure — it returns `QueueJobResult.Failed`. It will throw `QueueSubmitException`, `QueueStatusException`, or `QueueResultException` on network/API errors.
</Note>

***

## Submit and Observe (Flow)

Get a reactive `Flow` of progress updates, ideal for Jetpack Compose UIs:

```kotlin theme={null}
client.queue.submitAndObserve(VideoModels.LUCY_2_1, input)
    .collect { update ->
        when (update) {
            is QueueJobResult.InProgress -> {
                // Pending or processing
                Log.d("Decart", "Status: ${update.status}")
                updateProgressUI(update.status)
            }
            is QueueJobResult.Completed -> {
                Log.d("Decart", "Done: ${update.data.size} bytes")
                saveVideo(update.data)
            }
            is QueueJobResult.Failed -> {
                Log.e("Decart", "Failed: ${update.error}")
                showError(update.error)
            }
        }
    }
```

**Emits:**

1. `QueueJobResult.InProgress` - On each poll while pending/processing
2. `QueueJobResult.Completed` - Terminal: contains the video bytes
3. `QueueJobResult.Failed` - Terminal: contains the error message

<Tip>
  `submitAndObserve()` returns a cold Flow that runs on `Dispatchers.IO`. Collect it in `viewModelScope` or `lifecycleScope` for automatic cancellation.
</Tip>

***

## Input Types

Each model category has a typed input class that enforces required fields at compile time.

### VideoEditInput

For video-to-video editing models: `LUCY_2_1`, `LUCY_CLIP`.

```kotlin theme={null}
val input = VideoEditInput(
    prompt = "Cinematic color grade, soft contrast",  // required (can be empty)
    data = FileInput.fromUri(videoUri),                // required
    referenceImage = FileInput.fromUri(refImageUri),   // optional
    seed = 42,                                         // optional (0–4294967295)
    resolution = "720p",                               // optional
    enhancePrompt = true,                              // optional
)
```

<ParamField body="prompt" type="String" required>
  Text prompt describing the desired edit. Max 1000 characters. Can be empty string.
</ParamField>

<ParamField body="data" type="FileInput" required>
  Video file to transform.
</ParamField>

<ParamField body="referenceImage" type="FileInput">
  Optional reference image for style guidance.
</ParamField>

<ParamField body="seed" type="Int">
  Seed for reproducible output (0–4294967295).
</ParamField>

<ParamField body="resolution" type="String" default="720p">
  Output resolution.
</ParamField>

<ParamField body="enhancePrompt" type="Boolean" default="true">
  Whether to AI-enhance the prompt.
</ParamField>

### VideoRestyleInput

For video restyling: `LUCY_RESTYLE_2`. Must provide exactly one of `prompt` or `referenceImage` (mutually exclusive).

```kotlin theme={null}
// Option A: Restyle with prompt
val restyle = VideoRestyleInput(
    data = FileInput.fromUri(videoUri),
    prompt = "Oil painting style with bold brushstrokes",
    enhancePrompt = true,
)

// Option B: Restyle with reference image
val restyle = VideoRestyleInput(
    data = FileInput.fromUri(videoUri),
    referenceImage = FileInput.fromUri(styleImageUri),
    seed = 7,
)

client.queue.submit(VideoModels.LUCY_RESTYLE_2, restyle)
```

<Warning>
  `prompt` and `referenceImage` are mutually exclusive. Providing both throws an `IllegalArgumentException`. When using `referenceImage`, `enhancePrompt` must not be `true`.
</Warning>

## FileInput

`FileInput` is a sealed class that wraps common Android file sources. All queue input types use it for file fields.

```kotlin theme={null}
import ai.decart.sdk.queue.FileInput

// From gallery picker or camera capture result
val fromUri = FileInput.fromUri(contentUri)

// From a file on disk
val fromFile = FileInput.fromFile(java.io.File("/path/to/video.mp4"))

// From raw bytes in memory
val fromBytes = FileInput.fromBytes(videoBytes, "video/mp4")

// From an InputStream (streaming — read once at submission)
val fromStream = FileInput.fromInputStream(inputStream, "video/mp4")
```

| Factory method                      | Source                | Best for                                          |
| ----------------------------------- | --------------------- | ------------------------------------------------- |
| `fromUri(uri)`                      | Android content `Uri` | Gallery picker, camera capture, content providers |
| `fromFile(file)`                    | `java.io.File`        | Files on local storage                            |
| `fromBytes(bytes, mimeType)`        | `ByteArray`           | In-memory data                                    |
| `fromInputStream(stream, mimeType)` | `InputStream`         | Streaming from network or assets                  |

<Tip>
  `fromUri()` and `fromFile()` stream directly from disk into the HTTP body without buffering in memory, avoiding OOM errors on large video files.
</Tip>

***

## Error Handling

Queue methods throw typed exceptions on network/API errors:

```kotlin theme={null}
import ai.decart.sdk.queue.*

try {
    val result = client.queue.submitAndPoll(model, input)
    when (result) {
        is QueueJobResult.Completed -> saveVideo(result.data)
        is QueueJobResult.Failed -> showError(result.error)
        else -> Unit
    }
} catch (e: InvalidInputException) {
    Log.e("Decart", "Invalid input: ${e.message}")
} catch (e: QueueSubmitException) {
    Log.e("Decart", "Submit failed: ${e.message} (HTTP ${e.statusCode})")
} catch (e: QueueStatusException) {
    Log.e("Decart", "Status check failed: ${e.message}")
} catch (e: QueueResultException) {
    Log.e("Decart", "Download failed: ${e.message}")
}
```

**Exception hierarchy:**

| Exception               | Thrown when                              |
| ----------------------- | ---------------------------------------- |
| `QueueException`        | Base class for all queue errors          |
| `QueueSubmitException`  | Job submission fails                     |
| `QueueStatusException`  | Status check fails                       |
| `QueueResultException`  | Result download fails                    |
| `InvalidInputException` | Input validation fails before submission |

All queue exceptions include an optional `statusCode: Int?` for HTTP errors.

***

## Manual Polling

For custom polling logic, combine `submit()`, `status()`, and `result()`:

```kotlin theme={null}
import kotlinx.coroutines.delay

// Submit job
val job = client.queue.submit(VideoModels.LUCY_2_1, input)
Log.d("Decart", "Job submitted: ${job.jobId}")

// Manual polling loop
while (true) {
    val current = client.queue.status(job.jobId)
    Log.d("Decart", "Status: ${current.status}")

    when (current.status) {
        JobStatus.COMPLETED -> {
            val videoBytes = client.queue.result(job.jobId)
            saveVideo(videoBytes)
            break
        }
        JobStatus.FAILED -> {
            Log.e("Decart", "Generation failed")
            break
        }
        else -> delay(1_500L)  // Wait before next poll
    }
}
```

***

## Supported Models

| Model          | Constant                     | Input Type          | Description                                 |
| -------------- | ---------------------------- | ------------------- | ------------------------------------------- |
| Lucy 2.1       | `VideoModels.LUCY_2_1`       | `VideoEditInput`    | Video editing (latest)                      |
| Lucy VTON      | `VideoModels.LUCY_2_1_VTON`  | `VideoEditInput`    | Virtual try-on                              |
| Lucy Restyle 2 | `VideoModels.LUCY_RESTYLE_2` | `VideoRestyleInput` | Video restyling (prompt or reference image) |

<Accordion title="Latest aliases">
  | Alias               | Constant                          | Description                            |
  | ------------------- | --------------------------------- | -------------------------------------- |
  | Lucy Latest         | `VideoModels.LUCY_LATEST`         | Always the latest editing model        |
  | Lucy VTON Latest    | `VideoModels.LUCY_VTON_LATEST`    | Always the latest virtual try-on model |
  | Lucy Restyle Latest | `VideoModels.LUCY_RESTYLE_LATEST` | Always the latest restyling model      |
  | Lucy Clip Latest    | `VideoModels.LUCY_CLIP_LATEST`    | Always the latest legacy editing model |
</Accordion>

<Accordion title="Previous generation models">
  | Model     | Constant                | Input Type       | Description            |
  | --------- | ----------------------- | ---------------- | ---------------------- |
  | Lucy Clip | `VideoModels.LUCY_CLIP` | `VideoEditInput` | Video-to-video editing |
</Accordion>

***

## API Reference

### `client.queue.submit(model, input)`

Submit a job for processing. Suspend function.

**Parameters:**

* `model: VideoModel` - Video model from `VideoModels`
* `input: QueueJobInput` - Typed input (e.g., `VideoEditInput`)

**Returns:** `JobSubmitResponse`

* `jobId: String` - Unique job identifier
* `status: JobStatus` - Initial status (`PENDING`)

**Throws:** `InvalidInputException`, `QueueSubmitException`

***

### `client.queue.status(jobId)`

Check the status of a job. Suspend function.

**Parameters:**

* `jobId: String` - Job ID from `submit()`

**Returns:** `JobStatusResponse`

* `jobId: String` - Job identifier
* `status: JobStatus` - Current status

**Throws:** `QueueStatusException`

***

### `client.queue.result(jobId)`

Download the result of a completed job. Suspend function.

**Parameters:**

* `jobId: String` - Job ID from `submit()`

**Returns:** `ByteArray` - Output video bytes (MP4)

**Throws:** `QueueResultException`

***

### `client.queue.submitAndPoll(model, input, onStatusChange?)`

Submit a job and auto-poll until completion. Suspend function.

**Parameters:**

* `model: VideoModel` - Video model
* `input: QueueJobInput` - Typed input
* `onStatusChange: ((JobStatusResponse) -> Unit)?` - Optional status callback

**Returns:** `QueueJobResult.Completed` or `QueueJobResult.Failed`

**Throws:** `QueueSubmitException`, `QueueStatusException`, `QueueResultException` on network errors

***

### `client.queue.submitAndObserve(model, input)`

Submit a job and return a Flow of progress updates.

**Parameters:**

* `model: VideoModel` - Video model
* `input: QueueJobInput` - Typed input

**Returns:** `Flow<QueueJobResult>` - Emits `InProgress`, then `Completed` or `Failed`

***

### `client.queue.release()`

Release HTTP resources held by the queue client.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Realtime API" icon="bolt" href="/sdks/android-realtime">
    Transform camera video streams in realtime with WebRTC
  </Card>

  <Card title="SDK Overview" icon="android" href="/sdks/android">
    Installation, setup, and Android SDK fundamentals
  </Card>
</CardGroup>
