图片编辑 API 接入文档

版本:V1 | 更新日期:2026-10-28

图片编辑(image-edit)用于修改已有图片的内容,或基于底图及参考图创作新图片。支持人像编辑、商品背景调整、内容替换和参考图创作等场景。调用时至少提供一张底图,并填写编辑要求。

本文提供线上 API 的鉴权、请求参数、结果查询,以及 Python、Go、PHP、Java 接入示例。示例中的媒体 URL、凭证和任务 ID 均需替换为实际值。

目录

  1. 接入准备
  2. 提交任务
  3. 返回结果
  4. 查询任务
  5. 多语言接入示例
  6. 请求示例
  7. 相关文档

接入准备

获取 AK 和 SK

  1. 登录 MeituHub 控制台,进入“账户总览”页面。

  2. 在“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 字符串,具体见下文示例。

字段类型必填默认值说明
taskstring是固定值能力标识,固定为 image-edit。
task_typestring是固定值固定为 formula。
init_imagesobject[]是无输入图片列表,至少提供一张编辑底图;结构见下文。
paramsstring是无包含 parameter 对象的 JSON 字符串,业务字段如下。
params.parameter.promptstring是无编辑要求。描述要修改的内容及需要保留的细节;涉及文字添加或替换时,目标文字保持用户原文。
params.parameter.modelstring否auto编辑模型。可选:auto(根据场景自动选择)、gummy_pro(人像编辑)、praline_pro(高质量通用编辑)、praline_lite(轻量降级通道)、mint_edit(兜底编辑通道)。
params.parameter.ratiostring否auto输出宽高比。可选:auto、1:1、2:3、3:2、3:4、4:3、4:5、5:4、9:16、16:9、21:9。
params.parameter.sizestring否未指定固定档位输出分辨率。可选:1K、2K、4K;不需要时省略。
params.parameter.output_formatstring否jpeg结果图片格式。可选:jpeg、png。
sync_timeoutinteger否本文示例为 30同步等待时间,单位秒。超时后保存任务 ID 并继续查询。

image-edit 为调用时使用的能力标识。通过该标识提交任务时,使用本文列出的网关字段和业务参数。

媒体输入

init_images 中的每个元素使用以下结构:

{
  "url": "https://example.com/input.jpg",
  "profile": {
    "version": "v1",
    "media_profiles": {
      "media_data_type": "url"
    }
  }
}
字段类型必填说明
urlstring是服务端可直接访问的图片 URL。
profile.versionstring是固定为 v1。
profile.media_profiles.media_data_typestring是固定为 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 等字段。

字段类型说明
codeinteger,可选网关业务码,0 表示请求处理正常;非零时结合错误信息处理。
error_codeinteger,可选网关错误码,0 表示无对应网关错误。
messagestring,可选网关提示或错误信息。
request_id / trace_idstring,可选请求追踪标识,排查问题时保留。
dataobject,可选任务对象;直接返回任务对象时无该包装。

下表中的字段相对于任务对象描述:

字段类型说明
statusinteger任务状态,见任务状态表。
task_idstring任务 ID,用于后续查询。
progressnumber,可选任务进度,通常为 0–1。
resultobject / null任务结果;未完成或失败时可能为空。
result.idstring,可选结果关联的任务 ID。
result.media_info_listobject[],可选输出图片列表。
result.media_info_list[].media_datastring输出图片 URL。
result.media_info_list[].media_profilesobject,可选媒体描述;URL 结果的 media_data_type 为 url。
result.media_info_list[].media_extraobject / null,可选媒体附加信息。
result.parametersJSON 值,可选能力相关的附加业务数据。
result.code / result.error_codeinteger,可选结果中的业务错误码,非零时检查错误信息。
result.msg / result.error_msgstring,可选结果中的提示或错误信息。

任务状态

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_idQuerystring是提交任务后返回的任务 ID。

使用与提交账号一致的 AK/SK,对包含 task_id 的完整 URL 和 GET 方法签名,请求体为空字符串。查询返回的任务字段与状态含义见“返回结果”。

建议逐步延长查询间隔,例如 2 秒、5 秒、10 秒,并设置业务等待上限。到达等待上限后保留任务 ID,可稍后继续查询。任务信息和媒体链接可能过期,请及时查询和保存结果。

若提交请求发生网络超时且未获得任务 ID,请先核实任务是否已创建,避免直接重复提交。

多语言接入示例

四种语言示例共用 request.json:从“请求示例”复制完整请求体,替换媒体 URL 和其他占位值后保存为该文件。

SDK 准备

语言官方接入文档依赖
PythonPython SDKsign_sdk、requests
GoGo SDKgithub.com/mtlab/api/signer
PHPPHP SDK官方 signer.php、cURL 扩展,PHP 7.3+
JavaJava 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
}

相关文档