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>
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
| Field | Description |
|---|---|
source_id | External product ID (same as <offer id="..."> in the feed). Determines create vs update |
- If
source_idalready exists in Spefix — the product is updated (200). - If
source_idis new — the product is created (201).
Product fields
Required (on every request, including update)
| Field | Type | Description |
|---|---|---|
source_id | string | Product ID in the store system |
code | string | SKU / vendor code |
source_category_id | string | Category ID; the category must already exist in Spefix |
currency_code | string | UAH, USD, or EUR |
translations | array | At least one translation (see below) |
Optional (product)
| Field | Type | Description |
|---|---|---|
new_price | number | Current price (> 0) |
old_price | number | Previous price (> 0, ≥ new_price) |
picture | string | Image URL |
availability | boolean | Optional. If omitted, the product is in stock. If present, the request value is used (true / false). |
vendor | string | Manufacturer (max 255) |
group_id | string | Variant group ID; cannot be changed via API |
is_main | boolean | Main variant in the group |
warehouses | array | Warehouses (create and update). See Multi-warehouse |
Translation (translations[])
| Field | Required | Type | Description |
|---|---|---|---|
language | ✅ | string | uk, ru, en |
name | ✅ | string | Name |
url | ✅ | string | Product page URL |
description | — | string | Description |
labels | — | array | Labels |
keywords | — | array | Keywords |
synonyms | — | array | Synonyms |
params | — | array | Product attributes |
params item structure
{
"name": "Color",
"value": "Black",
"is_filter": true,
"is_variation": false,
"color": "#000000",
"swatch_image": "https://example.com/swatch-black.jpg"
}
| Field | Description |
|---|---|
name | Attribute name (e.g. "Color") |
value | Attribute value (e.g. "Black") |
is_filter | If true, the attribute is used in search filters |
is_variation | If true, the attribute is a variation |
color | HEX color for the filter swatch |
swatch_image | Swatch 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.
| Field | Required | Type | Description |
|---|---|---|---|
external_id | ✅ | string | Warehouse ID (same as in the feed / wid in the widget) |
available | — | boolean | In stock at this warehouse (default: false) |
quantity | — | integer | Quantity (≥ 0, default: 0) |
new_price | — | number | Price at this warehouse |
old_price | — | number | Previous price at this warehouse |
currency_code | — | string | Currency 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 ofwarehouses[].available.
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_idmust 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_priceandold_pricemust be positive (> 0);old_price≥new_price.currency_codeis onlyUAH,USD, orEUR.availabilityis optional. If omitted, the product is in stock; if present, the request value is used.vendoris limited to 255 characters.group_idcan be set on create; it cannot be changed via API.warehousesis allowed on create and on update.translationsmust include at least one translation; language codes must not be duplicated.languageisuk,ru, oren.- All URLs (
url,picture) must start withhttp://orhttps://.
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 errorserrors— error details for each index in the source array
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
| Situation | DB | OpenSearch |
|---|---|---|
Update (source_id exists) | Updated | Asynchronous patch (usually seconds) |
Create (source_id is new) | Created | Same asynchronous patch, if the entire batch has no errors |
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
- 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": [...]}]'
- 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.