Swift Signing SDK Integration Guide
Overview
The Swift signing SDK generates the following authentication data for Meitu Open Platform API requests:
X-Sdk-Date: the UTC request time.Authorization: an HMAC-SHA256 signature generated from the AK/SK and request content.
The SDK returns a URLRequest containing the HTTP method, headers, and body, ready to send with URLSession.
Store the SK only on the backend; never embed it in an Apple client distributed to users.
Requirements
- An Apple-platform Swift project.
- The complete examples require Swift 5.5 or later and macOS 12 or later.
FoundationandCommonCrypto; SDK 1.0.0 does not use CryptoKit.- An accurate backend system clock; signature timestamps use UTC.
Download and integration
The archive contains:
AIGCP-API-swift-sdk-1.0.0/
├── MTSigner.swift
└── MTDemo.swiftAdd MTSigner.swift to the backend Swift target and confirm that the file belongs to the correct Target Membership. MTDemo.swift is a standalone demo entry point and is not required when integrating into an existing project.
API reference
The SDK exposes the following constants and methods:
public let kBasicDateFormat = "yyyyMMdd'T'HHmmss'Z'"
public let kAlgorithm = "SDK-HMAC-SHA256"
public let kHeaderXDate = "X-Sdk-Date"
public let kHeaderHost = "Host"
public let kHeaderAuthorization = "Authorization"
public let kHeaderContentSha256 = "X-Sdk-Content-Sha256"
public class MTSigner {
public init(key: String, secret: String)
public func signRequest(
url: URL,
method: String,
headers: [String: String],
body: String
) throws -> URLRequest
}| Parameter | Description |
|---|---|
key | The application's AK |
secret | The SK paired with the AK |
url | The final request URL, including encoded and sorted query parameters |
method | The uppercase HTTP method required by the API, such as GET or POST |
headers | Headers included in the signature; Host is required |
body | The exact UTF-8 string to send; pass an empty string for GET |
| Return value | A URLRequest containing X-Sdk-Date, Authorization, and the request body |
If headers already contains X-Sdk-Date, the SDK uses that value; otherwise it generates the UTC time automatically. Every input header is signed and must remain unchanged afterward.
Complete POST example
The following example calls the production synchronous task endpoint:
https://openapi.meitu.com/api/v1/sdk/sync/push
Set MEITU_OPENAPI_AK, MEITU_OPENAPI_SK, MEITU_TASK, and MEITU_IMAGE_URL before running it. Follow the target capability's API documentation for the values and structures of MEITU_TASK, images, params, task_type, and sync_timeout.
The request body contains exactly five top-level fields: task, task_type, init_images, params, and sync_timeout. params must be a JSON string.
import Foundation
enum IntegrationError: LocalizedError {
case missingEnvironment(String)
case invalidURL(String)
case invalidUTF8Body
case invalidHTTPResponse
var errorDescription: String? {
switch self {
case .missingEnvironment(let name):
return "Missing environment variable: \(name)"
case .invalidURL(let value):
return "Invalid URL: \(value)"
case .invalidUTF8Body:
return "Failed to create a stable UTF-8 request body"
case .invalidHTTPResponse:
return "The server did not return an HTTP response"
}
}
}
func requiredEnvironment(_ name: String) throws -> String {
guard let value = ProcessInfo.processInfo.environment[name], !value.isEmpty else {
throw IntegrationError.missingEnvironment(name)
}
return value
}
@main
struct PostExample {
static func main() async {
do {
let accessKey = try requiredEnvironment("MEITU_OPENAPI_AK")
let secretKey = try requiredEnvironment("MEITU_OPENAPI_SK")
let taskName = try requiredEnvironment("MEITU_TASK")
let imageURL = try requiredEnvironment("MEITU_IMAGE_URL")
guard URL(string: imageURL) != nil else {
throw IntegrationError.invalidURL(imageURL)
}
let endpoint = "https://openapi.meitu.com/api/v1/sdk/sync/push"
guard let url = URL(string: endpoint) else {
throw IntegrationError.invalidURL(endpoint)
}
let paramsObject: [String: Any] = [
"parameter": ["rsp_media_type": "url"]
]
let paramsData = try JSONSerialization.data(withJSONObject: paramsObject)
guard let params = String(data: paramsData, encoding: .utf8) else {
throw IntegrationError.invalidUTF8Body
}
let payload: [String: Any] = [
"task": taskName,
"task_type": "formula",
"init_images": [[
"url": imageURL,
"profile": [
"media_profiles": ["media_data_type": "url"],
"version": "v1"
]
]],
"params": params,
"sync_timeout": 30
]
let bodyData = try JSONSerialization.data(withJSONObject: payload)
guard let body = String(data: bodyData, encoding: .utf8),
body.data(using: .utf8) == bodyData else {
throw IntegrationError.invalidUTF8Body
}
let headers: [String: String] = [
kHeaderHost: "openapi.meitu.com",
"Content-Type": "application/json; charset=utf-8",
"Accept": "application/json"
]
let signer = MTSigner(key: accessKey, secret: secretKey)
let request = try signer.signRequest(
url: url,
method: "POST",
headers: headers,
body: body
)
guard request.httpBody == bodyData else {
throw IntegrationError.invalidUTF8Body
}
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
let session = URLSession(configuration: configuration)
let (responseData, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw IntegrationError.invalidHTTPResponse
}
let responseBody = String(data: responseData, encoding: .utf8) ?? ""
print("HTTP \(httpResponse.statusCode)")
print(responseBody)
} catch {
print("Request failed: \(error.localizedDescription)")
}
}
}The code serializes the business payload once and sends the URLRequest returned by the signer directly. The same UTF-8 body Data is therefore used for signing and transmission.
GET example
GET requests use an empty body. Replace urlString with the final HTTPS URL of the target API. If it contains multiple query parameters, apply RFC 3986 encoding and sorting before signing.
import Foundation
enum GETExampleError: Error {
case invalidURL
case invalidHTTPResponse
}
func callSignedGET(
accessKey: String,
secretKey: String,
urlString: String
) async throws -> (statusCode: Int, body: Data) {
guard let url = URL(string: urlString),
url.scheme == "https",
url.host == "openapi.meitu.com" else {
throw GETExampleError.invalidURL
}
let headers: [String: String] = [
kHeaderHost: "openapi.meitu.com",
"Accept": "application/json"
]
let signer = MTSigner(key: accessKey, secret: secretKey)
let request = try signer.signRequest(
url: url,
method: "GET",
headers: headers,
body: ""
)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw GETExampleError.invalidHTTPResponse
}
return (httpResponse.statusCode, data)
}Notes
- Use HTTPS and set
Hosttoopenapi.meitu.com. - The method, URL, query, signed headers, and body must remain unchanged after signing. Send the request returned by
signRequestdirectly. - Serialize JSON only once. Do not change fields, whitespace, line endings, or character encoding after signing.
- Percent-encode and sort query parameters according to the signing specification before constructing the final URL.
X-Sdk-Datemust be UTCyyyyMMdd'T'HHmmss'Z'; keep the backend clock synchronized.- By default, the SDK calculates SHA-256 over the UTF-8 body. Only when the target API explicitly supports it, add the following before signing:
var headers: [String: String] = [
kHeaderHost: "openapi.meitu.com",
"Content-Type": "application/json; charset=utf-8",
kHeaderContentSha256: "UNSIGNED-PAYLOAD"
]- Keep the body of an AK/SK-authenticated request within 12 MB.
Common errors
| Symptom | Resolution |
|---|---|
no such module 'CommonCrypto' | Use an Apple-platform Xcode/Swift toolchain and confirm that MTSigner.swift belongs to the correct target |
401, 403, or signature verification failure | Check the AK/SK, backend time, HTTP method, Host, final URL, query, headers, and body for exact consistency |
| POST signature failure | Confirm JSON is serialized only once and send the URLRequest returned by the SDK directly |
| GET signature failure | Confirm query parameters are encoded and sorted and the URL is not changed after signing |
UNSIGNED-PAYLOAD has no effect | Confirm the target API supports it and use the exact header name and value |
| Request timeout | Set sync_timeout according to the target capability documentation and keep the URLSession timeout longer than the API processing time |
| Non-2xx response | Record the redacted status code and response body, then handle the business error code according to the target capability API documentation |