职场风写真 API 接入文档

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

职场风写真(meituhub_workflow_9486_v1)将输入照片转化为高知职场风写真。提交一张人物照片,可通过“其他要求”补充服装等要求。

本文提供线上 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.prompt_1 表示 params 字符串解析后的字段路径。将 prompt_1 放在 JSON 对象的顶层,再将该对象序列化为 params 字符串。

字段类型必填默认值说明
taskstring是固定值工作流标识,固定为 meituhub_workflow_9486_v1。
task_typestring是固定值固定为 cozeflow。
init_imagesobject[]是无输入图片列表,传入一张人物照片,放在 init_images[0]。
init_images[0].urlstring是无输入图。填写服务端可直接访问的图片 URL;请替换能力页的示例图片。
paramsstring是无工作流参数的 JSON 字符串;不填写其他要求时传 "{}"。
params.prompt_1string否能力页预填:黑色高领短袖其他要求,自由文本,无枚举限制。如需沿用预填要求,请显式传入该值。
sync_timeoutinteger否本文示例为 30同步等待时间,单位秒。超时后保存任务 ID 并继续查询。

媒体输入

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_1 字段。序列化前的对象示例:

{
  "prompt_1": "黑色高领短袖"
}

请求体中的 params 字段示例:

{
  "params": "{\"prompt_1\":\"黑色高领短袖\"}"
}

params 的外层类型必须是字符串。prompt_1 是可选的自由文本;不填写其他要求时,可将 params 设为 "{}"。能力页预填值与客户端实际提交值可能不同,如需使用预填要求,请显式传入。

返回结果

返回字段

网关响应可能包含 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[],可选输出图片列表;当前工作流配置的输出为 output_1,对应首张结果图。
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 为 JSON 字符串。

使用能力页预填要求

显式传入“黑色高领短袖”。将以下完整请求保存为 request.json,替换输入图片 URL 后即可用于上面的多语言示例:

{
  "task": "meituhub_workflow_9486_v1",
  "task_type": "cozeflow",
  "init_images": [
    {
      "url": "https://example.com/input.jpg",
      "profile": {
        "version": "v1",
        "media_profiles": {
          "media_data_type": "url"
        }
      }
    }
  ],
  "params": "{\"prompt_1\":\"黑色高领短袖\"}",
  "sync_timeout": 30
}

自定义其他要求

按需要修改 prompt_1,例如指定衬衫与外套:

{
  "task": "meituhub_workflow_9486_v1",
  "task_type": "cozeflow",
  "init_images": [
    {
      "url": "https://example.com/input.jpg",
      "profile": {
        "version": "v1",
        "media_profiles": {
          "media_data_type": "url"
        }
      }
    }
  ],
  "params": "{\"prompt_1\":\"白色衬衫搭配黑色西装外套\"}",
  "sync_timeout": 30
}

不填写其他要求

省略可选的 prompt_1,保留字符串类型的 params:

{
  "task": "meituhub_workflow_9486_v1",
  "task_type": "cozeflow",
  "init_images": [
    {
      "url": "https://example.com/input.jpg",
      "profile": {
        "version": "v1",
        "media_profiles": {
          "media_data_type": "url"
        }
      }
    }
  ],
  "params": "{}",
  "sync_timeout": 30
}

相关文档