New: Application API docs
Pick the API you are reading here — the Merchant API for your own store, the Application API for partner apps.
Everything you need to integrate with the Gini v2 Third-Party API.
Full CRUD for your product catalog, plus a quick stock/price update and a bulk upsert, all signed with API-key and HMAC-SHA256 under the /api/v2.0/third-party/products prefix.
Returns a paginated list of the products you own, newest first. Results are ordered by sort_order (nulls last) then by created_at descending. Each item includes its nested category and subcategory but not variants.
/api/v2.0/third-party/products
| Field | Type | Required | Description |
|---|---|---|---|
search
|
string | Optional | Filter by product name (partial match), SKU (partial match), or slug (exact match). Max 255 characters. |
per_page
|
integer | Optional | Results per page, 1–100. Defaults to 15. |
visibility
|
string | Optional | visible or hidden. Anything else is a 422. |
stock
|
string | Optional | in (quantity above security_stock), low (at or below it but above zero), or out (zero or less). out deliberately excludes pre-order products, whose normal state is no stock — use pre_order to find those. |
installments
|
string | Optional | yes or no — whether the product can be bought in installments at all. |
installment_same_price
|
boolean | Optional | Only products whose installments cost the cash price. Distinct from installments above. |
category_id
|
integer | Optional | Only products filed in this category. |
branch_id
|
integer | Optional | Only products assigned to this branch (client_store_id). |
wholesale
|
boolean | Optional | Only wholesale-enabled products (is_b2b_enabled). |
pre_order
|
boolean | Optional | Only pre-order products. |
sort
|
string | Optional | name, price, quantity, sold_count or created_at. Omit to keep the default order (sort_order first, then newest). Anything else is a 422. |
direction
|
string | Optional | asc or desc. Defaults to desc. Ignored unless sort is given. |
page
|
integer | Optional | Page number to fetch. Defaults to 1. |
{
"data": [
{
"id": 142,
"name": "Wireless Headphones",
"slug": "9f1c2e7a-3b4d-4e2a-8c1f-0a2b3c4d5e6f",
"sku": "WH-2026-001",
"sort_order": null,
"description": "Premium noise-cancelling headphones.",
"price": 45000,
"is_installment_same_price": false,
"discounted_price": 40000,
"final_price": 40000,
"formatted_amounts": {
"final_price": "IQD 40,000",
"price": "IQD 45,000",
"discounted_price": "IQD 40,000",
"difference": "IQD 5,000"
},
"security_stock": 10,
"quantity": 120,
"category_id": 3,
"subcategory_id": 12,
"category": {
"id": 3,
"name": "Electronics",
"slug": "electronics",
"is_active": 1,
"image_url": "https://s3.../categories/electronics.jpg",
"icon_url": "https://s3.../icons/electronics.svg"
},
"subcategory": {
"id": 12,
"name": "Audio",
"slug": "audio",
"is_active": 1,
"image": "https://s3.../subcategories/audio.jpg"
},
"client_id": 88,
"is_visible": 1,
"visibility_locked": false,
"visibility_lock_reason": null,
"is_pre_order": false,
"pre_order": null,
"is_b2b_enabled": false,
"b2b_price": null,
"b2b_min_quantity": null,
"is_installments": false,
"image": "https://s3.../products/img1.jpg",
"thumb_image": "https://s3.../products_thumbs-01/img1.webp",
"images": [
"https://s3.../products/img1.jpg",
"https://s3.../products/img2.jpg"
],
"allow_multiple_addons": false,
"tags": "electronics,audio,wireless",
"options": [
{
"key": "Warranty",
"value": "1 year"
}
],
"is_favouritable": false,
"is_cartable": false,
"discountable": {
"percentage": 11,
"code": "SUMMER",
"expires_at": "2026-08-01T00:00:00.000000Z",
"max_usage": 100,
"used_count": 12
},
"created_at": "2026-01-15T10:30:00.000000Z",
"updated_at": "2026-02-10T14:20:00.000000Z"
}
],
"links": {
"first": "https://.../api/v2.0/third-party/products?page=1",
"last": "https://.../api/v2.0/third-party/products?page=5",
"prev": null,
"next": "https://.../api/v2.0/third-party/products?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 5,
"per_page": 15,
"to": 15,
"total": 68
}
}
Returns a single product you own, resolved by SKU or by numeric ID. The detail payload additionally includes the product's variants (each with its own attributes and pricing). Returns 404 if no product with that identifier belongs to you.
/api/v2.0/third-party/products/{sku}
{
"data": {
"id": 142,
"name": "Wireless Headphones",
"slug": "9f1c2e7a-3b4d-4e2a-8c1f-0a2b3c4d5e6f",
"sku": "WH-2026-001",
"sort_order": null,
"description": "Premium noise-cancelling headphones.",
"price": 45000,
"is_installment_same_price": false,
"discounted_price": 40000,
"final_price": 40000,
"formatted_amounts": {
"final_price": "IQD 40,000",
"price": "IQD 45,000",
"discounted_price": "IQD 40,000",
"difference": "IQD 5,000"
},
"security_stock": 10,
"quantity": 120,
"category_id": 3,
"subcategory_id": 12,
"category": {
"id": 3,
"name": "Electronics",
"slug": "electronics",
"is_active": 1,
"image_url": "https://s3.../categories/electronics.jpg",
"icon_url": "https://s3.../icons/electronics.svg"
},
"subcategory": {
"id": 12,
"name": "Audio",
"slug": "audio",
"is_active": 1,
"image": "https://s3.../subcategories/audio.jpg"
},
"client_id": 88,
"is_visible": 1,
"visibility_locked": false,
"visibility_lock_reason": null,
"is_pre_order": false,
"pre_order": null,
"is_b2b_enabled": false,
"b2b_price": null,
"b2b_min_quantity": null,
"is_installments": false,
"image": "https://s3.../products/img1.jpg",
"thumb_image": "https://s3.../products_thumbs-01/img1.webp",
"images": [
"https://s3.../products/img1.jpg",
"https://s3.../products/img2.jpg"
],
"allow_multiple_addons": false,
"variants": [
{
"id": 501,
"product_id": 142,
"image": "https://s3.../products/variant-black.jpg",
"sku": "WH-2026-001-BLK",
"price": 45000,
"b2b_price": null,
"formatted_amounts": {
"price": "IQD 45,000",
"discounted_price": "IQD 40,000"
},
"quantity": 30,
"attributes": [
{
"id": 7,
"product_variant_id": 501,
"product_attribute_id": 7,
"value": "Black",
"name": "Color"
}
]
}
],
"tags": "electronics,audio,wireless",
"options": [
{
"key": "Warranty",
"value": "1 year"
}
],
"is_favouritable": false,
"is_cartable": false,
"discountable": null,
"created_at": "2026-01-15T10:30:00.000000Z",
"updated_at": "2026-02-10T14:20:00.000000Z"
}
}
Create a new product. Uses multipart/form-data because product images are uploaded as files. At least one image is required (max 10 files, 5 MB each). SKU is optional and auto-generated (UUID) when omitted; the slug is always auto-generated. Returns the created product (201) including its nested category and subcategory.
/api/v2.0/third-party/products
| Field | Type | Required | Description |
|---|---|---|---|
name
|
string | Required | Product name. Max 255 characters. |
description
|
string | Required | Full product description. Max 1000 words. |
price
|
numeric | Required | Price in the smallest currency unit. Must be at least the configured minimum price. |
quantity
|
numeric | Required | Available stock quantity. When is_gini_partner is true, must be at least the configured Gini partner minimum. |
security_stock
|
numeric | Required | Low-stock alert threshold. |
category_id
|
integer | Required | Must reference an existing category. |
subcategory_id
|
integer | Required | Must reference an existing subcategory. |
is_visible
|
boolean | Required | Whether the product is visible to customers. Stored as 1/0. |
images
|
file[] | Required | Product image files (images[]). Required, 1–10 files, each an image, max 5 MB. |
sku
|
string | Optional | Custom SKU. Must be unique within your account, across your own products and their variants. Another merchant holding the same SKU is not a conflict. Spaces are replaced with hyphens. Auto-generated UUID if omitted. |
is_installments
|
boolean | Optional | Whether the product supports installment payments. |
months
|
integer | Optional | Installment months (1–10). Only saved when your merchant config allows controlling product months; otherwise silently ignored. |
is_gini_partner
|
boolean | Optional | Promote via Gini partners (influencers/celebrities). When true, quantity must meet the Gini partner minimum quantity. |
tags
|
string | Optional | Comma-separated tags. |
options
|
array | Optional | Array of key/value option objects. Each entry needs options.*.key (string, max 255) and options.*.value (max 255). |
is_used
|
boolean | Optional | Mark the product as used (second-hand). Defaults to false. |
allow_multiple_addons
|
boolean | Optional | Let a customer pick more than one add-on per order. Defaults to false. |
sort_order
|
integer | Optional | Storefront ordering weight. |
product_label
|
string | Optional | Storefront badge — one of: best_seller, original, imported, guaranteed, limited_edition, exclusive. Requires the product-label feature enabled for your store; ignored otherwise (as in the dashboard). |
client_store_id
|
integer | Optional | Assign the product to one of your branches (must be your own branch id). Requires multi-store enabled for your store; ignored otherwise. |
is_b2b_enabled
|
boolean | Optional | Offer the product on the wholesale marketplace. Requires wholesale-seller enabled for your store; the b2b_* fields are ignored otherwise. |
b2b_price
|
number | Optional | Wholesale unit price. |
b2b_min_quantity
|
integer | Optional | Minimum wholesale order quantity. Defaults to 1. |
is_pre_order
|
boolean | Optional | Sell this product as a pre-order. Needs is_preorder_enabled on your merchant config; without it this field and the five terms below are silently dropped. Turning it on forces is_visible to 0 — a pre-order product never shows on the storefront. |
availability_date
|
date | Optional | Required when is_pre_order is true. YYYY-MM-DD, today or later. |
estimated_shipping_days
|
integer | Optional | Optional, 0–365. |
pre_order_quantity
|
integer | Optional | Required when is_pre_order is true. Minimum 1. |
terms_and_conditions
|
string | Optional | Required when is_pre_order is true. Max 2000 characters. |
product_cancellation_policy
|
string | Optional | Required when is_pre_order is true. Max 2000 characters. |
{
"name": "Wireless Headphones",
"description": "Premium noise-cancelling headphones.",
"price": 45000,
"quantity": 120,
"security_stock": 10,
"category_id": 3,
"subcategory_id": 12,
"is_visible": 1,
"is_installments": 0,
"months": 6,
"is_gini_partner": true,
"sku": "WH-2026-001",
"tags": "electronics,audio,wireless",
"options": [
{
"key": "Warranty",
"value": "1 year"
}
],
"images[]": "(binary image file)"
}
{
"data": {
"id": 142,
"name": "Wireless Headphones",
"slug": "9f1c2e7a-3b4d-4e2a-8c1f-0a2b3c4d5e6f",
"sku": "WH-2026-001",
"sort_order": null,
"description": "Premium noise-cancelling headphones.",
"price": 45000,
"is_installment_same_price": false,
"discounted_price": 0,
"final_price": 45000,
"formatted_amounts": {
"final_price": "IQD 45,000",
"price": "IQD 45,000",
"discounted_price": "IQD 0",
"difference": "IQD 45,000"
},
"security_stock": 10,
"quantity": 120,
"category_id": 3,
"subcategory_id": 12,
"category": {
"id": 3,
"name": "Electronics",
"slug": "electronics",
"is_active": 1,
"image_url": "https://s3.../categories/electronics.jpg",
"icon_url": "https://s3.../icons/electronics.svg"
},
"subcategory": {
"id": 12,
"name": "Audio",
"slug": "audio",
"is_active": 1,
"image": "https://s3.../subcategories/audio.jpg"
},
"client_id": 88,
"is_visible": 1,
"visibility_locked": false,
"visibility_lock_reason": null,
"is_pre_order": false,
"pre_order": null,
"is_b2b_enabled": false,
"b2b_price": null,
"b2b_min_quantity": null,
"is_installments": false,
"image": "https://s3.../products/img1.jpg",
"thumb_image": "https://s3.../products_thumbs-01/img1.webp",
"images": [
"https://s3.../products/img1.jpg"
],
"allow_multiple_addons": false,
"tags": "electronics,audio,wireless",
"options": [
{
"key": "Warranty",
"value": "1 year"
}
],
"is_favouritable": false,
"is_cartable": false,
"discountable": null,
"created_at": "2026-07-05T09:00:00.000000Z",
"updated_at": "2026-07-05T09:00:00.000000Z"
}
}
Partially update a product resolved by SKU or numeric ID — send only the fields you want to change (all fields optional). Send application/json for field-only updates, or multipart/form-data when uploading new images. Use delete_images to remove existing image paths. Returns the updated product (200).
/api/v2.0/third-party/products/{sku}
| Field | Type | Required | Description |
|---|---|---|---|
name
|
string | Optional | Product name. Max 255 characters. |
sku
|
string | Optional | New SKU. Spaces are replaced with hyphens. |
description
|
string | Optional | Full product description. Max 1000 words. |
price
|
numeric | Optional | Price in the smallest currency unit. Must be at least the configured minimum price. |
security_stock
|
numeric | Optional | Low-stock alert threshold. |
quantity
|
numeric | Optional | Available stock quantity. Required to meet the Gini partner minimum when is_gini_partner is true. |
category_id
|
integer | Optional | Must reference an existing category. |
subcategory_id
|
integer | Optional | Must reference an existing subcategory. |
is_visible
|
boolean | Optional | Whether the product is visible to customers. Stored as 1/0. |
is_installments
|
boolean | Optional | Whether the product supports installment payments. |
months
|
integer | Optional | Installment months (1–10), nullable. Only saved when your merchant config allows controlling product months. |
is_gini_partner
|
boolean | Optional | Promote via Gini partners. Enabling it requires the product quantity to meet the Gini partner minimum (see notes). |
tags
|
string | Optional | Comma-separated tags. |
options
|
array | Optional | Array of key/value option objects, replacing the existing options. |
images
|
file[] | Optional | New image files to append (images[]). Each an image, max 5 MB. Combined with existing images the total cannot exceed 10. |
delete_images
|
array | Optional | Array of existing image storage paths to remove from the product. |
{
"name": "Wireless Headphones Pro",
"price": 52000,
"is_visible": 1,
"is_installments": 0,
"months": 6,
"is_gini_partner": true,
"quantity": 25,
"delete_images": [
"products/old-img.jpg"
]
}
{
"data": {
"id": 142,
"name": "Wireless Headphones Pro",
"slug": "9f1c2e7a-3b4d-4e2a-8c1f-0a2b3c4d5e6f",
"sku": "WH-2026-001",
"sort_order": null,
"description": "Premium noise-cancelling headphones.",
"price": 52000,
"is_installment_same_price": false,
"discounted_price": 0,
"final_price": 52000,
"formatted_amounts": {
"final_price": "IQD 52,000",
"price": "IQD 52,000",
"discounted_price": "IQD 0",
"difference": "IQD 52,000"
},
"security_stock": 10,
"quantity": 25,
"category_id": 3,
"subcategory_id": 12,
"category": {
"id": 3,
"name": "Electronics",
"slug": "electronics",
"is_active": 1,
"image_url": "https://s3.../categories/electronics.jpg",
"icon_url": "https://s3.../icons/electronics.svg"
},
"subcategory": {
"id": 12,
"name": "Audio",
"slug": "audio",
"is_active": 1,
"image": "https://s3.../subcategories/audio.jpg"
},
"client_id": 88,
"is_visible": 1,
"visibility_locked": false,
"visibility_lock_reason": null,
"is_pre_order": false,
"pre_order": null,
"is_b2b_enabled": false,
"b2b_price": null,
"b2b_min_quantity": null,
"is_installments": false,
"image": "https://s3.../products/img2.jpg",
"thumb_image": "https://s3.../products_thumbs-01/img2.webp",
"images": [
"https://s3.../products/img2.jpg"
],
"allow_multiple_addons": false,
"tags": "electronics,audio,wireless",
"options": [
{
"key": "Warranty",
"value": "1 year"
}
],
"is_favouritable": false,
"is_cartable": false,
"discountable": null,
"created_at": "2026-01-15T10:30:00.000000Z",
"updated_at": "2026-07-05T09:15:00.000000Z"
}
}
Permanently delete a product resolved by SKU or numeric ID, cleaning up its images, addons, variants, and cart references. Refused with 422 when the product has been sold in previous orders (order history must retain the product reference).
/api/v2.0/third-party/products/{sku}
{
"message": "Product deleted successfully."
}
Lightweight endpoint to update price and/or quantity without resending the full product. Both fields are optional; only the fields you send (non-null) are applied. Returns the updated product (200).
/api/v2.0/third-party/products/{sku}/stock
| Field | Type | Required | Description |
|---|---|---|---|
price
|
numeric | Optional | New price in the smallest currency unit. Must be 0 or greater and at least the configured minimum price. |
quantity
|
integer | Optional | New stock quantity. Must be an integer of 0 or greater. |
{
"price": 22000,
"quantity": 50
}
{
"data": {
"id": 142,
"name": "Wireless Headphones",
"slug": "9f1c2e7a-3b4d-4e2a-8c1f-0a2b3c4d5e6f",
"sku": "WH-2026-001",
"sort_order": null,
"description": "Premium noise-cancelling headphones.",
"price": 22000,
"is_installment_same_price": false,
"discounted_price": 0,
"final_price": 22000,
"formatted_amounts": {
"final_price": "IQD 22,000",
"price": "IQD 22,000",
"discounted_price": "IQD 0",
"difference": "IQD 22,000"
},
"security_stock": 10,
"quantity": 50,
"category_id": 3,
"subcategory_id": 12,
"category": {
"id": 3,
"name": "Electronics",
"slug": "electronics",
"is_active": 1,
"image_url": "https://s3.../categories/electronics.jpg",
"icon_url": "https://s3.../icons/electronics.svg"
},
"subcategory": {
"id": 12,
"name": "Audio",
"slug": "audio",
"is_active": 1,
"image": "https://s3.../subcategories/audio.jpg"
},
"client_id": 88,
"is_visible": 1,
"visibility_locked": false,
"visibility_lock_reason": null,
"is_pre_order": false,
"pre_order": null,
"is_b2b_enabled": false,
"b2b_price": null,
"b2b_min_quantity": null,
"is_installments": false,
"image": "https://s3.../products/img1.jpg",
"thumb_image": "https://s3.../products_thumbs-01/img1.webp",
"images": [
"https://s3.../products/img1.jpg"
],
"allow_multiple_addons": false,
"tags": "electronics,audio,wireless",
"options": [],
"is_favouritable": false,
"is_cartable": false,
"discountable": null,
"created_at": "2026-01-15T10:30:00.000000Z",
"updated_at": "2026-07-05T09:20:00.000000Z"
}
}
Create or update up to 100 products in one signed request. Each row is upserted by SKU: if a product with that SKU already exists (and belongs to you) it is updated, otherwise a new product is created with a generated SKU (when none supplied) and slug. Rows are processed in chunks of 25 inside a single transaction; individual row failures are collected and reported without aborting the batch. Returns a summary of created/updated counts and per-row failures.
/api/v2.0/third-party/products/bulk
| Field | Type | Required | Description |
|---|---|---|---|
products
|
array | Required | Array of product rows. Min 1, max 100 items. |
products.*.name
|
string | Required | Product name. Max 255 characters. |
products.*.description
|
string | Required | Full product description. Max 1000 words. |
products.*.price
|
numeric | Required | Price in the smallest currency unit. Must be at least the configured minimum price. |
products.*.quantity
|
numeric | Required | Available stock quantity. When products.*.is_gini_partner is true, must meet the Gini partner minimum. |
products.*.security_stock
|
numeric | Required | Low-stock alert threshold. |
products.*.category_id
|
integer | Required | Must reference an existing category. |
products.*.subcategory_id
|
integer | Required | Must reference an existing subcategory. |
products.*.is_visible
|
boolean | Required | Whether the product is visible to customers. Stored as 1/0. |
products.*.sku
|
string | Optional | SKU used to match an existing product for update; when omitted a UUID is generated for a new product. |
products.*.is_gini_partner
|
boolean | Optional | Promote via Gini partners. When true, that row's quantity must meet the Gini partner minimum. |
{
"products": [
{
"name": "T-Shirt Red",
"sku": "TSHIRT-RED-M",
"description": "Cotton t-shirt, red, medium.",
"price": 12000,
"quantity": 50,
"security_stock": 5,
"category_id": 4,
"subcategory_id": 18,
"is_visible": 1,
"is_gini_partner": false
},
{
"name": "T-Shirt Blue",
"description": "Cotton t-shirt, blue, large.",
"price": 12000,
"quantity": 40,
"security_stock": 5,
"category_id": 4,
"subcategory_id": 18,
"is_visible": 1
}
]
}
{
"data": {
"created": 1,
"updated": 1,
"failed": [
{
"sku": "TSHIRT-GREEN-S",
"error": "SQLSTATE[23000]: Integrity constraint violation ..."
}
]
}
}
Set the price, the quantity, or both, of up to 500 products or variants, and switch products on or off the storefront, in one signed request. Send only what changes: an item with just a quantity leaves the price as it is. A product is named by its SKU, a variant by its id under that product. A SKU you do not have is ignored. A malformed request is refused with 422 before a single item changes. An accepted request is answered with 202 Accepted and its items are written in the background, usually within seconds.
/api/v2.0/third-party/products/stock-updates
| Field | Type | Required | Description |
|---|---|---|---|
items
|
array | Required | The prices, quantities and switches to set. Min 1, max 500 items. |
items.*.sku
|
string | Required | The SKU of one of your products; a SKU you do not have is ignored. Case does not matter, and spaces count as hyphens (16 PRO is 16-PRO). |
items.*.variant_id
|
integer | Optional | Set this variant's price and quantity instead of the product's. A variant of the product named by sku — the id field of GET /products/{sku}/variants; any other id is ignored. |
items.*.price
|
integer | Optional | The new price in whole IQD. Must be at least the configured minimum price. Required unless quantity or is_visible is sent. |
items.*.quantity
|
integer | Optional | The new quantity in stock, a whole number of 0 or more. Required unless price or is_visible is sent. |
items.*.is_visible
|
boolean | Optional | true puts the product on the storefront, false takes it off. Products only: refused with variant_id, since a variant sells on its stock. Required unless price or quantity is sent. |
{
"items": [
{
"sku": "TSHIRT",
"price": 25000,
"quantity": 40
},
{
"sku": "TSHIRT",
"variant_id": 8812,
"quantity": 12
},
{
"sku": "MUG-01",
"price": 9750,
"is_visible": true
},
{
"sku": "CAP-02",
"is_visible": false
}
]
}
{
"data": {
"status": "queued",
"accepted": 4
}
}
Delete up to 500 products in one signed request by sending their SKUs as a JSON list.
The request is checked at once and refused with 422 if it is malformed. An accepted request is answered
with 202 Accepted and its products are deleted in the background. When the run ends, Gini posts a
product_bulk_delete_finished event to your webhook with how many products
were deleted and every one of your products that was not, with the reason. A SKU you do not have is
ignored. A webhook must be configured before you send the request.
/api/v2.0/third-party/products/bulk-delete
| Field | Type | Required | Description |
|---|---|---|---|
skus
|
array | Required | The SKUs of the products to delete. Min 1, max 500. |
skus.*
|
string | Required | One SKU, max 255 characters. Case does not matter, and spaces count as hyphens (16 PRO is 16-PRO). |
{
"skus": [
"TSHIRT",
"MUG-01",
"16 PRO"
]
}
{
"data": {
"id": "9d3c6a1e-6b0f-4a4e-9a51-1f0c2d7b8e44",
"status": "queued",
"accepted": 3
}
}
{
"event": "product_bulk_delete_finished",
"bulk_delete": {
"id": "9d3c6a1e-6b0f-4a4e-9a51-1f0c2d7b8e44",
"status": "completed",
"total": 3,
"deleted": 1,
"failed": [
{
"sku": "TSHIRT",
"reason": "has_orders",
"message": "Cannot delete product that has been sold in previous orders."
}
]
}
}
| Reason | Meaning |
|---|---|
| has_orders | The product has been sold (a retail order, a wholesale order or a pre-order request) and order history must keep it. Hide it instead: PUT /products/{sku} with is_visible 0. |
| error | Deleting this product failed on our side and it was left as it was. Send its SKU again. |