## I. Getting Started

Complete these prerequisites before calling a BizyAir AI app API.

### 1. Register and Add Balance

Sign in or register for a BizyAir account and make sure the balance is sufficient. **AI apps are billed by runtime from the balance. Gift balance is used first; paid balance is used when the gift balance is not enough.**

### 2. Get Your API Key

Each user has a unique **API Key** provided by BizyAir.

Move the cursor over the user avatar in the top-right corner and click **API Key** to obtain your key.

**Keep your API Key secure.** It is required for every step below.

### 3. Get the AI App ID (web_app_id)

Calling an AI app requires its `web_app_id`. The number in the app detail page URL is the `web_app_id`.

---

## II. Invocation Modes

BizyAir provides 3 invocation modes, switched by HTTP headers. **The request body stays the same.**

| Mode | How to enable | Behavior | When to use |
| :--- | :--- | :--- | :--- |
| Synchronous | Do not set any special header | This request waits until the task finishes, and the response is the result | Short tasks, simple debugging |
| Async query | `X-Bizyair-Task-Async: enable` | Returns `request_id` immediately; the client polls for the result | Long tasks, unstable networks, no callback URL |
| WebHook | `X-Bizyair-Task-WebHook-Url: <url>` | Returns `request_id` immediately; BizyAir POSTs the result when the task finishes | Production, high concurrency, long-running tasks |

### How to choose a mode

| Your case | Recommended mode | Why |
| :--- | :--- | :--- |
| Quick local script check | Synchronous | Simplest: one request returns the result |
| Short task integrated into a backend | Synchronous | One request waits for the result |
| Video generation / 3D rendering (long tasks) | WebHook | You get `request_id` immediately; the result arrives by callback |
| Unstable client network | Async query | Get `request_id` first, then poll when needed |
| Hundreds of tasks in parallel | Async query or WebHook | Queue them all, then collect results over time |
| Production high availability | WebHook | Push delivery plus retries |

---

## III. Submit a Request

### 1. Request Parameters

| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `web_app_id` | integer | Yes | AI app ID. Take it from the app detail page URL. |
| `input_values` | object | Yes | Input key-value pairs. The key format is `{node_id}:{node_type}.{field_name}`, for example `"84:CLIPTextEncode.text"`. |
| `input_values.25:PrimitiveStringMultiline.value` | string | Yes | Prompt |
| `input_values.19:ResolutionSelector.aspect_ratio` | string | Yes | ⟨bz_enum_json⟩["1:1 (Square)","2:3 (Portrait Photo)","3:2 (Photo)","3:4 (Portrait Standard)","4:3 (Standard)","9:16 (Portrait Widescreen)","16:9 (Widescreen)","21:9 (Ultrawide)"]⟨/bz_enum_json⟩<br/>Aspect ratio |
| `suppress_preview_output` | boolean | No | Whether to disable preview output. When `true`, preview images from Preview nodes are omitted. Default is `false`. |

### 2. Synchronous Call

This is the default mode. The request waits until the task finishes, and the response body is the result. No extra header is required.

**Request example**

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
url = "https://api.bizyair.ai/v1/webapp/task/openapi/create"
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}
payload = {
    "web_app_id": 58177,
    "suppress_preview_output": False,
    "input_values": {
        "13:ResolutionSelector.aspect_ratio": "1:1 (Square)",
        "471:TextEncodeQwenImage21.prompt": "A cute cat, simple style",
        "471:TextEncodeQwenImage21.negative_prompt": ""
    }
}

response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("status") != "Success":
    raise RuntimeError(result.get("message") or "BizyAir API request failed")
print(result)
```

> **Note:** In synchronous mode, this request blocks until the task finishes, and the response body is the task result. A single run can take a long time. Prefer async query or WebHook. Both return `request_id` immediately, then deliver the result by polling or callback. Disconnecting the client early does not cancel the server-side task. The task may still finish and incur a charge.

**Response example**

```json
{
  "type": "API",
  "status": "Success",
  "created_at": "2026-09-23T07:44:34Z",
  "updated_at": "2026-09-23T07:45:19Z",
  "executed_at": "2026-09-23T07:44:34Z",
  "ended_at": "2026-09-23T07:45:19Z",
  "expired_at": "2026-10-08T00:00:00Z",
  "request_id": "2e752d06-3585-4a70-a55e-7c552d1399ef",
  "outputs": [
    {
      "object_url": "https://storage.bizyair.ai/outputs_examples/lXobDnouo90P3XoF1stRJhup4mgx4n26.png",
      "output_ext": ".png",
      "cost_time": 42789,
      "error_type": "NOT_ERROR"
    }
  ],
  "cost_times": {
    "inference_cost_time": 42930,
    "running_cost_time": 44873,
    "total_cost_time": 44880,
    "real_cpu_cost_time": 430,
    "real_total_cost_time": 430
  }
}
```

**Response fields:**

| Parameter | Type | Description |
| :--- | :--- | :--- |
| type | string | Task type, for example `"API"`. |
| status | string | Task status: `Queuing` / `Preparing` / `Running` / `Success` / `Failed` / `Canceled`. |
| request_id | string | Request ID. Use it for later queries. |
| created_at | string | Created time (ISO 8601, UTC). |
| updated_at | string | Last updated time (ISO 8601, UTC). |
| executed_at | string | Execution start time (ISO 8601, UTC). |
| ended_at | string | End time (ISO 8601, UTC). |
| expired_at | string | Result file expiry time (ISO 8601, UTC). Download the files before they expire. |
| outputs | array[object (Output)] | Result list. |
| outputs[].object_url | string | Download URL of the result file. |
| outputs[].output_ext | string | File extension, including `.`. |
| outputs[].cost_time | integer | Elapsed time from task start until this result was produced, in milliseconds. |
| outputs[].error_type | string | `"NOT_ERROR"` when the output succeeded. |
| cost_times | object | Timing statistics, in milliseconds. |
| cost_times.inference_cost_time | integer | Inference time, in milliseconds. |
| cost_times.running_cost_time | integer | Running time, in milliseconds. |
| cost_times.total_cost_time | integer | Total time, in milliseconds. |
| cost_times.real_cpu_cost_time | integer | CPU time, in milliseconds. |
| cost_times.real_total_cost_time | integer | Total time corresponding to `real_cpu_cost_time`, in milliseconds. |

### 3. Async Query

Enable it by adding the header `X-Bizyair-Task-Async: enable`.

**Step 1: Submit an async task and get `request_id` immediately**

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
url = "https://api.bizyair.ai/v1/webapp/task/openapi/create"
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json",
    "X-Bizyair-Task-Async": "enable"
}
payload = {
    "web_app_id": 58177,
    "suppress_preview_output": False,
    "input_values": {
        "13:ResolutionSelector.aspect_ratio": "1:1 (Square)",
        "471:TextEncodeQwenImage21.prompt": "A cute cat, simple style",
        "471:TextEncodeQwenImage21.negative_prompt": ""
    }
}

response = requests.post(url, json=payload, headers=headers, timeout=30)
response.raise_for_status()
result = response.json()
request_id = result.get("request_id")
if not request_id:
    raise RuntimeError("Failed to get request_id")
print(f"Task submitted: {request_id}")
```

**Response example**

```json
{
  "request_id": "5fc66bc7-2c07-446c-af6b-7bf1f8f8ddc3"
}
```

**Full Python example (with polling)**

```python
import os
import time
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
base_url = "https://api.bizyair.ai/v1/webapp/task/openapi"
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}

# 1. Submit the async task
async_headers = {**headers, "X-Bizyair-Task-Async": "enable"}
payload = {
    "web_app_id": 58177,
    "suppress_preview_output": False,
    "input_values": {
        "13:ResolutionSelector.aspect_ratio": "1:1 (Square)",
        "471:TextEncodeQwenImage21.prompt": "A cute cat, simple style",
        "471:TextEncodeQwenImage21.negative_prompt": ""
    }
}
resp = requests.post(f"{base_url}/create", json=payload, headers=async_headers)
resp.raise_for_status()
result = resp.json()
request_id = result.get("request_id")
if not request_id:
    raise RuntimeError("Failed to get request_id")
print(f"Task submitted: {request_id}")

# 2. Poll status
while True:
    r = requests.get(f"{base_url}/{request_id}", headers=headers)
    r.raise_for_status()
    status_result = r.json()
    if status_result.get("code") != 20000 or status_result.get("status") is not True:
        raise RuntimeError(status_result.get("message") or "Query failed")
    status = status_result["data"]["status"]
    print(f"Current status: {status}")
    if status == "Success":
        break
    elif status in ("Failed", "Canceled"):
        print(f"Task failed or canceled: {status_result['data'].get('message')}")
        exit(1)
    time.sleep(2)

# 3. Get the result
r = requests.get(f"{base_url}/{request_id}/outputs", headers=headers)
r.raise_for_status()
outputs_result = r.json()
if outputs_result.get("code") != 20000 or outputs_result.get("status") is not True:
    raise RuntimeError(outputs_result.get("message") or "Get outputs failed")
for out in outputs_result["data"]["outputs"]:
    print("Result URL:", out["object_url"])
```

### 4. WebHook Callback

Enable it by adding the header `X-Bizyair-Task-WebHook-Url`.

**Step 1: Submit a task with a WebHook**

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
url = "https://api.bizyair.ai/v1/webapp/task/openapi/create"
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json",
    "X-Bizyair-Task-WebHook-Url": "https://your-server.com/api/callback",
    "X-Bizyair-Task-Authorization": "Bearer YOUR_CALLBACK_TOKEN"
}
payload = {
    "web_app_id": 58177,
    "suppress_preview_output": False,
    "input_values": {
        "13:ResolutionSelector.aspect_ratio": "1:1 (Square)",
        "471:TextEncodeQwenImage21.prompt": "A cute cat, simple style",
        "471:TextEncodeQwenImage21.negative_prompt": ""
    }
}

response = requests.post(url, json=payload, headers=headers, timeout=30)
response.raise_for_status()
result = response.json()
request_id = result.get("request_id")
if not request_id:
    raise RuntimeError("Failed to get request_id")
print(f"Task submitted: {request_id}")
```

**Step 2: Receive the result on the callback endpoint**

When the task finishes, BizyAir sends a POST request to the URL you specified:

- **Method**: `POST`
- **Headers**: `Content-Type: application/json`, `User-Agent: Go-http-client/1.1`, `Authorization` (rewritten from `X-Bizyair-Task-Authorization` on the create request), and other `X-Bizyair-Task-*` headers
- **Body**:

```json
{
  "type": "API",
  "status": "Success",
  "created_at": "2026-09-28T07:49:59Z",
  "updated_at": "2026-09-28T07:50:10Z",
  "executed_at": "2026-09-28T07:49:59Z",
  "running_at": "2026-09-28T07:50:07Z",
  "ended_at": "2026-09-28T07:50:10Z",
  "expired_at": "2026-10-13T00:00:00Z",
  "request_id": "2e752d06-3585-4a70-a55e-7c552d1399ef",
  "outputs": [
    {
      "object_url": "https://storage.bizyair.ai/outputs_examples/lXobDnouo90P3XoF1stRJhup4mgx4n26.png",
      "output_ext": ".png",
      "cost_time": 4030,
      "error_type": "NOT_ERROR"
    }
  ],
  "cost_times": {
    "inference_cost_time": 4149,
    "running_cost_time": 11742,
    "total_cost_time": 11776,
    "real_cpu_cost_time": 430,
    "real_total_cost_time": 430
  }
}
```

**Body fields:**

| Parameter | Type | Description |
| :--- | :--- | :--- |
| type | string | Task type, for example `"API"`. |
| status | string | Task status: `Queuing` / `Preparing` / `Running` / `Success` / `Failed` / `Canceled`. |
| request_id | string | Request ID. |
| created_at | string | Created time (ISO 8601, UTC). |
| updated_at | string | Last updated time (ISO 8601, UTC). |
| executed_at | string | Execution start time (ISO 8601, UTC). |
| running_at | string | Time when the task entered the running state (ISO 8601, UTC). |
| ended_at | string | End time (ISO 8601, UTC). |
| expired_at | string | Result file expiry time (ISO 8601, UTC). |
| outputs | array[object (Output)] | Result list. |
| outputs[].object_url | string | Download URL of the result file. |
| outputs[].output_ext | string | File extension, including `.`. |
| outputs[].cost_time | integer | Elapsed time from task start until this result was produced, in milliseconds. |
| outputs[].error_type | string | `"NOT_ERROR"` when the output succeeded. |
| cost_times | object | Timing statistics, in milliseconds. |
| cost_times.inference_cost_time | integer | Inference time, in milliseconds. |
| cost_times.running_cost_time | integer | Running time, in milliseconds. |
| cost_times.total_cost_time | integer | Total time, in milliseconds. |
| cost_times.real_cpu_cost_time | integer | CPU time, in milliseconds. |
| cost_times.real_total_cost_time | integer | Total time corresponding to `real_cpu_cost_time`, in milliseconds. |

**WebHook request headers**

| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `X-Bizyair-Task-WebHook-Url` | string | Optional | Callback URL called when the task finishes. If omitted, no callback is sent; retrieve the result with the query API. The callback must be an HTTP or HTTPS endpoint that accepts POST. BizyAir does not guarantee callback delivery for overseas endpoints. |
| `X-Bizyair-Task-Authorization` | string | Optional | If the callback endpoint requires authorization, place the credential here. It is the only header that gets rewritten: the `X-Bizyair-Task-` prefix is removed, so it arrives as `Authorization: ${YOUR_WEBHOOK_AUTHORIZATION}`. |
| `X-Bizyair-Task-${HEADER_NAME}` | string | Optional | Except for `X-Bizyair-Task-Authorization`, every header prefixed with `X-Bizyair-Task-` is copied into the callback request unchanged, prefix included. |

**Callback server example (Python)**

```python
from flask import Flask, request, jsonify
import os

app = Flask(__name__)
EXPECTED_TOKEN = os.environ.get("CALLBACK_TOKEN")

@app.route("/api/callback", methods=["POST"])
def callback():
    auth = request.headers.get("Authorization", "")
    if EXPECTED_TOKEN:
        if not auth.startswith("Bearer ") or auth.split(" ")[1] != EXPECTED_TOKEN:
            return jsonify({"message": "invalid token"}), 401

    payload = request.get_json()
    request_id = payload.get("request_id")
    status = payload.get("status")
    outputs = payload.get("outputs")

    print(f"Received callback for request_id: {request_id}")
    print(f"Status: {status}")
    print(f"Outputs: {outputs}")

    return jsonify({"ok": True}), 200

if __name__ == "__main__":
    app.run(port=3000)
```

> A callback is treated as received when it returns HTTP 200. For any other status code, BizyAir retries up to 10 times, with 200 milliseconds between attempts.

---

## IV. Query the Result

At this stage, use the **request ID (`request_id`)** from the previous step to check whether the task has finished and to retrieve the final result.

### 1. Query Task Status

Get the current status and details, such as queue information and runtime.

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
request_id = "your_request_id_here"
url = f"https://api.bizyair.ai/v1/webapp/task/openapi/{request_id}"
headers = {
    "Authorization": f"Bearer {api_key}"
}

response = requests.get(url, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("code") != 20000 or result.get("status") is not True:
    raise RuntimeError(result.get("message") or "BizyAir API request failed")
print(result)
```

**Response example:**

```json
{
  "code": 20000,
  "message": "Ok",
  "status": true,
  "data": {
    "type": "API",
    "status": "Success",
    "created_at": "2026-09-23T07:44:34Z",
    "updated_at": "2026-09-23T07:45:19Z",
    "executed_at": "2026-09-23T07:44:34Z",
    "ended_at": "2026-09-23T07:45:19Z",
    "expired_at": "2026-10-08T00:00:00Z",
    "cost_times": {
      "inference_cost_time": 42930,
      "running_cost_time": 44873,
      "total_cost_time": 44880,
      "real_cpu_cost_time": 430,
      "real_total_cost_time": 430
    }
  }
}
```

**Response fields:**

| Parameter | Type | Description |
| :--- | :--- | :--- |
| code | integer | Response code. 20000 means success. |
| message | string | API-level message or error. |
| status | boolean | Whether the API call succeeded. This is not the same field as the task-status string `data.status`. |
| data | object | Task payload. |
| data.type | string | Task type, for example `"API"`. |
| data.status | string | Task status: `Queuing` / `Preparing` / `Running` / `Success` / `Failed` / `Canceled`. |
| data.created_at | string | Created time (ISO 8601, UTC). |
| data.updated_at | string | Last updated time (ISO 8601, UTC). |
| data.executed_at | string | Execution start time (ISO 8601, UTC). May be omitted before execution starts. |
| data.ended_at | string or null | End time (ISO 8601, UTC). `null` or omitted before the task ends. |
| data.expired_at | string | Result file expiry time (ISO 8601, UTC). |
| data.cost_times | object | Timing statistics, in milliseconds. An empty object `{}` when nothing has been recorded. |
| data.cost_times.inference_cost_time | integer | Inference time, in milliseconds. |
| data.cost_times.running_cost_time | integer | Running time, in milliseconds. |
| data.cost_times.total_cost_time | integer | Total time, in milliseconds. |
| data.cost_times.real_cpu_cost_time | integer | CPU time, in milliseconds. |
| data.cost_times.real_total_cost_time | integer | Total time corresponding to `real_cpu_cost_time`, in milliseconds. |
| data.queue_info | object or null | Present only while status is `Queuing`. |
| data.queue_info.queue_count | integer | A value greater than 0 is the number of tasks ahead. `-1` does not mean a specific count. |

**Status enum:**

| Status | Meaning | What to do |
| :--- | :--- | :--- |
| `Queuing` | Queued, waiting for resources | Keep polling, every 1–3 seconds |
| `Preparing` | Preparing, loading the model or environment | Keep polling |
| `Running` | Running | Keep polling |
| `Success` | Succeeded | **Stop polling** and fetch the result |
| `Failed` | Failed | **Stop polling**. The reason is `outputs[].error_msg` from Get Task Result |
| `Canceled` | Canceled | **Stop polling** |

### 2. Get Task Result

Get the result of a **finished task**, including `object_url`.

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
request_id = "your_request_id_here"
url = f"https://api.bizyair.ai/v1/webapp/task/openapi/{request_id}/outputs"
headers = {
    "Authorization": f"Bearer {api_key}"
}

response = requests.get(url, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("code") != 20000 or result.get("status") is not True:
    raise RuntimeError(result.get("message") or "BizyAir API request failed")
for out in result["data"]["outputs"]:
    print("Result URL:", out["object_url"])
```

**Response example:**

```json
{
  "code": 20000,
  "message": "Ok",
  "status": true,
  "data": {
    "request_id": "29f53793-12d3-4dd3-b2a8-4d9848e0c7da",
    "status": "Success",
    "outputs": [
      {
        "object_url": "https://storage.bizyair.ai/outputs_examples/lXobDnouo90P3XoF1stRJhup4mgx4n26.png",
        "output_ext": ".png",
        "cost_time": 10657,
        "audit_status": 2,
        "error_type": "NOT_ERROR"
      }
    ]
  }
}
```

**Response fields:**

| Parameter | Type | Description |
| :--- | :--- | :--- |
| code | integer | Response code. 20000 means success. |
| message | string | API-level message or error. |
| status | boolean | Whether the API call succeeded. This is not the same field as the task-status string `data.status`. |
| data | object | Task result. |
| data.request_id | string | Request ID. |
| data.status | string | Task status: `Queuing` / `Preparing` / `Running` / `Success` / `Failed` / `Canceled`. |
| data.outputs | array[object (Output)] | Result list. |
| data.outputs[].object_url | string | Download URL of the result file. |
| data.outputs[].output_ext | string | File extension, including `.`. |
| data.outputs[].cost_time | integer | Elapsed time from task start until this result was produced, in milliseconds. |
| data.outputs[].audit_status | integer | Review status: `0` no review needed / `1` not reviewed yet / `2` approved / `3` rejected / `4` review error. |
| data.outputs[].error_type | string | A specific error code on failure, or `"NOT_ERROR"` on success. |
| data.outputs[].error_msg | string | Detailed reason on failure. May be omitted on success. |

> **Note:** Output URLs are CDN download links and are not guaranteed to stay available. Copy the files to your own storage soon after the task completes.

---

## V. File Upload

When `input_values` needs an image, audio, or video (for example `LoadImage.image` or `ref_images`), do not pass a local file path or base64. Upload the file to BizyAir OSS first, then pass the returned URL as the `input_values` value.

**Upload flow:** get credentials, upload to OSS, then commit the resource.

### 1. Get Upload Credentials and Parameters

Call the upload-token endpoint. The server returns the OSS information and temporary STS credentials for this upload.

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
url = "https://api.bizyair.ai/v1/upload/token"
params = {
    "file_name": "example.png",
    "file_type": "inputs_temp"
}
headers = {
    "Authorization": f"Bearer {api_key}"
}

response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("code") != 20000 or result.get("status") is not True:
    raise RuntimeError(result.get("message") or "BizyAir API request failed")
print(result)
```

**Credential response:**

```json
{
  "code": 20000,
  "message": "Ok",
  "status": true,
  "data": {
    "file": {
      "object_key": "inputs_temp/20260907/example.png",
      "access_key_id": "STS.xxxx",
      "access_key_secret": "xxxx",
      "expiration": "2026-09-07T08:00:00Z",
      "security_token": "xxxx"
    },
    "storage": {
      "endpoint": "oss-us-east-1.aliyuncs.com",
      "bucket": "example-bucket",
      "region": "oss-example-region"
    },
    "access_url": "https://storage.example/inputs_temp/20260907/example.png"
  }
}
```

### 2. Upload to Alibaba Cloud OSS

Use the returned `endpoint`, `bucket`, `region`, `object_key`, and STS credentials to upload a local file to OSS.

**Python example:**

```python
# pip install alibabacloud-oss-v2
import os
import alibabacloud_oss_v2 as oss

def upload_to_oss(region, endpoint, bucket, object_key, file_path, access_key_id, access_key_secret, security_token):
    os.environ["OSS_ACCESS_KEY_ID"] = access_key_id
    os.environ["OSS_ACCESS_KEY_SECRET"] = access_key_secret
    os.environ["OSS_SESSION_TOKEN"] = security_token

    cfg = oss.config.load_default()
    cfg.credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg.region = region[4:] if region.startswith("oss-") else region
    cfg.endpoint = endpoint

    client = oss.Client(cfg)
    return client.put_object_from_file(
        oss.PutObjectRequest(bucket=bucket, key=object_key),
        file_path,
    )
```

> **Notes:**
> - Some SDKs require removing the `oss-` prefix from `region`, for example `oss-us-east-1` becomes `us-east-1`.
> - Set both `region` and `endpoint`. Use the returned `endpoint`.
> - STS-signed OSS upload is not shown as a curl example. Switch to Python or JavaScript.

### 3. Commit the Input Resource

After the OSS upload succeeds, commit the input resource so later tasks can reference it.

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
url = "https://meta.bizyair.ai/v1/input_resource/commit"
payload = {
    "name": "example.png",
    "object_key": "inputs_temp/20260907/example.png"
}
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("code") != 20000 or result.get("status") is not True:
    raise RuntimeError(result.get("message") or "BizyAir API request failed")
print(result)
```

**The commit request must reuse `data.file.object_key` from the credential response. After commit succeeds, write `data.url` into the matching `input_values` field of the call request:**

```json
{
  "code": 20000,
  "message": "Ok",
  "status": true,
  "data": {
    "id": 1711,
    "name": "example.png",
    "ext": ".png",
    "url": "https://storage.example/inputs_temp/20260907/example.png"
  }
}
```

### 4. Query the Inputs List (Optional)

Use this to list your inputs and verify the upload. Before the first commit, `data` may contain only `current` and `page_size`, with no `list`. After records exist, `list` and `total` are present.

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
url = "https://meta.bizyair.ai/v1/input_resource"
params = {
    "current": 1,
    "page_size": 20
}
headers = {
    "Authorization": f"Bearer {api_key}"
}

response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("code") != 20000 or result.get("status") is not True:
    raise RuntimeError(result.get("message") or "BizyAir API request failed")
print(result)
```

---

## VI. Task Management

### 1. Cancel a Task

Cancel applies only to tasks that are still **queued** (`Queuing`), and the call must happen before the task leaves the queue. On success the API returns HTTP 200 and `code` 20000. The status then becomes `Canceled`, and the task is not billed.

If you query first and then cancel, the task may already have left the queue. The API then returns HTTP 409 and `code` 30035 (`The task has been executed, please use interrupt.`). Use interrupt instead.

HTTP 202 and `code` 20002 only mean the cancel request was accepted. They do not mean the task is already canceled. Query the status again to confirm.

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
request_id = "your_request_id_here"
url = f"https://api.bizyair.ai/v1/webapp/task/openapi/{request_id}/cancel"
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}

response = requests.put(url, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("code") != 20000 or result.get("status") is not True:
    raise RuntimeError(result.get("message") or "BizyAir API request failed")
print("Task canceled")
```

> **Note:** Calling cancel again on an already canceled task returns HTTP 409 and `code` 30036 (`Task not in queue status.`). The status stays `Canceled`, and this call does not create a charge.

### 2. Interrupt a Task

Interrupt applies only to tasks that are **running** (`Running`) and stops them immediately. On success the API returns HTTP 200 and `code` 20000. If the task is not `Running`, the API returns HTTP 409 and `code` 30038. After a successful interrupt the status is `Failed`, `error_type` is `INTERRUPT`, and `error_msg` is `Interrupted`.

```python
import os
import requests

api_key = os.environ["BIZYAIR_API_KEY"]
request_id = "your_request_id_here"
url = f"https://api.bizyair.ai/v1/webapp/task/openapi/{request_id}/interrupt"
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}

response = requests.put(url, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("code") != 20000 or result.get("status") is not True:
    raise RuntimeError(result.get("message") or "BizyAir API request failed")
print("Task interrupted")
```

> **Warning:** Interrupting a running task **still charges for the work already done**. Cancel is free of charge only while the task is `Queuing`.

---

## Appendix: Mask Log Fields

To protect sensitive business information such as prompt design, you can name the call-log fields to mask in a request header. The API response body stays unchanged. `mask_fields` only records those field paths. The call log page shows matched fields as `[Masked by caller]`. Masking does not affect execution or billing.

**How to use it:** send `X-Bizyair-Log-Mask-Fields` in the request header. The value is a key from `input_values`. Separate multiple fields with commas.

```http
Content-Type: application/json
Authorization: Bearer ${BIZYAIR_API_KEY}
X-Bizyair-Log-Mask-Fields: 471:TextEncodeQwenImage21.prompt
```