Public API documentation
Ultra B2B API V1
Public V1 endpoints for partner integrations. The API covers authentication,
product catalog data, stock quantities, catalog changes, brands, categories, and orders.
All routes use the /api base path.
Basics
Base URL
Use your assigned Ultra host with the API prefix:
https://eshop.ultra.md/api
Headers
Send JSON and include the bearer token on protected endpoints:
Accept: application/json
Content-Type: application/json
Authorization: Bearer <access_token>
X-API-Token: <access_token> is also accepted for protected V1 routes.
Authentication
POST /api/auth/token and POST /api/auth/refresh are public.
All other V1 routes in this document require a valid bearer token and use the
bearer-api throttle. Access tokens expire after 1 hour. Refresh tokens expire
after 30 days and rotate on every refresh.
Create tokens
POST /api/auth/token
Request body:
{
"username": "dealer@example.com",
"password": "secret"
}
Response status: 200 OK
{
"access_token": "1|plain-access-token",
"refresh_token": "plain-refresh-token",
"expires_in": 3600,
"token_type": "Bearer",
"user_email": "dealer@example.com",
"user_id": 10
}
| Response field | Description |
|---|---|
access_token | Bearer token used in protected V1 API requests. |
refresh_token | Long-lived token used only to request a new access token. |
expires_in | Access token lifetime in seconds. |
token_type | Always Bearer. |
user_email | Email of the authenticated dealer account. |
user_id | Internal user ID for support and integration diagnostics. |
Refresh access token
POST /api/auth/refresh
Request body:
{
"refresh_token": "plain-refresh-token"
}
Response status: 200 OK
{
"access_token": "2|new-plain-access-token",
"refresh_token": "new-plain-refresh-token",
"expires_in": 3600,
"token_type": "Bearer"
}
Revoke access token
With an empty payload, the current access token is revoked.
POST /api/auth/revoke
Request body:
{}
To revoke a specific access token owned by the current user, pass the plain token value:
Request body:
{
"token": "2|plain-access-token"
}
Response status: 200 OK
{
"message": "Token revoked successfully"
}
Endpoints
| Method | Path | Authentication | Success status | Purpose |
|---|---|---|---|---|
| POST | /api/auth/token | No | 200 OK | Create access and refresh tokens. |
| POST | /api/auth/refresh | No | 200 OK | Rotate refresh token and get a new access token. |
| POST | /api/auth/revoke | Bearer | 200 OK | Revoke the current or specified access token. |
| GET | /api/health | Bearer | 200 OK | Check API service status. |
| GET | /api/product | Bearer | 200 OK | List products with filters and pagination. |
| POST | /api/product/batch | Bearer | 200 OK | Fetch products by identifiers. |
| GET | /api/product/{id} | Bearer | 200 OK | Fetch one product by ultra_code or ultra_uuid. |
| GET | /api/category | Bearer | 200 OK | List categories. |
| GET | /api/category/extra-prices | Bearer | 200 OK | List category extra prices for the authenticated user's store. |
| GET | /api/category/{id} | Bearer | 200 OK | Fetch one category by ultra_code or ultra_uuid. |
| GET | /api/quantity | Bearer | 200 OK | List product quantities. |
| POST | /api/quantity/batch | Bearer | 200 OK | Fetch quantities by identifiers. |
| GET | /api/quantity/{id} | Bearer | 200 OK | Fetch one product quantity. |
| GET | /api/brand | Bearer | 200 OK | List brands. |
| GET | /api/brand/{id} | Bearer | 200 OK | Fetch one brand by ultra_code or ultra_uuid. |
| GET | /api/changes | Bearer | 200 OK | Read incremental catalog changes. |
| POST | /api/order | Bearer | 200 OK | Create an order. |
| GET | /api/order | Bearer | 200 OK | List current user's orders. |
| GET | /api/order/{id} | Bearer | 200 OK | Fetch one current-user order with items. |
| GET | /api/orders/all | Bearer | 200 OK | List store-associated orders when permitted. |
Products
List products
| Query parameter | Type | Default | Description |
|---|---|---|---|
category | string | null | Category ultra_uuid or ultra_code. |
brand | string | null | Brand ultra_uuid or ultra_code. |
min_price | number | null | Minimum price. |
max_price | number | null | Maximum price. Must be greater than or equal to min_price. |
min_quantity | integer | null | Minimum quantity. |
in_stock | boolean | null | When true, returns products with quantity greater than 0. |
updated_since | date | null | Returns products updated after this date. |
sort | enum | updated_at_desc | price_asc, price_desc, name_asc, name_desc, updated_at_desc, or updated_at. |
limit | integer | 100 | Page size from 1 to 1000. |
offset | integer | 0 | Pagination offset. |
load_without_image | boolean | true | When false, products without images may be excluded. |
GET /api/product?category=CAT-001&in_stock=true&limit=50&offset=0
Response status: 200 OK
[
{
"ultra_code": "PRD-001",
"ultra_uuid": "product-uuid",
"product_name": {"ro": "Produs", "ru": "Product"},
"description": "Description",
"description_by_language": {
"romanian": "Descriere",
"russian": "Opisanie",
"english": "Description"
},
"quantity": 12,
"image_urls": ["https://cdn.example.com/product.jpg"],
"brand": {
"uuid": "brand-uuid",
"name": "Brand",
"updated_at": "2026-06-15T10:30:00"
},
"category": {
"uuid": "category-uuid",
"name": {"ro": "Categorie", "ru": "Category"},
"updated_at": "2026-06-15T10:30:00",
"level": null,
"hierarchy": []
},
"user_price": null,
"fixed_price": null,
"price_d": null,
"promo_b2b": null,
"property_groups": [],
"updated_at": "2026-06-15T10:30:00",
"created_at": "2026-06-01T10:30:00"
}
]
| Response field | Where it maps on the site |
|---|---|
ultra_code | Public product code used by integrations and usually shown as the product code/reference. |
ultra_uuid | Internal stable product UUID. Use it when your system stores UUID identifiers. |
product_name | Localized product title shown in product cards, category listings, search results, and product pages. |
description | Main product description used on the product details page. |
description_by_language | Localized description values for Romanian, Russian, and English when available. |
quantity | Available stock used for availability labels and quantity checks before ordering. |
image_urls | Product gallery images used in cards and on product pages. |
brand.uuid, brand.name | Brand attached to the product; used for brand filters and brand pages. |
category.uuid, category.name | Category attached to the product; used for category pages, filters, and breadcrumbs. |
category.hierarchy | Category tree used to build navigation or breadcrumbs when hierarchy data exists. |
user_price | Dealer-specific price visible to the authenticated user when configured. |
fixed_price | Fixed product price override when configured. |
price_d | Distributor/dealer price data when available. |
promo_b2b | B2B promotional price or promo data when active. |
property_groups | Grouped product characteristics/specifications shown on product pages. |
updated_at, created_at | Timestamps for synchronization, cache invalidation, and freshness checks. |
Fetch one product
GET /api/product/PRD-001
Response status: 200 OK
The response is a single product object with the same fields as the list endpoint. If the product is not found, the API returns 404 Not Found.
Fetch products in batch
ultra_codes accepts up to 1000 values. Each value can be an ultra_code or ultra_uuid.
POST /api/product/batch
Request body:
{
"ultra_codes": ["PRD-001", "PRD-002"]
}
Response status: 200 OK
The response is an array of product objects with the same fields as GET /api/product.
Categories
List categories
Use code to filter by category ultra_code.
GET /api/category?code=CAT-001
Response status: 200 OK
[
{
"uuid": "category-uuid",
"code": "CAT-001",
"name": {"ro": "Categorie", "ru": "Category"},
"product_count": 120,
"updated_at": "2026-06-15T10:30:00"
}
]
| Response field | Where it maps on the site |
|---|---|
uuid | Stable category UUID used by integrations and product filters. |
code | Category code used in API filters and external catalog synchronization. |
name | Localized category title shown in navigation, category pages, and filters. |
product_count | Number of products in the category; useful for category counters and filters. |
updated_at | Category freshness timestamp for synchronization. |
Fetch one category
Pass include_products=true to include a compact products list.
GET /api/category/CAT-001?include_products=true
Response status: 200 OK
{
"uuid": "category-uuid",
"code": "CAT-001",
"name": {"ro": "Categorie", "ru": "Category"},
"product_count": 2,
"updated_at": "2026-06-15T10:30:00",
"products": [
{
"ultra_code": "PRD-001",
"product_name": {"ro": "Produs", "ru": "Product"},
"quantity": 12,
"price": {
"amount": 100,
"currency": "MDL"
}
}
]
}
products contains compact product rows for category previews or quick category synchronization. Product detail pages should still use GET /api/product/{id}.
Category extra prices
The authenticated user must be associated with a store.
GET /api/category/extra-prices
Response status: 200 OK
[
{
"id": 1,
"category": {
"uuid": "category-uuid",
"code": "CAT-001",
"name": {"ro": "Categorie", "ru": "Category"}
},
"price_extra": 5
}
]
| Response field | Where it maps on the site |
|---|---|
category | Category to which the store-specific extra price rule applies. |
price_extra | Extra price percentage/value configured for the authenticated user's store and category. |
Quantities
Quantity identifiers can be ultra_code or ultra_uuid.
| Endpoint | Parameters or payload | Response |
|---|---|---|
GET /api/quantity | category, low_stock | Array of quantity objects. |
GET /api/quantity/{id} | Path identifier. | One quantity object. |
POST /api/quantity/batch | {"ultra_codes": ["PRD-001"]} | Array of quantity objects. |
List quantities
GET /api/quantity?category=CAT-001&low_stock=5
Response status: 200 OK
Fetch one quantity
GET /api/quantity/PRD-001
Response status: 200 OK
Fetch quantities in batch
POST /api/quantity/batch
Request body:
{
"ultra_codes": ["PRD-001", "PRD-002"]
}
Response status: 200 OK
{
"ultra_code": "PRD-001",
"product_name": {"ro": "Produs", "ru": "Product"},
"quantity": 12,
"updated_at": "2026-06-15T10:30:00"
}
| Response field | Where it maps on the site |
|---|---|
ultra_code | Product code used to match stock data with products in your system. |
product_name | Localized product title for stock reports or availability views. |
quantity | Available stock used for "in stock" labels, quantity selectors, and order validation. |
updated_at | Last product stock update timestamp. |
Brands
Use GET /api/brand to list brands and GET /api/brand/{id} to fetch one brand.
GET /api/brand?code=BR-001
Response status: 200 OK
[
{
"code": "BR-001",
"uuid": "brand-uuid",
"name": "Brand",
"product_count": 25,
"updated_at": "2026-06-15T10:30:00"
}
]
Fetch one brand
GET /api/brand/BR-001
Response status: 200 OK
The response is a single brand object with the same fields as the list endpoint.
| Response field | Where it maps on the site |
|---|---|
code | Brand code used in API filters and external synchronization. |
uuid | Stable brand UUID used to connect products to a brand. |
name | Brand name shown in product cards, product pages, brand filters, and brand pages. |
product_count | Number of products attached to the brand. |
updated_at | Brand freshness timestamp for synchronization. |
Changes
Use GET /api/changes for incremental synchronization. Store the returned
next_since and use it as the next request's since value while
has_more is true.
| Parameter | Type | Required | Description |
|---|---|---|---|
since | date | Yes | Return changes after this date. |
entity | enum | No | product, price, quantity, category, or brand. |
limit | integer | No | 1 to 1000. Default is 100. |
GET /api/changes?since=2026-06-15T00:00:00&entity=product&limit=100
Response status: 200 OK
{
"changes": [
{
"entity": "product",
"action": "updated",
"entity_id": "PRD-001",
"changed_at": "2026-06-15T10:30:00.000000Z",
"fields_changed": ["product"]
}
],
"next_since": "2026-06-15T10:30:00.000000Z",
"has_more": false
}
| Response field | Where it maps on the site |
|---|---|
changes | List of catalog entities that changed and should be refreshed in your local system. |
entity | Changed entity type: product, price, quantity, category, or brand. |
action | Change action, for example updated. |
entity_id | Identifier to use when reloading the changed product, category, brand, price, or stock data. |
changed_at | Timestamp of the change. |
fields_changed | High-level fields or data groups affected by the change. |
next_since | Use this value as the next request's since parameter. |
has_more | When true, request the next page with next_since. |
Orders
Create order
| Field | Type | Required | Description |
|---|---|---|---|
delivery | string or integer | Yes | Delivery method accepted by the store configuration. |
payment | string or integer | Yes | Payment method accepted by the store configuration. |
shipping_data | object | No | Optional customer, store, branch, address, city, and recipient data. |
products | array | Yes | 1 to 1000 order lines. |
products.*.ultra_uuid | string | Conditional | Required when ultra_code is not provided. |
products.*.ultra_code | string | Conditional | Required when ultra_uuid is not provided. |
products.*.quantity | integer | Yes | Quantity, minimum 1. |
POST /api/order
Request body:
{
"delivery": "pickup",
"payment": "cash",
"shipping_data": {
"storeId": "1",
"filialId": "2",
"client_type": "1",
"name": "Customer Name",
"phone": "+37300000000",
"address": "Street 1",
"city": "Chisinau",
"recipient": "Customer Name"
},
"products": [
{"ultra_code": "PRD-001", "quantity": 2},
{"ultra_uuid": "product-uuid", "quantity": 1}
]
}
Response status: 200 OK
{
"message": "Order created successfully",
"order_id": 12345
}
| Response field | Where it maps on the site |
|---|---|
message | Human-readable order creation result. |
order_id | Internal order ID used to open the order with GET /api/order/{id} and for support communication. |
Read orders
GET /api/orderreturns orders that belong to the authenticated user.GET /api/order/{id}returns one authenticated-user order and includes its items.GET /api/orders/allreturns store-associated orders when the user has access.
GET /api/order
GET /api/order/12345
GET /api/orders/all
Response status: 200 OK
{
"order_id": 12345,
"user_id": 10,
"delivery": "pickup",
"payment": "cash",
"total": 1200.5,
"status": 1,
"created_at": "2026-06-15T10:30:00",
"updated_at": "2026-06-15T10:45:00",
"shipping_data": {
"phone": "+37300000000",
"name": "Customer Name",
"storeId": "1",
"filialId": "2",
"client_type": "1",
"delivery_status": null,
"destination": null
},
"exchange_rate": null,
"items": [
{
"product_ultra_uuid": "product-uuid",
"product_name": {"ro": "Produs", "ru": "Product"},
"quantity": 2,
"price": 600.25
}
]
}
| Response field | Where it maps on the site |
|---|---|
order_id | Order ID shown in order history and used for order details. |
user_id, user_name | Customer/dealer owner of the order. Some fields are returned only for detailed or associated-order views. |
delivery | Selected delivery method displayed in checkout and order details. |
payment | Selected payment method displayed in checkout and order details. |
total | Order total shown in order history and order details. |
status | Numeric order status used to render the current order state. |
shipping_data | Delivery/contact data captured during checkout. |
ultra_uuid, reserve_ultra_uuid, order_code | External/order-system identifiers returned for store-associated order views when available. |
exchange_rate | Exchange rate used for the order when applicable. |
items | Order line items returned by GET /api/order/{id}. |
items.*.product_ultra_uuid | Product UUID for each ordered item. |
items.*.product_name | Product title shown in order item rows. |
items.*.quantity | Ordered quantity for the item. |
items.*.price | Unit price stored on the order item. |
Health
GET /api/health
Response status: 200 OK
{
"status": "healthy",
"version": "1.0.0",
"timestamp": "2026-06-15T10:30:00",
"services": {
"database": "healthy",
"cache": "healthy",
"auth": "healthy"
},
"uptime_seconds": 1
}
| Response field | Description |
|---|---|
status | Overall API health: healthy or degraded. |
version | Application version configured on the server. |
timestamp | Server-side health check timestamp. |
services.database | Database connectivity status. |
services.cache | Cache service read/write status. |
services.auth | Authentication provider configuration status. |
uptime_seconds | Request/runtime uptime value reported by the application. |
Errors and Status Codes
Error format
{
"message": "Bad Request",
"errors": {
"field": ["Validation message"]
}
}
{
"message": "Invalid or expired token."
}
Status codes
400: validation or business rule error.401: missing, invalid, expired, or revoked token.403: authenticated user is not allowed to access the resource.404: product, category, brand, order, or token was not found.429: rate limit exceeded.500: internal server error.
Integration notes
- Keep access tokens in server-side storage and rotate them through
/auth/refreshbefore expiry. - Use
limitandoffsetfor large catalog reads, and keep page sizes below 1000. - Prefer
/changesfor regular synchronization instead of repeatedly downloading the full catalog. - Retry
429responses with backoff. Do not retry validation errors without changing the request. - Use stable identifiers:
ultra_codefor business integrations andultra_uuidwhen the UUID is already stored in your system.