Catalog
Your catalog often already exists elsewhere — an ERP, a supplier file, a former shop. The catalog endpoints let your tools send it to your shop in batches and read each record back: characteristics (such as capacity or colour) and their values, categories, product groups with their selectors, products with their condition grades, characteristics and photos, and the history of customer reviews.
Every import follows the shared contract described in Your first API call: an items list, an optional dry_run, and a line-by-line report.
Endpoints
| Method | Path | Scope | Batch limit |
|---|---|---|---|
| POST | /v1/attribute/import | attribute:write | 25 characteristics, 400 values each |
| GET | /v1/attribute/import | attribute:read | One characteristic |
| POST | /v1/category/import | category:write | 50 categories |
| GET | /v1/category/import | category:read | One category |
| POST | /v1/product-group/import | product_group:write | 50 groups, 20 axes each |
| GET | /v1/product-group/import | product_group:read | One group |
| POST | /v1/product/import | product:write | 40 products |
| GET | /v1/product/import | product:read | One product |
| POST | /v1/product-review/import | product_review:write | 250 reviews |
| GET | /v1/product-review/import | product_review:read | One review |
Import in this order, because each record refers to the previous ones: characteristics, then categories, then product groups, then products, then reviews. Reviews also refer to customers and orders, imported through Orders & logistics.
How catalog records are recognised
| Resource | Recognised by | Echoed in the report's external_id |
|---|---|---|
| Characteristic | name, without regard to case | The name |
| Category | name, without regard to case | The name |
| Product group | name | The name |
| Product | sku | The SKU |
| Review | external_id | The review's external_id |
Because the name is the key of a characteristic, a category or a group, sending a new name creates a new record rather than renaming the old one.
When an item carries a child list — the values of a characteristic, the axes of a group, the grades or media of a product — that list is the whole set: missing entries are added, changed ones are corrected and entries you left out are deactivated. Leave the list out to keep the stored set untouched.
Characteristics
A characteristic describes products, such as Capacity or Colour, and can carry a closed list of values (64 GB, 128 GB). Your shop can also read the characteristics shared by the service; they are returned with "shared": true and cannot be written.
Import characteristics
POST /v1/attribute/import
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes, except for colour | Up to 190 bytes. A colour characteristic is always called Colour. |
kind | string | No | custom (default) or colour. The kind of an existing characteristic is never changed. |
unit | string | No | Up to 30 bytes, such as GB. custom characteristics only. |
scope | string | No | catalog (default), category or product. |
scope_ident | string | When scope is not catalog | The identifier of the category or product the characteristic applies to. |
position | integer | No | Display order. |
status | string | No | activated (default) or suspended. |
values | array | No | The whole list of values (see below). |
Each entry of values:
| Field | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Up to 190 bytes, unique in the characteristic without regard to case. |
hex | string | For colour only | A colour code of six hexadecimal digits, with or without #. Refused on a custom characteristic. |
position | integer | No | Display order. Left out, the value goes last. |
status | string | No | activated or suspended. |
description | string | No | A note about the value, up to 500 characters. |
link_label | string | No | The words of a link shown with the note. Send it together with link_page. |
link_page | string | No | The identifier of one of your shop's text pages the link opens. Send '' with link_label: '' to remove the link. |
curl https://api.ultineo.com/v1/attribute/import \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "Capacity",
"unit": "GB",
"values": [
{ "label": "64 GB", "position": 1 },
{ "label": "128 GB", "position": 2 }
]
},
{
"kind": "colour",
"values": [
{ "label": "Midnight", "hex": "#1c1c28" },
{ "label": "Starlight", "hex": "f4efe6" }
]
}
]
}' {
"code": 200,
"title": "OK",
"response": {
"object": "list",
"resource": "attribute",
"dry_run": false,
"data": [
{ "index": 0, "external_id": "Capacity", "action": "created", "id": "attr_9e2f41c07a3b5d8e6f10", "code": null, "title": null, "reason": null, "notice": [] },
{ "index": 1, "external_id": "Colour", "action": "updated", "id": "attr_3a7c0d95e1f24b6a8c52", "code": null, "title": null, "reason": null, "notice": [] }
],
"created": 1,
"updated": 1,
"unchanged": 0,
"refused": 0,
"total": 2
}
} Read a characteristic
GET /v1/attribute/import
| Parameter | In | Description |
|---|---|---|
name | query | The characteristic's name. Takes precedence over id. |
id | query | Its identifier, prefixed attr_. |
With neither, the call answers 400 Missing required parameter: name.; an unknown characteristic answers 404 Characteristic not found.
curl "https://api.ultineo.com/v1/attribute/import?name=Capacity" \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"object": "attribute",
"id": "attr_9e2f41c07a3b5d8e6f10",
"name": "Capacity",
"kind": "custom",
"unit": "GB",
"scope": "catalog",
"scope_ident": "",
"position": 0,
"status": "activated",
"shared": false,
"values": [
{ "id": "atvl_51d0e8a3c7b2f49e6a01", "label": "64 GB", "hex": "", "position": 1, "status": "activated", "description": "", "link_label": "", "link_page": "" },
{ "id": "atvl_c48b2e7f90a1d35e6b72", "label": "128 GB", "hex": "", "position": 2, "status": "activated", "description": "", "link_label": "", "link_page": "" }
],
"created_at": 1789143990,
"updated_at": 1789143990
}
} Only active values are returned, in display order.
Categories
A category is a shelf of your catalog; categories nest through parent.
Import categories
POST /v1/category/import
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Up to 190 bytes. |
parent | string | No | The parent category, by identifier (cate_…) or by name. '' makes it a top-level category. A parent that would make the category its own ancestor is refused. |
description | string | No | The category's description. |
image | string | No | The identifier of an image already in your media. |
position | integer | No | Display order. |
status | string | No | activated (default) or suspended. |
slug | string | No | The address segment. Generated from the name when left empty. |
seo_title | string | No | Up to 190 bytes. |
seo_description | string | No | The search-engine description. |
seo_image | string | No | The identifier of the image shared on social networks. |
reference | string | No | Your own reference, up to 190 bytes. |
warranty | string | No | The identifier of a warranty of your shop. |
Send parents before their children, in the same batch or an earlier one.
curl https://api.ultineo.com/v1/category/import \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"dry_run": true,
"items": [
{ "name": "Smartphones", "description": "Refurbished smartphones, tested and graded.", "position": 1 },
{ "name": "iPhone", "parent": "Smartphones", "position": 1 }
]
}' {
"code": 200,
"title": "OK",
"response": {
"object": "list",
"resource": "category",
"dry_run": true,
"data": [
{ "index": 0, "external_id": "Smartphones", "action": "unchanged", "id": "cate_4be1a07f93c2d58e6a10", "code": null, "title": null, "reason": null, "notice": [] },
{ "index": 1, "external_id": "iPhone", "action": "created", "id": null, "code": null, "title": null, "reason": null, "notice": [] }
],
"created": 1,
"updated": 0,
"unchanged": 1,
"refused": 0,
"total": 2
}
} Read a category
GET /v1/category/import
| Parameter | In | Description |
|---|---|---|
name | query | The category's name. Takes precedence over id. |
id | query | Its identifier, prefixed cate_. |
With neither, the call answers 400 Missing required parameter: name.; an unknown category answers 404 Category not found. The full request and response are shown in Your first API call.
Product groups
A product group gathers the variants of one product — for example every capacity and colour of one phone model — and defines the axes a buyer picks from on the product page. A product joins a group through its group field.
Import product groups
POST /v1/product-group/import
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Up to 190 bytes. |
status | string | No | activated (default) or suspended. |
axes | array | No | The whole list of axes, up to 20 (see below). |
Each entry of axes:
| Field | Type | Required | Description |
|---|---|---|---|
source | string | No | characteristic (default), grade or custom. |
attribute | string | When source is characteristic | The characteristic the axis offers, by identifier or name. Refused for the other sources. |
name | string | When source is custom | The axis label, up to 190 bytes. |
kind | string | No | How options are drawn: pill (default), swatch, card or list. A grade axis is card or list. |
price_mode | string | No | price, from, diff, none, or '' for the default. pill and swatch axes only accept none; a grade axis does not accept from. |
position | integer | No | Display order. Left out, the axis goes last. |
status | string | No | activated or suspended. |
An axis is recognised by what it offers: its characteristic, the grade (at most one grade axis per group) or, for a custom axis, its name.
curl https://api.ultineo.com/v1/product-group/import \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "iPhone 13",
"axes": [
{ "source": "characteristic", "attribute": "Capacity", "kind": "pill", "position": 1 },
{ "source": "characteristic", "attribute": "Colour", "kind": "swatch", "position": 2 },
{ "source": "grade", "kind": "card", "price_mode": "price", "position": 3 }
]
}
]
}' {
"code": 200,
"title": "OK",
"response": {
"object": "list",
"resource": "product_group",
"dry_run": false,
"data": [
{ "index": 0, "external_id": "iPhone 13", "action": "created", "id": "prgr_06b3e9d2a4c71f58e0b9", "code": null, "title": null, "reason": null, "notice": [] }
],
"created": 1,
"updated": 0,
"unchanged": 0,
"refused": 0,
"total": 1
}
} Read a product group
GET /v1/product-group/import
| Parameter | In | Description |
|---|---|---|
name | query | The group's name. Takes precedence over id. |
id | query | Its identifier, prefixed prgr_. |
With neither, the call answers 400 Missing required parameter: name.; an unknown group answers 404 Product group not found.
curl "https://api.ultineo.com/v1/product-group/import?name=iPhone%2013" \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"object": "product_group",
"id": "prgr_06b3e9d2a4c71f58e0b9",
"name": "iPhone 13",
"status": "activated",
"axes": [
{ "id": "pgax_7f1e0c39b52a4d86e3a1", "name": "", "kind": "pill", "source": "characteristic", "attribute": "attr_9e2f41c07a3b5d8e6f10", "price_mode": "", "position": 1, "status": "activated" },
{ "id": "pgax_b2d84a61f0e9c357a1d4", "name": "", "kind": "swatch", "source": "characteristic", "attribute": "attr_3a7c0d95e1f24b6a8c52", "price_mode": "", "position": 2, "status": "activated" },
{ "id": "pgax_e05c7b3a9d1f6248b0c6", "name": "", "kind": "card", "source": "grade", "attribute": "", "price_mode": "price", "position": 3, "status": "activated" }
],
"created_at": 1789144034,
"updated_at": 1789144034
}
} Products
A product is a sellable item, recognised by its SKU. It can carry condition grades (each with its own price and stock), characteristics and photos already uploaded to your media.
Import products
POST /v1/product/import
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | Yes | Your SKU, up to 190 bytes. external_id is accepted as an alias. |
currency | string | For a new product | The currency of every price of the product, such as EUR. |
name | string | No | Up to 190 bytes. Defaults to the SKU. |
description | string | No | The product description. |
ean | string | No | The barcode. |
status | string | No | draft (default), activated or suspended. |
category | string | No | The identifier of a category of your shop (cate_…). |
group | string | No | The identifier of a product group of your shop (prgr_…). |
brand | string | No | The identifier of a brand of your shop. |
warranty | string | No | The identifier of a warranty of your shop. |
condition | string | No | A short condition label, up to 20 bytes. |
price | number or string | No | Zero or more, with up to 2 decimals. |
price_compare | number or string | No | The reference price shown crossed out, same format. |
weight | number or string | No | Zero or more, with up to 3 decimals. |
stock | integer | No | Units in stock. |
stock_threshold | integer | No | The low-stock alert level. |
slug | string | No | The address segment. Generated from the name when left empty. |
seo_title | string | No | Up to 190 bytes. |
seo_description | string | No | The search-engine description. |
seo_image | string | No | The identifier of the image shared on social networks. |
badge | string | No | The product's badges. |
tag | string | No | The product's tags. |
note | string | No | An internal note. |
created_at | integer | No | The original creation time, in Unix seconds. Kept on creation and never changed by a later import. |
grades | array | No | The whole list of condition grades (see below). |
attributes | array | No | Characteristic values (see below). |
media | array | No | The whole list of photos (see below). |
Each entry of grades:
| Field | Type | Required | Description |
|---|---|---|---|
grade | string | Yes | new, like_new, very_good, good or poor, once each. |
price | number or string | No | The grade's price. |
stock | integer | No | The grade's stock. |
status | string | No | activated (default) or suspended. |
When a product has grades, its price is the lowest grade price and its stock is the sum of the grades' stock; a top-level price or stock you send is then not applied, and a notice says so.
Each entry of attributes:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | The characteristic, by identifier or name. |
value | string | Yes | The value, up to 190 bytes. When the characteristic has a list of values, it must be one of them. |
unit | string | No | A unit, up to 50 bytes. |
position | integer | No | Display order. |
Only the characteristics named in attributes are updated; the product's other characteristics are kept.
Each entry of media:
| Field | Type | Required | Description |
|---|---|---|---|
file | string | Yes | The identifier of a file already uploaded to your media. |
is_main | boolean | No | true for the cover photo; at most one per product. |
position | integer | No | Display order. Defaults to the order of the list. |
Note — The API attaches photos that are already in your media; it does not upload files. A deleted product keeps its status: an import never brings it back.
curl https://api.ultineo.com/v1/product/import \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"sku": "IP13-128-MID",
"name": "iPhone 13 128 GB Midnight",
"currency": "EUR",
"status": "activated",
"category": "cate_4be1a07f93c2d58e6a10",
"group": "prgr_06b3e9d2a4c71f58e0b9",
"price_compare": "729.00",
"weight": 0.174,
"grades": [
{ "grade": "very_good", "price": 459.00, "stock": 6 },
{ "grade": "good", "price": 419.00, "stock": 3 }
],
"attributes": [
{ "type": "Capacity", "value": "128 GB" },
{ "type": "Colour", "value": "Midnight" }
],
"media": [
{ "file": "file_2c7e91a0d4b3f56e8a17", "is_main": true }
]
}
]
}' {
"code": 200,
"title": "OK",
"response": {
"object": "list",
"resource": "product",
"dry_run": false,
"data": [
{ "index": 0, "external_id": "IP13-128-MID", "action": "created", "id": "prod_a81f3c6e0b2d49e7c5f0", "code": null, "title": null, "reason": null, "notice": [] }
],
"created": 1,
"updated": 0,
"unchanged": 0,
"refused": 0,
"total": 1
}
} Note — In a dry run, the product's own fields are compared with what is stored; its
grades,attributesandmediaare checked but not compared.
Read a product
GET /v1/product/import
| Parameter | In | Description |
|---|---|---|
sku | query | The product's SKU (external_id is accepted as an alias). Takes precedence over id. |
id | query | Its identifier, prefixed prod_. |
With neither, the call answers 400 Missing required parameter: sku.; an unknown product answers 404 Product not found. A product deleted in your workspace is still returned, with "status": "deleted".
curl "https://api.ultineo.com/v1/product/import?sku=IP13-128-MID" \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"object": "product",
"id": "prod_a81f3c6e0b2d49e7c5f0",
"sku": "IP13-128-MID",
"status": "activated",
"name": "iPhone 13 128 GB Midnight",
"description": "",
"ean": "",
"category": "cate_4be1a07f93c2d58e6a10",
"group": "prgr_06b3e9d2a4c71f58e0b9",
"brand": "",
"warranty": "",
"condition": "",
"price": "419.00",
"price_compare": "729.00",
"currency": "EUR",
"weight": "0.174",
"stock": 9,
"stock_threshold": 0,
"slug": "iphone-13-128-gb-midnight-1842",
"seo_title": "",
"seo_description": "",
"seo_image": "",
"badge": "",
"tag": "",
"note": "",
"created_at": 1789144100,
"updated_at": 1789144100
}
} The read returns the product's own fields; its grades, characteristics and photos are not part of the response.
Customer reviews
A review is a rating and comment left by a customer on a product. Import the review history of your former shop, linked to the customer, and optionally the order, it came from.
Import reviews
POST /v1/product-review/import
| Field | Type | Required | Description |
|---|---|---|---|
external_id | string | Yes | Your reference for the review, up to 50 characters, unique in the batch. |
customer_external_id | string | Yes | The external_id of an already imported customer. |
product_sku | string | Yes | The SKU of the reviewed product. An SKU not yet in your catalog is accepted and the review is kept with the SKU only; a notice says so. |
rating | integer | Yes | A whole number from 1 to 5. |
order_external_id | string | No | The external_id of an already imported order. A named order that does not exist refuses the review. |
order_item_external_id | string | No | The external_id of a line of that order. Requires order_external_id. |
name | string | No | The reviewer's display name, up to 50 characters. |
text | string | No | The review text. |
lang | string | No | The language as a three-letter ISO 639-3 code, such as fra or eng. |
status | string | No | pending (default for a new review), published or rejected. deleted withdraws a review already imported. |
created_at | integer | No | The original date, in Unix seconds. Left out, the order's date is used. Never changed by a later import. |
curl https://api.ultineo.com/v1/product-review/import \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_id": "REV-88412",
"customer_external_id": "C1001",
"order_external_id": "ORD-10458",
"product_sku": "IP13-128-MID",
"rating": 5,
"name": "Claire M.",
"text": "As good as new, battery at 92%.",
"lang": "eng",
"status": "published",
"created_at": 1781517600
}
]
}' {
"code": 200,
"title": "OK",
"response": {
"object": "list",
"resource": "product_review",
"dry_run": false,
"data": [
{ "index": 0, "external_id": "REV-88412", "action": "created", "id": "prvw_f3a90b1d6c2e47e85a03", "code": null, "title": null, "reason": null, "notice": [] }
],
"created": 1,
"updated": 0,
"unchanged": 0,
"refused": 0,
"total": 1
}
} Read a review
GET /v1/product-review/import
| Parameter | In | Description |
|---|---|---|
external_id | query | The review's reference in your former system. Takes precedence over id. |
id | query | Its identifier, prefixed prvw_. |
With neither, the call answers 400 Missing required parameter: external_id.; an unknown review answers 404 Product review not found. A withdrawn review is still returned, with "status": "deleted".
curl "https://api.ultineo.com/v1/product-review/import?external_id=REV-88412" \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"id": "prvw_f3a90b1d6c2e47e85a03",
"object": "product_review",
"external_id": "REV-88412",
"status": "published",
"customer": "cust_c3e9a1074b5d28f6e0a7",
"order": "ordr_5e0a8c2f71d94b36e1c8",
"order_item": "",
"product": "prod_a81f3c6e0b2d49e7c5f0",
"product_sku": "IP13-128-MID",
"name": "Claire M.",
"rating": 5,
"text": "As good as new, battery at 92%.",
"lang": "eng",
"created_at": 1781517600,
"updated_at": 1791745200
}
} customer, order, order_item and product are identifiers in your shop; order, order_item and product are empty when the review is not linked to one.
Note — The catalog endpoints do not list, search or delete records. Brands and warranties are created in your workspace and only referred to here, and stock movements stay in your workspace.