تاجر جني
Which API?
Merchant API For merchants: manage your own catalogue, orders and finances through the v2 Third-Party API. Application API For partner apps: browse Gini merchants and products, send customers to checkout and follow their orders.

New: Application API docs

Pick the API you are reading here — the Merchant API for your own store, the Application API for partner apps.

Merchant dashboard
Getting started
Authentication

API documentation

Everything you need to integrate with the Gini v2 Third-Party API.

Authentication

The API is stateless. Every request must carry an API key and an HMAC-SHA256 signature computed from the request itself, so the signature is unique per request and cannot be replayed after it expires.

Required headers
Header Description
X-API-KEY Your API Client ID (the public key from Settings → Integrations).
X-TIMESTAMP Current Unix time in seconds. Must be within 300 seconds of server time.
X-SIGNATURE Lowercase hex HMAC-SHA256 of the signing string, keyed with your API secret.
Building the signature

Concatenate the HTTP method, the request path, the raw body, and the timestamp — in that exact order, with no separators — then HMAC-SHA256 it with your API secret.

Signing string
signing_string = METHOD + PATH + BODY + TIMESTAMP
signature      = HMAC_SHA256(signing_string, api_secret)   // lowercase hex
PHP example
PHP
$method = 'GET';
$path = '/api/v2.0/third-party/products';
$body = '';            // raw JSON for POST/PUT/PATCH, empty for GET/DELETE and multipart
$timestamp = (string) time();

$signingString = $method . $path . $body . $timestamp;
$signature = hash_hmac('sha256', $signingString, $apiSecret);

$headers = [
    'X-API-KEY: ' . $apiClientId,
    'X-TIMESTAMP: ' . $timestamp,
    'X-SIGNATURE: ' . $signature,
];
Multipart upload example

File uploads are the one case that trips people up. A multipart/form-data body is not part of the signature — sign an empty body, exactly as you would for a GET.

PHP
$method = 'POST';
$path = '/api/v2.0/third-party/products';
$timestamp = (string) time();

// The multipart body is NOT signed — use an empty string for BODY.
$signingString = $method . $path . '' . $timestamp;
$signature = hash_hmac('sha256', $signingString, $apiSecret);

$headers = [
    'X-API-KEY: ' . $apiClientId,
    'X-TIMESTAMP: ' . $timestamp,
    'X-SIGNATURE: ' . $signature,
    // Do NOT set Content-Type yourself — cURL must add the multipart boundary.
];

$fields = [
    'name' => 'My Product',
    'price' => 15000,
    'quantity' => 10,
    'images[0]' => new CURLFile('/path/to/image.jpg'),
];

$ch = curl_init('https://merchant.gini.iq' . $path);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => $headers,
    CURLOPT_POSTFIELDS => $fields,
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
cURL
TIMESTAMP=$(date +%s)

# No body — sign METHOD + PATH + TIMESTAMP only.
SIGNING_STRING="POST/api/v2.0/third-party/products${TIMESTAMP}"
SIGNATURE=$(echo -n "$SIGNING_STRING" | openssl dgst -sha256 -hmac "$API_CLIENT_SECRET" | cut -d' ' -f2)

curl -X POST https://merchant.gini.iq/api/v2.0/third-party/products \
  -H "X-API-KEY: $API_CLIENT_ID" \
  -H "X-SIGNATURE: $SIGNATURE" \
  -H "X-TIMESTAMP: $TIMESTAMP" \
  -F "name=My Product" \
  -F "price=15000" \
  -F "quantity=10" \
  -F "images[]=@/path/to/image.jpg"
Signing rules
  • PATH is the full request path including its version prefix (/api/v2.0/third-party for most endpoints, /api/v1.0/third-party/wholesale for wholesale), with no query string and no host.
  • BODY is the raw JSON body for JSON requests, and an empty string for GET, DELETE, and multipart/form-data uploads.
  • METHOD is the real HTTP verb. For multipart uploads spoofed with _method=PUT, sign as POST.
  • For multipart uploads, let your HTTP client set multipart/form-data with its own boundary — never send Content-Type: application/json, or the body will be read as JSON and the signature will not match.
  • TIMESTAMP is Unix seconds and must be within 300 seconds (5 minutes) of server time, or the request is rejected with 401 Request expired.
  • Reuse the exact same timestamp in both the signing string and the X-TIMESTAMP header.
  • Signatures are compared in constant time — a mismatch returns 401 Invalid signature; an unknown key returns 401 Invalid API key; an inactive account returns 403.