Your first API call
The Ultineo API lets your own tools fill your shop and read it back: a migration script, an ERP sync or a supplier feed sends records in batches, and reads any single record to check what was saved. It is a JSON-over-HTTPS API. You authenticate with your shop's API key, call a path under /v1/, and read the result from one response envelope.
Before you start — You need an API key for your shop (it starts with
sk-project-) holding the read and/or write scopes of the resources you call, and a way to send an HTTP request with a JSON body, such ascurl.
Base URL
Every request goes to:
https://api.ultineo.com/v1/ Only HTTPS is served. A request on http:// is answered with a 301 redirect to the same path on https://.
Important — Your key travels in a request header, so a single plaintext request discloses it in full. Always call the
https://address directly rather than relying on the redirect.
One endpoint needs no key, and you can use it to check that the host is reachable:
curl https://api.ultineo.com/health {
"code": 200,
"title": "OK",
"response": "api gateway"
} Authenticate
Send your key as a Bearer token on every call:
| Header | Value |
|---|---|
Authorization | Bearer sk-project-… |
Content-Type | application/json (only when you send a body) |
A key belongs to one shop. Every read and every write is limited to that shop, whatever you put in the URL or the body: a record of another shop answers 404, exactly as a record that does not exist. A key can also be disabled or carry an expiry date; a disabled or expired key is refused (see Error codes).
Scopes
Each resource has one read scope and one write scope. A GET needs the resource's :read scope and a POST needs its :write scope.
| Resource | Read scope | Write scope |
|---|---|---|
| Characteristics | attribute:read | attribute:write |
| Categories | category:read | category:write |
| Product groups | product_group:read | product_group:write |
| Products | product:read | product:write |
| Product reviews | product_review:read | product_review:write |
| Customers | customer:read | customer:write |
| Orders | order:read | order:write |
| Payments | payment:read | payment:write |
| Shipments | shipment:read | shipment:write |
| Carrier claims | shipment_claim:read | shipment_claim:write |
| Refused parcels | shipment_refused:read | shipment_refused:write |
| Refunds | refund:read | refund:write |
| Order returns | order_return:read | order_return:write |
| Customer wallet | customer_wallet:read | customer_wallet:write |
| Support requests | support_request:read | support_request:write |
| Vouchers | promotion:read | promotion:write |
A key may also hold <resource>:*, which grants both actions on that resource, or *, which grants everything. A call that is short of its scope is refused with 403 and a message naming the missing scope.
Note — Vouchers use the
promotion:readandpromotion:writescopes, even though their path is/v1/voucher/import.
Make the call
This reads back one category of your shop by its name:
curl "https://api.ultineo.com/v1/category/import?name=Smartphones" \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"object": "category",
"id": "cate_4be1a07f93c2d58e6a10",
"name": "Smartphones",
"slug": "smartphones-cate_4be1a07f93c2d58e6a10",
"description": "Refurbished smartphones, tested and graded.",
"parent": "",
"image": "",
"position": 1,
"status": "activated",
"seo_title": "",
"seo_description": "",
"seo_image": "",
"reference": "",
"warranty": "",
"created_at": 1789143990,
"updated_at": 1789143990
}
} The response envelope
Every endpoint answers with the same three keys, on success and on failure alike. Read the HTTP status code first, then read response.
| Field | Type | Description |
|---|---|---|
code | integer | The HTTP status code, repeated in the body. |
title | string | The short status label, such as OK or Not Found. |
response | object or string | The record or the import report on success; an explanatory message on failure. |
How every import works
Every resource is written through the same POST contract, so once you have imported one resource you know how to import them all.
The request body
{
"dry_run": true,
"items": [
{ "name": "Smartphones" },
{ "name": "Tablets" }
]
} | Field | Type | Required | Description |
|---|---|---|---|
items | array | Yes | The records to import, one object each. It must be a non-empty list, no longer than the resource's batch limit. |
dry_run | boolean | No | true checks the whole batch and reports what would happen, without saving anything. Defaults to false. |
The body must be valid JSON and no larger than 200,000 bytes. Each resource has its own batch limit, from 25 to 500 items, given in its endpoint table. Larger sets are sent as several batches.
Send it again without duplicates
Each record is recognised by its own key — its reference in your former system (external_id), or its SKU or name for the catalog. Sending a record that is already in your shop updates it if something changed, or reports it as unchanged; it is never created twice. You can therefore replay an interrupted import from the beginning. An update only changes the fields you sent; fields you leave out keep their stored value.
Test with dry_run
With "dry_run": true you get the full report — what would be created, updated, left unchanged or refused — and nothing is written. Always check the dry_run field of the response before you trust a report.
The import report
{
"code": 200,
"title": "OK",
"response": {
"object": "list",
"resource": "category",
"dry_run": false,
"data": [
{
"index": 0,
"external_id": "Smartphones",
"action": "updated",
"id": "cate_4be1a07f93c2d58e6a10",
"code": null,
"title": null,
"reason": null,
"notice": []
},
{
"index": 1,
"external_id": "Tablets",
"action": "refused",
"id": null,
"code": 422,
"title": "Unprocessable Entity",
"reason": "status must be one of: activated, suspended.",
"notice": []
}
],
"created": 0,
"updated": 1,
"unchanged": 0,
"refused": 1,
"total": 2
}
} data holds one entry per item, in the order you sent them:
| Field | Type | Description |
|---|---|---|
index | integer | The item's position in your items array, from 0. |
external_id | string or null | The key the item was recognised by. |
action | string | created, updated, unchanged or refused. |
id | string or null | The record's identifier in your shop. null when refused, and for a creation in a dry run. |
code | integer or null | For a refused item, the status code of the refusal. |
title | string or null | For a refused item, the status label of the refusal. |
reason | string or null | For a refused item, what to correct. |
notice | array | Informational messages about an item that was accepted, such as a value that was adjusted. |
The counters created, updated, unchanged and refused add up to total, the number of items you sent. A refused item never blocks the others: the call answers 200 as soon as at least one item was accepted, and 422 only when every item was refused. So read the report line by line even on a 200.
Some resources add their own fields to each entry, such as the lines of an order or the codes of a voucher; they are described in each resource's topic.
Reading a record back
Each resource reads back ONE record at a time with a GET on its import path, naming the record by its key (such as ?external_id=…) or by its identifier in your shop (?id=…). The parameters each resource accepts are given in its topic.
Note — The API reads one record at a time: there is no list, no search and no pagination. It does not delete anything either: deleting is done in your workspace, and an import never brings a deleted record back.
Dates and amounts
Timestamps, such as created_at, are sent and returned as integer numbers of seconds since the Unix epoch, in UTC; a date in another format is refused, not converted. In a response, 0 means the date is not set. Amounts are returned as strings holding a decimal number, such as "129.90", so that no precision is lost.
Recommended import order
Records refer to each other, so import the records others depend on first:
- Catalog: characteristics, then categories, then product groups, then products, then reviews — see Catalog.
- Sales history: customers, then orders, then payments and shipments, then refunds, returns, carrier claims, refused parcels, wallet movements and support requests — see Orders & logistics.
- Vouchers, once the customers their codes are reserved for are imported — see Vouchers.