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.Http 4.3.4

Download and integration

  1. Download and extract the C# SDK and demo.
  2. Copy signer/Signer.cs into the application project.
  3. 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:

ParameterDescription
keyAccess Key (AK)
secretSecret Key (SK)
urlFinal request URL, including the complete path and query
methodHTTP method, such as HttpMethod.Post or HttpMethod.Get
headersHeaders covered by the signature; set Host correctly and set Content-Type for JSON requests
bodyFinal 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:

ConstantHeader
Signer.HeaderHostHost
Signer.HeaderContentTypeContent-Type
Signer.HeaderContentSha256X-Sdk-Content-Sha256
Signer.HeaderXDateX-Sdk-Date
Signer.HeaderAuthorizationAuthorization

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

The 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 Host equals the URL's Authority.
  • Finish constructing the method, URL, query, headers, and payload before signing. Do not change them after signing.
  • The current SDK uses UTF-8 StringContent and sends Content-Type: application/json; the payload must be the final JSON string.
  • For every request or retry, create new headers, call Sign again, and send a new HttpRequestMessage.
  • Keep the server clock synchronized so that the SDK-generated X-Sdk-Date remains valid.
  • Set X-Sdk-Content-Sha256: UNSIGNED-PAYLOAD before 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

SymptomCheck
Authentication failure or signature mismatchConfirm the AK/SK and check whether the URL, method, headers, or body changed after signing
Host mismatchConfirm that Signer.HeaderHost uses endpoint.Authority from the final URL
Time errorSynchronize the server clock and confirm that an old request or signature was not reused
POST payload verification failsBuild the final JSON only once and pass the same body to Sign
GET signature failsAdd the query before signing and pass "" when there is no payload
Content-Type mismatchUse application/json and a UTF-8 JSON payload
UNSIGNED-PAYLOAD failsConfirm that the target API supports the mode and check the exact header name and value
Timeout or non-2xx responseCheck the HTTPS endpoint, network, timeout, HTTP status, and response body