AI Product Images: Scenes, Cutouts, and Generation Tasks
Description
Retrieve recommended product-image scenes, remove product backgrounds, and submit scene-generation tasks. Query progress and results using the business task ID returned on submission.
Endpoints
| API | Method | URL |
|---|---|---|
| Recommended scenes | GET | https://openapi.meitu.com/design/enterprise/ai_product/materials |
| Product cutout | POST | https://openapi.meitu.com/design/enterprise/ai_product/sod |
| Submit task | POST | https://openapi.meitu.com/design/enterprise/ai_product/create |
| Query task | GET | https://openapi.meitu.com/design/enterprise/ai_product/query |
Authentication
Use an AK/SK pair with access to this capability and sign requests with the SDK. See API Request Signing.
Place GET parameters in the URL query string and sign the complete URL. POST requests use Content-Type: application/json. The product-cutout example also includes X-Aigcp-Way: handler.
Call Sequence
- Retrieve recommended scenes and select a scene
id. - If you need to adjust the product region, call the cutout API for the result and its
x,y,width, andheight. Otherwise, omitmask_urlon submission to use automatic background removal. - Submit a generation task and save
data.idfrom the response. - Query the dedicated endpoint in this document using that
id.process_status=2means completed;3means failed.
Use data.id, not task_ids[] or scene_result_images[].task_id. This API does not use the generic task API's status values.
1. Retrieve Recommended Scenes
Request
GET https://openapi.meitu.com/design/enterprise/ai_product/materials
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| cursor | string | Pagination cursor. Omit on the first request; the default is empty. Use the previous response's data.cursor for subsequent requests. |
| count | integer | Requested number of entries. The example uses 20; no default or maximum is specified in the source. |
Example URL:
https://openapi.meitu.com/design/enterprise/ai_product/materials?count=20Response Fields
code=0 indicates success; reqid identifies the request for troubleshooting. Read scenes from data.data and the pagination cursor from data.cursor. Treat the returned cursor as opaque; do not construct it yourself.
data.data Element
| Field | Type | Description |
|---|---|---|
| id | integer (64-bit) | Scene material ID. Convert it to a string for material_id when submitting. |
| default_name | string | Scene name. |
| thumbnail | object | Thumbnail with url, width, and height. |
| type | string | Type, such as ai_product_photo_scene. |
| ai_product_type / is_color_scene / can_image_video | integer | Product type, color-scene flag, and image/video support flag; can_image_video=0 means unsupported. |
| alg_params / extra / light_mode | string | Reserved algorithm parameters, additional information, and lighting mode; may be empty. |
| max_ratio / min_ratio | number | Maximum/minimum ratio; the documented default is 0. |
| prompt_id | string | Associated prompt ID. |
| preview_v2 | object | Preview configuration. |
| portrait_style_category_ids / v2_category_ids / v2_style_category_ids | array | Portrait-style, category, and style-category ID lists. |
Response Example
HTTP 200. This example follows the source structure. IDs, names, and URLs are illustrative and do not guarantee current material availability. Use the IDs returned by the actual scene-list request.
{
"reqid": "example-request-id",
"code": 0,
"data": {
"cursor": "cs-1-1",
"data": [
{
"ai_product_type": 0,
"alg_params": "",
"can_image_video": 0,
"default_name": "Example scene",
"extra": "",
"id": 1759202837959028,
"is_color_scene": 0,
"light_mode": "",
"max_ratio": 0,
"min_ratio": 0,
"portrait_style_category_ids": [],
"preview_v2": {
"4592": {}
},
"prompt_id": "10893",
"thumbnail": {
"height": 1024,
"url": "https://example.com/scene.jpeg",
"width": 1024
},
"type": "ai_product_photo_scene",
"v2_category_ids": [
4592
],
"v2_style_category_ids": [
4606
]
}
]
}
}2. Product Cutout
Description and Version
Remove the background from a product image and return the cutout URL, position, and dimensions. Version: 1.0.
Image Requirements
JPG and PNG are supported.
Request
POST https://openapi.meitu.com/design/enterprise/ai_product/sod
Headers: Content-Type: application/json, X-Aigcp-Way: handler, and the signing headers generated by the SDK.
Request Parameters
| Required | Parameter | Type | Description |
|---|---|---|---|
| Yes | url | string | Product image URL. |
Request Example
{
"url": "https://example.com/product.png"
}Response Fields
| Field | Type | Description |
|---|---|---|
| reqid | string | Request ID. |
| code | integer | 0 indicates success. |
| data.url | string | Cutout result URL. |
| data.x / data.y | integer | Product-region starting coordinates. |
| data.width / data.height | integer | Product-region dimensions in pixels. |
To construct box1, combine these values in x,y,width,height order. The example below gives "1,0,286,550".
Response Example
HTTP 200.
{
"reqid": "example-request-id",
"code": 0,
"data": {
"url": "https://example.com/cutout.png",
"height": 550,
"width": 286,
"x": 1,
"y": 0
}
}3. Submit a Task
Description and Version
Submit an AI product scene-generation task. Version: 1.0. JPG and PNG input images are supported.
Request
POST https://openapi.meitu.com/design/enterprise/ai_product/create
Content-Type: application/json
Request Parameters
Place all fields directly at the root of the JSON body.
| Required | Parameter | Type | Description |
|---|---|---|---|
| Yes | url | string | Original product image URL. |
| Yes | material_id | string | Scene material ID from the scene list's id; separate multiple IDs with commas and send as one string. |
| No | mask_url | string | Product mask URL. If omitted, background removal is performed automatically. |
| No | box1 | string | Original product region in x,y,width,height format, using the cutout coordinates and dimensions. Omit if no position or size adjustment is needed. |
| No | box2 | string | Product bounding box on the canvas, in x,y,w,h format. Automatically centered if omitted; values may contain decimals. |
| No | product_category | string | Product category in English. Default: product. |
| No | callback_url | string | POST callback URL for receiving task status. |
| No | scene_type | number | 1: recommended scene, requires material_id; 2: custom scene, requires prompt. Default: 1. |
| Conditional | prompt | string | Custom scene description, required when scene_type=2. |
| No | canvas_w | number | Canvas width. Default: 512; range: 50–1024; step: 0.5. Output width is twice the canvas width. |
| No | canvas_h | number | Canvas height. Default: 512; range: 50–1024; step: 0.5. Output height is twice the canvas height. |
| No | angle | number | Counterclockwise rotation angle, range 0–360. Supply pre-rotation coordinates in box2. |
| No | flip | number | 0: no flip; 1: horizontal; 2: vertical; 3: both horizontal and vertical. |
| No | refimage_url | string | Reference image URL, applicable only when scene_type=2. |
| No | prompt_vs_refimage | number | Text-versus-reference-image generation preference, range -1.0–1.0; only applies when scene_type=2. |
| No | guidance_scale | number | Creative relevance parameter, range 5.0–15.0; only applies when scene_type=2. |
| No | steps | integer | Sampling steps, integer range 15–35; only applies when scene_type=2. |
| No | prompt_neg | string | Description of unwanted content; only applies when scene_type=2. |
material_id remains required as specified in the source. Custom scenes also require prompt; the source does not explicitly allow omission of material_id for custom scenes.
Request Example
Generate a recommended scene with automatic background removal. Replace material_id with an ID actually returned by the scene list.
{
"url": "https://example.com/product.png",
"material_id": "1759202837959028",
"scene_type": 1,
"product_category": "product",
"canvas_w": 512,
"canvas_h": 512
}For position and size adjustments, add mask_url, box1, and box2 as needed. Supply a suitable mask URL; do not assume an arbitrary image result is a mask solely because of a field name.
Response Fields
code=0 indicates successful submission, not completion. Save data.id and use the query endpoint below to obtain the final result.
| Field | Type | Description |
|---|---|---|
| id | integer | Business task ID used in /query?id=.... |
| process_status | integer | 1: processing; 2: completed; 3: failed. |
| result_image_urls | string[] | Generated image URLs. Not available on submission and may be an empty array; obtain them through querying after completion. |
| result_images | object[] | Result objects: string id, image url, and process_status=2 for a completed image. |
| scene_result_images | object[] | Scene-grouped results: scene_id, urls, process_status, and task_id. |
| result_num | integer | Number of generated results. |
| task_ids | array | Internal task list, not the ID for /query. |
| materials | object / null | Scene material information. |
| url / mask_url / refimage_url | string | Original, mask, and reference image URLs. |
| box1 / box2 | string | Original and adjusted regions in x,y,width,height format. |
| canvas_w / canvas_h | number | Canvas dimensions. |
| angle / flip / scene_type | number | Rotation, flipping, and scene type, as defined in the request parameters. |
| product_category / prompt / translate_prompt | string | Product category, prompt, and translated prompt. |
| prompt_vs_refimage / guidance_scale / steps | number | Corresponding generation parameters. |
| created_at / finished_at / updated_at | integer | Creation, completion, and update timestamps. |
| client_id / uid / user_material_id / z_index | integer | Client, user, custom material, and layer-order information. |
Response Example
HTTP 200. This condensed response shows a task still processing. A full response may contain additional fields listed above.
{
"reqid": "example-request-id",
"code": 0,
"data": {
"id": 46062184,
"process_status": 1,
"result_image_urls": [],
"result_images": [],
"scene_result_images": [],
"result_num": 1,
"task_ids": [
995952
],
"scene_type": 1,
"canvas_w": 512,
"canvas_h": 512,
"url": "https://example.com/product.png"
}
}4. Query a Task
Request
GET https://openapi.meitu.com/design/enterprise/ai_product/query
Query Parameters
| Required | Parameter | Type | Description |
|---|---|---|---|
| Yes | id | integer | data.id returned by task submission. |
https://openapi.meitu.com/design/enterprise/ai_product/query?id=46062184Replace the example ID with the ID of your task; do not use an ID from task_ids.
Response Fields
The task-detail structure matches that of submission:
process_status=1: processing; query again later.process_status=2: completed; readresult_image_urls.process_status=3: failed.
result_images contains individual image IDs, URLs, and statuses. scene_result_images groups results by scene. Do not resubmit simply because a task is still processing.
Response Example
HTTP 200, with a condensed completed-task response:
{
"reqid": "example-request-id",
"code": 0,
"data": {
"id": 46062184,
"process_status": 2,
"result_image_urls": [
"https://example.com/result.jpeg"
],
"result_images": [
{
"id": "995952-0",
"url": "https://example.com/result.jpeg",
"process_status": 2
}
],
"result_num": 1,
"scene_result_images": [
{
"scene_id": 10023,
"urls": [
"https://example.com/result.jpeg"
],
"process_status": 2,
"task_id": 995952
}
],
"scene_type": 1,
"canvas_w": 512,
"canvas_h": 512,
"url": "https://example.com/product.png"
}
}Error Codes and Error Response
On failure, inspect code and message and retain reqid. The source error examples use HTTP 400.
| Code | Description |
|---|---|
| 11001 | Invalid or missing parameters, such as material_id or a query ID. |
| 11002 | Processing failure, such as a failed cutout. |
Example submission failure:
{
"reqid": "example-request-id",
"code": 11001,
"message": "Missing required parameter: material_id"
}See API Error Codes for other errors.
These response examples are based on the source documentation, not live results from this work.
SDK Examples
Each language provides one complete example. Set operation to materials, sod, create, or query; each run calls only one endpoint. The default retrieves scenes. It does not automatically submit generation tasks or poll.
Set up the corresponding signing SDK, then replace the AK, SK, image URL, actual scene ID, or query ID. Request bodies are embedded; no external JSON files are required. For pagination, URL-encode the returned cursor, add it to the scene-list URL, and sign the final URL.
Python
import json
import requests
from sign_sdk import sign
def build_request(operation):
base = "https://openapi.meitu.com/design/enterprise/ai_product/"
options = {
"materials": ("GET", base + "materials?count=20", None),
"sod": ("POST", base + "sod", {
"url": "https://example.com/product.png"
}),
"create": ("POST", base + "create", {
"url": "https://example.com/product.png",
"material_id": "1759202837959028",
"scene_type": 1,
"product_category": "product",
"canvas_w": 512,
"canvas_h": 512
}),
"query": ("GET", base + "query?id=46062184", None),
}
method, url, payload = options[operation]
body = "" if payload is None else json.dumps(payload, ensure_ascii=True)
headers = {sign.HeaderHost: "openapi.meitu.com"}
if method == "POST":
headers["Content-Type"] = "application/json"
if operation == "sod":
headers["X-Aigcp-Way"] = "handler"
signer = sign.Signer("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY")
return signer.sign(url, method, headers, body)
def main():
operation = "materials" # materials, sod, create, query
request = build_request(operation)
with requests.Session() as session:
response = session.send(
request, timeout=(10, 60), verify=True, allow_redirects=False
)
print("Status:", response.status_code)
print("Response:", response.text)
if __name__ == "__main__":
main()
Go
package main
import (
"fmt"
"io"
"net/http"
"time"
"github.com/mtlab/api/signer"
)
func main() {
operation := "materials" // materials, sod, create, query
base := "https://openapi.meitu.com/design/enterprise/ai_product/"
var method, url, body string
headers := make(http.Header)
headers.Set(signer.HeaderHost, "openapi.meitu.com")
switch operation {
case "materials":
method, url = http.MethodGet, base+"materials?count=20"
case "sod":
method, url = http.MethodPost, base+"sod"
body = `{"url":"https://example.com/product.png"}`
headers.Set("X-Aigcp-Way", "handler")
case "create":
method, url = http.MethodPost, base+"create"
body = `{"url":"https://example.com/product.png","material_id":"1759202837959028","scene_type":1,"product_category":"product","canvas_w":512,"canvas_h":512}`
case "query":
method, url = http.MethodGet, base+"query?id=46062184"
default:
fmt.Println("Unknown operation:", operation)
return
}
if method == http.MethodPost {
headers.Set("Content-Type", "application/json")
}
signObj := signer.NewSigner("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY")
req, err := signObj.Sign(url, method, headers, body)
if err != nil {
fmt.Println("Sign error:", err)
return
}
client := &http.Client{
Timeout: 60 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Request error:", err)
return
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Read error:", err)
return
}
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Response:", string(data))
}
PHP
<?php
require_once __DIR__ . '/signer.php';
$operation = 'materials'; // materials, sod, create, query
$base = 'https://openapi.meitu.com/design/enterprise/ai_product/';
$options = [
'materials' => ['GET', $base . 'materials?count=20', null],
'sod' => ['POST', $base . 'sod', [
'url' => 'https://example.com/product.png'
]],
'create' => ['POST', $base . 'create', [
'url' => 'https://example.com/product.png',
'material_id' => '1759202837959028',
'scene_type' => 1,
'product_category' => 'product',
'canvas_w' => 512,
'canvas_h' => 512
]],
'query' => ['GET', $base . 'query?id=46062184', null],
];
[$method, $url, $payload] = $options[$operation];
$body = $payload === null ? '' : json_encode(
$payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
$headers = ['Host' => 'openapi.meitu.com'];
if ($method === 'POST') {
$headers['Content-Type'] = 'application/json';
}
if ($operation === 'sod') {
$headers['X-Aigcp-Way'] = 'handler';
}
$signer = new Signer('YOUR_ACCESS_KEY', 'YOUR_SECRET_KEY');
$curl = $signer->sign($url, $method, $headers, $body);
if ($method === 'GET') {
curl_setopt($curl, CURLOPT_HTTPGET, true);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 60);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
$response = curl_exec($curl);
if ($response === false) {
echo 'Error: ' . curl_error($curl) . PHP_EOL;
} else {
echo 'Status: ' . curl_getinfo($curl, CURLINFO_HTTP_CODE) . PHP_EOL;
echo 'Response: ' . $response . PHP_EOL;
}
curl_close($curl);
Java
import com.meitu.openai.common.Signer;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
String operation = "materials"; // materials, sod, create, query
String base = "https://openapi.meitu.com/design/enterprise/ai_product/";
String method;
String url;
String body = "";
Map<String, String> headers = new HashMap<>();
headers.put(Signer.HeaderHost, "openapi.meitu.com");
switch (operation) {
case "materials":
method = "GET";
url = base + "materials?count=20";
break;
case "sod":
method = "POST";
url = base + "sod";
body = "{\"url\":\"https://example.com/product.png\"}";
headers.put("X-Aigcp-Way", "handler");
break;
case "create":
method = "POST";
url = base + "create";
body = "{\"url\":\"https://example.com/product.png\",\"material_id\":\"1759202837959028\",\"scene_type\":1,\"product_category\":\"product\",\"canvas_w\":512,\"canvas_h\":512}";
break;
case "query":
method = "GET";
url = base + "query?id=46062184";
break;
default:
throw new IllegalArgumentException("Unknown operation: " + operation);
}
if ("POST".equals(method)) {
headers.put("Content-Type", "application/json");
}
Signer signer = new Signer("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY");
Map<String, String> signedHeaders = signer.sign(url, method, headers, body);
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
try {
connection.setRequestMethod(method);
connection.setConnectTimeout(10000);
connection.setReadTimeout(60000);
connection.setInstanceFollowRedirects(false);
for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
if ("POST".equals(method)) {
connection.setDoOutput(true);
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
connection.setFixedLengthStreamingMode(bytes.length);
try (OutputStream output = connection.getOutputStream()) {
output.write(bytes);
}
}
int status = connection.getResponseCode();
System.out.println("Status: " + status);
InputStream responseStream = status >= 400
? connection.getErrorStream() : connection.getInputStream();
if (responseStream != null) {
try (InputStream input = responseStream;
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int length;
while ((length = input.read(buffer)) != -1) {
output.write(buffer, 0, length);
}
System.out.println("Response: " + output.toString("UTF-8"));
}
}
} finally {
connection.disconnect();
}
}
}