Kotlin Signing SDK Integration Guide

Overview

The Kotlin signing SDK generates the following authentication headers for Meitu Open Platform API requests:

  • X-Sdk-Date: the UTC request time.
  • Authorization: a signature generated from the AK/SK pair with SDK-HMAC-SHA256.

The SDK supports HTTP methods such as GET and POST and provides both a synchronous HTTP interface and a coroutine-based asynchronous call. Store the SK only on a trusted server; never embed it in Android or another client application.

Requirements

  • JDK 8 or later.
  • Kotlin 1.5 or later.
  • kotlinx-coroutines-core is required when using MTHttpClient.kt or MTDemo.kt.
  • The SDK download is a Kotlin source package. Copy its source into a server-side Kotlin/JVM project.

Download and integration

Download Kotlin SDK 1.0.0 and the demo

The extracted package contains:

AIGCP-API-kotlin-sdk-1.0.0/
├── MTSigner.kt
├── MTHttpClient.kt
├── MTDemo.kt
└── README.md

Copy MTSigner.kt and MTHttpClient.kt to this directory in your project:

src/main/kotlin/com/meitu/signer/

Add the coroutine dependency to the Gradle project. The following version can be used with a Kotlin 1.9 project; an existing project can use the compatible version managed centrally by that project:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
}

Import these classes in application code:

import com.meitu.signer.MTHttpClient
import com.meitu.signer.MTSigner
import com.meitu.signer.SignatureInfo

API reference

Create a signer

val signer = MTSigner(accessKey, secretKey)
ParameterDescription
accessKeyThe application's API Key (AK)
secretKeyThe application's Secret Key (SK)

Sign a request

fun sign(
    url: String,
    method: String,
    headers: Map<String, String>,
    body: String = ""
): SignatureInfo
ParameterDescription
urlComplete request URL, including the path and query parameters
methodUppercase HTTP method, such as GET or POST
headersHeaders to sign; must include Host
bodyExact request body to send; GET normally uses an empty string

Return value:

data class SignatureInfo(
    val url: String,
    val method: String,
    val headers: Map<String, String>,
    val body: String?
)

headers contains the generated X-Sdk-Date and Authorization. Send the URL, method, headers, and body from the returned object.

HTTP calls

Synchronous call:

val client = MTHttpClient.createDefault()
val response = client.execute(signedInfo)

Coroutine call:

val client = MTHttpClient.createDefault()
val response = with(MTHttpClient.Companion) {
    client.executeAsync(signedInfo)
}

Response type:

data class Response(
    val code: Int,
    val headers: Map<String, List<String>>,
    val body: String?
)

Skip the body digest check

By default, the SHA-256 digest of the body participates in signing. If the target API permits UNSIGNED-PAYLOAD, set this header before signing:

headers[MTSigner.HEADER_CONTENT_SHA256] = "UNSIGNED-PAYLOAD"

Complete POST example

This example calls the production task-submission endpoint:

https://openapi.meitu.com/api/v1/sdk/sync/push

The body contains five top-level fields: task, task_type, init_images, params, and sync_timeout. params is a JSON string. Replace the example task, business parameters, image URL, and timeout according to the target capability's API documentation.

package example

import com.meitu.signer.MTSigner
import com.meitu.signer.MTHttpClient

suspend fun main() {
    val signer = MTSigner(
        requireEnv("MEITU_OPENAPI_AK"),
        requireEnv("MEITU_OPENAPI_SK")
    )
    val url = "https://openapi.meitu.com/api/v1/sdk/sync/push"
    val body = """
        {
          "task": "/v1/Text_Chart_High_Definition/472492",
          "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
        }
    """.trimIndent()
    val headers = mapOf(
        MTSigner.HEADER_HOST to "openapi.meitu.com",
        "Content-Type" to "application/json; charset=UTF-8"
    )

    val signedInfo = signer.sign(url, "POST", headers, body)
    val client = MTHttpClient.createDefault()
    val response = with(MTHttpClient.Companion) {
        client.executeAsync(signedInfo)
    }

    println("HTTP ${response.code}")
    println(response.body.orEmpty())
}

private fun requireEnv(name: String): String {
    return requireNotNull(System.getenv(name)) {
        "Missing environment variable: $name"
    }
}

The example passes the returned signedInfo directly to the HTTP client so that the sent content matches the signed content.

GET example

This example queries task status. Add the function to the same file as requireEnv from the POST example:

suspend fun queryTask(taskId: String) {
    val signer = MTSigner(
        requireEnv("MEITU_OPENAPI_AK"),
        requireEnv("MEITU_OPENAPI_SK")
    )
    val url = "https://openapi.meitu.com/api/v1/sdk/status?task_id=$taskId"
    val headers = mapOf(
        MTSigner.HEADER_HOST to "openapi.meitu.com",
        "Accept" to "application/json"
    )

    val signedInfo = signer.sign(url, "GET", headers)
    val client = MTHttpClient.createDefault()
    val response = with(MTHttpClient.Companion) {
        client.executeAsync(signedInfo)
    }

    println("HTTP ${response.code}")
    println(response.body.orEmpty())
}

If the task ID contains special characters, URL-encode it before building and signing the URL.

Notes

  • Set Host to openapi.meitu.com without a scheme or path.
  • Do not modify the URL, HTTP method, query parameters, signed headers, or body after signing.
  • Send the same POST body used for signing as UTF-8 bytes.
  • params must be a JSON string, not a nested JSON object.
  • Follow the target capability's API documentation for task, task_type, image fields, and sync_timeout.
  • Let the SDK generate X-Sdk-Date by default, and keep the calling server's system time accurate.
  • The bundled sender writes the body with the JVM default charset. If the body contains non-ASCII characters, ensure that the JVM default charset is UTF-8.
  • The bundled sender uses blocking HTTP. Call it from an I/O thread or Dispatchers.IO and configure timeouts on the production sender.

Common errors

SymptomResolution
HTTP 401 or signature validation failureCheck the AK/SK, server time, and Host, and confirm that the URL, headers, and body were not modified after signing
HTTP 400 or invalid business parametersConfirm that the body is valid JSON, params is serialized as a string, and business fields follow the target capability's documentation
Unresolved kotlinx.coroutines symbolsAdd a kotlinx-coroutines-core version compatible with the project's Kotlin version
GET signature failureEncode query parameters before signing and do not change their order or encoding afterward
Signature failure with non-ASCII body contentConfirm that signing and sending use exactly the same UTF-8 body bytes
Request timeout or no responseConfigure connection and read timeouts, and check the network, DNS, and endpoint URL