Get User Info
Overview
After a user completes TapTap login (SDK login or QR code login), your server can use the Access Token returned by the login flow to call TapTap OpenAPI and retrieve the user's public information, including:
- openid: The user's unique identifier within a single application. For the same user, the openid never changes within one application, but differs across applications. Suitable as the user identifier inside your game.
- unionid: The user's unique identifier across all applications under the same developer account. For the same user, the unionid is identical in all games of one developer, and differs across developers. Suitable for sharing data between multiple games of the same developer.
- Name and avatar: The user's public profile.
Calling the API takes two steps:
- Use the
kidandmac_keyfrom the Access Token to generate a MAC Token as theAuthorizationrequest header; - Call the user info APIs with that header.
Prerequisite: Obtain an Access Token
The Access Token is the credential of the user's authorization. Where it comes from depends on the login method:
- SDK login: The client calls
GetCurrentTapAccountto obtain the current valid Access Token and sends it to your server. - QR code login: The response of the token polling endpoint is the Access Token.
The Access Token contains the following fields:
| Field | Type | Description |
|---|---|---|
kid | string | Identifier of this authorization credential; used as the value of MAC id="..." in the Authorization header after signing |
token_type | string | Credential type, e.g. mac |
mac_key | string | Key for HMAC-SHA1 signing |
mac_algorithm | string | Signing algorithm, e.g. hmac-sha-1 |
scopes | array | Scopes actually granted in this login, e.g. basic_info, public_profile; determines which APIs you can call, see APIs and scopes |
kid and mac_key change with every login authorization and are issued dynamically by the login flow. mac_key is NOT the fixed Server Secret shown in the developer console — never mix them up.
About validity:
- A single Access Token is valid for at most 30 days, but it may expire earlier, for example when the user deletes their account or revokes the authorization in the TapTap client.
- The SDK automatically refreshes the Access Token on every app launch.
- Do not cache the Access Token on your server or game client; fetch the latest value through the SDK whenever you need it, to avoid sending requests with a revoked credential.
MAC Token Signing
The MAC Token is the Authorization request header generated from the kid and mac_key of the Access Token using the HMAC-SHA1 algorithm. It authenticates your requests to TapTap OpenAPI.
Signing steps
-
Build the signing string from 6 non-empty parts, each ending with
\n, then append one more\n(so it ends with two consecutive\n):{timestamp}\n{nonce}\n{method}\n{uri}\n{host}\n{port}\n\ntimestamp: current timestamp in secondsnonce: a random stringmethod: HTTP method (e.g.GET)uri: request path including the query string (e.g./account/profile/v1?client_id=xxx)host: request domain (e.g.open.tapapis.cn)port: port number (443for HTTPS)
-
Sign the string with
mac_keyusing HMAC-SHA1 and Base64-encode the result to get themacvalue. -
Build the
Authorizationheader:MAC id="{kid}",ts="{timestamp}",nonce="{nonce}",mac="{mac}"
Verify your implementation
The most common signing mistake is a wrong signing string (especially the two trailing \n). Verify your implementation against the fixed inputs below first, and only switch to real values after the output matches.
Inputs (all demo data):
| Parameter | Sample value |
|---|---|
kid | demo_kid |
mac_key | demo_mac_key |
timestamp | 1618221750 |
nonce | adssd |
method | GET |
uri | /account/basic-info/v1?client_id=demo_client_id |
host | open.tapapis.cn |
port | 443 |
Signing string (\n is the newline character; note the two consecutive ones at the end):
1618221750\nadssd\nGET\n/account/basic-info/v1?client_id=demo_client_id\nopen.tapapis.cn\n443\n\n
Expected result:
mac = oYWjWxKzPBbeK2xKY7+ADJKAlmE=
Authorization: MAC id="demo_kid",ts="1618221750",nonce="adssd",mac="oYWjWxKzPBbeK2xKY7+ADJKAlmE="
You can also verify it from the command line:
printf '1618221750\nadssd\nGET\n/account/basic-info/v1?client_id=demo_client_id\nopen.tapapis.cn\n443\n\n' \
| openssl dgst -sha1 -hmac 'demo_mac_key' -binary | base64
# Output: oYWjWxKzPBbeK2xKY7+ADJKAlmE=
Sample signing code
Node.js Request Example
const http = require('http');
const https = require('https');
const crypto = require('crypto');
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
const client_id = "Replace with Client ID from the console";
const kid = "Replace with kid from the Access Token returned by the client SDK";
const macKey = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.cn/account/basic-info/v1
*/
const url = "https://open.tapapis.cn/account/profile/v1";
/**
* Main program logic
*/
// Step 1: Set request method and URL
const method = 'GET';
const requestUrl = url + '?client_id=' + client_id;
// Step 2: Generate timestamp and nonce
const ts = Math.floor(Date.now() / 1000).toString().padStart(10, '0');
const nonce = getRandomString(5);
// Step 3: Build signing string and generate signature
const parsedUrl = new URL(requestUrl);
const host = parsedUrl.hostname;
const uri = parsedUrl.pathname + parsedUrl.search;
const port = parsedUrl.port || (parsedUrl.protocol === 'https:' ? '443' : '80');
const signingString = buildSigningString(ts, nonce, method, uri, host, port);
const mac = sign(signingString, macKey);
// Step 4: Generate Authorization header
const authorization = `MAC id="${kid}",ts="${ts}",nonce="${nonce}",mac="${mac}"`;
console.log('Authorization: ' + authorization);
// Step 5: Execute HTTP request and output result
const client = parsedUrl.protocol === 'https:' ? https : http;
const req = client.request({
hostname: host,
port: port,
path: uri,
method: method,
headers: { 'Authorization': authorization }
}, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => { console.log(data); });
});
req.end();
/**
* Generate a random string
* @param {number} length - Length of the random string
* @returns {string} Randomly generated string (alphanumeric only)
*/
function getRandomString(length) {
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const bytes = crypto.randomBytes(length);
let result = '';
for (let i = 0; i < length; i++) {
result += chars[bytes[i] % chars.length];
}
return result;
}
/**
* Build the signing string
* @param {string} ts - Timestamp
* @param {string} nonce - Random string
* @param {string} method - HTTP method
* @param {string} uri - Request path (including query string)
* @param {string} host - Request domain
* @param {string} port - Port number
* @returns {string} The signing string
*/
function buildSigningString(ts, nonce, method, uri, host, port) {
return `${ts}\n${nonce}\n${method}\n${uri}\n${host}\n${port}\n\n`;
}
/**
* Generate signature using HMAC-SHA1
* @param {string} signingString - The signing string
* @param {string} key - MAC key
* @returns {string} Base64-encoded signature value
*/
function sign(signingString, key) {
const hmac = crypto.createHmac('sha1', key);
hmac.update(signingString);
return hmac.digest('base64');
}
Java Request Example
package com.taptap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Authorization {
public static void main(String[] args) throws IOException {
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
String client_id = "Replace with Client ID from the console";
String kid = "Replace with kid from the Access Token returned by the client SDK";
String mac_key = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.cn/account/basic-info/v1
*/
String url = "https://open.tapapis.cn/account/profile/v1";
/**
* Main program logic
*/
// Step 1: Set request method and URL
String method = "GET";
String request_url = url + "?client_id=" + client_id;
// Step 2: Generate timestamp and nonce
String ts = String.format(Locale.US, "%010d", System.currentTimeMillis() / 1000);
String nonce = getRandomString(5);
// Step 3: Build signing string and generate signature
URL parsedUrl = new URL(request_url);
String host = parsedUrl.getHost();
String uri = request_url.substring(request_url.lastIndexOf(host) + host.length());
String port = request_url.startsWith("https") ? "443" : "80";
String signingString = buildSigningString(ts, nonce, method, uri, host, port);
String mac = sign(signingString, mac_key);
// Step 4: Generate Authorization header
String authorization = String.format("MAC id=\"%s\",ts=\"%s\",nonce=\"%s\",mac=\"%s\"",
kid, ts, nonce, mac);
System.out.println("Authorization: " + authorization);
// Step 5: Execute HTTP request and output result
HttpURLConnection conn = (HttpURLConnection) new URL(request_url).openConnection();
conn.setRequestProperty("Authorization", authorization);
conn.setRequestMethod(method);
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuilder result = new StringBuilder();
while ((line = rd.readLine()) != null) {
result.append(line);
}
rd.close();
System.out.println(result.toString());
}
/**
* Generate a random string
* @param length Length of the random string
* @return Randomly generated string (alphanumeric only)
*/
private static String getRandomString(int length) {
String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
SecureRandom random = new SecureRandom();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}
/**
* Build the signing string
* @param ts Timestamp
* @param nonce Random string
* @param method HTTP method
* @param uri Request path (including query string)
* @param host Request domain
* @param port Port number
* @return The signing string
*/
private static String buildSigningString(String ts, String nonce, String method,
String uri, String host, String port) {
return ts + "\n" + nonce + "\n" + method + "\n" + uri + "\n" + host + "\n" + port + "\n\n";
}
/**
* Generate signature using HMAC-SHA1
* @param signingString The signing string
* @param key MAC key
* @return Base64-encoded signature value
*/
private static String sign(String signingString, String key) {
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(signingString.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalStateException(e);
}
}
}
PHP Request Example
<?php
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
$client_id = "Replace with Client ID from the console";
$kid = "Replace with kid from the Access Token returned by the client SDK";
$mac_key = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.cn/account/basic-info/v1
*/
$url = "https://open.tapapis.cn/account/profile/v1";
/**
* Main program logic
*/
// Step 1: Set $method and $request_url
$method = "GET";
$request_url = $url . "?client_id=" . urlencode($client_id);
// Step 2: Generate timestamp and nonce
$ts = time(); // Current timestamp in seconds
$nonce = randomString(5); // Random string, at least 5 characters
// Step 3: Build signing string and generate signature
$signing_string = createSigningString($request_url, $ts, $nonce, $method);
$mac = sign($signing_string, $mac_key);
// Step 4: Generate Authorization header
$auth = sprintf('MAC id="%s",ts="%s",nonce="%s",mac="%s"', $kid, $ts, $nonce, $mac);
echo "Authorization: " . $auth . PHP_EOL . PHP_EOL;
// Step 5: Execute HTTP request and output result
$headers = array("Authorization: " . $auth);
$response = executeCurlRequest($request_url, $headers, $method);
echo "HTTP Status Code: " . $response['http_code'] . PHP_EOL . PHP_EOL;
if (isset($response['error'])) {
echo "Error: " . $response['error'] . PHP_EOL . PHP_EOL;
}
if (isset($response['body'])) {
echo "Response Body: " . PHP_EOL . json_encode($response['body'], JSON_PRETTY_PRINT) . PHP_EOL;
}
/**
* Build the signing string
*
* @param string $request_url Request URL
* @param int $ts Timestamp
* @param string $nonce Random string
* @param string $method HTTP method
* @return string The signing string
*/
function createSigningString($request_url, $ts, $nonce, $method)
{
$parsed_url = parse_url($request_url);
$uri = $parsed_url['path'] . '?' . $parsed_url['query'];
$domain = $parsed_url['host'];
$port = 443; // Fixed port for HTTPS
return implode("\n", [
$ts,
$nonce,
$method,
$uri,
$domain,
$port,
""
]) . "\n";
}
/**
* Generate signature value
*
* @param string $signing_string The signing string
* @param string $mac_key MAC key
* @return string Generated signature value
* @example sign('abc', 'def') -> dYTuFEkwcs2NmuhQ4P8JBTgjD4w=
*/
function sign($signing_string, $mac_key)
{
return base64_encode(hash_hmac('sha1', $signing_string, $mac_key, true));
}
/**
* Execute cURL request and return result
*
* @param string $url Request URL
* @param array $headers Request headers
* @param string $method HTTP method
* @return array Array containing status code and response content or error information
*/
function executeCurlRequest($url, $headers, $method)
{
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
// Execute request and capture response
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
// Check for cURL errors
if (curl_errno($curl)) {
$error_message = curl_error($curl);
curl_close($curl);
return array(
'http_code' => 0,
'error' => "cURL error: " . $error_message,
'body' => json_decode($response, true)
);
}
// Close cURL handle
curl_close($curl);
// Parse response
$parsed_body = json_decode($response, true);
// Return result based on HTTP status code
if ($httpCode >= 200 && $httpCode < 300) {
return array(
'http_code' => $httpCode,
'body' => $parsed_body
);
} else {
return array(
'http_code' => $httpCode,
'error' => "HTTP request failed, status code: $httpCode",
'body' => $parsed_body
);
}
}
/**
* Generate a random string
*
* @param int $length Length of the random string
* @return string Randomly generated string
*/
function randomString($length = 5)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomIndex = mt_rand(0, $charactersLength - 1);
$randomString .= $characters[$randomIndex];
}
return $randomString;
}
Python3 Request Example
import base64
import hmac
import random
import string
import time
from hashlib import sha1
from urllib.parse import urlparse
from urllib.request import Request, urlopen
#
# Configuration: replace the following parameters with actual values before making authorization requests.
#
client_id = "Replace with Client ID from the console"
kid = "Replace with kid from the Access Token returned by the client SDK"
mac_key = "Replace with mac_key from the Access Token returned by the client SDK"
#
# Endpoint for getting current account detailed info.
# To get basic account info, replace with:
# https://open.tapapis.cn/account/basic-info/v1
#
url = "https://open.tapapis.cn/account/profile/v1"
#
# Main program logic
#
def main():
# Step 1: Set request method and URL
method = 'GET'
request_url = f'{url}?client_id={client_id}'
# Step 2: Generate timestamp and nonce
ts = str(int(time.time()))
nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
# Step 3: Build signing string and generate signature
parsed = urlparse(request_url)
signing_string = build_signing_string(
ts, nonce, method, parsed.path + '?' + parsed.query, parsed.hostname, '443')
mac = sign(signing_string, mac_key)
# Step 4: Generate Authorization header
authorization = f'MAC id="{kid}",ts="{ts}",nonce="{nonce}",mac="{mac}"'
print('Authorization:', authorization)
# Step 5: Execute HTTP request and output result
req = Request(request_url, headers={'Authorization': authorization})
with urlopen(req) as response:
print(response.read().decode('UTF-8'))
def build_signing_string(ts, nonce, method, uri, host, port):
"""Build the signing string
Args:
ts: Timestamp
nonce: Random string
method: HTTP method
uri: Request path (including query string)
host: Request domain
port: Port number
Returns:
The signing string
"""
return '\n'.join([ts, nonce, method, uri, host, port, '']) + '\n'
def sign(signing_string, key):
"""Generate signature using HMAC-SHA1
Args:
signing_string: The signing string
key: MAC key
Returns:
Base64-encoded signature value
"""
hmac_code = hmac.new(key.encode('UTF-8'), signing_string.encode('UTF-8'), sha1)
return base64.b64encode(hmac_code.digest()).decode('UTF-8')
if __name__ == '__main__':
main()
Go Request Example
package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
func main() {
/*
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
clientId := "Replace with Client ID from the console"
kid := "Replace with kid from the Access Token returned by the client SDK"
macKey := "Replace with mac_key from the Access Token returned by the client SDK"
reqHost := "open.tapapis.cn"
/*
* Endpoint for getting current account detailed info.
* To get basic account info, replace the path below with:
* /account/basic-info/v1
*/
reqURI := "/account/profile/v1?client_id=" + clientId
reqURL := "https://" + reqHost + reqURI
/*
* Main program logic
*/
// Step 1: Set request method and URL
method := "GET"
// Step 2: Generate timestamp and nonce
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
nonce := randomString(5)
// Step 3: Build signing string and generate signature
signingString := buildSigningString(timestamp, nonce, method, reqURI, reqHost, "443")
mac := hmacSha1(signingString, macKey)
// Step 4: Generate Authorization header
authorization := fmt.Sprintf(`MAC id="%s",ts="%s",nonce="%s",mac="%s"`, kid, timestamp, nonce, mac)
fmt.Println("Authorization:", authorization)
// Step 5: Execute HTTP request and output result
client := http.Client{}
req, err := http.NewRequest(method, reqURL, nil)
if err != nil {
fmt.Println(err.Error())
return
}
req.Header.Add("Authorization", authorization)
resp, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(string(respBody))
}
// buildSigningString builds the signing string
//
// Parameters:
// - ts: Timestamp
// - nonce: Random string
// - method: HTTP method
// - uri: Request path (including query string)
// - host: Request domain
// - port: Port number
func buildSigningString(ts, nonce, method, uri, host, port string) string {
return ts + "\n" + nonce + "\n" + method + "\n" + uri + "\n" + host + "\n" + port + "\n\n"
}
// hmacSha1 generates a signature using HMAC-SHA1, returns Base64-encoded signature value
//
// Parameters:
// - signingString: The signing string
// - key: MAC key
func hmacSha1(signingString, key string) string {
mac := hmac.New(sha1.New, []byte(key))
mac.Write([]byte(signingString))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// randomString generates a random string
//
// Parameters:
// - length: Length of the random string
func randomString(length int) string {
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, length)
rand.Read(b)
for i := range b {
b[i] = chars[b[i]%byte(len(chars))]
}
return string(b)
}
C# Request Example
using System.Collections;
using UnityEngine;
using System;
using System.Net.Http;
using System.Text;
public class TapLoginOAuth : MonoBehaviour
{
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
private string clientId = "Replace with Client ID from the console";
private string kid = "Replace with kid from the Access Token returned by the client SDK";
private string macKey = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.cn/account/basic-info/v1
*/
private string url = "https://open.tapapis.cn/account/profile/v1";
void Start()
{
StartCoroutine(SendRequest());
}
IEnumerator SendRequest()
{
/**
* Main program logic
*/
// Step 1: Set request method and URL
string method = "GET";
string requestUrl = url + "?client_id=" + clientId;
// Step 2: Generate timestamp and nonce
string ts = ((int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString();
string nonce = GenerateNonce(5);
// Step 3: Build signing string and generate signature
var parsedUrl = new Uri(requestUrl);
string host = parsedUrl.Host;
string uri = parsedUrl.PathAndQuery;
string signingString = BuildSigningString(ts, nonce, method, uri, host, "443");
string mac = Sign(signingString, macKey);
// Step 4: Generate Authorization header
string authorization = string.Format("MAC id=\"{0}\",ts=\"{1}\",nonce=\"{2}\",mac=\"{3}\"",
kid, ts, nonce, mac);
Debug.Log("Authorization: " + authorization);
// Step 5: Execute HTTP request and output result
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(requestUrl));
request.Headers.Add("Authorization", authorization);
var response = httpClient.SendAsync(request).Result;
var responseBody = response.Content.ReadAsStringAsync().Result;
Debug.Log("Response: " + responseBody);
}
yield return null;
}
/// <summary>
/// Build the signing string
/// </summary>
/// <param name="ts">Timestamp</param>
/// <param name="nonce">Random string</param>
/// <param name="method">HTTP method</param>
/// <param name="uri">Request path (including query string)</param>
/// <param name="host">Request domain</param>
/// <param name="port">Port number</param>
/// <returns>The signing string</returns>
string BuildSigningString(string ts, string nonce, string method, string uri, string host, string port)
{
string[] signArray = { ts, nonce, method, uri, host, port, "" };
return string.Join("\n", signArray) + "\n";
}
/// <summary>
/// Generate signature using HMAC-SHA1
/// </summary>
/// <param name="signingString">The signing string</param>
/// <param name="key">MAC key</param>
/// <returns>Base64-encoded signature value</returns>
string Sign(string signingString, string key)
{
using (var hmac = new System.Security.Cryptography.HMACSHA1(Encoding.UTF8.GetBytes(key)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signingString));
return Convert.ToBase64String(hash);
}
}
/// <summary>
/// Generate a random string
/// </summary>
/// <param name="length">Length of the random string</param>
/// <returns>Randomly generated string</returns>
string GenerateNonce(int length)
{
const string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var random = new System.Random();
var nonce = new char[length];
for (int i = 0; i < length; i++)
{
nonce[i] = chars[random.Next(chars.Length)];
}
return new string(nonce);
}
}
C++ Request Example
#include <iostream>
#include <string>
#include <sstream>
#include <ctime>
#include <random>
#include <curl/curl.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
/**
* Configuration: replace the following parameters with actual values before making authorization requests.
*/
const std::string client_id = "Replace with Client ID from the console";
const std::string kid = "Replace with kid from the Access Token returned by the client SDK";
const std::string mac_key = "Replace with mac_key from the Access Token returned by the client SDK";
/**
* Endpoint for getting current account detailed info.
* To get basic account info, replace with:
* https://open.tapapis.cn/account/basic-info/v1
*/
const std::string url = "https://open.tapapis.cn/account/profile/v1";
// Function declarations
std::string createSigningString(const std::string& request_url, time_t ts,
const std::string& nonce, const std::string& method);
std::string sign(const std::string& signing_string, const std::string& key);
std::string randomString(int length);
std::string base64Encode(const unsigned char* input, unsigned int length);
size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp);
int main()
{
/**
* Main program logic
*/
// Step 1: Set method and request_url
std::string method = "GET";
std::string request_url = url + "?client_id=" + client_id;
// Step 2: Generate timestamp and nonce
time_t ts = time(nullptr); // Current timestamp in seconds
std::string nonce = randomString(5); // Random string, at least 5 characters
// Step 3: Build signing string and generate signature
std::string signing_string = createSigningString(request_url, ts, nonce, method);
std::string mac = sign(signing_string, mac_key);
// Step 4: Generate Authorization header
std::ostringstream auth_stream;
auth_stream << "MAC id=\"" << kid << "\",ts=\"" << ts
<< "\",nonce=\"" << nonce << "\",mac=\"" << mac << "\"";
std::string auth = auth_stream.str();
std::cout << "Authorization: " << auth << std::endl << std::endl;
// Step 5: Execute HTTP request and output result
CURL* curl = curl_easy_init();
if (!curl) {
std::cerr << "Error: curl init failed" << std::endl;
return 1;
}
std::string response_body;
struct curl_slist* headers = curl_slist_append(nullptr, ("Authorization: " + auth).c_str());
curl_easy_setopt(curl, CURLOPT_URL, request_url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_body);
CURLcode ret = curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
std::cout << "HTTP Status Code: " << http_code << std::endl << std::endl;
if (ret != CURLE_OK) {
std::cerr << "Error: cURL error: " << curl_easy_strerror(ret) << std::endl;
}
std::cout << "Response Body: " << std::endl << response_body << std::endl;
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return 0;
}
/**
* Build the signing string
*
* @param request_url Request URL
* @param ts Timestamp
* @param nonce Random string
* @param method HTTP method
* @return The signing string
*/
std::string createSigningString(const std::string& request_url, time_t ts,
const std::string& nonce, const std::string& method)
{
// Parse URL: extract domain, path, and query parameters
size_t scheme_end = request_url.find("://");
size_t host_start = (scheme_end != std::string::npos) ? scheme_end + 3 : 0;
size_t path_start = request_url.find('/', host_start);
std::string domain = request_url.substr(host_start, path_start - host_start);
std::string uri = request_url.substr(path_start);
int port = 443; // Fixed port for HTTPS
std::ostringstream oss;
oss << ts << "\n" << nonce << "\n" << method << "\n"
<< uri << "\n" << domain << "\n" << port << "\n\n";
return oss.str();
}
/**
* Generate signature value
*
* @param signing_string The signing string
* @param key MAC key
* @return Generated signature value
* @example sign("abc", "def") -> dYTuFEkwcs2NmuhQ4P8JBTgjD4w=
*/
std::string sign(const std::string& signing_string, const std::string& key)
{
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_len = 0;
HMAC(EVP_sha1(),
key.c_str(), key.length(),
reinterpret_cast<const unsigned char*>(signing_string.c_str()), signing_string.length(),
digest, &digest_len);
return base64Encode(digest, digest_len);
}
/**
* Generate a random string
*
* @param length Length of the random string
* @return Randomly generated string
*/
std::string randomString(int length)
{
const std::string characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::default_random_engine generator(static_cast<unsigned long>(time(nullptr)));
std::uniform_int_distribution<int> distribution(0, characters.size() - 1);
std::string result;
for (int i = 0; i < length; ++i) {
result += characters[distribution(generator)];
}
return result;
}
/**
* Base64 encoding
*
* @param input Input data
* @param length Input data length
* @return Base64-encoded string
*/
std::string base64Encode(const unsigned char* input, unsigned int length)
{
int encoded_size = 4 * ((length + 2) / 3);
std::string output(encoded_size + 1, '\0');
EVP_EncodeBlock(reinterpret_cast<unsigned char*>(&output[0]), input, length);
output.resize(encoded_size);
return output;
}
/**
* cURL write callback function
*/
size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp)
{
static_cast<std::string*>(userp)->append(static_cast<char*>(contents), size * nmemb);
return size * nmemb;
}
Calling the APIs
APIs and scopes
Which APIs you can call is determined by the scopes requested at login time. The actually granted scopes are available in the scopes field of the Access Token:
| Scope requested at login | Available APIs |
|---|---|
basic_info | Get basic account info |
public_profile | Get basic account info, Get detailed account info |
All examples below use the China mainland domain. If the client SDK is initialized for the overseas region, the login is an overseas login; the flow stays the same — just replace the request domain open.tapapis.cn with the overseas domain open.tapapis.com.
Get basic account info
GET https://open.tapapis.cn/account/basic-info/v1?client_id=xxx
Authorization mac token
Request parameters
| Field | Type | Description |
|---|---|---|
| client_id | string | The Client ID of your application |
Response parameters
| Field | Type | Description |
|---|---|---|
| openid | string | The user's unique identifier within this application, see Overview |
| unionid | string | The user's unique identifier across all applications of this developer, see Overview |
Get detailed account info
GET https://open.tapapis.cn/account/profile/v1?client_id=xxx
Authorization mac token
The request parameters are the same as Get basic account info.
Response parameters
| Field | Type | Description |
|---|---|---|
| name | string | User name |
| avatar | string | URL of the user's avatar image |
| openid | string | The user's unique identifier within this application, see Overview |
| unionid | string | The user's unique identifier across all applications of this developer, see Overview |
Request example
Put the Authorization header produced by MAC Token signing into the request as-is (using the basic info API as an example):
curl -H 'Authorization: MAC id="<kid>",ts="<timestamp>",nonce="<nonce>",mac="<mac>"' \
"https://open.tapapis.cn/account/basic-info/v1?client_id=<Client ID>"
Error codes
Response format
| Field | Type | Description |
|---|---|---|
| code | int | Reserved field for future troubleshooting |
| error | string | Error code, for use in program logic |
| error_description | string | Human-readable description to help understand and resolve the error |
Error responses
| Error code | Description | Suggested handling |
|---|---|---|
| invalid_request | The request is missing a required parameter, contains an unsupported parameter or value, or is malformed | Check the request parameters and retry |
| invalid_time | The ts used for signing is invalid | Sync your clock, or fetch the server time and sign again |
| invalid_client | The client_id parameter is invalid | Check that client_id matches the developer console |
| access_denied | The credential is invalid or has expired (token expired, account deleted, authorization revoked, etc.) | Clear the local login state and ask the user to log in again; see FAQ |
| forbidden | The user has no permission for this action | Do not retry; re-authentication will not help |
| not_found | The requested resource does not exist on the server | Do not repeat the request with the same parameters |
| server_error | The server encountered an error | Retry later, at most 3 times; if it keeps failing, stop and inform the user |
| insufficient_scope | The scopes granted at login do not include the permission required by this API | Check APIs and scopes; see FAQ |
FAQ
The API returns the access_denied error code
Troubleshoot in this order:
- Is the signing correct? Verify your implementation against the signing verification sample; a mismatch means the signing string or algorithm is wrong.
- Has the Access Token expired? A token is valid for at most 30 days. The SDK refreshes it on every app launch; if the player has not logged in within 30 days, ask them to log in again.
- Did the user delete the account or revoke the authorization? The token becomes invalid immediately in these cases. Clear the local login state on verification failure and ask the user to log in again.
Also make sure you are not caching the Access Token — it may become invalid at any time; fetch the latest value through the SDK on every use, see Prerequisite.
The API returns the insufficient_scope error code
The scopes granted at login do not match the permission required by the API you called — for example, the login only requested basic_info, but your server called Get detailed account info, which requires public_profile.
Check against APIs and scopes: either request a scope at login that covers the API you call, or switch to Get basic account info, which only needs basic_info.