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

MethodPathScopeBatch limit
POST/v1/attribute/importattribute:write25 characteristics, 400 values each
GET/v1/attribute/importattribute:readOne characteristic
POST/v1/category/importcategory:write50 categories
GET/v1/category/importcategory:readOne category
POST/v1/product-group/importproduct_group:write50 groups, 20 axes each
GET/v1/product-group/importproduct_group:readOne group
POST/v1/product/importproduct:write40 products
GET/v1/product/importproduct:readOne product
POST/v1/product-review/importproduct_review:write250 reviews
GET/v1/product-review/importproduct_review:readOne 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

ResourceRecognised byEchoed in the report's external_id
Characteristicname, without regard to caseThe name
Categoryname, without regard to caseThe name
Product groupnameThe name
ProductskuThe SKU
Reviewexternal_idThe 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

FieldTypeRequiredDescription
namestringYes, except for colourUp to 190 bytes. A colour characteristic is always called Colour.
kindstringNocustom (default) or colour. The kind of an existing characteristic is never changed.
unitstringNoUp to 30 bytes, such as GB. custom characteristics only.
scopestringNocatalog (default), category or product.
scope_identstringWhen scope is not catalogThe identifier of the category or product the characteristic applies to.
positionintegerNoDisplay order.
statusstringNoactivated (default) or suspended.
valuesarrayNoThe whole list of values (see below).

Each entry of values:

FieldTypeRequiredDescription
labelstringYesUp to 190 bytes, unique in the characteristic without regard to case.
hexstringFor colour onlyA colour code of six hexadecimal digits, with or without #. Refused on a custom characteristic.
positionintegerNoDisplay order. Left out, the value goes last.
statusstringNoactivated or suspended.
descriptionstringNoA note about the value, up to 500 characters.
link_labelstringNoThe words of a link shown with the note. Send it together with link_page.
link_pagestringNoThe identifier of one of your shop's text pages the link opens. Send '' with link_label: '' to remove the link.
curl
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" }
        ]
      }
    ]
  }'
application/json
{
  "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

ParameterInDescription
namequeryThe characteristic's name. Takes precedence over id.
idqueryIts identifier, prefixed attr_.

With neither, the call answers 400 Missing required parameter: name.; an unknown characteristic answers 404 Characteristic not found.

curl
curl "https://api.ultineo.com/v1/attribute/import?name=Capacity" \
  -H "Authorization: Bearer sk-project-YOUR_KEY"
application/json
{
  "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

FieldTypeRequiredDescription
namestringYesUp to 190 bytes.
parentstringNoThe 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.
descriptionstringNoThe category's description.
imagestringNoThe identifier of an image already in your media.
positionintegerNoDisplay order.
statusstringNoactivated (default) or suspended.
slugstringNoThe address segment. Generated from the name when left empty.
seo_titlestringNoUp to 190 bytes.
seo_descriptionstringNoThe search-engine description.
seo_imagestringNoThe identifier of the image shared on social networks.
referencestringNoYour own reference, up to 190 bytes.
warrantystringNoThe identifier of a warranty of your shop.

Send parents before their children, in the same batch or an earlier one.

curl
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 }
    ]
  }'
application/json
{
  "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

ParameterInDescription
namequeryThe category's name. Takes precedence over id.
idqueryIts 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

FieldTypeRequiredDescription
namestringYesUp to 190 bytes.
statusstringNoactivated (default) or suspended.
axesarrayNoThe whole list of axes, up to 20 (see below).

Each entry of axes:

FieldTypeRequiredDescription
sourcestringNocharacteristic (default), grade or custom.
attributestringWhen source is characteristicThe characteristic the axis offers, by identifier or name. Refused for the other sources.
namestringWhen source is customThe axis label, up to 190 bytes.
kindstringNoHow options are drawn: pill (default), swatch, card or list. A grade axis is card or list.
price_modestringNoprice, from, diff, none, or '' for the default. pill and swatch axes only accept none; a grade axis does not accept from.
positionintegerNoDisplay order. Left out, the axis goes last.
statusstringNoactivated 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
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 }
        ]
      }
    ]
  }'
application/json
{
  "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

ParameterInDescription
namequeryThe group's name. Takes precedence over id.
idqueryIts identifier, prefixed prgr_.

With neither, the call answers 400 Missing required parameter: name.; an unknown group answers 404 Product group not found.

curl
curl "https://api.ultineo.com/v1/product-group/import?name=iPhone%2013" \
  -H "Authorization: Bearer sk-project-YOUR_KEY"
application/json
{
  "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

FieldTypeRequiredDescription
skustringYesYour SKU, up to 190 bytes. external_id is accepted as an alias.
currencystringFor a new productThe currency of every price of the product, such as EUR.
namestringNoUp to 190 bytes. Defaults to the SKU.
descriptionstringNoThe product description.
eanstringNoThe barcode.
statusstringNodraft (default), activated or suspended.
categorystringNoThe identifier of a category of your shop (cate_…).
groupstringNoThe identifier of a product group of your shop (prgr_…).
brandstringNoThe identifier of a brand of your shop.
warrantystringNoThe identifier of a warranty of your shop.
conditionstringNoA short condition label, up to 20 bytes.
pricenumber or stringNoZero or more, with up to 2 decimals.
price_comparenumber or stringNoThe reference price shown crossed out, same format.
weightnumber or stringNoZero or more, with up to 3 decimals.
stockintegerNoUnits in stock.
stock_thresholdintegerNoThe low-stock alert level.
slugstringNoThe address segment. Generated from the name when left empty.
seo_titlestringNoUp to 190 bytes.
seo_descriptionstringNoThe search-engine description.
seo_imagestringNoThe identifier of the image shared on social networks.
badgestringNoThe product's badges.
tagstringNoThe product's tags.
notestringNoAn internal note.
created_atintegerNoThe original creation time, in Unix seconds. Kept on creation and never changed by a later import.
gradesarrayNoThe whole list of condition grades (see below).
attributesarrayNoCharacteristic values (see below).
mediaarrayNoThe whole list of photos (see below).

Each entry of grades:

FieldTypeRequiredDescription
gradestringYesnew, like_new, very_good, good or poor, once each.
pricenumber or stringNoThe grade's price.
stockintegerNoThe grade's stock.
statusstringNoactivated (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:

FieldTypeRequiredDescription
typestringYesThe characteristic, by identifier or name.
valuestringYesThe value, up to 190 bytes. When the characteristic has a list of values, it must be one of them.
unitstringNoA unit, up to 50 bytes.
positionintegerNoDisplay order.

Only the characteristics named in attributes are updated; the product's other characteristics are kept.

Each entry of media:

FieldTypeRequiredDescription
filestringYesThe identifier of a file already uploaded to your media.
is_mainbooleanNotrue for the cover photo; at most one per product.
positionintegerNoDisplay 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
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 }
        ]
      }
    ]
  }'
application/json
{
  "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, attributes and media are checked but not compared.

Read a product

GET /v1/product/import

ParameterInDescription
skuqueryThe product's SKU (external_id is accepted as an alias). Takes precedence over id.
idqueryIts 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
curl "https://api.ultineo.com/v1/product/import?sku=IP13-128-MID" \
  -H "Authorization: Bearer sk-project-YOUR_KEY"
application/json
{
  "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

FieldTypeRequiredDescription
external_idstringYesYour reference for the review, up to 50 characters, unique in the batch.
customer_external_idstringYesThe external_id of an already imported customer.
product_skustringYesThe 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.
ratingintegerYesA whole number from 1 to 5.
order_external_idstringNoThe external_id of an already imported order. A named order that does not exist refuses the review.
order_item_external_idstringNoThe external_id of a line of that order. Requires order_external_id.
namestringNoThe reviewer's display name, up to 50 characters.
textstringNoThe review text.
langstringNoThe language as a three-letter ISO 639-3 code, such as fra or eng.
statusstringNopending (default for a new review), published or rejected. deleted withdraws a review already imported.
created_atintegerNoThe original date, in Unix seconds. Left out, the order's date is used. Never changed by a later import.
curl
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
      }
    ]
  }'
application/json
{
  "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

ParameterInDescription
external_idqueryThe review's reference in your former system. Takes precedence over id.
idqueryIts 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
curl "https://api.ultineo.com/v1/product-review/import?external_id=REV-88412" \
  -H "Authorization: Bearer sk-project-YOUR_KEY"
application/json
{
  "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.