New: Application API docs
Pick the API you are reading here — the Merchant API for your own store, the Application API for partner apps.
For partner applications: browse merchants and products, hand customers a checkout link, and follow the orders it creates.
Every request carries your app_id and an HMAC-SHA256 signature computed from the request itself — method, path, canonical query, body and timestamp — keyed with your app_secret.
Every request must carry three headers:
| Header | Description |
|---|---|
| X-API-KEY | Your app_id. |
| X-TIMESTAMP | Current Unix time in seconds (UTC). |
| X-SIGNATURE | Hex-encoded HMAC-SHA256 of the string to sign (below). |
<METHOD><PATH><CANONICAL_QUERY><BODY><TIMESTAMP>
X-SIGNATURE = hex( HMAC_SHA256(app_secret, stringToSign) )
| Part | What it is |
|---|---|
| METHOD | Uppercase HTTP method (GET, POST). |
| PATH | Request path including the /api/... prefix, e.g. /api/third-party/v1/applications/products. |
| CANONICAL_QUERY | The canonical query string prefixed with ?, or the empty string when the request has no query parameters. See Canonical query string below. |
| BODY | The raw request body, byte for byte. Empty for GET. For the two POST endpoints, sign exactly the bytes you send — re-serialising the JSON after signing breaks the signature. |
| TIMESTAMP | The exact value sent in X-TIMESTAMP. |
The query string you sign must be canonical. The URL you actually request does not need to be — the server normalises the incoming query before verifying.
In PHP this is exactly:
parse_str($rawQuery, $params);
ksort($params);
$canonical = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
| Condition | Response |
|---|---|
| Any of the three headers missing | 401 {"message": "Missing authentication headers"} |
Unknown or deactivated app_id |
401 {"message": "Invalid API key"} |
|now − X-TIMESTAMP| > 300 seconds |
401 {"message": "Request expired"} |
| Signature does not match | 401 {"message": "Invalid signature"} |
$method = 'GET';
$path = '/api/third-party/v1/applications/products';
$query = 'page_size=20&page=1';
$body = '';
$timestamp = (string) time();
parse_str($query, $params);
ksort($params);
$canonicalQuery = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$stringToSign = $method . $path
. ($canonicalQuery === '' ? '' : '?' . $canonicalQuery)
. $body . $timestamp;
$signature = hash_hmac('sha256', $stringToSign, $appSecret);
$headers = [
'X-API-KEY: ' . $appId,
'X-TIMESTAMP: ' . $timestamp,
'X-SIGNATURE: ' . $signature,
'Accept: application/json',
];
import crypto from 'crypto';
// encodeURIComponent leaves ! ' ( ) * unencoded; PHP's rawurlencode does not.
// Without this, any value containing them fails with "Invalid signature".
const rfc3986 = (s) =>
encodeURIComponent(String(s)).replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
// params: flat values or arrays, e.g. { page: '1', categories: ['3', '5'] }
function canonicalQuery(params) {
const pairs = [];
for (const key of Object.keys(params).sort()) {
const value = params[key];
if (Array.isArray(value)) {
// mirror PHP: categories[] -> categories[0], categories[1]
value.forEach((v, i) => pairs.push(`${rfc3986(`${key}[${i}]`)}=${rfc3986(v)}`));
} else {
pairs.push(`${rfc3986(key)}=${rfc3986(value)}`);
}
}
return pairs.join('&');
}
const method = 'GET';
const path = '/api/third-party/v1/applications/products';
const body = ''; // for POST: the exact JSON string you send
const timestamp = Math.floor(Date.now() / 1000).toString();
const query = canonicalQuery({ page_size: '20', page: '1' });
const stringToSign = method + path + (query ? '?' + query : '') + body + timestamp;
const signature = crypto.createHmac('sha256', appSecret).update(stringToSign).digest('hex');
APP_ID="<your app_id>"
APP_SECRET="<your app_secret>"
HOST="https://api.gini.iq"
METHOD="GET"
PATH="/api/third-party/v1/applications/categories"
QUERY="page=1&page_size=10" # already canonical: sorted, RFC 3986
BODY=""
TS=$(date +%s)
STRING_TO_SIGN="${METHOD}${PATH}?${QUERY}${BODY}${TS}"
SIG=$(printf '%s' "$STRING_TO_SIGN" | openssl dgst -sha256 -hmac "$APP_SECRET" -r | cut -d' ' -f1)
curl -sS "${HOST}${PATH}?${QUERY}" \
-H "X-API-KEY: $APP_ID" \
-H "X-TIMESTAMP: $TS" \
-H "X-SIGNATURE: $SIG" \
-H 'Accept: application/json'