To start using our API, you'll need to register for an account.
Here you'll find detailed documentation for each available endpoint.
POST /api/v3/products
| Field | Type | Description |
|---|---|---|
id |
integer | External ID of the product (required) |
name |
string | Name of the product (required) |
description |
string | Detailed description of the product |
image |
string | URL of the product image |
suggested_category_name |
string | Suggested category name for the product |
brand |
string | Brand name of the product |
in_stock |
boolean | Whether the product is in stock (default: true). This determines the product's active status along with canonical relationships. |
active |
boolean | Read-only. Computed field: canonical products are active if (canonical.in_stock OR any_duplicate.in_stock), duplicates are always inactive. |
gtin |
string | Global Trade Item Number (GTIN) of the product |
gtins |
array of strings | Array of Global Trade Item Numbers (GTINs) for the product. If gtin is not provided but gtins are, the first GTIN from the array will be used as the primary gtin. |
On success, returns the created product with status code 201 (Created).
On error, returns validation errors with status code 422 (Unprocessable Entity).
curl https://mcp.sortillus.com/api/v3/products \
-X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"id": 6,
"name": "your product name",
"suggested_category_name": "your suggested category name",
"brand": "product brand",
"description": "long product description",
"image": "https://some.com/productid.image.jpg",
"gtin": "1234567890123",
"gtins": ["1234567890123", "1234567890124"]
}
}'
{
"sortillus_id": 101,
"external_id": 6,
"name": "your product name",
"description": "long product description",
"image": "https://some.com/productid.image.jpg",
"canonical_id": null,
"category_external_id": null,
"active": true,
"in_stock": true,
"state": "new",
"updated_at": "2025-06-12T18:08:59Z",
"created_at": "2025-06-12T18:08:58Z",
"recommendations": [],
"recommended_at": null,
"suggest_new_leaf_category": null,
"suggest_category_branch_id": null,
"external_similar_product_id": null,
"similarity": null,
"stats": {
"processed_products": 150,
"free_tier_limit": 1000,
"limit_reached": false,
"limit_warning": null
}
}
PATCH /api/v3/products/:id
Update an existing product by its external ID. Only the provided fields will be updated.
Note: Parameters can be sent either wrapped in a product object or directly at the root level. Both formats are supported for backward compatibility.
updated_at timestamp is preserved and will not change when updating product attributes. This ensures that product update timestamps reflect actual data changes, not API calls.
| Parameter | Type | Description |
|---|---|---|
id |
integer | External ID of the product to update (required) |
| Field | Type | Description |
|---|---|---|
name |
string | Updated name of the product |
description |
string | Updated description of the product |
image |
string | URL of the updated product image. To clear the image, send an empty string "". |
category_id |
string | Full external ID (e.g., "L4_2345") or regular external ID of the category to assign to the product. The endpoint will first try to find by full_external_id, then fall back to external_id if not found. |
in_stock |
boolean | Whether the product is in stock (default: true). This determines the product's active status along with canonical relationships. |
active |
boolean | Read-only. Computed field: canonical products are active if (canonical.in_stock OR any_duplicate.in_stock), duplicates are always inactive. |
canonical |
boolean | Make this product the canonical for its cluster (true) or leave unchanged (false/not provided) |
external_canonical_product_id |
string | External ID of the product to use as canonical for this product's cluster |
gtin |
string | Global Trade Item Number (GTIN) of the product. To clear the GTIN, send an empty string "". If both gtin and gtins are provided, gtin takes precedence. |
gtins |
array of strings | Array of Global Trade Item Numbers (GTINs) for the product. If gtin is not provided but gtins are, the first non-empty GTIN from the array will be used as the primary gtin. To clear all GTINs, send an empty array []. |
On success, returns status code 200 (OK) with an empty response body. The update is queued for processing in the background.
If the product is not found, returns status code 404 (Not Found) with an error message.
If required parameters are missing (e.g., missing product wrapper when using strict format), returns status code 400 (Bad Request).
Note: The id parameter in the request body (if provided) is ignored. The product is identified by the id in the URL path only.
Async Behavior: Since updates are processed asynchronously, validation errors (e.g., category not found, external canonical product not found) are handled internally by the background job and logged. The endpoint always returns 200 OK if the product exists and the request is valid. To verify the update was successful, query the product using GET /api/v3/products/:id after a short delay.
updated_at Preservation: The updated_at field is not modified when updating product attributes. This ensures timestamps reflect actual data changes rather than API activity.
curl https://mcp.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"name": "Updated product name",
"description": "Updated product description",
"active": true,
"category_id": "L4_2345"
}
}'
curl https://mcp.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"name": "Updated product name",
"description": "Updated product description",
"active": true,
"category_id": "L4_2345"
}'
curl https://mcp.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"gtin": "1234567890123",
"gtins": ["1234567890123", "9876543210987"]
}
}'
curl https://mcp.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"gtin": "",
"gtins": []
}
}'
curl https://mcp.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"image": ""
}
}'
curl https://mcp.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"canonical": true
}
}'
curl https://mcp.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"external_canonical_product_id": "123"
}
}'
The endpoint returns an empty response body (200 OK) since updates are processed asynchronously.
(empty response body)
To verify the update was successful, query the product after a short delay:
curl https://mcp.sortillus.com/api/v3/products/6 \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
Example response after update:
{
"sortillus_id": 101,
"external_id": 6,
"name": "Updated product name",
"description": "Updated product description",
"image": "https://some.com/productid.image.jpg",
"canonical_id": null,
"category_external_id": "L4_2345",
"sortillus_category_id": 123,
"active": true,
"state": "ready",
"updated_at": "2025-06-12T18:08:59Z",
"created_at": "2025-06-12T18:08:58Z",
"recommendations": [],
"recommended_at": null
}
Note: The updated_at timestamp shown above is the original timestamp - it will not change when updating product attributes.
Retrieve detailed information about a specific product by its external ID.
| Parameter | Type | Description |
|---|---|---|
id |
integer | External ID of the product to retrieve (required) |
On success, returns the product details with status code 200 (OK).
If the product is not found, returns status code 404 (Not Found).
The response includes the following fields:
| Field | Type | Description |
|---|---|---|
sortillus_id |
integer | Internal product ID (sortillus_id) |
external_id |
integer | External product ID |
name |
string | Product name |
description |
string | Product description |
image |
string | Product image URL |
canonical_id |
integer/null | External ID of the canonical product if this product is a duplicate; null for canonical products |
category_external_id |
string/integer/null | External ID or full external ID of the product's category (if any) |
active |
boolean | Read-only. Computed field: canonical products are active if (canonical.in_stock OR any_duplicate.in_stock), duplicates are always inactive |
in_stock |
boolean | Whether the product is in stock |
state |
string | Current AASM processing state |
updated_at |
string (ISO8601) | Last update timestamp |
created_at |
string (ISO8601) | Creation timestamp |
recommendations |
array | Array of recommended products (see below for structure) |
recommended_at |
string (ISO8601)/null | Timestamp when recommendations were generated (null if not yet recommended) |
suggest_new_leaf_category |
boolean/null | Whether a new leaf category was suggested |
suggest_category_branch_id |
integer/null | Suggested category branch ID |
external_similar_product_id |
integer/null | External ID of a similar product |
similarity |
float/null | Similarity score |
stats |
object | Domain processing statistics (processed_products, free_tier_limit, limit_reached, limit_warning) |
The recommendations array contains objects with the following structure:
| Field | Type | Description |
|---|---|---|
product_id |
integer | External ID of the recommended product (not the internal sortillus_id) |
explanation |
string | Human-readable explanation for why this product is recommended |
reason_json |
object (optional) | Additional metadata about the recommendation (e.g., similarity score, source) |
curl https://mcp.sortillus.com/api/v3/products/6 \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
{
"sortillus_id": 101,
"external_id": 6,
"name": "your product name",
"description": "long product description",
"image": "https://some.com/productid.image.jpg",
"canonical_id": null,
"category_external_id": null,
"active": true,
"in_stock": true,
"state": "new",
"updated_at": "2025-06-12T18:08:59Z",
"created_at": "2025-06-12T18:08:58Z",
"recommendations": [],
"recommended_at": null,
"suggest_new_leaf_category": null,
"suggest_category_branch_id": null,
"external_similar_product_id": null,
"similarity": null,
"stats": {
"processed_products": 150,
"free_tier_limit": 1000,
"limit_reached": false,
"limit_warning": null
}
}
{
"sortillus_id": 101,
"external_id": 6,
"name": "your product name",
"description": "long product description",
"image": "https://some.com/productid.image.jpg",
"canonical_id": null,
"category_external_id": "L4_2345",
"active": true,
"in_stock": true,
"state": "recommended_products",
"updated_at": "2025-06-12T18:08:59Z",
"created_at": "2025-06-12T18:08:58Z",
"recommendations": [
{
"product_id": 789,
"explanation": "Same model; different color.",
"reason_json": {"source": "embedding", "score": 0.92}
},
{
"product_id": 456,
"explanation": "Bundle frequently bought together."
},
{
"product_id": 123,
"explanation": "Complementary product"
}
],
"recommended_at": "2025-06-12T18:10:00Z",
"suggest_new_leaf_category": null,
"suggest_category_branch_id": null,
"external_similar_product_id": null,
"similarity": null,
"stats": {
"processed_products": 150,
"free_tier_limit": 1000,
"limit_reached": false,
"limit_warning": null
}
}
POST /api/v3/products/:id/recommend
Trigger the product recommendation process for a specific product identified by its internal product ID (sortillus_id). The domain_id is automatically determined from the access token provided in the Authorization header. This endpoint initiates the AASM state machine transition to start generating product recommendations asynchronously.
id parameter must be the internal product ID (sortillus_id), not the external_id. You can find the sortillus_id by calling GET /api/v3/products or GET /api/v3/products/:external_id (using external_id) and looking at the sortillus_id or id field in the response.
| Parameter | Type | Description |
|---|---|---|
id |
integer | Internal product ID (sortillus_id) of the product to trigger recommendations for (required). Note: This is NOT the external_id. |
| Header | Value | Description |
|---|---|---|
Authorization |
Bearer <access_token> |
Your domain access token (required) |
Content-Type |
application/json |
Content type header |
On success, returns a confirmation message with status code 200 (OK).
If the product is not found, returns status code 404 (Not Found).
If the access token is missing or invalid, returns status code 401 (Unauthorized).
If the recommendation process fails to start (e.g., invalid product state, AASM guard conditions not met), returns status code 422 (Unprocessable Entity).
| Field | Type | Description |
|---|---|---|
message |
string | Confirmation message indicating the recommendation process was triggered |
product_id |
integer | The internal database ID of the product (same as the id parameter) |
external_id |
integer | The external ID of the product |
state |
string | The current AASM state of the product after triggering recommendations (typically recommending_products) |
extraction_triggered |
boolean | Whether extraction was automatically triggered before recommendations (true if product was not yet extracted) |
stats |
object | Domain processing statistics (processed_products, free_tier_limit, limit_reached, limit_warning) |
should_recommend_products enabled for recommendations to work properly (checked by AASM guards).GET /api/v3/products/:id endpoint to see when recommendations are complete.domain_id is automatically determined from the access token, so you don't need to include it in the URL.Note: The ID in the URL must be the internal sortillus_id, not the external_id. In this example, 12345 is the sortillus_id.
curl https://mcp.sortillus.com/api/v3/products/12345/recommend \
-X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
{
"message": "Recommendation process triggered successfully",
"product_id": 12345,
"external_id": 6,
"state": "recommending_products",
"extraction_triggered": false,
"stats": {
"processed_products": 150,
"free_tier_limit": 1000,
"limit_reached": false,
"limit_warning": null
}
}
{
"error": "Product not found"
}
{
"error": "Invalid access token"
}
{
"error": "Failed to trigger recommendations",
"message": "Detailed error message"
}
Retrieve a paginated list of products for your domain. Each product includes its current processing state.
| Parameter | Type | Description |
|---|---|---|
page |
integer | Page number (default: 1) |
per_page |
integer | Items per page (default: 1000). Note: The actual query uses 1000 as default, but pagination metadata calculation uses this value. |
aasm_states |
string / array |
Filter products by AASM state(s). Accepts a comma-separated list
(?aasm_states=ready,error) or array form
(?aasm_states[]=ready&aasm_states[]=error). Only
states defined on the model are permitted; invalid values return
422 Unprocessable Entity.
When this filter or updated_before/updated_after is provided, the response omits the total_pages and total_count keys to improve performance.
|
updated_before |
string (ISO8601) | |
updated_after |
string (ISO8601) |
Return only products updated on or after this timestamp (e.g.,
2025-09-01T00:00:00Z). Can be combined with updated_before to form a range.
When supplied, the response omits total_pages and total_count in meta.
|
Return only products updated on or before this timestamp (e.g.,
2025-09-15T00:00:00Z). When supplied, the response omits
total_pages and total_count in meta. Results are ordered by
updated_at DESC, id DESC.
|
Returns a list of products with their id, external_id, processing state, timestamps, optional category_external_id, and external_canonical_id (the external_id of the canonical product if this product is a duplicate), along with pagination metadata.
Note: If the aasm_states or any of updated_before/updated_after is supplied, the meta object will only contain current_page; total_pages and total_count are omitted.
Results are ordered by updated_at DESC with id DESC as a tiebreaker.
| Field | Type | Description |
|---|---|---|
id |
integer | Internal product ID |
external_id |
integer | External product ID |
state |
string | Current processing state |
updated_at |
string (ISO8601) | Last update timestamp |
created_at |
string (ISO8601) | Creation timestamp |
category_external_id |
string/integer | External ID of the product's category (if any) |
external_canonical_id |
integer/null | The external_id of the canonical product if this product is a duplicate; null for canonical products. |
curl https://mcp.sortillus.com/api/v3/products?per_page=100&aasm_states=ready,error \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
curl https://mcp.sortillus.com/api/v3/products?updated_before=2025-09-15T00:00:00Z&per_page=1000 \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
POST /api/v3/products/batch
Import multiple products or process existing products in a single request. This endpoint allows you to control which processing steps run during import, making it ideal for initial bulk imports where you may want to skip certain steps like similarity matching or recommendations. You can also use it to trigger processing steps on existing products by their sortillus IDs.
| Field | Type | Description |
|---|---|---|
products |
array |
Array of product objects for creating/updating products (required if product_ids not provided, max 500 products per batch). See "Product Object Structure" below for details.
|
product_ids |
array |
Array of sortillus IDs (internal database IDs) for processing existing products (required if products not provided, max 500 products per batch). Example: [234, 55, 33]. All product IDs must exist and belong to the authenticated domain.
|
allow |
array |
Array of processing steps to allow. Valid values: extract, similarize, categorize, recommend, notify, allow_all.
Note: When using |
Each product in the products array supports the same fields as the single product creation endpoint:
| Field | Type | Description |
|---|---|---|
id |
integer | External ID of the product (required) |
name |
string | Name of the product |
description |
string | Detailed description of the product |
image or imageThumb290 |
string | URL of the product image |
gtin |
string | Global Trade Item Number (GTIN) of the product |
gtins |
array of strings | Array of Global Trade Item Numbers (GTINs) for the product |
in_stock |
boolean | Whether the product is in stock (default: true). This determines the product's active status along with canonical relationships. |
active |
boolean | Read-only. Computed field: canonical products are active if (canonical.in_stock OR any_duplicate.in_stock), duplicates are always inactive. |
| Step | Description | Final State |
|---|---|---|
extract |
Extract product data using LLM and generate embeddings | vectors_stored_v2 |
similarize |
Find similar/duplicate products | canonized |
categorize |
Automatically categorize the product | categorized |
recommend |
Generate product recommendations | recommended_products |
notify |
Send notification (if domain has notifications enabled) | notification_sent |
allow_all |
Process normally according to domain settings (all steps that domain allows) | Varies (based on domain configuration) |
On success, returns a 202 Accepted status with batch job information:
message: Confirmation messagejob_status: Status of the queued job (always "queued")batch_settings: The batch configuration (length, allow steps)
curl https://mcp.sortillus.com/api/v3/products/batch \
-X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"products": [
{
"id": 1001,
"name": "Product 1",
"description": "Description 1",
"image": "https://example.com/image1.jpg"
},
{
"id": 1002,
"name": "Product 2",
"description": "Description 2",
"image": "https://example.com/image2.jpg"
}
],
"allow": ["extract", "notify"]
}'
curl https://mcp.sortillus.com/api/v3/products/batch \
-X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"products": [
{
"id": 1001,
"name": "Product 1",
"description": "Description 1",
"image": "https://example.com/image1.jpg"
}
],
"allow": ["allow_all"]
}'
curl https://mcp.sortillus.com/api/v3/products/batch \
-X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product_ids": [234, 55, 33],
"allow": ["recommend", "notify"]
}'
Note: product_ids are sortillus IDs (internal database IDs), not external IDs. Use this to trigger processing steps on existing products. The system will intelligently advance each product through the allowed steps based on its current state. You can find sortillus IDs by calling GET /api/v3/products and looking at the id field in the response.
{
"message": "Batch import queued for 2 products",
"job_status": "queued",
"batch_settings": {
"length": 2,
"allow": ["extract", "notify"]
}
}
products or product_ids, but not bothGET /api/v3/products to monitor progressproducts: Creates or updates products, then processes them through allowed stepsproduct_ids: Processes existing products through allowed steps using smart state advancement (skips completed steps)allow_all), batch completion is automatically tracked. The batch settings are cleared when all products reach their last allowed step or error stateallow_all, products process normally and batch completion tracking is optionalerror state), it still counts toward batch completion
curl https://mcp.sortillus.com/api/v3/products?updated_after=2025-09-01T00:00:00Z&updated_before=2025-09-15T00:00:00Z&per_page=1000 \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
{
"products": [
{
"id": 101,
"external_id": 6,
"state": "ready",
"updated_at": "2025-06-12T18:08:59Z",
"created_at": "2025-06-12T18:08:58Z",
"category_external_id": "L4_2345",
"external_canonical_id": null
},
{
"id": 102,
"external_id": 7,
"state": "error",
"updated_at": "2025-06-12T17:00:00Z",
"created_at": "2025-06-12T16:50:00Z",
"category_external_id": null,
"external_canonical_id": 6
}
],
"meta": {
"current_page": 1
}
}
Retrieve a paginated list of shop categories for your domain. By default, returns all categories (including those without attributes). Use the with_attributes parameter to filter to only categories that have attributes assigned. Categories are ordered by the most recently created attribute, with categories without attributes appearing last.
| Parameter | Type | Description |
|---|---|---|
page |
integer | Page number (default: 1) |
per_page |
integer | Items per page (default: 100) |
level |
integer | Filter categories by level (e.g., 1 for top-level categories) |
parent_external_id |
integer | Filter categories by parent external ID |
search |
string | Search categories by name (case-insensitive) |
with_attributes |
boolean | If true, 1, or yes, returns only categories that have at least one product attribute assigned. Default: false (returns all categories) |
Returns a list of categories with their attributes and options, along with pagination metadata. Categories are ordered by the most recently created attribute (categories without attributes appear last, ordered by updated_at). Categories without attributes will have an empty attributes array and last_attribute_created_at will be null.
| Field | Type | Description |
|---|---|---|
id |
integer | Internal category ID |
external_id |
integer | External category ID |
full_external_id |
string | Full external ID of the category |
level |
integer | Category level in the hierarchy |
name |
string | Category name |
en_name |
string | English category name |
en_description |
string | English category description |
updated_at |
string (ISO8601) | Last update timestamp |
created_at |
string (ISO8601) | Creation timestamp |
last_attribute_created_at |
string (ISO8601) or null | Timestamp of the most recently created attribute. null if the category has no attributes. |
attributes |
array | Array of attributes assigned to this category |
| Field | Type | Description |
|---|---|---|
id |
integer | Internal attribute ID |
name |
string | Attribute name |
external_id |
integer | External attribute ID |
measurement |
string | Measurement unit for the attribute |
description |
string | Attribute description |
options |
array | Array of options for this attribute |
| Field | Type | Description |
|---|---|---|
id |
integer | Internal option ID |
name |
string | Option name |
external_id |
integer | External option ID |
measurement |
string | Measurement unit for the option |
# Get all categories
curl https://mcp.sortillus.com/api/v3/shop/categories?page=1&level=1 \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
# Get only categories with attributes
curl https://mcp.sortillus.com/api/v3/shop/categories?with_attributes=true \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
{
"categories": [
{
"id": 1,
"external_id": 2345,
"full_external_id": "L4_2345",
"level": 4,
"name": "Electronics",
"en_name": "Electronics",
"en_description": "Electronic devices and components",
"updated_at": "2024-01-15T10:30:00Z",
"created_at": "2024-01-01T00:00:00Z",
"last_attribute_created_at": "2024-01-15T10:30:00Z",
"attributes": [
{
"id": 1,
"name": "Color",
"external_id": 100,
"measurement": null,
"description": "Product color",
"options": [
{
"id": 1,
"name": "Red",
"external_id": 101,
"measurement": "free_text"
},
{
"id": 2,
"name": "Blue",
"external_id": 102,
"measurement": "free_text"
}
]
},
{
"id": 2,
"name": "Size",
"external_id": 200,
"measurement": "cm",
"description": "Product size",
"options": [
{
"id": 3,
"name": "25",
"external_id": 201,
"measurement": "cm"
}
]
}
]
}
],
"meta": {
"current_page": 1,
"total_pages": 1,
"total_count": 1
}
}
Retrieve detailed information about a specific shop category by its internal ID.
| Parameter | Type | Description |
|---|---|---|
id |
integer | Internal category ID (not the external_id) (required) |
On success, returns the category details with status code 200 (OK).
If the category is not found, returns status code 404 (Not Found).
| Field | Type | Description |
|---|---|---|
id |
integer | Internal category ID |
full_external_id |
string | Full external ID of the category (e.g., "L4_2345") |
name |
string | Category name |
en_name |
string | English category name |
en_description |
string | English category description |
curl https://mcp.sortillus.com/api/v3/shop/categories/123 \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
{
"id": 123,
"full_external_id": "L4_2345",
"name": "Electronics",
"en_name": "Electronics",
"en_description": "Electronic devices and components"
}
Creates a new category or updates an existing one. The endpoint matches or initializes by external_id within your domain. All required fields must be provided.
| Field | Type | Description |
|---|---|---|
external_id |
integer | External category ID. Required. Used to find or create within your domain. Must be a positive integer. |
name |
string | Category name. Required. |
en_name |
string | English category name. Required. Used for vectorization and embeddings. |
en_description |
string | English category description. Required. Used for vectorization and embeddings. |
description |
string | Category description. Optional. |
external_parent_id |
integer | External parent category ID. Optional. If provided, sets this category as a child of the parent. |
On create, returns the category with status code 201 Created. On update, returns 200 OK.
On validation error (e.g., missing required fields, invalid parent), returns 422 Unprocessable Entity.
curl https://mcp.sortillus.com/api/v3/shop/categories \
-X POST \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"category": {
"external_id": 456,
"name": "Electronics",
"en_name": "Electronics",
"en_description": "Electronic devices and components",
"description": "Long category description",
"external_parent_id": 20
}
}'
{
"id": 123,
"external_id": 456,
"name": "Electronics",
"description": "Long category description",
"en_name": "Electronics",
"en_description": "Electronic devices and components",
"parent_id": 20,
"full_external_id": "L2_456",
"level": 2,
"created_at": "2025-06-12T18:08:58Z",
"updated_at": "2025-06-12T18:08:59Z"
}