تاجر جني
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
API reference
Products

API documentation

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

Products

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.

List products

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.

GET /api/v2.0/third-party/products
Signed
Query parameters
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.
Response
{
    "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
    }
}
Notes
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • search matches name (LIKE), sku (LIKE), and slug (exact) in one clause.
  • Ordering defaults to sort_order ascending (nulls last) then created_at descending. Send sort to override it.
  • visibility_locked marks a product whose visibility Gini is holding while a listing check is reviewed. While it is true, an is_visible you send is silently DROPPED — you can still hide the product, you just cannot put it back on sale — and visibility_lock_reason carries the sentence to show. Check it before treating an unchanged is_visible as a failed write.
  • List items include nested category and subcategory, but NOT variants (use Show for variants).
  • is_visible is an integer (1 visible / 0 hidden), not a boolean.
  • Prices are integers in the smallest IQD unit; formatted_amounts holds the display strings.
Show product

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.

GET /api/v2.0/third-party/products/{sku}
Signed
Response
{
    "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"
    }
}
Notes
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • {sku} resolves by SKU or by numeric product ID.
  • Only the Show endpoint embeds variants; list/create/update responses omit them.
  • Returns 404 with body {'message': 'Product not found.'} when the product does not belong to you.
  • discountable is null when the product has no active discount.
Create product

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.

POST /api/v2.0/third-party/products
Signed
Body parameters
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.
Request body
{
    "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)"
}
Response
{
    "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"
    }
}
Notes
  • Content type is multipart/form-data — images are uploaded as files under images[].
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • SKU is optional; when omitted a UUID is generated. Provided SKUs have spaces replaced with hyphens and must be unique within your account, across your own products and their variants. A SKU another merchant already uses is accepted.
  • slug is always server-generated (UUID) and cannot be set by the caller.
  • When is_gini_partner is true, quantity must be at least the configured Gini partner minimum, or validation fails (422).
  • months is only persisted when your merchant config can_control_product_months is enabled; otherwise it is stripped before saving.
  • price must be at least the configured minimum price floor.
  • Newly uploaded images may take a few minutes to appear/propagate (thumbnails are generated asynchronously).
  • Response is 201 with the created product; it does NOT include variants.
Update product

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

PUT /api/v2.0/third-party/products/{sku}
Signed
Body parameters
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.
Request body
{
    "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"
    ]
}
Response
{
    "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"
    }
}
Notes
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • {sku} resolves by SKU or by numeric product ID; returns 404 with body {'message': 'Product not found.'} if not yours.
  • All fields are optional (partial update) — only send what you want to change.
  • Send application/json for field-only changes; use multipart/form-data when uploading images[].
  • delete_images takes existing image storage paths; new images are appended. Existing (after deletions) plus new uploads cannot exceed 10 or the request is rejected (422).
  • Enabling is_gini_partner without sending quantity validates against the product's CURRENT stored quantity; if that is below the Gini partner minimum the request returns 422.
  • months is only persisted when your merchant config can_control_product_months is enabled; otherwise it is stripped.
  • Newly uploaded images may take a few minutes to appear/propagate.
  • Response does NOT include variants.
Delete product

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

DELETE /api/v2.0/third-party/products/{sku}
Signed
Response
{
    "message": "Product deleted successfully."
}
Notes
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • {sku} resolves by SKU or by numeric product ID; returns 404 with body {'message': 'Product not found.'} if not yours.
  • Returns 422 with body {'message': 'Cannot delete product that has been sold in previous orders.'} when the product has any order items.
  • On success the deletion cascades (best-effort, in a transaction): S3 images, addons + their images, variants + their images, and cart/cart-addon references.
  • Success response is 200 with a plain message (not wrapped in data).
Update stock

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

PATCH /api/v2.0/third-party/products/{sku}/stock
Signed
Body parameters
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.
Request body
{
    "price": 22000,
    "quantity": 50
}
Response
{
    "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"
    }
}
Notes
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • {sku} resolves by SKU or by numeric product ID; returns 404 with body {'message': 'Product not found.'} if not yours.
  • Only non-null fields in the payload are applied — omit (or send null for) a field to leave it unchanged.
  • price must be 0 or greater and at least the configured minimum price; quantity must be an integer of 0 or greater.
  • Response does NOT include variants.
Bulk upsert products

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.

POST /api/v2.0/third-party/products/bulk
Signed
Body parameters
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.
Request body
{
    "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
        }
    ]
}
Response
{
    "data": {
        "created": 1,
        "updated": 1,
        "failed": [
            {
                "sku": "TSHIRT-GREEN-S",
                "error": "SQLSTATE[23000]: Integrity constraint violation ..."
            }
        ]
    }
}
Notes
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • Upsert key is SKU: a matching SKU you own is updated, otherwise a new product is created (with a generated SKU when none is sent, and an auto-generated slug).
  • Rows are validated up front by the Form Request; the whole request is rejected if any row fails the shared rules (name/price/quantity/category/subcategory/is_visible, Gini partner minimum, etc.).
  • Processing runs in chunks of 25 within one DB transaction. Individual row exceptions during upsert are caught and reported in failed[] (with the row's sku or 'N/A') without stopping the batch.
  • A transaction-level failure rolls everything back and returns 500 with body {'message': 'Bulk operation failed: ...'}.
  • is_visible values are coerced to 1/0 before saving.
  • Response is not the product objects — it is a summary of the shape {'data': {'created': int, 'updated': int, 'failed': [...]}}.
  • Bulk rows do not support image uploads; created products start with an empty images array.
Bulk update prices, quantities and visibility

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.

POST /api/v2.0/third-party/products/stock-updates
Signed
Body parameters
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.
Request body
{
    "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
        }
    ]
}
Response
{
    "data": {
        "status": "queued",
        "accepted": 4
    }
}
Notes
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • Refused with 422 when the same product or variant appears twice, a price is not a whole number or is under the minimum price, a quantity is not a whole number or is under 0, an item has none of price, quantity and is_visible, or is_visible comes with a variant_id. Fix the items named in errors and send the request again.
  • A sku that is not one of your products, or a variant_id that is not a variant of that product, is ignored and the other items are written. Another store's SKU or variant is ignored the same way. accepted counts every item you sent, ignored ones included.
  • The items are written within seconds of the 202; read a product back with GET /products/{sku} to see its new price, quantity and is_visible (returned there as 1 or 0).
  • is_visible true does not put back on sale a product Gini is reviewing (visibility_locked on GET /products/{sku}) or a pre-order product: it stays hidden and the switch is dropped without an error. A price in the same item is still set. Switching a product off always works.
  • Requests from your store are applied one at a time, but not necessarily in the order they were sent. Do not send the same product or variant in two requests close together: which price lands last is not guaranteed.
  • Send an Idempotency-Key header to retry safely: a retry with the same key returns the original 202 and queues nothing new.
  • An item without variant_id sets the product's price and quantity; with variant_id it changes only that variant. New prices, quantities and visibility also reach storefront search.
  • A product deleted, or a SKU renamed, in the seconds between your request and its turn in the queue is ignored.
  • Changes made here never fire a webhook back at you, like every other write through this API.
  • Your discounts are not re-checked when a price changes.
Bulk delete products

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.

Max 500 SKUs per request. A request with more is refused whole with 422 and nothing is deleted; split it into requests of 500 or fewer.
The result arrives only as a product_bulk_delete_finished webhook. The 202 response does not say which products were deleted, so configure a webhook first and match the event to your request by id.
POST /api/v2.0/third-party/products/bulk-delete
Signed
Body parameters
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).
Request body
{
    "skus": [
        "TSHIRT",
        "MUG-01",
        "16 PRO"
    ]
}
Response
{
    "data": {
        "id": "9d3c6a1e-6b0f-4a4e-9a51-1f0c2d7b8e44",
        "status": "queued",
        "accepted": 3
    }
}
Webhook when the run ends
{
    "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.
Notes
  • Signed request: send the API key + HMAC-SHA256 signature headers.
  • Refused with 422 and {'message': 'No webhook configured. Configure a webhook first.'} when you have no webhook: it is the only place the outcome is reported. Set one with PUT /webhook.
  • Refused with 422 when skus is missing, empty or over 500 entries, or an entry is not a non-empty string of at most 255 characters.
  • SKUs match as everywhere else in this API: case does not matter, and spaces count as hyphens (16 PRO is 16-PRO). A SKU sent twice is deleted once and counted once in accepted and total. A product is matched by SKU only, never by numeric id.
  • Each product is deleted exactly as DELETE /products/{sku} deletes it, images, add-ons, variants and cart references included, each on its own: a SKU that is refused never undoes the ones deleted before it.
  • A SKU that matches none of your products, another store's included, is ignored: it counts in total but is in neither deleted nor failed. In the example above, 16-PRO was ignored.
  • deleted is a count. Every one of your products listed in the request and not in failed was deleted. A SKU in failed is written as it was matched, with spaces turned into hyphens.
  • If the run stops part way, the event arrives with status failed, total and a message instead of deleted and failed. Some products may already be gone, so read your products back with GET /products before sending the request again.
  • Match the event to your request by id. The products deleted here fire no product_deleted events; product_bulk_delete_finished is the one webhook this endpoint sends.
  • Send an Idempotency-Key header to retry safely: a retry with the same key returns the original 202 and queues nothing new.