AI 商品图:场景列表、抠图与任务生成
描述
获取商品图推荐场景,对商品进行抠图处理,并提交场景图生成任务。任务提交后,使用业务任务 ID 查询生成进度和结果。
接口概览
| 接口 | 方法 | 地址 |
|---|---|---|
| 推荐场景列表 | GET | https://openapi.meitu.com/design/enterprise/ai_product/materials |
| 商品抠图 | POST | https://openapi.meitu.com/design/enterprise/ai_product/sod |
| 任务投递 | POST | https://openapi.meitu.com/design/enterprise/ai_product/create |
| 任务查询 | GET | https://openapi.meitu.com/design/enterprise/ai_product/query |
权限
使用已开通该能力的 AK/SK,通过签名 SDK 签名。详见开放平台接口签名。
GET 参数放在 URL 查询串中,并将完整 URL 用于签名;POST 使用 Content-Type: application/json。商品抠图调用示例额外包含 X-Aigcp-Way: handler。
调用顺序
- 获取推荐场景列表,选择场景
id。 - 如需调整商品区域,调用抠图接口获取结果及
x、y、width、height;无需自行抠图时,投递请求可省略mask_url,由服务自动处理。 - 提交生成任务,保存响应中的
data.id。 - 调用本篇专用查询接口,传入该
id。当process_status=2时获取结果,3表示失败。
本篇任务查询使用 data.id,不是 task_ids[] 或 scene_result_images[].task_id,也不使用通用任务接口的状态值。
一、获取推荐场景列表
调用方式
GET https://openapi.meitu.com/design/enterprise/ai_product/materials
请求参数
| 参数名 | 类型 | 参数说明 |
|---|---|---|
| cursor | string | 分页游标,首次请求可省略,默认空;后续请求使用上次响应的 data.cursor。 |
| count | integer | 本次希望返回的条数,示例为 20;原文未说明默认值和上限。 |
示例 URL:
https://openapi.meitu.com/design/enterprise/ai_product/materials?count=20返回值说明
code=0 表示成功;reqid 为排查问题使用的请求 ID。场景列表在 data.data,分页游标在 data.cursor。游标是接口返回的不透明值,不自行拼接。
data.data 元素
| 字段 | 类型 | 说明 |
|---|---|---|
| id | integer (64-bit) | 场景素材 ID。投递时转为字符串传给 material_id。 |
| default_name | string | 场景名称。 |
| thumbnail | object | 缩略图,包含 url、width、height。 |
| type | string | 类型,例如 ai_product_photo_scene。 |
| ai_product_type / is_color_scene / can_image_video | integer | 产品类型、色彩场景标识、图片与视频支持标识;can_image_video=0 表示不支持。 |
| alg_params / extra / light_mode | string | 预留算法参数、扩展信息和光照模式,可为空。 |
| max_ratio / min_ratio | number | 最大、最小比例,原文默认 0。 |
| prompt_id | string | 关联提示词 ID。 |
| preview_v2 | object | 预览配置。 |
| portrait_style_category_ids / v2_category_ids / v2_style_category_ids | array | 人像风格、分类和风格分类 ID 列表。 |
返回值示例
HTTP 200。以下响应由原文示例整理,ID、名称、地址等为演示数据,不保证素材当前可用;调用时使用场景列表实际返回的 ID。
{
"reqid": "example-request-id",
"code": 0,
"data": {
"cursor": "cs-1-1",
"data": [
{
"ai_product_type": 0,
"alg_params": "",
"can_image_video": 0,
"default_name": "示例场景",
"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
]
}
]
}
}二、商品抠图
描述与版本
对商品图进行抠图,返回抠图地址及位置、尺寸信息。版本:1.0。
图片要求
支持 JPG、PNG。
调用方式
POST https://openapi.meitu.com/design/enterprise/ai_product/sod
请求头:Content-Type: application/json、X-Aigcp-Way: handler,以及 SDK 生成的签名头。
请求参数
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 是 | url | string | 商品图片 URL。 |
输入值示例
{
"url": "https://example.com/product.png"
}返回值说明
| 字段 | 类型 | 说明 |
|---|---|---|
| reqid | string | 请求 ID。 |
| code | integer | 0 表示成功。 |
| data.url | string | 抠图结果地址。 |
| data.x / data.y | integer | 商品区域起始坐标。 |
| data.width / data.height | integer | 商品区域宽高,单位为像素。 |
需要构造 box1 时,按 x,y,width,height 顺序组合,以下示例对应 "1,0,286,550"。
返回值示例
HTTP 200。
{
"reqid": "example-request-id",
"code": 0,
"data": {
"url": "https://example.com/cutout.png",
"height": 550,
"width": 286,
"x": 1,
"y": 0
}
}三、任务投递
描述与版本
提交 AI 商品场景图生成任务。版本:1.0。支持 JPG、PNG 输入图片。
调用方式
POST https://openapi.meitu.com/design/enterprise/ai_product/create
Content-Type: application/json
请求参数
所有字段直接放在请求体根级。
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 是 | url | string | 商品原图 URL。 |
| 是 | material_id | string | 场景素材 ID,从场景列表的 id 获取;多个 ID 用英文逗号分隔,整体仍为字符串。 |
| 否 | mask_url | string | 商品抠图 mask 地址,不传时自动抠图。 |
| 否 | box1 | string | 原始商品区域,格式 x,y,width,height,取自抠图返回坐标和宽高;不调整位置或大小时可省略。 |
| 否 | box2 | string | 商品在画布上的外框,格式 x,y,w,h;不传时自动居中,数值可包含小数。 |
| 否 | product_category | string | 英文商品类别,默认 product。 |
| 否 | callback_url | string | 接收任务状态的 POST 回调地址。 |
| 否 | scene_type | number | 1 推荐场景,需 material_id;2 自定义场景,需 prompt。默认 1。 |
| 条件必选 | prompt | string | 自定义场景描述,scene_type=2 时必填。 |
| 否 | canvas_w | number | 画布宽度,默认 512,范围 50–1024,步长 0.5。生成图宽度为画布宽度的两倍。 |
| 否 | canvas_h | number | 画布高度,默认 512,范围 50–1024,步长 0.5。生成图高度为画布高度的两倍。 |
| 否 | angle | number | 逆时针旋转角度,范围 0–360;box2 填旋转前的数据。 |
| 否 | flip | number | 0 不镜像,1 左右镜像,2 上下镜像,3 左右和上下镜像。 |
| 否 | refimage_url | string | 参考图 URL,仅 scene_type=2 时有效。 |
| 否 | prompt_vs_refimage | number | 文本与参考图的生图倾向,范围 -1.0–1.0,仅 scene_type=2 时有效。 |
| 否 | guidance_scale | number | 创意相关性参数,范围 5.0–15.0,仅 scene_type=2 时有效。 |
| 否 | steps | integer | 采样步数,范围 15–35,仅 scene_type=2 时有效。 |
| 否 | prompt_neg | string | 不希望生成的内容描述,仅 scene_type=2 时有效。 |
material_id 按原文保留为必选。自定义场景还需填写 prompt;原文没有明确允许自定义场景省略 material_id。
输入值示例
推荐场景生成,使用自动抠图。请将 material_id 替换为场景列表实际返回的 ID。
{
"url": "https://example.com/product.png",
"material_id": "1759202837959028",
"scene_type": 1,
"product_category": "product",
"canvas_w": 512,
"canvas_h": 512
}如需位置和尺寸调整,可在请求中补充 mask_url、box1、box2。其中 mask_url 必须是适用于该请求的 mask 地址,不能仅凭字段名把任意图片结果当作 mask。
返回值说明
code=0 表示任务投递成功,不表示图片已经生成。保存 data.id,通过下一节的查询接口获取最终结果。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | integer | 用于 /query?id=... 的业务任务 ID。 |
| process_status | integer | 1 处理中,2 已完成,3 失败。 |
| result_image_urls | string[] | 生成结果地址。投递时尚无结果,可能为空数组;完成后通过查询获取。 |
| result_images | object[] | 结果对象列表:id 为字符串,url 为地址,process_status=2 表示该图完成。 |
| scene_result_images | object[] | 按场景分组的结果:scene_id、urls、process_status、task_id。 |
| result_num | integer | 生成结果数量。 |
| task_ids | array | 内部任务列表,不是 /query 的查询 ID。 |
| materials | object / null | 场景素材信息。 |
| url / mask_url / refimage_url | string | 原图、mask 和参考图地址。 |
| box1 / box2 | string | 原始区域及调整后区域,格式 x,y,width,height。 |
| canvas_w / canvas_h | number | 画布宽高。 |
| angle / flip / scene_type | number | 旋转、镜像和场景类型,含义同请求参数。 |
| product_category / prompt / translate_prompt | string | 商品类别、提示词及翻译后的提示词。 |
| prompt_vs_refimage / guidance_scale / steps | number | 对应的生成参数。 |
| created_at / finished_at / updated_at | integer | 创建、完成、更新时间戳。 |
| client_id / uid / user_material_id / z_index | integer | 客户端、用户、自定义素材及图层顺序信息。 |
返回值示例
HTTP 200。以下为精简的处理中响应;完整响应可能包含上表中的其他字段。
{
"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"
}
}四、任务查询
调用方式
GET https://openapi.meitu.com/design/enterprise/ai_product/query
请求参数
| 是否必选 | 参数名 | 类型 | 参数说明 |
|---|---|---|---|
| 是 | id | integer | 投递接口返回的 data.id。 |
https://openapi.meitu.com/design/enterprise/ai_product/query?id=46062184替换为本次任务的真实 ID;不使用 task_ids 中的 ID。
返回值说明
响应结构与任务投递的任务详情一致:
process_status=1:处理中,稍后继续查询。process_status=2:已完成,读取result_image_urls。process_status=3:失败。
result_images 包含逐张图片的 ID、URL 和状态;scene_result_images 包含按场景分组的结果。不要因任务仍在处理中而重复投递。
返回值示例
HTTP 200,精简的完成响应:
{
"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"
}
}错误码与失败响应
失败时读取 code、message,并保留 reqid。原文失败示例为 HTTP 400。
| 错误码 | 说明 |
|---|---|
| 11001 | 参数错误,例如缺少 material_id 或查询 ID。 |
| 11002 | 处理失败,例如抠图失败。 |
以下为投递失败示例:
{
"reqid": "example-request-id",
"code": 11001,
"message": "缺少必要参数material_id"
}其他错误详见 API 错误码。
以上响应示例基于原文整理,不是本次实测返回。
SDK 调用示例
每种语言提供一个完整示例,修改 operation 选择 materials、sod、create 或 query,每次只调用一个接口。默认获取场景列表,不自动串联投递任务或轮询。
引入对应签名 SDK 后,替换 AK、SK、图片 URL、实际场景 ID 或查询 ID。请求体已内嵌,不依赖外部 JSON 文件。分页时将返回的 cursor 进行 URL 编码后加入场景列表 URL,再对最终 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();
}
}
}