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 withSDK-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-coreis required when usingMTHttpClient.ktorMTDemo.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.mdCopy 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.SignatureInfoAPI reference
Create a signer
val signer = MTSigner(accessKey, secretKey)| Parameter | Description |
|---|---|
accessKey | The application's API Key (AK) |
secretKey | The application's Secret Key (SK) |
Sign a request
fun sign(
url: String,
method: String,
headers: Map<String, String>,
body: String = ""
): SignatureInfo| Parameter | Description |
|---|---|
url | Complete request URL, including the path and query parameters |
method | Uppercase HTTP method, such as GET or POST |
headers | Headers to sign; must include Host |
body | Exact 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
Hosttoopenapi.meitu.comwithout 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.
paramsmust be a JSON string, not a nested JSON object.- Follow the target capability's API documentation for
task,task_type, image fields, andsync_timeout. - Let the SDK generate
X-Sdk-Dateby 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.IOand configure timeouts on the production sender.
Common errors
| Symptom | Resolution |
|---|---|
| HTTP 401 or signature validation failure | Check 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 parameters | Confirm that the body is valid JSON, params is serialized as a string, and business fields follow the target capability's documentation |
Unresolved kotlinx.coroutines symbols | Add a kotlinx-coroutines-core version compatible with the project's Kotlin version |
| GET signature failure | Encode query parameters before signing and do not change their order or encoding afterward |
| Signature failure with non-ASCII body content | Confirm that signing and sending use exactly the same UTF-8 body bytes |
| Request timeout or no response | Configure connection and read timeouts, and check the network, DNS, and endpoint URL |