C# Signing SDK Integration Guide
Overview
The C# signature SDK authenticates Meitu Open Platform API requests with an Access Key (AK) and Secret Key (SK). It generates an SDK-HMAC-SHA256 signature from the request method, URL, query parameters, headers, and payload, then returns an HttpRequestMessage that can be sent with HttpClient.
The SDK signs and creates the request. Use the endpoint, HTTP method, and business fields specified by the target capability's API reference.
Requirements
- .NET 6.0
- Visual Studio 2022 or .NET 6 SDK
- Sample project dependency:
System.Net.Http4.3.4
Download and integration
- Download and extract the C# SDK and demo.
- Copy
signer/Signer.csinto the application project. - Import its namespace in the calling code:
using Signature;The sample project uses the following target framework and package dependency:
<TargetFramework>net6.0</TargetFramework>
<PackageReference Include="System.Net.Http" Version="4.3.4" />API reference
The SDK exposes these public APIs:
public Signer(string key, string secret);
public HttpRequestMessage Sign(
string url,
HttpMethod method,
Dictionary<string, string> headers,
string body);Parameters:
| Parameter | Description |
|---|---|
key | Access Key (AK) |
secret | Secret Key (SK) |
url | Final request URL, including the complete path and query |
method | HTTP method, such as HttpMethod.Post or HttpMethod.Get |
headers | Headers covered by the signature; set Host correctly and set Content-Type for JSON requests |
body | Final request payload; pass an empty string "" when there is no payload, never null |
The return value is an HttpRequestMessage that contains the signed headers and request content. Sign writes X-Sdk-Date with the current UTC time and the generated Authorization value into the supplied headers.
Common header constants:
| Constant | Header |
|---|---|
Signer.HeaderHost | Host |
Signer.HeaderContentType | Content-Type |
Signer.HeaderContentSha256 | X-Sdk-Content-Sha256 |
Signer.HeaderXDate | X-Sdk-Date |
Signer.HeaderAuthorization | Authorization |
Complete POST example
This example calls the production synchronous task endpoint:
https://openapi.meitu.com/api/v1/sdk/sync/push
The payload has the five top-level fields task, task_type, init_images, params, and sync_timeout. params must be a JSON string. The task, image, and parameter values only demonstrate the structure; replace them according to the target capability's API reference.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Signature;
namespace MainNamespace
{
class Program
{
private static readonly HttpClient Client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(60)
};
static async Task<int> Main()
{
var accessKey = Environment.GetEnvironmentVariable("AIGCP_ACCESS_KEY");
var secretKey = Environment.GetEnvironmentVariable("AIGCP_SECRET_KEY");
if (string.IsNullOrWhiteSpace(accessKey) ||
string.IsNullOrWhiteSpace(secretKey))
{
Console.Error.WriteLine(
"AIGCP_ACCESS_KEY and AIGCP_SECRET_KEY are required");
return 2;
}
var endpoint = new Uri("https://openapi.meitu.com/api/v1/sdk/sync/push");
var payload = new
{
task = "/v1/replace-with-product-task",
task_type = "formula",
init_images = new[]
{
new
{
url = "https://example.com/input.jpg",
profile = new
{
media_profiles = new
{
media_data_type = "url"
},
version = "v1"
}
}
},
@params = JsonSerializer.Serialize(new
{
parameter = new
{
rsp_media_type = "url"
}
}),
sync_timeout = 30
};
var body = JsonSerializer.Serialize(payload);
var headers = new Dictionary<string, string>
{
{ Signer.HeaderContentType, "application/json" },
{ Signer.HeaderHost, endpoint.Authority }
};
var signer = new Signer(accessKey, secretKey);
try
{
using (var request = signer.Sign(
endpoint.AbsoluteUri, HttpMethod.Post, headers, body))
using (var response = await Client.SendAsync(request))
{
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"status={(int)response.StatusCode} {response.StatusCode}");
Console.WriteLine(responseBody);
return response.IsSuccessStatusCode ? 0 : 1;
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"request failed: {ex.Message}");
return 1;
}
}
}
}Run the example:
export AIGCP_ACCESS_KEY='<your-access-key>'
export AIGCP_SECRET_KEY='<your-secret-key>'
dotnet run --project csharp.csprojThe code serializes the final body once and passes that same string to Sign. The SDK hashes the UTF-8 bytes of that string and creates the transmitted StringContent from it, keeping the signed and transmitted payloads identical. Use the target capability's API reference for the actual values of task, task_type, image data, params, and sync_timeout: 30.
GET example
/api/v1/sdk/sync/push is a POST endpoint and must not be changed to GET. The following code applies only to an endpoint explicitly documented as GET. Replace the path and query with the actual values from that API reference.
var endpoint = new Uri("https://openapi.meitu.com/replace-with-get-api-path?page=1");
var headers = new Dictionary<string, string>
{
{ Signer.HeaderContentType, "application/json" },
{ Signer.HeaderHost, endpoint.Authority }
};
var signer = new Signer(accessKey, secretKey);
using (var request = signer.Sign(endpoint.AbsoluteUri, HttpMethod.Get, headers, ""))
using (var response = await Client.SendAsync(request))
{
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"status={(int)response.StatusCode} {response.StatusCode}");
Console.WriteLine(responseBody);
}Add all GET query parameters to the final URL before calling Sign. Pass an empty string "" when there is no payload.
Notes
- In production, use the final HTTPS URL from the target API reference and make sure
Hostequals the URL'sAuthority. - Finish constructing the method, URL, query, headers, and payload before signing. Do not change them after signing.
- The current SDK uses UTF-8
StringContentand sendsContent-Type: application/json; the payload must be the final JSON string. - For every request or retry, create new headers, call
Signagain, and send a newHttpRequestMessage. - Keep the server clock synchronized so that the SDK-generated
X-Sdk-Dateremains valid. - Set
X-Sdk-Content-Sha256: UNSIGNED-PAYLOADbefore signing only when the target API explicitly supports it; otherwise, use the default payload digest. - Store the SK only on a trusted server. Never include it in desktop, mobile, or browser code.
Set UNSIGNED-PAYLOAD as follows:
headers[Signer.HeaderContentSha256] = "UNSIGNED-PAYLOAD";Common errors
| Symptom | Check |
|---|---|
| Authentication failure or signature mismatch | Confirm the AK/SK and check whether the URL, method, headers, or body changed after signing |
| Host mismatch | Confirm that Signer.HeaderHost uses endpoint.Authority from the final URL |
| Time error | Synchronize the server clock and confirm that an old request or signature was not reused |
| POST payload verification fails | Build the final JSON only once and pass the same body to Sign |
| GET signature fails | Add the query before signing and pass "" when there is no payload |
| Content-Type mismatch | Use application/json and a UTF-8 JSON payload |
UNSIGNED-PAYLOAD fails | Confirm that the target API supports the mode and check the exact header name and value |
| Timeout or non-2xx response | Check the HTTPS endpoint, network, timeout, HTTP status, and response body |