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: the SDK-HMAC-SHA256 signature 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

Download Java SDK 1.0.3

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.java

Use the 1.0.3 source and pom.xml in the download. Run the following command in the SDK root:

mvn clean install

Add 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)
ParameterTypeDescription
keyStringAccess Key (AK)
secretStringSecret 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
ParameterDescription
urlFinal request URL, including the path and query parameters
methodFinal HTTP method, such as GET or POST
headersMutable header map containing at least a Host that matches the URL
bodyFinal 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:

ConstantHeaderDescription
Signer.HeaderHostHostRequest host, set to openapi.meitu.com
Signer.HeaderXDateX-Sdk-DateOptional; the SDK generates the current UTC time when omitted
Signer.HeaderContentSha256X-Sdk-Content-Sha256May be set to UNSIGNED-PAYLOAD when supported by the target API
Signer.HeaderAuthorizationAuthorizationGenerated 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

  1. Sign the final url, method, headers, and body values, then keep those values unchanged for transmission.
  2. Set Host to the domain only and keep it consistent with the URL host. Production requests use HTTPS.
  3. Send POST bodies as UTF-8, exactly matching the string supplied to sign.
  4. Put query parameters in the URL before signing. Re-sign after any parameter change.
  5. Normally let the SDK generate X-Sdk-Date, and keep the runtime system clock accurate.
  6. Call sign again for every request and retry to generate a new timestamp and signature.
  7. Store the SK only on a trusted server, not in client applications or source code.

Common errors

SymptomResolution
package com.meitu.openai.common does not existConfirm that mvn clean install completed, the application dependency is version 1.0.3, or Signer.java is in the correct package path
URISyntaxExceptionConfirm that the URL is a complete HTTPS URL and that special characters in the path and query parameters are encoded correctly
Signature verification failsVerify the AK/SK, HTTP method, complete URL, Host, signed headers, transmitted body, and system time
Request-body parameter errorConfirm 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 403Confirm that the credentials are valid and that the application has access to the target capability
Request timeoutCheck network connectivity, image URL accessibility, and API processing time, then adjust connection and read timeouts as required