Back to API docs

apicreateproduct JSON request body create or update single or bulk optional images ZIP copy/paste docs

POST /api/createproduct is the supported endpoint for this payload. Send JSON as application/json, or use multipart/form-data with payload + images_zip to import product images — see Product images (optional ZIP upload).

Create-or-update behavior
The endpoint creates a new product by default. If id is provided, that product is updated instead. If no id is provided but the submitted sku already exists, the existing SKU product is updated instead of returning a duplicate-SKU error. On update, omitted product fields are kept as-is; attributes and variations are resynced only when those fields are included in the request.

Product images (optional ZIP upload)

In addition to the JSON body, you can attach a ZIP archive of image files in the same request. Products are created or updated from JSON first; then images from the ZIP are imported and linked to products or variations by SKU (case-insensitive).

When to use

How to name image files inside the ZIP

Each image filename must encode the target SKU. Supported formats: JPG, JPEG, PNG, GIF, WebP.

Single image per SKU
SKU.ext

Use the product or variation SKU as the filename (before the extension).

API-TEST-001.jpg
VAR-PARENT-001-RED.png
Multiple images per SKU
SKU__N.ext — double underscore + number

When a product needs a gallery (more than one image), add a double underscore and a sequence number before the extension. Numbers determine gallery order (__1 first, then __2, etc.).

API-TEST-001__1.jpg
API-TEST-001__2.jpg
API-TEST-001__3.png

Do not mix numbered and unnumbered names for the same SKU in one ZIP — use either SKU.ext or SKU__N.ext consistently.

ZIP layout tips

Example ZIP contents

For a bulk import with products CHAIR-001, CHAIR-001-RED (variation), and CHAIR-001-BLUE (variation):

product-images.zip
├── CHAIR-001__1.jpg      → main product gallery (image 1)
├── CHAIR-001__2.jpg      → main product gallery (image 2)
├── CHAIR-001-RED.png     → variation image
└── CHAIR-001-BLUE.png    → variation image

How to send images with the API

Use POST /api/createproduct with Content-Type: multipart/form-data and two parts:

curl example

curl -X POST "https://YOUR-DOMAIN/api/createproduct" \
  -F 'payload={
    "key": "YOUR_API_KEY",
    "products": [
      {
        "name": "Office chair",
        "category": 1,
        "sku": "CHAIR-001",
        "attributes": [
          {
            "name": "Color",
            "create_if_missing": 1,
            "type": 1,
            "multiselect": 0,
            "values": [
              { "value": "Red", "add": "#ff0000" },
              { "value": "Blue", "add": "#0000ff" }
            ],
            "selected": ["Red", "Blue"]
          }
        ],
        "variation_features_by_name": ["Color"],
        "variations": [
          { "sku": "CHAIR-001-RED", "name": "Red", "combo": { "Color": "Red" } },
          { "sku": "CHAIR-001-BLUE", "name": "Blue", "combo": { "Color": "Blue" } }
        ]
      }
    ]
  }' \
  -F "images_zip=@product-images.zip"

JavaScript (FormData) example

const payload = {
  key: "YOUR_API_KEY",
  products: [
    { name: "Office chair", category: 1, sku: "CHAIR-001" }
  ]
};

const form = new FormData();
form.append("payload", JSON.stringify(payload));
form.append("images_zip", zipFileInput.files[0]);

const res = await fetch("/api/createproduct", {
  method: "POST",
  body: form
});
const data = await res.json();

Do not set Content-Type manually when using FormData — the browser adds the correct multipart boundary.

How images are assigned after import

Image import in the response

When a ZIP was uploaded, the response includes an images summary (inside data for bulk/single success responses, or at the top level when data is a plain error string):

{
  "status": "ok",
  "data": {
    "created": [ /* ... */ ],
    "errors": [],
    "count_created": 1,
    "count_errors": 0,
    "images": {
      "assigned": [
        {
          "sku": "CHAIR-001",
          "type": "product",
          "id": 123,
          "image_ids": [45, 46],
          "count": 2
        },
        {
          "sku": "CHAIR-001-RED",
          "type": "variation",
          "id": 88,
          "product_id": 123,
          "image_ids": [47],
          "count": 1
        }
      ],
      "errors": [],
      "skipped": 0,
      "count_assigned": 2,
      "count_errors": 0
    }
  }
}

Recommended workflow: create products in JSON (bulk products[] with all SKUs and variations), then attach the ZIP so every filename SKU already exists when images are processed.

Top-level JSON fields

key
string, required

Your API key.

products
array of objects, optional

If provided, the endpoint creates or updates multiple products in one request. Each item in products[] uses the same fields as the single-product payload (everything documented below), just without the top-level key.

Important when creating products used in Included/Related attributes (type 4/5)
  • These “child” products should be created with "visibility": 2.
  • In bulk mode, put those “child” products first in the products array, and put “parent” products (that reference them) last.

Reason: parent products may reference children by id/sku during creation; ordering ensures referenced products exist already.

Backward compatibility
If products is missing, the request is treated as a single product payload (old behavior), where fields like name and category are top-level.

Example bulk request (children first, parents last):

{
  "key": "YOUR_API_KEY",
  "products": [
    {
      "name": "Child product (used in Included/Related)",
      "category": 1,
      "visibility": 2,
      "sku": "CHILD-001"
    },
    {
      "name": "Parent product",
      "category": 2,
      "attributes": [
        {
          "name": "Included products",
          "type": 4,
          "create_if_missing": 1,
          "selected": ["CHILD-001"]
        }
      ]
    }
  ]
}

Bulk response:

{
  "status": "partial",
  "data": {
    "created": [ /* Created or updated Product objects */ ],
    "errors": [
      { "index": 1, "error": "required not set" }
    ],
    "count_created": 1,
    "count_errors": 1
  }
}

Example bulk request (1st product becomes a Related product on the 2nd):

Notes
  • The “child” product is created with "visibility": 2.
  • The “parent” product references it via the Related products attribute (type: 5) using the child SKU in selected.
  • Order matters: create the referenced product first.
{
  "key": "YOUR_API_KEY",
  "products": [
    {
      "name": "Child product (will be referenced as Related)",
      "category": 1,
      "visibility": 2,
      "sku": "CHILD-REL-001"
    },
    {
      "name": "Parent product (has Related products attribute)",
      "category": 1,
      "sku": "PARENT-REL-001",
      "attributes": [
        {
          "name": "Related products",
          "type": 5,
          "create_if_missing": 1,
          "selected": ["CHILD-REL-001"]
        }
      ]
    }
  ]
}
id
int, optional

Existing product ID. If provided, the endpoint updates that product. If the ID is invalid, the request returns id invalid and does not create a duplicate product.

name
string, required when creating; optional when updating

Product name (base/default). In bulk mode this field belongs to each products[] item.

category
int|string, required when creating; optional when updating

Category ID or name. In bulk mode this field belongs to each products[] item.

sku
string, optional

If missing/empty on create, a unique SKU is generated. If provided and it already belongs to an existing product, that product is updated. If updating by id, the SKU must not belong to a different product.

price
object|string, optional

If object, it will be JSON-encoded and saved. Typical structure:

{
  "Supplier A": {
    "1": [100, 110, 0, 5],
    "10": [90, 95, 0, 0]
  }
}

Meaning:

Important for variable products
  • If you provide "variations": [...], the product is treated as variable.
  • Base product price is forced to JSON [] when missing/empty/null/"0" to avoid breaking JSON consumers.
url
array|string, optional

Supplier product URLs list. If array, it will be JSON-encoded and saved.

If missing/empty/"null" it defaults to:

[""]

Example:

["https://example.com/product/api-test-001"]
names
object, optional

Localized names by language code:

{ "en": "Name", "lt": "Pavadinimas" }
description
string, required*

Main (default) short description saved into products.description.

*Required in the sense that the system expects a main description field; if you don't provide it, the API will attempt to fill it from descriptions using the rules below.

descriptionl
string, required*

Main (default) long description saved into products.descriptionl.

*Required in the sense that the system expects a main long description field; if you don't provide it, the API will attempt to fill it from descriptionls using the rules below.

descriptions
object, optional

Localized short descriptions by language code (used for additional languages / addlangs).

How main vs localized descriptions are chosen
  • If description is provided → it is used as the main short description.
  • Else if descriptions[mainlang] exists → it is copied into description.
  • Else if any descriptions value exists → the first available value is copied into description. If its language key is not mainlang, it is also kept in descriptions.

If you only have one description available, you can send it in any language — it will still populate description.

descriptionls
object, optional

Localized long descriptions (often HTML) by language code (used for additional languages / addlangs).

How main vs localized long descriptions are chosen
  • If descriptionl is provided → it is used as the main long description.
  • Else if descriptionls[mainlang] exists → it is copied into descriptionl.
  • Else if any descriptionls value exists → the first available value is copied into descriptionl. If its language key is not mainlang, it is also kept in descriptionls.
withpvm
int (0|1), optional

Defaults to 0.

NEW: Type 4 “Selectable products” (Included products only)

This setting applies ONLY to type 4 attributes (Included products).

Meaning in proposal UI (Step 2)
When enabled, included products added via this attribute are selectable — the user can select or unselect these add-on products in the proposal frontend. When disabled, included products are included without the ability to exclude them in the proposal frontend.

How to set it

You can set it in two ways:

Note: type4_selectable_products is not a product field. If a client sends it at top-level, it is ignored. For backward compatibility you may also send attributes[].type4_selectable_products as an alias. The standalone /api/createattribute endpoint accepts the same aliases when creating a type 4 attribute. When this flag is enabled and the attribute adds included products to a proposal, those generated child entries get selectable = 1 and are selected by default.

NEW: Step-2 selection mode for Included/Related products (type 4/5)

This setting affects ONLY proposal “Step 2” selection behavior (when adding product/variation to proposal):

This setting does NOT change how many included/related items can be stored on the product itself.

You can set it in two ways:

1) type45_select_modes_by_name

object, optional — Map FeatureName → "single" | "multiple"

{
  "Included products": "single",
  "Related products": "multiple"
}

2) attributes[].select_mode

string, optional (type 4/5 only) — Per-attribute override (wins over type45_select_modes_by_name)

Allowed values: "single" or "multiple"

NEW: Chaining Included/Related product options (Step‑2 “inline” options)

What “chaining” means
If a product has a type 4 or 5 feature (Included / Related products) and the user selects a “child” product in proposal Step‑2, the UI can show that child product’s own options inline (child attributes like type 0/1/2/3, and even its own type 4/5). Those inline choices are saved onto the created proposal lines.

How to enable it via API

Defaults: the inline option controls are prefilled from the child product’s saved selections (the same values you send in the child’s attributes[].selected).

Example: Parent includes a child, child has its own options

{
  "key": "YOUR_API_KEY",
  "products": [
    {
      "name": "Child: Backpack (has its own options)",
      "category": 1,
      "visibility": 2,
      "sku": "CHILD-BACKPACK-001",
      "attributes": [
        {
          "name": "Length (cm)",
          "create_if_missing": 1,
          "type": 3,
          "type3_calc_target": 1,
          "selected": "200"
        },
        {
          "name": "Color",
          "create_if_missing": 1,
          "type": 1,
          "multiselect": 0,
          "values": [
            { "value": "Black", "add": "#111827" },
            { "value": "White", "add": "#ffffff" }
          ],
          "selected": ["Black"]
        },
        {
          "name": "Needs packaging?",
          "create_if_missing": 1,
          "type": 0,
          "multiselect": 0,
          "values": ["Yes", "No"],
          "selected": ["No"]
        }
      ]
    },
    {
      "name": "Parent: Bundle product",
      "category": 1,
      "sku": "PARENT-BUNDLE-001",
      "type45_select_modes_by_name": {
        "Included products": "single"
      },
      "type45_required_features_by_name": {
        "Included products": true
      },
      "attributes": [
        {
          "name": "Included products",
          "create_if_missing": 1,
          "type": 4,
          "selectable_products": true,
          "selected": ["CHILD-BACKPACK-001"]
        }
      ]
    }
  ]
}

Attributes / features (attributes)

attributes
array, optional

Defines product attributes (features), selected values, and optionally creates missing features/options.

Each attributes[] item can contain

1) Identify or create the feature
2) Ensure selectable options exist (values) type 0/1 only
3) Select value(s) for this product (selected)

selected is optional; format depends on feature type.

Alias: you may send value instead of selected on the same attribute object — both use the same parsing rules below.

Feature types (type)

selected formats by feature type

Type 0 / 1 (selectable options)

Single label or id:

"Red"
12

Multiple:

["Red", "Blue"]
[12, 15]

Type 2 (custom text)

Preferred: a plain string or number (converted to string).

"Cotton"

Structured payloads are also accepted and normalized to one string:

Type 3 (calc / numeric-like)

Preferred: string or number (stored as string).

"15"
15

Structured payloads use the same normalization as type 2:

Type 4 / 5 (included / related products)

selected can be any mix of:

You can also nest arrays; they will be flattened. If a plain string SKU does not match a product SKU, the API tries to match it as a variation SKU.

Variations

Variation configuration

variation_features_by_name
array of strings, optional

Feature names that form the variation combination, e.g.:

["Color", "Size"]
preset_features_by_name
array of strings, optional

Feature names that are “preset” (not part of the combo).

Variations list

variations
array, optional

Each item can contain:

Important

If variations are provided, the system automatically marks the product as variable and also auto-selects all variation-axis values (type 0/1) on the base product, so attributes won’t appear empty in the edit UI.

NEW: Per-variation bulk product blocks (Included mandatory / Included selectable / Related)

Each variation can define up to three product lists used in proposal Step 2 when that variation is added to an offer. These are configured per variation in the admin bulk-variation editor; the API uses the same fields.

Order in Step 2: mandatory included → selectable included → related → then parent type 4/5 attributes.
Selectable in proposal is determined by which block a product is in — not by the mode number: included_mandatory_products and related_products are not user-toggleable; included_selectable_products behaves like selectable add-ons (selectable = 1).

Fields on each variations[] item

included_mandatory_products
array|string, optional

Products always included with this variation (mandatory). Same ref formats as type 4 selected (see below).

included_mandatory_products_mode
int 0–3, optional (default 0)

Step 2 selection rules for this block:

included_mandatory_products_title
string, optional

Custom Step 2 card title. Empty = system default (“Included mandatory products”).

included_selectable_products
array|string, optional

Included products the user may toggle on/off in proposal Step 2.

included_selectable_products_mode
int 0–3, optional (default 0)

Same mode values as mandatory block.

included_selectable_products_title
string, optional

Custom Step 2 card title. Empty = system default (“Included selectable products”).

related_products
array|string, optional

Related / add-on products for this variation (shown in the related lane). Same ref formats as type 5 selected.

related_products_mode
int 0–3, optional (default 0)

Same mode values as above.

related_products_title
string, optional

Custom Step 2 card title. Empty = system default (“Related products”).

Product ref formats (all three blocks)

Each list accepts the same mix as type 4/5 selected:

Arrays may be nested; they are flattened. Plain string SKUs that are not product SKUs are tried as variation SKUs. In bulk products[] mode, create referenced child products before the parent (same as type 4/5).

Legacy fields (still supported)

If you send the old single included list, it is migrated automatically:

Legacy is used only when neither included_mandatory_products nor included_selectable_products is set.

Example: variable product with all three blocks

{
  "key": "YOUR_API_KEY",
  "products": [
    {
      "name": "Mandatory addon",
      "category": 1,
      "visibility": 2,
      "sku": "VAR-MANDATORY-001"
    },
    {
      "name": "Selectable addon",
      "category": 1,
      "visibility": 2,
      "sku": "VAR-SELECT-001"
    },
    {
      "name": "Related addon",
      "category": 1,
      "visibility": 2,
      "sku": "VAR-RELATED-001"
    },
    {
      "name": "Variable parent",
      "category": 1,
      "sku": "VAR-PARENT-001",
      "attributes": [
        {
          "name": "Color",
          "create_if_missing": 1,
          "type": 1,
          "multiselect": 0,
          "values": [
            { "value": "Red", "add": "#ff0000" },
            { "value": "Blue", "add": "#0000ff" }
          ],
          "selected": ["Red", "Blue"]
        }
      ],
      "variation_features_by_name": ["Color"],
      "variations": [
        {
          "sku": "VAR-PARENT-001-RED",
          "name": "Red",
          "combo": { "Color": "Red" },
          "prices": { "Supplier": { "1": [100, 110, 0, 5] } },
          "included_mandatory_products": ["VAR-MANDATORY-001"],
          "included_mandatory_products_mode": 3,
          "included_mandatory_products_title": "Required parts",
          "included_selectable_products": ["VAR-SELECT-001"],
          "included_selectable_products_mode": 0,
          "included_selectable_products_title": "Optional extras",
          "related_products": ["VAR-RELATED-001"],
          "related_products_mode": 2,
          "related_products_title": "You may also need"
        },
        {
          "sku": "VAR-PARENT-001-BLUE",
          "name": "Blue",
          "combo": { "Color": "Blue" },
          "prices": { "Supplier": { "1": [105, 115, 0, 0] } },
          "included_selectable_products": ["VAR-SELECT-001"],
          "related_products": ["VAR-RELATED-001"]
        }
      ]
    }
  ]
}

Omit any block you do not need. Modes and titles are optional; defaults apply when omitted.

Step-2 visibility rules (optional)

step2_visibility_rules_by_name
object, optional

Map of TargetFeatureName → list of prerequisites:

{
  "Size": [
    { "feature": "Color", "value": "Red" }
  ]
}

Minimal valid JSON

{
  "key": "YOUR_API_KEY",
  "name": "My product",
  "category": 1
}

Full example JSON (attributes + variations + included + related + new selection options)

{
  "key": "YOUR_API_KEY",
  "name": "API Test Product (Attributes + Variations + Included + Related)",
  "category": "Spare parts",
  "sku": "API-TEST-001",
  "type45_select_modes_by_name": {
    "Included products": "single",
    "Related products": "multiple"
  },
  "type45_required_features_by_name": {
    "Included products": true,
    "Related products": false
  },
  "price": {
    "Supplier A": {
      "1": [100, 110, 0, 5],
      "10": [90, 95, 0, 0]
    }
  },
  "url": ["https://example.com/product/api-test-001"],
  "names": {
    "en": "API Test Product",
    "lt": "API Test Produktas"
  },
  "descriptions": {
    "en": "Created to test apicreateproduct payload: attributes, variations, included + related products.",
    "lt": "Sukurta testuoti apicreateproduct payload: atributai, variacijos, įtraukti + susiję produktai."
  },
  "descriptionls": {
    "en": "<ul><li>Line 1</li><li>Line 2</li></ul>"
  },
  "attributes": [
    {
      "name": "Color",
      "create_if_missing": 1,
      "type": 1,
      "multiselect": 0,
      "values": [
        { "value": "Red", "add": "#ff0000" },
        { "value": "Blue", "add": "#0000ff" }
      ],
      "selected": ["Red"]
    },
    {
      "name": "Size",
      "create_if_missing": 1,
      "type": 0,
      "multiselect": 0,
      "values": ["S", "M", "L"],
      "selected": ["M"]
    },
    {
      "name": "Material",
      "create_if_missing": 1,
      "type": 2,
      "selected": "Cotton"
    },
    {
      "name": "Setup fee",
      "create_if_missing": 1,
      "type": 3,
      "type3_calc_target": 1,
      "selected": "15"
    },
    {
      "name": "Included products",
      "create_if_missing": 1,
      "type": 4,
      "select_mode": "single",
      "selectable_products": true,
      "selected": [
        { "name": "Included Item A", "category": 1, "sku": "INC-A-001" },
        { "name": "Included Item B", "category": 1, "sku": "INC-B-001" }
      ]
    },
    {
      "name": "Related products",
      "create_if_missing": 1,
      "type": 5,
      "select_mode": "multiple",
      "selected": [
        { "name": "Related Item A", "category": 1, "sku": "REL-A-001" },
        { "name": "Related Item B", "category": 1, "sku": "REL-B-001" }
      ]
    }
  ],
  "variation_features_by_name": ["Color", "Size"],
  "preset_features_by_name": ["Material"],
  "variations": [
    {
      "sku": "API-TEST-001-RED-S",
      "name": "Red / S",
      "combo": { "Color": "Red", "Size": "S" },
      "prices": {
        "Supplier A": {
          "1": [110, 120, 0, 0],
          "10": [95, 105, 0, 0]
        }
      },
      "included_mandatory_products": ["INC-A-001"],
      "included_mandatory_products_mode": 1,
      "included_mandatory_products_title": "Required included",
      "included_selectable_products": ["INC-B-001"],
      "included_selectable_products_mode": 0,
      "related_products": ["REL-A-001"],
      "related_products_mode": 0,
      "hidden_features_by_name": ["Material"]
    },
    {
      "sku": "API-TEST-001-BLUE-M",
      "name": "Blue / M",
      "combo": { "Color": "Blue", "Size": "M" },
      "prices": {
        "Supplier A": {
          "1": [120, 130, 0, 0],
          "10": [105, 115, 0, 0]
        }
      },
      "included_selectable_products": ["INC-B-001"],
      "related_products": ["REL-A-001", "REL-B-001"],
      "hidden_features_by_name": []
    }
  ],
  "step2_visibility_rules_by_name": {
    "Size": [
      { "feature": "Color", "value": "Red" }
    ]
  }
}

Type 4/5 required features (proposal Step 2)

For attributes with type 4 (Included products) or 5 (Related products), you can mark the block as required in the proposal Step 2 picker: the user must satisfy that block’s rules (same idea as ticking “required” for that Included/Related feature in the admin product editor).

Two ways to set it

type45_required_features_by_name
object, optional — map feature name → required flag

Keys must match the "name" of a type 4 or 5 row in the same request’s attributes[] array (after trimming; spelling and accents must match exactly).

Example:

{
  "Included products": true,
  "Related products": false,
  "Volai su rėmeliais": true
}
attributes[].required
boolean | number | string, optional (type 4/5 only)

Set on the attribute object. If both type45_required_features_by_name and attributes[].required apply to the same feature, attributes[].required wins.

{
  "name": "Included products",
  "create_if_missing": 1,
  "type": 4,
  "required": true,
  "selected": ["CHILD-SKU-001"]
}

Truthy / falsy values

Required is on when the value is: true, 1, "1", "true", "on", "yes" (strings are compared case-insensitively). Other values are treated as not required.

How it is stored

The API sets products.type45_required_features to a JSON array of product_features.id values (as strings), e.g. ["12","34"], listing which type 4/5 features are required — same as the admin UI. Do not send a top-level type45_required_features field in the request; it is not read from the client.

Create vs update

Bulk products[]

Each product object may include its own type45_required_features_by_name and type 4/5 rows in attributes; rules apply per product.

Together with type45_select_modes_by_name

Required is separate from single vs multiple selection. Use type45_select_modes_by_name or attributes[].select_mode for single / multiple Step 2 behaviour.

A working bulk example with type45_required_features_by_name + type45_select_modes_by_name is under Example: Parent includes a child, child has its own options earlier on this page.