## I. Getting Started

Complete these prerequisites before calling a BizyAir standard model API.

### 1. Register and Add Balance

Sign in or register for a BizyAir account and ensure that the paid balance is sufficient. **Standard model APIs use paid balance.**

### 2. Get an API Key

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

**Keep the API Key secure. Do not commit it to source control or print it in logs.**

## II. Submit a Request

You can now submit API requests.

### 1. Request Example

Set `BIZYAIR_API_KEY` in the runtime environment, then copy and run the example for your language.

Before running the request, you can adjust the parameters in the payload to generate the exact output you need.

Important: follow the parameter format and requirements in **2. Request Parameters** carefully.

```python
import os

import requests

api_key = os.environ["BIZYAIR_API_KEY"]
url = "https://api.bizyair.ai/v1/modelzoo/tasks/openapi/gemini-4-0-official/large-language-models"
headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {api_key}"
}
payload = {
  "prompt": "Explain the trade-offs between vector databases and BM25 keyword search for a product recommendation engine, then recommend an architecture for a catalog of 50 million items with sub-100ms retrieval latency. Structure the answer with clear headings, concrete numbers, and a short summary table.",
  "max_tokens": 8192,
  "thinking_level": "MEDIUM"
}

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 submit response contains `request_id`. Use the query API in section III to get the result. If you also send the header `X-Bizyair-Task-WebHook-Url: https://your-website/webhook`, BizyAir POSTs the result to that URL when the task finishes. Example:

```python
import os

import requests

api_key = os.environ["BIZYAIR_API_KEY"]
webhook_url = os.environ["WEBHOOK_URL"]
webhook_authorization = os.environ["WEBHOOK_AUTHORIZATION"]
webhook_trace_id = os.environ["WEBHOOK_TRACE_ID"]
url = "https://api.bizyair.ai/v1/modelzoo/tasks/openapi/gemini-4-0-official/large-language-models"
headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {api_key}",
    "X-Bizyair-Task-WebHook-Url": webhook_url,
    "X-Bizyair-Task-Authorization": webhook_authorization,
    "X-Bizyair-Task-Trace-Id": webhook_trace_id
}
payload = {
  "prompt": "Explain the trade-offs between vector databases and BM25 keyword search for a product recommendation engine, then recommend an architecture for a catalog of 50 million items with sub-100ms retrieval latency. Structure the answer with clear headings, concrete numbers, and a short summary table.",
  "max_tokens": 8192,
  "thinking_level": "MEDIUM"
}

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 request body is the same whether or not a Webhook callback is configured. Pay special attention to these headers:

| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| X-Bizyair-Task-WebHook-Url | string | Optional | Callback URL that BizyAir calls when the task finishes. If omitted, no callback is sent; retrieve the result with the query endpoint in Section III. The callback must be an HTTP or HTTPS POST endpoint. 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, such as `X-Bizyair-Task-Trace-Id` in the example above. |

### 2. Request Parameters

Read **Request Parameters** below to refine your request. Following these parameter requirements improves the likelihood of a successful run.

| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| prompt | string | Required | Prompt |
| system_prompt | string | Optional | System prompt |
| max_tokens | number | Optional | Value range: 1 ~ 65536<br/>Max tokens |
| thinking_level | string | Optional | ⟨bz_enum_json⟩["LOW","MEDIUM","HIGH"]⟨/bz_enum_json⟩<br/>Thinking level |

> To protect sensitive business information such as prompt design, BizyAir supports masking specific fields in API call logs. Masked fields appear as `[Masked by caller]` in log queries without affecting execution or billing.
>
> **How to use it:** send `X-Bizyair-Log-Mask-Fields` in the request header and provide the fields to mask, separated by commas.
>
> ```http
> Content-Type: application/json
> Authorization: Bearer ${BIZYAIR_API_KEY}
> X-Bizyair-Log-Mask-Fields: prompt, system_prompt
> ```

### 3. Response Example

After a successful submission, you receive a response similar to the following.

This is an **asynchronous task submission receipt**. It indicates that the request has been accepted and the task is queued for execution.

If you receive a different response, refer to **4. Response Fields** below for details.

```json
{
  "code": 20000,
  "message": "Ok",
  "status": true,
  "data": {
    "request_id": "4569bb94-1d30-417a-a987-9715de1e2633"
  }
}
```

### 4. Response Fields

Read the **Response Fields** section below for field meanings and possible values.

| Parameter | Type | Description |
| :--- | :--- | :--- |
| code | integer | Response code, 20000 indicates success. |
| message | string | Message about the API call itself. |
| status | boolean | Whether the API call succeeded. It is not the same field as the task-state string data.status. |
| data | object | Response data container. |
| data.request_id | string | Request ID for subsequent task status queries. |

## III. Query the Result

At this stage, use the generated **request_id** to check whether the task has finished and retrieve the final result.

### 1. Request Example

Set `BIZYAIR_API_KEY` in the runtime environment, and replace the sample `request_id` with the ID returned by the submit response.

```python
import os

import requests

api_key = os.environ["BIZYAIR_API_KEY"]
request_id = "${REQUEST_ID}"
url = f"https://api.bizyair.ai/v1/modelzoo/tasks/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)
```

### 2. Response Example

This is the final receipt returned by the BizyAir query endpoint after the task completes successfully and generates content.

It is the **final result** of the entire generation workflow. By examining this response, you can understand the outcome of every step above.

If you receive a different response, refer to **3. Response Fields** below for details.

```json
{
  "code": 20000,
  "message": "Ok",
  "status": true,
  "data": {
    "request_id": "4569bb94-1d30-417a-a987-9715de1e2633",
    "status": "Success",
    "message": null,
    "created_at": "2026-04-15T13:32:30Z",
    "executed_at": "2026-04-15T13:32:32Z",
    "ended_at": "2026-04-15T13:42:32Z",
    "outputs": {
      "texts": [
        "### Trade-offs: Vector Databases vs. BM25 Keyword Search\n\nIn a product recommendation engine, the core trade-off between Vector Databases (Dense Retrieval) and BM25 (Sparse Retrieval) comes down to **semantic understanding vs. exact token matching**, alongside significant differences in infrastructure cost.\n\n#### 1. Relevance and Recall\n*   **Vector Databases (Semantic):** Represent products and queries as dense embeddings (e.g., 768-dimensional floats). They excel at capturing intent, synonyms, and cross-modal relationships (e.g., matching a query for \"beach outfit\" to \"linen shorts\" even if no words overlap). However, they struggle with \"out-of-vocabulary\" exact matches, such as specific model numbers, SKUs, or rare brand names.\n*   **BM25 (Lexical):** Uses term frequency-inverse document frequency (TF-IDF) mechanics. It guarantees precision for exact keyword matches (e.g., \"Sony WH-1000XM5\" or \"size 10 red\"). However, it suffers from the \"vocabulary mismatch\" problem—if a user searches for \"warm winter coat\" and the product description only says \"insulated parka,\" BM25 returns a score of zero.\n\n#### 2. Computational Cost and Memory Footprint\n*   **Vector Databases:** Very memory-intensive. To achieve sub-100ms latency, Approximate Nearest Neighbor (ANN) indices like HNSW (Hierarchical Navigable Small World) must reside entirely in RAM. Updating an HNSW index is computationally expensive, making real-time inventory updates difficult.\n*   **BM25:** Extremely lightweight. It relies on inverted indices, which compress well and can be partially stored on disk (SSD) while relying on the OS page cache for hot terms. Real-time updates (e.g., changing price or stock status) are near-instantaneous.\n\n#### 3. Cold Start and Personalization\n*   **Vector Databases:** Naturally support personalization. You can average a user's historical interaction vectors to create a \"User Embedding\" and query the product space directly without a text query.\n*   **BM25:** Poor at personalization unless heavily engineered with manual query-expansion rules (e.g., appending a user's favorite brands as boosted optional terms to their search).\n\n---\n\n### Summary Comparison Table (Scale: 50M Products)\n\n*Assumptions: 50M products, average 200 words per product description, 768-dimensional float32 embeddings.*\n\n| Metric / Feature | Vector Database (HNSW) | BM25 (Inverted Index) |\n| :--- | :--- | :--- |\n| **Primary Strength** | Semantic similarity, intent, personalization | Exact matches (SKUs, brands, specs) |\n| **Primary Weakness** | Hallucinates on exact SKUs; high RAM cost | Zero recall on synonyms/concepts |\n| **Est. Storage / RAM** | **~220 GB – 300 GB RAM** (uncompressed) | **~30 GB – 50 GB Disk** (~16 GB RAM cache) |\n| **P99 Latency (50M items)** | 15ms – 40ms | 5ms – 15ms |\n| **Ingestion/Update Speed** | Slow (CPU-bound graph rebuilding) | Fast (Segment merging) |\n| **Compute Cost** | High (Requires GPU for encoding + High-RAM nodes) | Low (Standard CPU + NVMe SSD nodes) |\n\n---\n\n### Recommended Architecture: 50M Items @ <100ms Latency\n\nFor a catalog of 50 million items, relying solely on one method will yield poor business results. The industry standard is a **Multi-Stage Hybrid Retrieval & Ranking Architecture**. \n\nTo keep costs reasonable while hitting the sub-100ms SLA, you must use **Scalar Quantization (SQ8)** for the vectors and a unified search engine (like Elasticsearch, OpenSearch, or Vespa) to avoid network hop penalties.\n\n#### Concrete Infrastructure Sizing (The \"Unified Hybrid\" Cluster)\nInstead of maintaining a separate vector DB and text DB, use **OpenSearch or Vespa** to house both the BM25 inverted index and the HNSW vector index on the same shard.\n*   **Vector Math:** 50M items $\\times$ 768 dimensions $\\times$ 4 bytes (float32) = 153.6 GB. With HNSW graph overhead ($\\sim 1.5\\times$), this requires $\\sim 230$ GB of pure RAM. \n*   **Optimization:** Apply **INT8 Scalar Quantization (SQ8)**. This reduces the vector size by 75% with $<1\\%$ recall loss. Vector RAM drops to **~60 GB**.\n*   **Cluster Specs:** **4 Data Nodes** (e.g., AWS `r6gd.4xlarge` – 16 vCPU, 128 GB RAM, NVMe SSD). Total cluster RAM: 512 GB (plenty of headroom for the 60GB vectors, 50GB BM25 index, JVM heap, and OS file cache).\n*   **Sharding:** 16 primary shards with 1 replica (32 shards total, 8 shards per node). Each shard searches ~3.1M items.\n\n#### The Sub-100ms Execution Pipeline (Total Time: ~65ms)\n\n**Stage 1: Query Processing & Encoding (Latency: ~10ms)**\n*   Incoming request hits the Recommendation API.\n*   User context and query are converted into a 768d vector using a distilled model (e.g., `bge-base` or a custom Two-Tower model) running on an **NVIDIA T4/L4 GPU** via Triton Inference Server using ONNX Runtime / TensorRT.\n\n**Stage 2: Parallel Hybrid Retrieval & Pre-Filtering (Latency: ~25ms)**\nThe engine executes a single network request to the 4-node cluster, running two queries in parallel per shard, **pre-filtered** by hard attributes (e.g., `in_stock: true`, `region: US`):\n1.  **BM25 Query:** Retrieves top 250 items based on keyword/attribute matching.\n2.  **ANN Vector Query:** Searches the SQ8 HNSW index (`ef_search=100`) to retrieve the top 250 semantically similar items.\n\n**Stage 3: Score Normalization & Fusion (Latency: ~5ms)**\n*   Because BM25 scores (unbounded) and Cosine Similarity scores (0 to 1) cannot be added directly, combine the two lists of 250 items using **Reciprocal Rank Fusion (RRF)**.\n*   Deduplicate and select the **Top 100 candidates**.\n\n**Stage 4: Lightweight Cross-Encoder / GBDT Re-ranking (Latency: ~20ms)**\n*   Retrieval optimizes for *recall*; re-ranking optimizes for *precision and business logic*.\n*   Pass the Top 100 candidates into a fast **LightGBM / XGBoost** model (hosted in-memory on the API layer).\n*   This model incorporates real-time features that cannot be indexed in HNSW: historical Click-Through Rate (CTR), profit margin, dynamic pricing, and user-specific affinity scores.\n*   Sort and return the final **Top 20 recommendations** to the user.\n\n**Stage 5: Network Overhead (Latency: ~5ms)**\n*   gRPC serialization/deserialization between internal microservices. \n\n**Total P99 Latency:** $10 + 25 + 5 + 20 + 5 = \\mathbf{65\\text{ms}}$ (Well within the 100ms budget)."
      ]
    },
    "cost_times": {
      "total_cost_time": 600000,
      "inference_duration": 598000
    }
  }
}
```

### 3. Response Fields

Read the **Response Fields** section below for field meanings and possible values.

| Parameter | Type | Description |
| :--- | :--- | :--- |
| code | integer | Response code, 20000 indicates success. |
| message | string | Message about the API call itself; the reason a task failed is in data.message. |
| status | boolean | Whether the API call succeeded. It is not the same field as the task-state string data.status. |
| data | object | Response data container. |
| data.request_id | string | Request ID for subsequent task status queries. |
| data.status | string | Task status. Confirmed terminal states are Success and Failed; continue querying for other states. |
| data.message | string or null | Error details for Failed tasks; null or omitted for other states. |
| data.created_at | string | Task creation time in ISO 8601 UTC, e.g. 2026-09-07T07:34:56Z. |
| data.executed_at | string | Task execution start time in ISO 8601 UTC. |
| data.ended_at | string | Task end time in ISO 8601 UTC; some Failed responses omit this field. |
| data.outputs | object | Output object; an empty object {} while incomplete or when no output exists. |
| data.outputs.texts | string[] | Text outputs. |
| data.cost_times | object | Timing object; an empty object {} before completion or when timing is unavailable. |
| data.cost_times.total_cost_time | integer | Total task duration in milliseconds. Returned after the task completes. |
| data.cost_times.inference_duration | integer | Model inference duration in milliseconds. Returned after the task completes. |

> 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.

### 4. Webhook Callback

If you specify a Webhook callback URL with the `X-Bizyair-Task-WebHook-Url` header when creating the task, BizyAir sends a callback notification to that address when the task finishes.

When the task succeeds:

```json
{
  "request_id": "6b88a97e-76e8-480a-bae7-a6f7f37b4e97",
  "status": "Success",
  "created_at": "2026-05-22T17:14:07Z",
  "executed_at": "2026-05-22T17:14:07Z",
  "ended_at": "2026-05-22T17:14:44Z",
  "outputs": {
    "texts": [
      "### Trade-offs: Vector Databases vs. BM25 Keyword Search\n\nIn a product recommendation engine, the core trade-off between Vector Databases (Dense Retrieval) and BM25 (Sparse Retrieval) comes down to **semantic understanding vs. exact token matching**, alongside significant differences in infrastructure cost.\n\n#### 1. Relevance and Recall\n*   **Vector Databases (Semantic):** Represent products and queries as dense embeddings (e.g., 768-dimensional floats). They excel at capturing intent, synonyms, and cross-modal relationships (e.g., matching a query for \"beach outfit\" to \"linen shorts\" even if no words overlap). However, they struggle with \"out-of-vocabulary\" exact matches, such as specific model numbers, SKUs, or rare brand names.\n*   **BM25 (Lexical):** Uses term frequency-inverse document frequency (TF-IDF) mechanics. It guarantees precision for exact keyword matches (e.g., \"Sony WH-1000XM5\" or \"size 10 red\"). However, it suffers from the \"vocabulary mismatch\" problem—if a user searches for \"warm winter coat\" and the product description only says \"insulated parka,\" BM25 returns a score of zero.\n\n#### 2. Computational Cost and Memory Footprint\n*   **Vector Databases:** Very memory-intensive. To achieve sub-100ms latency, Approximate Nearest Neighbor (ANN) indices like HNSW (Hierarchical Navigable Small World) must reside entirely in RAM. Updating an HNSW index is computationally expensive, making real-time inventory updates difficult.\n*   **BM25:** Extremely lightweight. It relies on inverted indices, which compress well and can be partially stored on disk (SSD) while relying on the OS page cache for hot terms. Real-time updates (e.g., changing price or stock status) are near-instantaneous.\n\n#### 3. Cold Start and Personalization\n*   **Vector Databases:** Naturally support personalization. You can average a user's historical interaction vectors to create a \"User Embedding\" and query the product space directly without a text query.\n*   **BM25:** Poor at personalization unless heavily engineered with manual query-expansion rules (e.g., appending a user's favorite brands as boosted optional terms to their search).\n\n---\n\n### Summary Comparison Table (Scale: 50M Products)\n\n*Assumptions: 50M products, average 200 words per product description, 768-dimensional float32 embeddings.*\n\n| Metric / Feature | Vector Database (HNSW) | BM25 (Inverted Index) |\n| :--- | :--- | :--- |\n| **Primary Strength** | Semantic similarity, intent, personalization | Exact matches (SKUs, brands, specs) |\n| **Primary Weakness** | Hallucinates on exact SKUs; high RAM cost | Zero recall on synonyms/concepts |\n| **Est. Storage / RAM** | **~220 GB – 300 GB RAM** (uncompressed) | **~30 GB – 50 GB Disk** (~16 GB RAM cache) |\n| **P99 Latency (50M items)** | 15ms – 40ms | 5ms – 15ms |\n| **Ingestion/Update Speed** | Slow (CPU-bound graph rebuilding) | Fast (Segment merging) |\n| **Compute Cost** | High (Requires GPU for encoding + High-RAM nodes) | Low (Standard CPU + NVMe SSD nodes) |\n\n---\n\n### Recommended Architecture: 50M Items @ <100ms Latency\n\nFor a catalog of 50 million items, relying solely on one method will yield poor business results. The industry standard is a **Multi-Stage Hybrid Retrieval & Ranking Architecture**. \n\nTo keep costs reasonable while hitting the sub-100ms SLA, you must use **Scalar Quantization (SQ8)** for the vectors and a unified search engine (like Elasticsearch, OpenSearch, or Vespa) to avoid network hop penalties.\n\n#### Concrete Infrastructure Sizing (The \"Unified Hybrid\" Cluster)\nInstead of maintaining a separate vector DB and text DB, use **OpenSearch or Vespa** to house both the BM25 inverted index and the HNSW vector index on the same shard.\n*   **Vector Math:** 50M items $\\times$ 768 dimensions $\\times$ 4 bytes (float32) = 153.6 GB. With HNSW graph overhead ($\\sim 1.5\\times$), this requires $\\sim 230$ GB of pure RAM. \n*   **Optimization:** Apply **INT8 Scalar Quantization (SQ8)**. This reduces the vector size by 75% with $<1\\%$ recall loss. Vector RAM drops to **~60 GB**.\n*   **Cluster Specs:** **4 Data Nodes** (e.g., AWS `r6gd.4xlarge` – 16 vCPU, 128 GB RAM, NVMe SSD). Total cluster RAM: 512 GB (plenty of headroom for the 60GB vectors, 50GB BM25 index, JVM heap, and OS file cache).\n*   **Sharding:** 16 primary shards with 1 replica (32 shards total, 8 shards per node). Each shard searches ~3.1M items.\n\n#### The Sub-100ms Execution Pipeline (Total Time: ~65ms)\n\n**Stage 1: Query Processing & Encoding (Latency: ~10ms)**\n*   Incoming request hits the Recommendation API.\n*   User context and query are converted into a 768d vector using a distilled model (e.g., `bge-base` or a custom Two-Tower model) running on an **NVIDIA T4/L4 GPU** via Triton Inference Server using ONNX Runtime / TensorRT.\n\n**Stage 2: Parallel Hybrid Retrieval & Pre-Filtering (Latency: ~25ms)**\nThe engine executes a single network request to the 4-node cluster, running two queries in parallel per shard, **pre-filtered** by hard attributes (e.g., `in_stock: true`, `region: US`):\n1.  **BM25 Query:** Retrieves top 250 items based on keyword/attribute matching.\n2.  **ANN Vector Query:** Searches the SQ8 HNSW index (`ef_search=100`) to retrieve the top 250 semantically similar items.\n\n**Stage 3: Score Normalization & Fusion (Latency: ~5ms)**\n*   Because BM25 scores (unbounded) and Cosine Similarity scores (0 to 1) cannot be added directly, combine the two lists of 250 items using **Reciprocal Rank Fusion (RRF)**.\n*   Deduplicate and select the **Top 100 candidates**.\n\n**Stage 4: Lightweight Cross-Encoder / GBDT Re-ranking (Latency: ~20ms)**\n*   Retrieval optimizes for *recall*; re-ranking optimizes for *precision and business logic*.\n*   Pass the Top 100 candidates into a fast **LightGBM / XGBoost** model (hosted in-memory on the API layer).\n*   This model incorporates real-time features that cannot be indexed in HNSW: historical Click-Through Rate (CTR), profit margin, dynamic pricing, and user-specific affinity scores.\n*   Sort and return the final **Top 20 recommendations** to the user.\n\n**Stage 5: Network Overhead (Latency: ~5ms)**\n*   gRPC serialization/deserialization between internal microservices. \n\n**Total P99 Latency:** $10 + 25 + 5 + 20 + 5 = \\mathbf{65\\text{ms}}$ (Well within the 100ms budget)."
    ]
  },
  "cost_times": {
    "total_cost_time": 36815,
    "inference_duration": 36090
  }
}
```

When the task fails:

```json
{
  "request_id": "ee8f5246-77ff-4df7-af62-7c55f311bcb2",
  "status": "Failed",
  "message": "Third-party api response error. No image generated.",
  "created_at": "2026-05-25T13:13:04Z",
  "executed_at": "2026-05-25T13:13:04Z",
  "ended_at": "2026-05-25T13:13:06Z",
  "outputs": {
    "texts": [
      "Sorry, I can't provide an image for this kind of content."
    ]
  },
  "cost_times": {
    "total_cost_time": 1930
  }
}
```

Callback JSON fields are at the top level. Their meanings match the task fields inside `data` in the query result above.

## IV. File Upload

Use this workflow to upload image, audio, or video resources to the BizyAir server.

Uploaded files can then be referenced as input resources in your tasks: pass the returned URL to this endpoint's media parameter `image_urls`.

Note: upload credentials come from `https://api.bizyair.ai/v1`, while committing and listing input resources use the metadata service at `https://meta.bizyair.ai/v1`. The different hosts are expected.

### 1. Get Upload Credentials and Parameters

Call the upload-token endpoint. The server returns the OSS information and temporary STS credentials required for the 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.webp",
    "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)
```

The credential response contains `data.file.object_key`, temporary STS credentials, and `data.storage`:

```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": "https://example-oss-endpoint",
      "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. For more details, see [Alibaba Cloud OSS simple upload](https://help.aliyun.com/zh/oss/user-guide/simple-upload) and the [BizyAir upload guide](https://docs.bizyair.ai/en/api/upload-tutorial.html#oss).

```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["ALIBABA_CLOUD_ACCESS_KEY_ID"] = access_key_id
    os.environ["ALIBABA_CLOUD_ACCESS_KEY_SECRET"] = access_key_secret
    os.environ["ALIBABA_CLOUD_SECURITY_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`.
- It is recommended to set both `region` and `endpoint`, with the returned `endpoint` taking precedence.

### 3. Commit the Input Resource

After the OSS upload succeeds, commit the uploaded input resource so it can be referenced directly in subsequent tasks.

```python
import os

import requests

api_key = os.environ["BIZYAIR_API_KEY"]
url = "https://meta.bizyair.ai/v1/input_resource/commit"
payload = {
    "name": "example.webp",
    "object_key": "inputs_temp/20250911/abc123.webp"
}
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, pass the returned `data.url` to this endpoint's media parameter `image_urls`:

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

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

You can query your inputs list to verify the uploaded content.

```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)
```