图片编辑 API 接入文档
版本:V1 | 更新日期:2026-10-28
图片编辑(image-edit)用于修改已有图片的内容,或基于底图及参考图创作新图片。支持人像编辑、商品背景调整、内容替换和参考图创作等场景。调用时至少提供一张底图,并填写编辑要求。
本文提供线上 API 的鉴权、请求参数、结果查询,以及 Python、Go、PHP、Java 接入示例。示例中的媒体 URL、凭证和任务 ID 均需替换为实际值。
目录
接入准备
获取 AK 和 SK
-
登录 MeituHub 控制台,进入“账户总览”页面。
-
在“API 密钥”区域,分别点击以下字段旁的“复制”按钮,获取 AK 和 SK:
MEITU_OPENAPI_ACCESS_KEY:Access Key(AK)。MEITU_OPENAPI_SECRET_KEY:Secret Key(SK)。
SK 仅用于服务端签名,请勿放入浏览器代码、移动端、公开仓库或日志。
请求签名
提交和查询请求均需使用 AK/SK 签名。使用官方签名 SDK 对完整 URL、HTTP 方法、请求头和请求体进行签名,并发送 SDK 生成的请求;签名后请勿修改请求体。
签名规则见 开放平台接口签名。
提交任务
POST https://openapi.meitu.com/api/v1/sdk/sync/push
Content-Type: application/json请求参数
下表中的 params.parameter.* 表示 params 字符串解析后的业务字段路径。发起请求时,将这些字段放入 parameter 对象,再将包含该对象的完整 JSON 序列化为 params 字符串,具体见下文示例。
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| task | string | 是 | 固定值 | 能力标识,固定为 image-edit。 |
| task_type | string | 是 | 固定值 | 固定为 formula。 |
| init_images | object[] | 是 | 无 | 输入图片列表,至少提供一张编辑底图;结构见下文。 |
| params | string | 是 | 无 | 包含 parameter 对象的 JSON 字符串,业务字段如下。 |
| params.parameter.prompt | string | 是 | 无 | 编辑要求。描述要修改的内容及需要保留的细节;涉及文字添加或替换时,目标文字保持用户原文。 |
| params.parameter.model | string | 否 | auto | 编辑模型。可选:auto(根据场景自动选择)、gummy_pro(人像编辑)、praline_pro(高质量通用编辑)、praline_lite(轻量降级通道)、mint_edit(兜底编辑通道)。 |
| params.parameter.ratio | string | 否 | auto | 输出宽高比。可选:auto、1:1、2:3、3:2、3:4、4:3、4:5、5:4、9:16、16:9、21:9。 |
| params.parameter.size | string | 否 | 未指定固定档位 | 输出分辨率。可选:1K、2K、4K;不需要时省略。 |
| params.parameter.output_format | string | 否 | jpeg | 结果图片格式。可选:jpeg、png。 |
| sync_timeout | integer | 否 | 本文示例为 30 | 同步等待时间,单位秒。超时后保存任务 ID 并继续查询。 |
image-edit 为调用时使用的能力标识。通过该标识提交任务时,使用本文列出的网关字段和业务参数。
媒体输入
init_images 中的每个元素使用以下结构:
{
"url": "https://example.com/input.jpg",
"profile": {
"version": "v1",
"media_profiles": {
"media_data_type": "url"
}
}
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| url | string | 是 | 服务端可直接访问的图片 URL。 |
| profile.version | string | 是 | 固定为 v1。 |
| profile.media_profiles.media_data_type | string | 是 | 固定为 url。 |
单图编辑时,将底图放在 init_images[0]。多图编辑或参考图创作时,保持底图和参考图顺序,并在 prompt 中说明各图用途;图片数量受所选模型的实际支持范围限制。下文示例均使用一张底图。
能力参数
将编辑要求及可选设置放入 parameter 对象,再将整个对象序列化为 params 字符串。序列化前的对象示例:
{
"parameter": {
"prompt": "将背景替换为纯白色,保留主体外观、颜色和细节。",
"model": "auto",
"ratio": "auto",
"output_format": "jpeg"
}
}请求体中的 params 字段示例:
{
"params": "{\"parameter\":{\"prompt\":\"将背景替换为纯白色,保留主体外观、颜色和细节。\",\"model\":\"auto\",\"ratio\":\"auto\",\"output_format\":\"jpeg\"}}"
}params 的外层类型必须是字符串。prompt 为必填字段;可选参数不需要时可省略。size 未配置固定默认档位,省略时由实际执行通道处理。所有枚举值区分大小写。
返回结果
返回字段
网关响应可能包含 code、error_code、message、data 包装。包含包装时,先检查外层业务码,再读取 data 中的任务对象;SDK 若直接返回任务对象,则从顶层读取 status、task_id、result 等字段。
| 字段 | 类型 | 说明 |
|---|---|---|
| code | integer,可选 | 网关业务码,0 表示请求处理正常;非零时结合错误信息处理。 |
| error_code | integer,可选 | 网关错误码,0 表示无对应网关错误。 |
| message | string,可选 | 网关提示或错误信息。 |
| request_id / trace_id | string,可选 | 请求追踪标识,排查问题时保留。 |
| data | object,可选 | 任务对象;直接返回任务对象时无该包装。 |
下表中的字段相对于任务对象描述:
| 字段 | 类型 | 说明 |
|---|---|---|
| status | integer | 任务状态,见任务状态表。 |
| task_id | string | 任务 ID,用于后续查询。 |
| progress | number,可选 | 任务进度,通常为 0–1。 |
| result | object / null | 任务结果;未完成或失败时可能为空。 |
| result.id | string,可选 | 结果关联的任务 ID。 |
| result.media_info_list | object[],可选 | 输出图片列表。 |
| result.media_info_list[].media_data | string | 输出图片 URL。 |
| result.media_info_list[].media_profiles | object,可选 | 媒体描述;URL 结果的 media_data_type 为 url。 |
| result.media_info_list[].media_extra | object / null,可选 | 媒体附加信息。 |
| result.parameters | JSON 值,可选 | 能力相关的附加业务数据。 |
| result.code / result.error_code | integer,可选 | 结果中的业务错误码,非零时检查错误信息。 |
| result.msg / result.error_msg | string,可选 | 结果中的提示或错误信息。 |
任务状态
| status | 含义 | 处理方式 |
|---|---|---|
| -1 | 未找到任务 | 检查任务 ID、调用账号和任务有效期。 |
| 0 | 已创建 | 保存任务 ID,继续查询。 |
| 1 | 执行中 | 继续查询。 |
| 2 | 执行失败 | 根据网关和结果中的错误信息处理。 |
| 9 | 同步等待超时 | 保存任务 ID,继续查询。 |
| 10 | 执行成功 | 读取 result.media_info_list。 |
HTTP 200 仅表示 HTTP 请求已完成。请结合外层业务码、任务 status 和结果错误信息判断任务是否成功,不要仅以 progress=1 或存在 task_id 作为成功依据。
成功示例
以下为含网关包装的字段结构示例,任务 ID 和媒体地址为占位值:
{
"code": 0,
"error_code": 0,
"message": "success",
"data": {
"progress": 1,
"result": {
"id": "example-task-id",
"media_info_list": [
{
"media_data": "https://example.com/result.png",
"media_extra": {},
"media_profiles": {
"media_data_type": "url"
}
}
],
"parameters": {}
},
"status": 10,
"task_id": "example-task-id"
}
}含 data 包装时,首张结果图片地址为 data.result.media_info_list[0].media_data;直接返回任务对象时,为 result.media_info_list[0].media_data。请按返回列表处理全部媒体,并及时下载保存结果文件。
同步等待超时示例
{
"code": 0,
"error_code": 0,
"message": "success",
"data": {
"progress": 0,
"result": {
"id": "example-task-id"
},
"status": 9,
"task_id": "example-task-id"
}
}status=9 不代表任务执行失败。使用返回的 task_id 查询后续结果。
失败示例
{
"code": 0,
"error_code": 0,
"message": "success",
"data": {
"progress": 0,
"result": null,
"status": 2,
"task_id": "example-task-id"
}
}外层业务码为 0 时,任务仍可能以 status=2 执行失败。鉴权失败、限流或参数错误时,也可能直接返回非零业务码、非 2xx HTTP 状态,且不包含任务 ID。请保留错误响应和请求追踪标识;通用错误码见 API 错误码。
查询任务
GET https://openapi.meitu.com/api/v1/sdk/status?task_id=example-task-id| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| task_id | Query | string | 是 | 提交任务后返回的任务 ID。 |
使用与提交账号一致的 AK/SK,对包含 task_id 的完整 URL 和 GET 方法签名,请求体为空字符串。查询返回的任务字段与状态含义见“返回结果”。
建议逐步延长查询间隔,例如 2 秒、5 秒、10 秒,并设置业务等待上限。到达等待上限后保留任务 ID,可稍后继续查询。任务信息和媒体链接可能过期,请及时查询和保存结果。
若提交请求发生网络超时且未获得任务 ID,请先核实任务是否已创建,避免直接重复提交。
多语言接入示例
四种语言示例共用 request.json:从“请求示例”复制完整请求体,替换媒体 URL 和其他占位值后保存为该文件。
SDK 准备
| 语言 | 官方接入文档 | 依赖 |
|---|---|---|
| Python | Python SDK | sign_sdk、requests |
| Go | Go SDK | github.com/mtlab/api/signer |
| PHP | PHP SDK | 官方 signer.php、cURL 扩展,PHP 7.3+ |
| Java | Java SDK | 官方签名 SDK,JDK 11+ |
Python 签名包可通过 SDK 下载地址 获取。解压后将 sign_sdk 加入导入路径,并安装 requests:
python -m pip install requests运行配置
提交任务:
export MEITU_OPENAPI_ACTION='push'
export MEITU_REQUEST_FILE='request.json'查询任务时,改为以下配置:
export MEITU_OPENAPI_ACTION='status'
export MEITU_TASK_ID='example-task-id'每次运行仅执行所选操作,并打印完整 HTTP 响应。示例不会自动轮询或重试提交;获取响应后,按“返回结果”中的业务码和任务状态处理。
Python
保存为 client.py,运行 python client.py:
import json
import os
from pathlib import Path
from urllib.parse import urlencode
import requests
from sign_sdk import sign
HOST = "openapi.meitu.com"
ACTION = os.environ.get("MEITU_OPENAPI_ACTION", "push")
ak = os.environ["MEITU_OPENAPI_ACCESS_KEY"]
sk = os.environ["MEITU_OPENAPI_SECRET_KEY"]
if not ak or not sk:
raise ValueError("Missing AK/SK")
if ACTION == "push":
payload = json.loads(Path(os.environ.get("MEITU_REQUEST_FILE", "request.json")).read_text(encoding="utf-8"))
if not isinstance(payload.get("params"), str):
raise ValueError("params must be a JSON string")
json.loads(payload["params"])
method = "POST"
url = f"https://{HOST}/api/v1/sdk/sync/push"
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
elif ACTION == "status":
task_id = os.environ["MEITU_TASK_ID"]
if not task_id:
raise ValueError("Missing task_id")
method = "GET"
url = f"https://{HOST}/api/v1/sdk/status?" + urlencode({"task_id": task_id})
body = ""
else:
raise ValueError("MEITU_OPENAPI_ACTION must be push or status")
headers = {"Content-Type": "application/json", sign.HeaderHost: HOST}
signed_request = sign.Signer(ak, sk).sign(url, method, headers, body)
with requests.Session() as session:
response = session.send(signed_request, timeout=(10, 100), verify=True, allow_redirects=False)
print("HTTP", response.status_code)
print(response.text)
response.raise_for_status()Go
保存为 main.go,在已引入签名 SDK 的 Go 模块中运行 go run main.go:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
"github.com/mtlab/api/signer"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
ak, sk := os.Getenv("MEITU_OPENAPI_ACCESS_KEY"), os.Getenv("MEITU_OPENAPI_SECRET_KEY")
if ak == "" || sk == "" {
return fmt.Errorf("missing AK/SK")
}
method, endpoint, body := http.MethodPost, "https://openapi.meitu.com/api/v1/sdk/sync/push", ""
switch os.Getenv("MEITU_OPENAPI_ACTION") {
case "", "push":
filename := os.Getenv("MEITU_REQUEST_FILE")
if filename == "" {
filename = "request.json"
}
raw, err := os.ReadFile(filename)
if err != nil {
return err
}
var payload map[string]interface{}
if err = json.Unmarshal(raw, &payload); err != nil {
return err
}
params, ok := payload["params"].(string)
if !ok || !json.Valid([]byte(params)) {
return fmt.Errorf("params must be a JSON string")
}
body = string(raw)
case "status":
taskID := os.Getenv("MEITU_TASK_ID")
if taskID == "" {
return fmt.Errorf("missing task_id")
}
method = http.MethodGet
endpoint = "https://openapi.meitu.com/api/v1/sdk/status?" + url.Values{"task_id": {taskID}}.Encode()
default:
return fmt.Errorf("MEITU_OPENAPI_ACTION must be push or status")
}
headers := make(http.Header)
headers.Set("Content-Type", "application/json")
headers.Set(signer.HeaderHost, "openapi.meitu.com")
req, err := signer.NewSigner(ak, sk).Sign(endpoint, method, headers, body)
if err != nil {
return err
}
client := &http.Client{Timeout: 100 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
raw, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Println("HTTP", resp.StatusCode)
fmt.Println(string(raw))
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("HTTP request failed")
}
return nil
}PHP
将官方 signer.php 放在同一目录,保存为 client.php,运行 php client.php:
<?php
require_once __DIR__ . '/signer.php';
$ak = getenv('MEITU_OPENAPI_ACCESS_KEY');
$sk = getenv('MEITU_OPENAPI_SECRET_KEY');
if (!$ak || !$sk) { throw new RuntimeException('Missing AK/SK'); }
$action = getenv('MEITU_OPENAPI_ACTION') ?: 'push';
$body = '';
if ($action === 'push') {
$file = getenv('MEITU_REQUEST_FILE') ?: 'request.json';
$body = file_get_contents($file);
if ($body === false) { throw new RuntimeException('Cannot read request.json'); }
$payload = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if (!isset($payload['params']) || !is_string($payload['params'])) { throw new RuntimeException('params must be a JSON string'); }
json_decode($payload['params'], true, 512, JSON_THROW_ON_ERROR);
$method = 'POST';
$url = 'https://openapi.meitu.com/api/v1/sdk/sync/push';
} elseif ($action === 'status') {
$taskId = getenv('MEITU_TASK_ID');
if (!$taskId) { throw new RuntimeException('Missing task_id'); }
$method = 'GET';
$url = 'https://openapi.meitu.com/api/v1/sdk/status?' . http_build_query(['task_id' => $taskId], '', '&', PHP_QUERY_RFC3986);
} else {
throw new RuntimeException('MEITU_OPENAPI_ACTION must be push or status');
}
$headers = ['Content-Type' => 'application/json', 'Host' => 'openapi.meitu.com'];
$curl = (new Signer($ak, $sk))->sign($url, $method, $headers, $body);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 100,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$response = curl_exec($curl);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException($error);
}
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
echo 'HTTP ' . $status . PHP_EOL . $response . PHP_EOL;
if ($status < 200 || $status >= 300) { throw new RuntimeException('HTTP request failed'); }Java
将官方 SDK 加入 classpath,保存为 Main.java,使用 JDK 11+ 编译运行。request.json 使用 UTF-8 编码:
import com.meitu.openai.common.Signer;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
public class Main {
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) throw new IllegalArgumentException("Missing " + name);
return value;
}
public static void main(String[] args) throws Exception {
String ak = requiredEnv("MEITU_OPENAPI_ACCESS_KEY");
String sk = requiredEnv("MEITU_OPENAPI_SECRET_KEY");
String action = System.getenv().getOrDefault("MEITU_OPENAPI_ACTION", "push");
String method;
String url;
String body = "";
if ("push".equals(action)) {
String filename = System.getenv().getOrDefault("MEITU_REQUEST_FILE", "request.json");
body = Files.readString(Path.of(filename), StandardCharsets.UTF_8);
method = "POST";
url = "https://openapi.meitu.com/api/v1/sdk/sync/push";
} else if ("status".equals(action)) {
method = "GET";
url = "https://openapi.meitu.com/api/v1/sdk/status?task_id="
+ URLEncoder.encode(requiredEnv("MEITU_TASK_ID"), StandardCharsets.UTF_8);
} else {
throw new IllegalArgumentException("MEITU_OPENAPI_ACTION must be push or status");
}
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put(Signer.HeaderHost, "openapi.meitu.com");
Map<String, String> signedHeaders = new Signer(ak, sk).sign(url, method, headers, body);
HttpURLConnection connection = (HttpURLConnection) URI.create(url).toURL().openConnection();
try {
connection.setRequestMethod(method);
connection.setConnectTimeout(10000);
connection.setReadTimeout(100000);
connection.setInstanceFollowRedirects(false);
for (Map.Entry<String, String> header : signedHeaders.entrySet()) connection.setRequestProperty(header.getKey(), header.getValue());
if ("POST".equals(method)) {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
connection.setDoOutput(true);
connection.setFixedLengthStreamingMode(bytes.length);
try (OutputStream output = connection.getOutputStream()) { output.write(bytes); }
}
int status = connection.getResponseCode();
System.out.println("HTTP " + status);
InputStream stream = status >= 400 ? connection.getErrorStream() : connection.getInputStream();
if (stream != null) {
try (InputStream input = stream) { System.out.println(new String(input.readAllBytes(), StandardCharsets.UTF_8)); }
}
if (status < 200 || status >= 300) throw new IllegalStateException("HTTP request failed");
} finally {
connection.disconnect();
}
}
}请求示例
业务字段的类型、必填项、默认值和完整枚举见请求参数。以下示例展示如何将业务字段序列化为 params 字符串。
size 使用大写的 1K、2K、4K;output_format 使用小写的 jpeg、png。宽高比和分辨率为输出设置,实际结果以所选模型的支持范围为准。
最小请求
仅填写底图和编辑要求,其余业务参数使用默认处理。保存为 request.json 后可直接用于上面的多语言示例;请先替换示例图片 URL:
{
"task": "image-edit",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"version": "v1",
"media_profiles": {
"media_data_type": "url"
}
}
}
],
"params": "{\"parameter\":{\"prompt\":\"将背景替换为纯白色,保留主体外观、颜色和细节。\"}}",
"sync_timeout": 30
}指定模型和输出设置
使用 praline_pro 执行商品图片编辑,并显式传入正方形比例、2K 分辨率和 PNG 格式:
{
"task": "image-edit",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"version": "v1",
"media_profiles": {
"media_data_type": "url"
}
}
}
],
"params": "{\"parameter\":{\"prompt\":\"将背景替换为浅灰色摄影棚场景,保留商品外观、颜色和标识,增加自然阴影。\",\"model\":\"praline_pro\",\"ratio\":\"1:1\",\"size\":\"2K\",\"output_format\":\"png\"}}",
"sync_timeout": 30
}该请求的 params 解码后为:
{
"parameter": {
"prompt": "将背景替换为浅灰色摄影棚场景,保留商品外观、颜色和标识,增加自然阴影。",
"model": "praline_pro",
"ratio": "1:1",
"size": "2K",
"output_format": "png"
}
}人像编辑
选择 gummy_pro 并填写发型调整要求。该示例省略其他可选设置:
{
"task": "image-edit",
"task_type": "formula",
"init_images": [
{
"url": "https://example.com/input.jpg",
"profile": {
"version": "v1",
"media_profiles": {
"media_data_type": "url"
}
}
}
],
"params": "{\"parameter\":{\"prompt\":\"将人物发型调整为齐肩短发,保持面部特征、表情、服装和背景。\",\"model\":\"gummy_pro\"}}",
"sync_timeout": 30
}