Java Signing SDK Integration Guide
Overview
The Java signing SDK uses an AK/SK pair to sign Meitu Open Platform API requests. After Signer.sign is called, the request headers contain:
X-Sdk-Date: the UTC signing time.Authorization: theSDK-HMAC-SHA256signature result.
The SDK signs requests. HTTP transmission can use the JDK standard library or an HTTP client already used by the application. The examples in this guide use only the Java standard library and can be applied directly in server-side projects.
Requirements
- JDK 8 or later.
- A valid Access Key (AK) and Secret Key (SK).
- HTTPS connectivity from the service runtime to
openapi.meitu.com. - The request endpoint,
task, image fields, and business parameters from the target capability documentation.
Download and integration
The main files in the extracted package are:
AIGCP-API-java-sdk-1.0.3/
├── pom.xml
└── src/main/java/com/meitu/openai/common/Signer.javaUse the 1.0.3 source and pom.xml in the download. Run the following command in the SDK root:
mvn clean installAdd this dependency to the application's pom.xml after the build completes:
<dependency>
<groupId>com.meitu.openai</groupId>
<artifactId>openai-common-signer</artifactId>
<version>1.0.3</version>
</dependency>The SDK has no third-party runtime dependencies. Alternatively, place Signer.java in the same package path in the application. Use this import in calling code:
import com.meitu.openai.common.Signer;API reference
Create a signer
public Signer(String key, String secret)| Parameter | Type | Description |
|---|---|---|
key | String | Access Key (AK) |
secret | String | Secret Key (SK) |
Example:
Signer signer = new Signer(accessKey, secretKey);Sign a request
public Map<String, String> sign(
String url,
String method,
Map<String, String> headers,
String body
) throws URISyntaxException| Parameter | Description |
|---|---|
url | Final request URL, including the path and query parameters |
method | Final HTTP method, such as GET or POST |
headers | Mutable header map containing at least a Host that matches the URL |
body | Final request body; pass null when there is no body |
sign updates the supplied headers map directly, adds X-Sdk-Date and Authorization, and returns that same map:
Map<String, String> signedHeaders = signer.sign(url, method, headers, body);Common header constants:
| Constant | Header | Description |
|---|---|---|
Signer.HeaderHost | Host | Request host, set to openapi.meitu.com |
Signer.HeaderXDate | X-Sdk-Date | Optional; the SDK generates the current UTC time when omitted |
Signer.HeaderContentSha256 | X-Sdk-Content-Sha256 | May be set to UNSIGNED-PAYLOAD when supported by the target API |
Signer.HeaderAuthorization | Authorization | Generated by the SDK |
By default, the SDK calculates SHA-256 over body and includes it in the signature. A null body is treated as an empty string. If the target API explicitly supports excluding the body from signing, set this before calling sign:
headers.put(Signer.HeaderContentSha256, "UNSIGNED-PAYLOAD");Complete POST example
This example calls the production synchronous task endpoint at https://openapi.meitu.com/api/v1/sdk/sync/push. The request body contains the five top-level fields used by the synchronous endpoint: task, task_type, init_images, params, and sync_timeout.
The task, image URL, image profile, and params are placeholders. Replace them with values from the target capability documentation. Keep params as a JSON string, and follow the target API documentation for the supported sync_timeout range.
import com.meitu.openai.common.Signer;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public final class Main {
public static void main(String[] args) throws Exception {
String accessKey = requireEnv("MEITU_OPENAPI_AK");
String secretKey = requireEnv("MEITU_OPENAPI_SK");
String url = "https://openapi.meitu.com/api/v1/sdk/sync/push";
String method = "POST";
String body = "{"
+ "\"task\":\"/v1/replace-with-product-task\","
+ "\"task_type\":\"formula\","
+ "\"init_images\":[{"
+ "\"url\":\"https://example.com/input.jpg\","
+ "\"profile\":{"
+ "\"media_profiles\":{\"media_data_type\":\"url\"},"
+ "\"version\":\"v1\""
+ "}"
+ "}],"
+ "\"params\":\"{\\\"parameter\\\":{\\\"rsp_media_type\\\":\\\"url\\\"}}\","
+ "\"sync_timeout\":30"
+ "}";
URI uri = new URI(url);
Map<String, String> headers = new HashMap<>();
headers.put(Signer.HeaderHost, uri.getHost());
headers.put("Content-Type", "application/json; charset=UTF-8");
Signer signer = new Signer(accessKey, secretKey);
Map<String, String> signedHeaders = signer.sign(url, method, headers, body);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setRequestMethod(method);
connection.setConnectTimeout(10_000);
connection.setReadTimeout(60_000);
connection.setDoOutput(true);
for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
connection.setFixedLengthStreamingMode(bodyBytes.length);
try (OutputStream output = connection.getOutputStream()) {
output.write(bodyBytes);
}
int status = connection.getResponseCode();
InputStream responseStream = status >= 400
? connection.getErrorStream()
: connection.getInputStream();
String responseBody = readUtf8(responseStream);
connection.disconnect();
System.out.println("HTTP " + status);
System.out.println(responseBody);
}
private static String requireEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new IllegalStateException("Missing environment variable: " + name);
}
return value;
}
private static String readUtf8(InputStream input) throws Exception {
if (input == null) {
return "";
}
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(input, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append('\n');
}
}
return result.toString();
}
}The example reads credentials from environment variables:
export MEITU_OPENAPI_AK='<ACCESS_KEY>'
export MEITU_OPENAPI_SK='<SECRET_KEY>'GET example
For a GET request, place the query parameters in the final URL and pass a null body. This example calls the task status endpoint. Replace task_id with the actual task ID.
import com.meitu.openai.common.Signer;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public final class GetExample {
public static void main(String[] args) throws Exception {
String accessKey = requireEnv("MEITU_OPENAPI_AK");
String secretKey = requireEnv("MEITU_OPENAPI_SK");
String url = "https://openapi.meitu.com/api/v1/sdk/status?task_id=replace-with-task-id";
String method = "GET";
String body = null;
URI uri = new URI(url);
Map<String, String> headers = new HashMap<>();
headers.put(Signer.HeaderHost, uri.getHost());
Signer signer = new Signer(accessKey, secretKey);
Map<String, String> signedHeaders = signer.sign(url, method, headers, body);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setRequestMethod(method);
connection.setConnectTimeout(10_000);
connection.setReadTimeout(60_000);
for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
int status = connection.getResponseCode();
InputStream responseStream = status >= 400
? connection.getErrorStream()
: connection.getInputStream();
String responseBody = readUtf8(responseStream);
connection.disconnect();
System.out.println("HTTP " + status);
System.out.println(responseBody);
}
private static String requireEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new IllegalStateException("Missing environment variable: " + name);
}
return value;
}
private static String readUtf8(InputStream input) throws Exception {
if (input == null) {
return "";
}
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(input, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append('\n');
}
}
return result.toString();
}
}Notes
- Sign the final
url,method, headers, and body values, then keep those values unchanged for transmission. - Set
Hostto the domain only and keep it consistent with the URL host. Production requests use HTTPS. - Send POST bodies as UTF-8, exactly matching the string supplied to
sign. - Put query parameters in the URL before signing. Re-sign after any parameter change.
- Normally let the SDK generate
X-Sdk-Date, and keep the runtime system clock accurate. - Call
signagain for every request and retry to generate a new timestamp and signature. - Store the SK only on a trusted server, not in client applications or source code.
Common errors
| Symptom | Resolution |
|---|---|
package com.meitu.openai.common does not exist | Confirm that mvn clean install completed, the application dependency is version 1.0.3, or Signer.java is in the correct package path |
URISyntaxException | Confirm that the URL is a complete HTTPS URL and that special characters in the path and query parameters are encoded correctly |
| Signature verification fails | Verify the AK/SK, HTTP method, complete URL, Host, signed headers, transmitted body, and system time |
| Request-body parameter error | Confirm that the body contains the required fields, params is a JSON string, and the image fields and task come from the target capability documentation |
| HTTP 401 or 403 | Confirm that the credentials are valid and that the application has access to the target capability |
| Request timeout | Check network connectivity, image URL accessibility, and API processing time, then adjust connection and read timeouts as required |