Skip to main content

Product import

This endpoint creates new products or updates existing ones by unique source_id (upsert). The request body is a JSON array: one item = one product.

Endpoint

POST https://smartsearch.spefix.com/api/v2/content/product/bulk/
Content-Type: application/json
X-Secret-Token: <domain secret_token>
Authentication

Every request must include the X-Secret-Token header with your secret token.

Do not confuse this with the widget/search token. The API requires secret_token (Django admin → Domain → Authentication).

See API import overview for details.


Upsert key

FieldDescription
source_idExternal product ID (same as <offer id="..."> in the feed). Determines create vs update
  • If source_id already exists in Spefix — the product is updated (200).
  • If source_id is new — the product is created (201).

Product fields

Required (on every request, including update)

FieldTypeDescription
source_idstringProduct ID in the store system
codestringSKU / vendor code
source_category_idstringCategory ID; the category must already exist in Spefix
currency_codestringUAH, USD, or EUR
translationsarrayAt least one translation (see below)

Optional (product)

FieldTypeDescription
new_pricenumberCurrent price (> 0)
old_pricenumberPrevious price (> 0, ≥ new_price)
picturestringImage URL
availabilitybooleanOptional. If omitted, the product is in stock. If present, the request value is used (true / false).
vendorstringManufacturer (max 255)
group_idstringVariant group ID; cannot be changed via API
is_mainbooleanMain variant in the group
warehousesarrayWarehouses (create and update). See Multi-warehouse

Translation (translations[])

FieldRequiredTypeDescription
languagestringuk, ru, en
namestringName
urlstringProduct page URL
descriptionstringDescription
labelsarrayLabels
keywordsarrayKeywords
synonymsarraySynonyms
paramsarrayProduct attributes

params item structure

{
"name": "Color",
"value": "Black",
"is_filter": true,
"is_variation": false,
"color": "#000000",
"swatch_image": "https://example.com/swatch-black.jpg"
}
FieldDescription
nameAttribute name (e.g. "Color")
valueAttribute value (e.g. "Black")
is_filterIf true, the attribute is used in search filters
is_variationIf true, the attribute is a variation
colorHEX color for the filter swatch
swatch_imageSwatch image URL

Multi-warehouse (warehouses[])

The field is allowed on both create and update. An unknown external_id creates the warehouse automatically.

See Multi-warehouse inventory for warehouse search behavior. Warehouse external_id must match the ID in the feed and the wid in the widget.

FieldRequiredTypeDescription
external_idstringWarehouse ID (same as in the feed / wid in the widget)
availablebooleanIn stock at this warehouse (default: false)
quantityintegerQuantity (≥ 0, default: 0)
new_pricenumberPrice at this warehouse
old_pricenumberPrevious price at this warehouse
currency_codestringCurrency for this warehouse (locale-channel)

Behavior:

  • warehouses in the payload are upserted;
  • warehouses missing from the payload are deleted for this product;
  • Product.availability = OR of warehouses[].available.
Full warehouse replace

If you send warehouses, the array must include all warehouses you want to keep. Warehouses omitted from the request are deleted for this product.


Validation rules

  • source_category_id must match an existing category in your domain.
  • Required fields (source_id, code, source_category_id, currency_code, translations) must be sent on every request, including update.
  • new_price and old_price must be positive (> 0); old_pricenew_price.
  • currency_code is only UAH, USD, or EUR.
  • availability is optional. If omitted, the product is in stock; if present, the request value is used.
  • vendor is limited to 255 characters.
  • group_id can be set on create; it cannot be changed via API.
  • warehouses is allowed on create and on update.
  • translations must include at least one translation; language codes must not be duplicated.
  • language is uk, ru, or en.
  • All URLs (url, picture) must start with http:// or https://.

Examples

Example 1 — minimal price update

[
{
"source_id": "580756609",
"code": "DEMO-580756609",
"source_category_id": "4660599",
"new_price": 37777,
"currency_code": "UAH",
"translations": [
{
"language": "uk",
"name": "Ноутбук Apple MacBook Neo 13\" A18 Pro 8/512GB 2026",
"url": "https://shop.example/product/580756609"
}
]
}
]

Example 2 — price + multi-warehouse

[
{
"source_id": "580756609",
"code": "DEMO-580756609",
"source_category_id": "4660599",
"new_price": 41199,
"old_price": 45999,
"currency_code": "UAH",
"availability": true,
"picture": "https://cdn.example/image.jpg",
"vendor": "Apple",
"warehouses": [
{
"external_id": "kyiv-main",
"available": true,
"quantity": 3,
"new_price": 41199,
"old_price": 45999
},
{
"external_id": "lviv-1",
"available": false,
"quantity": 0
}
],
"translations": [
{
"language": "uk",
"name": "Ноутбук Apple MacBook Neo 13\" A18 Pro 8/512GB 2026",
"url": "https://shop.example/product/580756609"
}
]
}
]

Example 3 — several products in one request

[
{
"source_id": "580756609",
"code": "DEMO-580756609",
"source_category_id": "4660599",
"new_price": 37777,
"currency_code": "UAH",
"translations": [
{ "language": "uk", "name": "MacBook Neo", "url": "https://shop.example/580756609" }
]
},
{
"source_id": "569726152",
"code": "DEMO-569726152",
"source_category_id": "4660596",
"new_price": 23999,
"currency_code": "UAH",
"translations": [
{ "language": "uk", "name": "HP 255R G10", "url": "https://shop.example/569726152" }
]
}
]

Responses

Successful update (200)

{
"successful": [
{
"index": 0,
"status": 200,
"data": { "...": "..." }
}
]
}

Create, if the product did not exist (201)

{
"successful": [
{
"index": 0,
"status": 201,
"data": { "...": "..." }
}
]
}

Validation error (400)

{
"errors": [
{
"index": 0,
"errors": {
"currency_code": ["This field is required."]
}
}
],
"successful": []
}

The response may contain two arrays:

  • successful — items processed without errors
  • errors — error details for each index in the source array
caution

After a partial success, fix the data according to the errors messages and retry the request.

Invalid or missing token (401)

{
"detail": "Authentication credentials were not provided."
}

Behavior after the request

SituationDBOpenSearch
Update (source_id exists)UpdatedAsynchronous patch (usually seconds)
Create (source_id is new)CreatedSame asynchronous patch, if the entire batch has no errors
warning

The search change is not instant — indexing is asynchronous. A patch is queued only when every item in the batch succeeds. If the request has any validation error, successful products are saved to the DB but are not patched in OpenSearch. The patch applies to an existing domain index; if the index does not exist yet, the product appears after a feed reindex.


curl

curl -X POST 'https://smartsearch.spefix.com/api/v2/content/product/bulk/' \
-H 'Content-Type: application/json' \
-H 'X-Secret-Token: YOUR_SECRET_TOKEN' \
-d @payload.json

Workflow examples

Creating a new catalog

  1. Create categories (see Category import):
curl -X POST https://smartsearch.spefix.com/api/v2/content/category/bulk/ \
-H "X-Secret-Token: your-token" \
-H "Content-Type: application/json" \
-d '[{"source_category_id": "1", "translations": [...]}]'
  1. Create products:
curl -X POST https://smartsearch.spefix.com/api/v2/content/product/bulk/ \
-H "X-Secret-Token: your-token" \
-H "Content-Type: application/json" \
-d '[{"code": "PROD-001", "source_id": "1", "source_category_id": "1", "currency_code": "UAH", "translations": [...]}]'

New products appear in search via an asynchronous patch (if the batch fully succeeds and the domain index already exists). Otherwise after a feed reindex.

Updating prices and availability

Send the same source_id with updated new_price / old_price / availability (and warehouses if needed). Required fields must still be included. The search change appears after the asynchronous index patch.