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

> ## Agent Instructions
> This site documents two API families. Choose the family before generating integration code and keep its request fields, billing, statuses, responses, and webhooks together.
> SwitchX API: POST /v1/switchx/generations; use /quickstart and /authentication. Completed generation files are under output.
> Enterprise API: POST /v1/products/{product}/jobs and GET /v1/product-jobs/{job_id}; use /enterprise/quickstart and /products. Check /enterprise for current access requirements. Completed product files are under outputs.
> For product jobs, fetch model-specific input_schema from GET /v1/products/{product}/models. OpenAPI defines the common job response; product guides show illustrative completed outputs. Do not treat output examples as exhaustive schemas.
> SwitchX 2.0 Finish uses the separate switchx_finish product and model_id switchx-2.0. Follow /products/switchx-finish: pass inputs.parent_job_id (a completed Standard switchx product generation's public dap_ ID owned by the same key owner, organization, and team) and inputs.target_resolution (1080 or 2160), without uploading media. Fast results, Finish results, and legacy SwitchX API generations cannot be parents. Estimate the transition, then submit and poll the new child ID; result files are under outputs.
> For product APIs, authenticate with an Organization API Key in x-api-key and keep the same organization and X-Beeble-Team-Id context for uploads, estimates, submissions, and reads. Use Beeble Cloud credits; product routes do not support USD.
> Follow /enterprise/llms-txt for the product API workflow: discover products and models, upload media, estimate credits, submit with an idempotency_key, and poll or receive webhooks. On an uncertain submission, retry the same body and key; do not create a replacement job.
> Product-job success is status=success; stop polling on failed, cancelled, or credit_required. Use the chosen product guide for completed response examples and download fields. Refer to /enterprise/errors, /enterprise/rate-limits, /guides/billing, and /guides/jobs for failures, limits, and refunds.

# SwitchX 2.0 Finish

> Finish a completed SwitchX 2.0 image or video at a higher resolution.

**Product ID:** `switchx_finish`

**Model ID:** `switchx-2.0`

**Billing:** Beeble Cloud credits

Finish renders a higher-resolution version of a completed [SwitchX](/docs/products/switchx)
result. It reuses the original source, mask, and generation settings, so you do
not upload media again. Each Finish request creates a new child job with its
own result; it does not replace the parent.

## Before you start

1. Create an [API key](/docs/enterprise/authentication) for an organization with
   `switchx_finish` enabled. SwitchX generation and Finish are enabled separately;
   the same SwitchX model permissions apply.
2. Complete a SwitchX 2.0 job through `POST /v1/products/switchx/jobs` in
   `mode: "standard"`. Wait for its public status to become `success` and save
   its `dap_…` ID. Use the same key owner, organization, and internal team for Finish.
3. Choose an eligible target below. The parent's original source and completed
   render must still be available.

### Eligible parents and targets

Both images and videos support these transitions. The parent resolution is its
original `max_resolution` setting; `target_resolution` must be an integer.

| Parent resolution | Finish target | Original source's shorter edge |
| ----------------- | ------------- | ------------------------------ |
| 720               | 1080          | More than 720 pixels           |
| 720               | 2160          | More than 1080 pixels          |
| 1080              | 2160          | More than 1080 pixels          |

The target is a resolution cap. Finish does not stretch beyond the source's
native size; the output can be smaller than the selected tier. Submission also
checks the source, plan, output-size limits, and available credits.

SwitchX 1.0, unfinished jobs, and results already at the target resolution
are ineligible. Jobs from the legacy `/v1/switchx/generations` API,
web app, or MCP are not public product parents. A Finish result cannot be used
as another Finish parent: to go from an original 720 result to 2160, use that
original generation's `dap_…` ID, even if you already finished it at 1080.

## Models and inputs

Set `BEEBLE_API_KEY` and, for internal teams, `BEEBLE_TEAM_ID`.
Python requires `requests`; JavaScript examples run in Node.js.

<CodeGroup>
  ```bash cURL theme={null}
  curl --fail-with-body "https://api.beeble.ai/v1/products/switchx_finish/models" \
    -H "x-api-key: $BEEBLE_API_KEY" \
    -H "X-Beeble-Team-Id: ${BEEBLE_TEAM_ID:-}"
  ```

  ```python Python theme={null}
  import json
  import os

  import requests

  headers = {"x-api-key": os.environ["BEEBLE_API_KEY"]}
  if team_id := os.environ.get("BEEBLE_TEAM_ID"):
      headers["X-Beeble-Team-Id"] = team_id

  response = requests.get(
      "https://api.beeble.ai/v1/products/switchx_finish/models",
      headers=headers,
      timeout=30,
  )
  response.raise_for_status()
  print(json.dumps(response.json(), indent=2))
  ```

  ```javascript JavaScript theme={null}
  const headers = { "x-api-key": process.env.BEEBLE_API_KEY }
  if (process.env.BEEBLE_TEAM_ID) {
    headers["X-Beeble-Team-Id"] = process.env.BEEBLE_TEAM_ID
  }
  const response = await fetch(
    "https://api.beeble.ai/v1/products/switchx_finish/models",
    {
      headers,
      signal: AbortSignal.timeout(30_000),
    },
  )
  if (!response.ok) throw new Error(await response.text())
  console.log(JSON.stringify(await response.json(), null, 2))
  ```
</CodeGroup>

**Response (200):** `models` contains the available model's `id`, `name`, and
`input_schema`. Select `switchx-2.0` and use its schema for required inputs and
supported values.

[Request parameters and response schema](/docs/api-reference/products/list-product-model-input-schemas)

The Finish `inputs` object contains only `parent_job_id` and `target_resolution`.
Use the parent's public product-job ID, not an internal generation ID or an
output URL. Do not send `source`, `prompt`, `mode`, `max_resolution`, or client
media measurements such as `frame_count`.

## Estimate the transition

[Request body and response schema](/docs/api-reference/products/estimate-a-product-job-without-creating-or-charging-it)

Save this as `finish-estimate.json`, replacing the parent ID with your completed
Standard generation's `dap_…` ID and selecting an eligible target.

```json theme={null}
{
  "model_id": "switchx-2.0",
  "billing_unit": "credits",
  "inputs": {
    "parent_job_id": "dap_your_completed_switchx_job",
    "target_resolution": 1080
  }
}
```

<CodeGroup>
  ```bash cURL theme={null}
  curl --fail-with-body "https://api.beeble.ai/v1/products/switchx_finish/estimate" \
    -H "x-api-key: $BEEBLE_API_KEY" \
    -H "X-Beeble-Team-Id: ${BEEBLE_TEAM_ID:-}" \
    -H "Content-Type: application/json" \
    --data-binary @finish-estimate.json
  ```

  ```python Python theme={null}
  import json
  import os
  from pathlib import Path

  import requests

  headers = {"x-api-key": os.environ["BEEBLE_API_KEY"]}
  if team_id := os.environ.get("BEEBLE_TEAM_ID"):
      headers["X-Beeble-Team-Id"] = team_id

  response = requests.post(
      "https://api.beeble.ai/v1/products/switchx_finish/estimate",
      headers=headers,
      json=json.loads(Path("finish-estimate.json").read_text()),
      timeout=120,
  )
  response.raise_for_status()
  print(json.dumps(response.json(), indent=2))
  ```

  ```javascript JavaScript theme={null}
  import { readFile } from "node:fs/promises"

  const headers = { "x-api-key": process.env.BEEBLE_API_KEY }
  if (process.env.BEEBLE_TEAM_ID) {
    headers["X-Beeble-Team-Id"] = process.env.BEEBLE_TEAM_ID
  }
  headers["Content-Type"] = "application/json"
  const response = await fetch(
    "https://api.beeble.ai/v1/products/switchx_finish/estimate",
    {
      method: "POST",
      body: await readFile("finish-estimate.json", "utf8"),
      headers,
      signal: AbortSignal.timeout(120_000),
    },
  )
  if (!response.ok) throw new Error(await response.text())
  console.log(JSON.stringify(await response.json(), null, 2))
  ```
</CodeGroup>

**Example response (200, abbreviated; amount is illustrative):**

```json theme={null}
{
  "product": "switchx_finish",
  "model_id": "switchx-2.0",
  "billing_unit": "credits",
  "estimated_credits": 12,
  "source_metadata": null
}
```

The quote uses the parent's stored media type, frame count, and resolution to
price the transition. `source_metadata` is `null` because this request does not
measure a new upload. Estimation creates no job and charges no credits.

An estimate reserves neither price nor balance. Submission checks eligibility
and quotes again; use `max_credits` to cap the charge. See
[Billing & credits](/docs/guides/billing#estimate-before-submitting).

## Submit a Finish job

**Schemas:** [Request body](/docs/enterprise/schemas/product-job-request) ·
[Response](/docs/enterprise/schemas/product-job).

Save as `finish-request.json` using the same model and inputs as your estimate.
Replace the illustrative `max_credits: 12` with your chosen ceiling; you can
copy the returned `estimated_credits` to cap spending at the quoted amount.
Zero allows no charge. Use a unique `idempotency_key` for each new Finish job,
and keep the same body and key when retrying an uncertain submission.

```json theme={null}
{
  "model_id": "switchx-2.0",
  "billing_unit": "credits",
  "idempotency_key": "switchx-finish-example-001",
  "max_credits": 12,
  "inputs": {
    "parent_job_id": "dap_your_completed_switchx_job",
    "target_resolution": 1080
  }
}
```

<CodeGroup>
  ```bash cURL theme={null}
  curl --fail-with-body "https://api.beeble.ai/v1/products/switchx_finish/jobs" \
    -H "x-api-key: $BEEBLE_API_KEY" \
    -H "X-Beeble-Team-Id: ${BEEBLE_TEAM_ID:-}" \
    -H "Content-Type: application/json" \
    --data-binary @finish-request.json
  ```

  ```python Python theme={null}
  import json
  import os
  from pathlib import Path

  import requests

  headers = {"x-api-key": os.environ["BEEBLE_API_KEY"]}
  if team_id := os.environ.get("BEEBLE_TEAM_ID"):
      headers["X-Beeble-Team-Id"] = team_id

  response = requests.post(
      "https://api.beeble.ai/v1/products/switchx_finish/jobs",
      headers=headers,
      json=json.loads(Path("finish-request.json").read_text()),
      timeout=120,
  )
  response.raise_for_status()
  print(json.dumps(response.json(), indent=2))
  ```

  ```javascript JavaScript theme={null}
  import { readFile } from "node:fs/promises"

  const headers = { "x-api-key": process.env.BEEBLE_API_KEY }
  if (process.env.BEEBLE_TEAM_ID) {
    headers["X-Beeble-Team-Id"] = process.env.BEEBLE_TEAM_ID
  }
  headers["Content-Type"] = "application/json"
  const response = await fetch(
    "https://api.beeble.ai/v1/products/switchx_finish/jobs",
    {
      method: "POST",
      body: await readFile("finish-request.json", "utf8"),
      headers,
      signal: AbortSignal.timeout(120_000),
    },
  )
  if (!response.ok) throw new Error(await response.text())
  console.log(JSON.stringify(await response.json(), null, 2))
  ```
</CodeGroup>

**Response (202, abbreviated):**

```json theme={null}
{
  "id": "dap_finish_example",
  "product": "switchx_finish",
  "model_id": "switchx-2.0",
  "status": "processing",
  "outputs": {}
}
```

Save the **new child ID** from `id`. A different key can create another paid
Finish job for the same parent. If the response is lost or the job is
`submitting` or `unknown`, follow [safe retries](/docs/guides/jobs#safe-retries).
Existing child reads and idempotent replays do not require resolving the parent
again, including after the parent is deleted or Finish is disabled; normal
owner, organization, and team authorization still applies.

Optionally add a public HTTPS `callback_url` at the top level of the submission
body. Completion uses the same `product.job.completed` callback and child job
response as other products; see [Webhooks](/docs/enterprise/webhooks#product-job-callbacks).

## Retrieve the finished result

Set `BEEBLE_JOB_ID` to the returned child ID, then read its status.

[Request parameters and response schema](/docs/api-reference/products/get-a-product-job)

<CodeGroup>
  ```bash cURL theme={null}
  curl --fail-with-body "https://api.beeble.ai/v1/product-jobs/$BEEBLE_JOB_ID" \
    -H "x-api-key: $BEEBLE_API_KEY" \
    -H "X-Beeble-Team-Id: ${BEEBLE_TEAM_ID:-}"
  ```

  ```python Python theme={null}
  import json
  import os

  import requests

  headers = {"x-api-key": os.environ["BEEBLE_API_KEY"]}
  if team_id := os.environ.get("BEEBLE_TEAM_ID"):
      headers["X-Beeble-Team-Id"] = team_id

  job_id = os.environ["BEEBLE_JOB_ID"]

  response = requests.get(
      f"https://api.beeble.ai/v1/product-jobs/{job_id}",
      headers=headers,
      timeout=30,
  )
  response.raise_for_status()
  print(json.dumps(response.json(), indent=2))
  ```

  ```javascript JavaScript theme={null}
  const headers = { "x-api-key": process.env.BEEBLE_API_KEY }
  if (process.env.BEEBLE_TEAM_ID) {
    headers["X-Beeble-Team-Id"] = process.env.BEEBLE_TEAM_ID
  }
  const response = await fetch(
    `https://api.beeble.ai/v1/product-jobs/${process.env.BEEBLE_JOB_ID}`,
    {
      headers,
      signal: AbortSignal.timeout(30_000),
    },
  )
  if (!response.ok) throw new Error(await response.text())
  console.log(JSON.stringify(await response.json(), null, 2))
  ```
</CodeGroup>

[Poll for completion](/docs/guides/jobs#poll-for-completion) until `status` is
`success`. Stop on `failed`, `cancelled`, or `credit_required`.

**Example response (200, success; abbreviated):** URLs are illustrative. This
image example inherits the parent's `alpha_mode: "fill"` setting. Available
outputs depend on the parent's media and settings.

```json theme={null}
{
  "id": "dap_finish_example",
  "product": "switchx_finish",
  "model_id": "switchx-2.0",
  "status": "success",
  "outputs": {
    "render": "https://cdn.beeble.ai/example/finish/render.png?signed-parameters",
    "source": "https://cdn.beeble.ai/example/finish/source.png?signed-parameters",
    "alpha": null
  }
}
```

Download `outputs.render`: PNG for an image or MP4 for a video. With inherited
`alpha_mode: "fill"`, `outputs.alpha` is `null`. Read the child again for fresh
signed URLs.

`credits_charged: null` means the actual charge was not reported, not that the
job was free. Finish uses the normal SwitchX refund process; `refunded` remains
`null` when its state is unreported. See [Billing & credits](/docs/guides/billing#charges-and-refunds).
