To start using our API, you'll need to register for an account.
Here you'll find detailed documentation for each available endpoint.
Read and update domain feature toggles (e.g. should_recommend_products, should_extract) for the domain identified by your access token. Use the same domain access token as for the products API (Authorization: Bearer <your_domain_access_token>).
Returns the six feature toggles for your domain. All keys are always present; nil is returned as false.
Response example:
{
"should_similarize": false,
"should_categorize": true,
"should_recommend_products": false,
"should_notify": true,
"should_ascribe": false,
"should_extract": true
}
Update any subset of the six toggles. Send a JSON body with the keys you want to change (boolean or "true"/"false"). Response is the same shape as GET (full settings after update).
Request example:
curl https://admin.sortillus.com/api/v3/domain/settings \
-X PATCH \
-H "Authorization: Bearer YOUR_DOMAIN_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"should_recommend_products": true, "should_extract": false}'
Errors: 401 Unauthorized if the token is missing or invalid; 422 Unprocessable Entity if validation fails (with errors in the response).
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). Used for active status when backorders_allowed is false. |
backorders_allowed |
boolean | Whether backorders are allowed (default: false). If true, the product is active regardless of in_stock. |
price |
string/decimal | Product price (e.g. "24.99"). Stored as provided. |
active |
boolean | Read-only. Computed: active if backorders_allowed is true, else if in_stock is true; for canonicals, also active if any duplicate is effective available. 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://admin.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. Used for active status when backorders_allowed is false. |
backorders_allowed |
boolean | Whether backorders are allowed. If true, the product is active regardless of in_stock. |
price |
string/decimal | Product price (e.g. "24.99"). |
active |
boolean | Read-only. Computed: active if backorders_allowed is true, else if in_stock is true; for canonicals, also if any duplicate is effective available. 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://admin.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://admin.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://admin.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://admin.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"gtin": "",
"gtins": []
}
}'
curl https://admin.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"image": ""
}
}'
curl https://admin.sortillus.com/api/v3/products/6 \
-X PATCH \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"product": {
"canonical": true
}
}'
curl https://admin.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://admin.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: active if backorders_allowed is true, else if in_stock is true; for canonicals, also if any duplicate is effective available. Duplicates are always inactive. |
in_stock |
boolean | Whether the product is in stock |
backorders_allowed |
boolean | Whether backorders are allowed |
price |
string/decimal/null | Product price |
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 |
improved_localized_description |
string/null | Improved localized description from the last LLM response (if available) |
improved_localized_product_name |
string/null | Improved localized product name from the last LLM response (if available) |
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://admin.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,
"improved_localized_description": null,
"improved_localized_product_name": 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://admin.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. |
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 |
suggested_category_branch_id |
integer/null | Suggested category branch ID from categorization JSON |
suggested_new_leaf_category_name |
string/null | Suggested new leaf category name from categorization JSON |
suggested_new_leaf_category_description |
string/null | Suggested new leaf category description from categorization JSON |
improved_localized_description |
string/null | Improved localized description from the last LLM response (if available) |
improved_localized_product_name |
string/null | Improved localized product name from the last LLM response (if available) |
curl https://admin.sortillus.com/api/v3/products?per_page=100&aasm_states=ready,error \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
curl https://admin.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). Used for active when backorders_allowed is false. |
backorders_allowed |
boolean | Whether backorders are allowed (default: false). If true, the product is active regardless of in_stock. |
price |
string/decimal | Product price (e.g. "24.99"). |
active |
boolean | Read-only. Computed: active if backorders_allowed is true, else if in_stock is true; for canonicals, also if any duplicate is effective available. 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://admin.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://admin.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://admin.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://admin.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://admin.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://admin.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://admin.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://admin.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"
}
Deletes a shop category by its external identifier. You must use either external_id (integer) or full_external_id (string, e.g., "L1_123"). Internal IDs are not accepted for security reasons to prevent accidentally deleting the wrong category.
This operation will:
category_id for all products in this category (products become uncategorized)canonical_product_id)Important: Categories with subcategories cannot be deleted. You must delete all subcategories first.
| Parameter | Type | Description |
|---|---|---|
id |
integer or string | Category external identifier (required). Must be one of:
|
On success, returns a success message and statistics with status code 200 OK.
If the category has subcategories, returns 422 Unprocessable Entity with an error message.
If the category is not found or belongs to a different domain, returns 404 Not Found.
| Field | Type | Description |
|---|---|---|
message |
string | Success message confirming deletion |
stats |
object | Statistics about the deletion operation |
stats.total_products_count |
integer | Total number of products that were in this category |
stats.canonical_products_count |
integer | Number of canonical products that were recategorized |
stats.recategorized_count |
integer | Number of products successfully recategorized |
stats.recategorization_errors_count |
integer | Number of products that failed to recategorize (if any) |
# Delete by external_id (numeric parameter)
curl https://admin.sortillus.com/api/v3/shop/categories/100 \
-X DELETE \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
# Delete by full_external_id (string parameter)
curl https://admin.sortillus.com/api/v3/shop/categories/L1_100 \
-X DELETE \
-H "Authorization: Bearer " \
-H "Content-Type: application/json"
{
"message": "Category 'Electronics' was successfully deleted.",
"stats": {
"total_products_count": 15,
"canonical_products_count": 10,
"recategorized_count": 10,
"recategorization_errors_count": 0
}
}
{
"error": "Cannot delete category 'Electronics' because it has 3 subcategories. Please delete the subcategories first."
}
{
"error": "Cannot delete ROOT category 'Electronics' because it has 3 subcategories. Root categories with subcategories cannot be deleted as they form the foundation of your category hierarchy. Please delete all subcategories first."
}