# ByteDance: Seedream 5.0 Flash Layerize Layer Decomposition

> Decompose images into editable layers with ByteDance Seedream 5.0 Flash Layerize for asset extraction, compositing and design workflows. Supply one image and optionally describe the objects or groups to separate; without instructions, the model identifies major elements automatically. The API returns a base image and up to 16 separated layers, together with stacking order, layer labels and available position coordinates. Choose automatic sizing or 1K, 1.5K and 2K output tiers. Transparent layers can be reused in an external image editor.

- **Vendor**: ByteDance
- **Series**: Seedream
- **Model / version**: 5.0
- **Variant**: Flash

## Overview

- **Endpoint**: `POST https://api.musespan.com/v1/model-tasks`
- **Model ID**: `bytedance/seedream-5-flash-layerize/image-to-image`
- **Category**: `image-to-image`
- **Execution**: asynchronous task. Submit it, poll its status, then download the declared result files.

## Pricing

Price: $0.027 per image. Billed by the number of images actually generated.
Each request temporarily reserves $0.027; you are charged for actual usage, usually within a minute: any unused amount is released and any usage above it is charged to your balance, even if that takes it below zero; while your balance is below zero, new requests are paused until a top-up covers it.
Failed generations are not charged.

## How to call

Authenticate with `Authorization: Bearer <API_KEY>` and send JSON. Model parameters belong inside input; do not flatten them beside model. Maximum request body: 4 MiB.

- Model: bytedance/seedream-5-flash-layerize/image-to-image
- POST /v1/model-tasks with {"model":"bytedance/seedream-5-flash-layerize/image-to-image","input":{...}}.
- X-Request-Id is required (1-128 letters, digits, _ or -; a UUID is recommended). The task ID equals the X-Request-Id.
- Submission is idempotent: if the POST times out, the connection fails, or it returns 429 or 5xx, resend the byte-identical body with the same X-Request-Id. It returns the existing task and is never charged twice. A different body with the same ID returns 409 request_id_conflict.
- Poll GET /v1/model-tasks/{id} every 5 seconds with a total deadline; queued and running are pending, and only status=completed includes output. Null or empty output can be successful. If it returns 404 task_not_found, the POST never arrived: resend it with the same X-Request-Id instead of polling.
- Declared file URLs are authenticated downloads. Preserve the API authorization header when downloading files.
- Task status and file downloads require the API key that submitted the task.

## Input parameters (inside `input`)

- `image_url` (`string`, required):
  URL of the image to decompose into a base image and independently editable layers. The image must contain between 512x512 and 6000x6000 total pixels, have an aspect ratio between 1/16 and 16, and be no larger than 30 MB.
- `image_size` (`string`, optional):
  Resolution tier for the output base image and layers. `auto` adapts to the input image while preserving each element's aspect ratio.
  - Default: `"auto"`
  - Options: `auto`, `auto_1K`, `auto_1.5K`, `auto_2K`
- `prompt` (`string`, optional):
  Optional instructions describing which elements to separate. When empty, the model automatically separates the major elements. Normalized `<bbox>left top right bottom</bbox>` tags may be used for precise coordinate targeting.
  - Default: `""`
  - Example: `"Return name and description in english."`

Do not send these platform-controlled fields: `enable_safety_checker`, `sync_mode`. Requests that include them fail validation.

## Request example

```json
{
  "model": "bytedance/seedream-5-flash-layerize/image-to-image",
  "input": {
    "prompt": "Separate the three main foreground objects into individual transparent layers: the complete ivory origami crane, the coral-orange sphere, and the dark green ginkgo leaf. Keep the crane as one object rather than splitting its folded paper parts. Return a clean base image containing only the pale aqua studio background and tabletop, plus one layer for each of the three objects. Preserve the original positions, colors, edges and appearance. Return layer names and descriptions in English.",
    "image_url": "https://pub-a7287c8d5de944d984e22a5bd3ab9bbe.r2.dev/uploads/capability-demo-0072d694a35c704c7b203372df1fd60d.png",
    "image_size": "auto_1K"
  }
}
```

## Results

POST returns HTTP 202 with `{id, model, status}`. `queued` and `running` are pending; `completed` includes `output`; `failed` includes `error.code`.

Tasks beyond the number your account (or the platform) may run at once wait in our queue instead of being refused: they stay `queued` with an estimated `queue_position` (1 = next), which can change. Your own tasks wait in order while your account is at its limit.

A task still waiting in the queue (status queued with queue_position) can be cancelled with POST /v1/model-tasks/{id}/cancel and the same API key: it ends as failed with error.code cancelled and its hold is refunded in full. A task no longer waiting in the queue (being sent, sent, or ended) cannot be cancelled (409 task_not_cancellable).

Task objects may gain fields in later versions: ignore fields you do not know.

Completed task (example):

```json
{
  "id": "task_example",
  "model": "bytedance/seedream-5-flash-layerize/image-to-image",
  "status": "completed",
  "output": {
    "layers": [
      {
        "image": {
          "url": "https://api.musespan.com/v1/model-tasks/task_example/artifacts/0"
        }
      }
    ],
    "images": [
      {
        "url": "https://api.musespan.com/v1/model-tasks/task_example/artifacts/1"
      }
    ]
  }
}
```

Output schema (`output` of a completed task; same contract as OpenAPI):

```json
{
  "$ref": "#/components/schemas/SeedreamV5FlashLayerizeOutput",
  "components": {
    "schemas": {
      "Image": {
        "description": "Represents an image file.",
        "properties": {
          "content_type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The mime type of the file.",
            "examples": [
              "image/png"
            ],
            "title": "Content Type"
          },
          "file_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The name of the file. It will be auto-generated if not provided.",
            "examples": [
              "z9RV14K95DvU.png"
            ],
            "title": "File Name"
          },
          "file_size": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "The size of the file in bytes.",
            "examples": [
              4404019
            ],
            "title": "File Size"
          },
          "height": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "The height of the image in pixels.",
            "examples": [
              1024
            ],
            "title": "Height"
          },
          "url": {
            "description": "The URL where the file can be downloaded from.",
            "title": "Url",
            "type": "string"
          },
          "width": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "The width of the image in pixels.",
            "examples": [
              1024
            ],
            "title": "Width"
          }
        },
        "required": [
          "url"
        ],
        "title": "Image",
        "type": "object"
      },
      "SeedDream50ProLayer": {
        "properties": {
          "bounding_box": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/SeedDream50ProLayerBoundingBox"
              },
              {
                "type": "null"
              }
            ],
            "description": "The layer's position in the base image coordinate system. Omitted for the base image."
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Detailed model-generated description of the layer. Omitted for the base image.",
            "title": "Description"
          },
          "image": {
            "$ref": "#/components/schemas/Image",
            "description": "The generated base image or separated layer."
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Model-generated label for the layer. Omitted for the base image.",
            "title": "Name"
          },
          "z_index": {
            "description": "Stacking order from bottom to top. The base image always has z_index 0.",
            "maximum": 16,
            "minimum": 0,
            "title": "Z Index",
            "type": "integer"
          }
        },
        "required": [
          "image",
          "z_index"
        ],
        "title": "SeedDream50ProLayer",
        "type": "object"
      },
      "SeedDream50ProLayerBoundingBox": {
        "properties": {
          "absolute": {
            "description": "Layer bounds as [left, top, right, bottom] pixel coordinates in the output base image coordinate system.",
            "items": {
              "type": "integer"
            },
            "maxItems": 4,
            "minItems": 4,
            "title": "Absolute",
            "type": "array"
          },
          "normalized": {
            "description": "Layer bounds as [left, top, right, bottom] coordinates normalized to the integer range [0, 1000].",
            "items": {
              "type": "integer"
            },
            "maxItems": 4,
            "minItems": 4,
            "title": "Normalized",
            "type": "array"
          }
        },
        "required": [
          "absolute",
          "normalized"
        ],
        "title": "SeedDream50ProLayerBoundingBox",
        "type": "object"
      },
      "SeedreamV5FlashLayerizeOutput": {
        "properties": {
          "images": {
            "description": "The same images as `layers`, in the same order, flattened for gallery rendering.",
            "items": {
              "$ref": "#/components/schemas/Image"
            },
            "title": "Images",
            "type": "array"
          },
          "layers": {
            "description": "The base image followed by up to 16 separated layers, ordered by increasing z_index.",
            "items": {
              "$ref": "#/components/schemas/SeedDream50ProLayer"
            },
            "title": "Layers",
            "type": "array"
          }
        },
        "required": [
          "images",
          "layers"
        ],
        "title": "SeedDream50ProLayerizeOutput",
        "type": "object"
      }
    }
  }
}
```

Result files: the URLs at `layers[].image.url`, `images[].url` point to `GET https://api.musespan.com/v1/model-tasks/{id}/artifacts/{index}`. Download them with the same `Authorization` header (the API key that submitted the task) and never send the key to any other host. Files are served as `application/octet-stream`; never execute SVG or HTML result bytes. Keep the files you need.

## Errors

### Submit (POST)

- `400`: Invalid request: missing or malformed X-Request-Id, or input that does not match the model schema. Nothing was created. native_model_unavailable: this model is served on a different endpoint; read its openapi_url in GET /v1/models, and use an ID from suggested_models there.
- `401`: Missing or invalid API key.
- `402`: Insufficient balance. Nothing was created; top up and resend with the same X-Request-Id.
- `403`: This API key cannot use this model.
- `404`: model_not_found: this ID cannot be called with this API key. Nothing was created. Do not resend it unchanged: use an ID from suggested_models or an id from GET /v1/models.
- `409`: request_id_conflict: this X-Request-Id was used with a different request body (another spelling of the model ID also counts as a different body). Resend byte-identical bytes, or use a new X-Request-Id.
- `429`: Too many requests, or too many of your tasks already waiting in the queue (too_many_active_tasks). Tasks beyond the number your account may run at once wait in the queue instead of being refused; this means the queue itself is full. Back off a moment, then resend with the same X-Request-Id.
- `503`: Temporarily unavailable. Retry later with the same X-Request-Id.

### Query (GET /v1/model-tasks/{id})

- `401`: Missing or invalid API key.
- `404`: task_not_found: no task with this ID exists for this API key. If the POST may not have arrived, resend the exact same body with the same X-Request-Id instead of polling.
- `429`: Too many requests. Back off, then query the same task again.
- `503`: Temporarily unavailable. Query the same task again later.

### Cancel (POST /v1/model-tasks/{id}/cancel)

- `401`: Missing or invalid API key.
- `404`: task_not_found: no task with this ID exists for this API key.
- `409`: task_not_cancellable: the task is no longer waiting in the queue (being sent, sent, or ended). The body includes its current status; keep polling it.
- `429`: Too many requests. Back off, then try again.
- `503`: Temporarily unavailable. Try again later.

### Download (GET /v1/model-tasks/{id}/artifacts/{index})

- `401`: Missing or invalid API key.
- `404`: artifact_not_found: the task is not completed yet, the index is not one the completed task declares, or the task was submitted with another API key. Poll GET /v1/model-tasks/{id} until status is completed, then download only the indexes its output declares, with the API key that submitted the task.
- `429`: Too many requests or downloads at once. Back off, then download the same file again.
- `503`: Temporarily unavailable. Download the same file again later.

Task failures (HTTP 200 with `status` `failed`): read `error.code` and keep a branch for unknown codes. A failed task is final and not charged.

- `generation_timeout`: A model task may first wait in our queue (status queued, with an estimated queue_position) while the account or the platform is at its limit of tasks running at once; one still waiting 5 minutes after it was submitted ends as failed with error.code queue_timeout and is not charged. Once sent, a task with no result 15 minutes after it was accepted, or whose submission is still not confirmed 15 minutes after it was sent, ends as failed with error.code generation_timeout and is not charged (any hold is refunded in full); poll for up to 25 minutes to see the final status. To try again, submit a new task with a new X-Request-Id: the old ID keeps returning the failed task.
- `upstream_unavailable`: A task that could not be started because generation is temporarily unavailable ends at once as failed with error.code upstream_unavailable and is not charged (any hold is refunded in full); it does not mean your input was wrong. Try again later with a new X-Request-Id: the old ID keeps returning the failed task.
- `cancelled`: You cancelled the task (POST /v1/model-tasks/{id}/cancel) while it was waiting in the queue. It was never sent and is not charged; the hold is refunded in full.
- `queue_timeout`: The task waited in the queue for 5 minutes without being sent. It is not charged; submit it again with a new X-Request-Id.
- `model_unavailable_while_queued`: The model became unavailable while the task waited in the queue, so it was not started and is not charged. Submit it again with a new X-Request-Id; resending the old ID only returns this failure.
- `insufficient_balance`: Your balance went below zero while the task waited in the queue (another charge exceeded its hold), so it was not started and is not charged. Top up, then submit it again with a new X-Request-Id.
- `invalid_parameters`: A task whose input was rejected ends as failed with error.code invalid_parameters and is not charged (any hold is refunded in full). error.issues lists up to 8 entries {field, rule}: field is the input path (for example voice, voice_setting.voice_id or image_urls[0]) and rule is one of required, string, integer, number, boolean, array, object, allowed_value, range, length, multiple, invalid, unsupported_value, file_size, file_format, file_unreadable, duration, content_policy. Free-form error text is never returned. Fix those fields, then submit a new task with a new X-Request-Id: the old ID keeps returning the failed task.

## Usage Examples

Runtime: Node.js 20+ (native fetch / AbortSignal.timeout), or Python 3 with `pip install requests`.

Keep API keys on the server. The examples below save a request ID and resume querying when rerun.

### cURL

```bash
# Use a new directory for each new generation. Rerunning here continues this task.
set -eu
test -s model-task-id.txt || python3 -c "import uuid; print(uuid.uuid4())" > model-task-id.txt
REQUEST_ID=$(cat model-task-id.txt)
# Idempotent: resending the same bytes with the same ID never charges twice.
curl --fail-with-body --retry 3 --retry-connrefused --request POST 'https://api.musespan.com/v1/model-tasks' \
  --header "Authorization: Bearer $MUSESPAN_API_KEY" \
  --header "X-Request-Id: $REQUEST_ID" \
  --header "Content-Type: application/json" \
  --data '{"model":"bytedance/seedream-5-flash-layerize/image-to-image","input":{"prompt":"Separate the three main foreground objects into individual transparent layers: the complete ivory origami crane, the coral-orange sphere, and the dark green ginkgo leaf. Keep the crane as one object rather than splitting its folded paper parts. Return a clean base image containing only the pale aqua studio background and tabletop, plus one layer for each of the three objects. Preserve the original positions, colors, edges and appearance. Return layer names and descriptions in English.","image_url":"https://pub-a7287c8d5de944d984e22a5bd3ab9bbe.r2.dev/uploads/capability-demo-0072d694a35c704c7b203372df1fd60d.png","image_size":"auto_1K"}}'
# Poll every 5 seconds for up to 25 minutes until status is completed or failed.
# Still queued (with queue_position) 5 minutes after submission: failed with queue_timeout, not charged.
# No result 15 minutes after acceptance (or no confirmation 15 minutes after submission): failed with generation_timeout, not charged.
# Any failure is final: generate again in a new directory (a new X-Request-Id).
# A 404 means the POST did not arrive: rerun this script to resend it with the same ID.
curl --fail-with-body 'https://api.musespan.com/v1/model-tasks/'"$REQUEST_ID"'' --header "Authorization: Bearer $MUSESPAN_API_KEY"
# When status is completed, download the first declared file with the same key.
curl --fail-with-body --output result-0 'https://api.musespan.com/v1/model-tasks/'"$REQUEST_ID"'/artifacts/0' --header "Authorization: Bearer $MUSESPAN_API_KEY"
```

### Complete async JavaScript

```javascript
import { readFile, writeFile } from 'node:fs/promises';
// Use a separate working directory for each new generation. Rerunning here continues this task.
let requestId;
try {
  requestId = (await readFile('model-task-id.txt', 'utf8')).trim();
} catch (error) {
  if (error.code !== 'ENOENT') throw error;
  requestId = crypto.randomUUID();
  await writeFile('model-task-id.txt', requestId);
}
const headers = { Authorization: `Bearer ${process.env.MUSESPAN_API_KEY}` };
// Keep the body as fixed bytes: a resend must be byte-identical.
const body = "{\"model\":\"bytedance/seedream-5-flash-layerize/image-to-image\",\"input\":{\"prompt\":\"Separate the three main foreground objects into individual transparent layers: the complete ivory origami crane, the coral-orange sphere, and the dark green ginkgo leaf. Keep the crane as one object rather than splitting its folded paper parts. Return a clean base image containing only the pale aqua studio background and tabletop, plus one layer for each of the three objects. Preserve the original positions, colors, edges and appearance. Return layer names and descriptions in English.\",\"image_url\":\"https://pub-a7287c8d5de944d984e22a5bd3ab9bbe.r2.dev/uploads/capability-demo-0072d694a35c704c7b203372df1fd60d.png\",\"image_size\":\"auto_1K\"}}";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Idempotent: the same ID with the same bytes returns the existing task and is never charged twice.
async function submit() {
  for (let attempt = 1; attempt <= 5; attempt++) {
    try {
      const response = await fetch('https://api.musespan.com/v1/model-tasks', {
        method: 'POST', signal: AbortSignal.timeout(30_000),
        headers: { ...headers, 'Content-Type': 'application/json', 'X-Request-Id': requestId },
        body,
      });
      if (response.ok) return;
      if (response.status !== 429 && response.status < 500) {
        // 409: this ID was used with a different body; use a new directory.
        // Other 4xx: nothing was created; fix the request, then rerun.
        throw new Error(`${response.status} ${await response.text()}`);
      }
    } catch (error) {
      if (!(error instanceof TypeError) && error.name !== 'TimeoutError') throw error;
    }
    await sleep(attempt * 2000); // Timeout, network error, 429 or 5xx: resend with the same ID.
  }
  throw new Error('Submission did not succeed; rerun to resend with the same ID.');
}

await submit();
// Up to 5 minutes in the queue (queue_timeout), then no result 15 minutes after acceptance (or no confirmation 15 minutes after submission) ends the task (generation_timeout); neither is charged, and 25 minutes covers both.
const deadline = Date.now() + 25 * 60_000;
let completed = false;
while (!completed && Date.now() < deadline) {
  await sleep(5000);
  let response;
  try {
    response = await fetch('https://api.musespan.com/v1/model-tasks/{id}'.replace('{id}', requestId), { headers, signal: AbortSignal.timeout(30_000) });
  } catch {
    continue; // Retry the read.
  }
  if (response.status === 404) { await submit(); continue; } // The POST never arrived: resend it with the same ID.
  if (response.status === 429 || response.status >= 500) continue;
  if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
  const task = await response.json();
  // Failed is final; to try again, use a new directory (a new X-Request-Id).
  if (task.status === 'failed') {
    // invalid_parameters lists each rejected field and its rule in error.issues.
    const issues = (task.error?.issues ?? []).map((issue) => `${issue.field} (${issue.rule})`).join(', ');
    throw new Error(`Task failed (${task.error?.code ?? 'unknown'}): ${task.error?.message ?? ''}${issues ? ` Issues: ${issues}` : ''}`);
  }
  if (task.status === 'completed') { console.log(task.output); completed = true; }
  // queued / running are still pending; a queued task may report its estimated queue_position.
}
if (!completed) console.log('Still pending after 25 minutes. Rerun to continue:', requestId);
```

### Complete async Python

```python
import os, time, uuid
import requests
# Use a separate working directory for each new generation. Rerunning here continues this task.
try:
    with open('model-task-id.txt') as saved:
        request_id = saved.read().strip()
except FileNotFoundError:
    request_id = str(uuid.uuid4())
    with open('model-task-id.txt', 'w') as saved:
        saved.write(request_id)
headers = {"Authorization": f"Bearer {os.environ['MUSESPAN_API_KEY']}"}
# Keep the body as fixed bytes: a resend must be byte-identical.
BODY = "{\"model\":\"bytedance/seedream-5-flash-layerize/image-to-image\",\"input\":{\"prompt\":\"Separate the three main foreground objects into individual transparent layers: the complete ivory origami crane, the coral-orange sphere, and the dark green ginkgo leaf. Keep the crane as one object rather than splitting its folded paper parts. Return a clean base image containing only the pale aqua studio background and tabletop, plus one layer for each of the three objects. Preserve the original positions, colors, edges and appearance. Return layer names and descriptions in English.\",\"image_url\":\"https://pub-a7287c8d5de944d984e22a5bd3ab9bbe.r2.dev/uploads/capability-demo-0072d694a35c704c7b203372df1fd60d.png\",\"image_size\":\"auto_1K\"}}".encode()

def submit():
    # Idempotent: the same ID with the same bytes returns the existing task and is never charged twice.
    for attempt in range(1, 6):
        try:
            response = requests.post('https://api.musespan.com/v1/model-tasks', headers={**headers, 'X-Request-Id': request_id, 'Content-Type': 'application/json'}, data=BODY, timeout=(10, 30))
            if response.ok:
                return
            if response.status_code != 429 and response.status_code < 500:
                # 409: this ID was used with a different body; use a new directory.
                # Other 4xx: nothing was created; fix the request, then rerun.
                raise RuntimeError(f'{response.status_code} {response.text}')
        except requests.RequestException:
            pass  # Timeout or network error: resend with the same ID.
        time.sleep(attempt * 2)
    raise RuntimeError('Submission did not succeed; rerun to resend with the same ID.')

submit()
# Up to 5 minutes in the queue (queue_timeout), then no result 15 minutes after acceptance (or no confirmation 15 minutes after submission) ends the task (generation_timeout); neither is charged, and 25 minutes covers both.
deadline = time.time() + 1500
while time.time() < deadline:
    time.sleep(5)
    try:
        response = requests.get('https://api.musespan.com/v1/model-tasks/{id}'.replace('{id}', request_id), headers=headers, timeout=(10, 30))
    except requests.RequestException:
        continue  # Retry the read.
    if response.status_code == 404:
        submit()  # The POST never arrived: resend it with the same ID.
        continue
    if response.status_code == 429 or response.status_code >= 500:
        continue
    response.raise_for_status()
    task = response.json()
    if task['status'] == 'failed':
        # Failed is final; to try again, use a new directory (a new X-Request-Id).
        error = task.get('error') or {}
        # invalid_parameters lists each rejected field and its rule in error.issues.
        issues = ', '.join(f"{issue['field']} ({issue['rule']})" for issue in error.get('issues') or [])
        raise RuntimeError(f"Task failed ({error.get('code', 'unknown')}): {error.get('message', '')}" + (f' Issues: {issues}' if issues else ''))
    if task['status'] == 'completed':
        print(task['output'])
        break
    # queued / running are still pending; a queued task may report its estimated queue_position.
else:
    print('Still pending after 25 minutes. Rerun to continue:', request_id)
```

## Additional Resources

- [Model Playground](https://www.musespan.com/models/bytedance/seedream-5-flash-layerize/image-to-image)
- [OpenAPI Schema](https://www.musespan.com/v1/models/bytedance/seedream-5-flash-layerize/image-to-image/openapi.json)
- [Platform overview](https://www.musespan.com/llms.txt)
