Categories
This domain covers three related but distinct layers:
- Fenicia category tree — your catalog's own taxonomy.
- Automatic classification — endpoints that, given a product title/description, suggest a category or tax code using AI: SAT (CFDI), T1/Sears/Sanborns, Walmart, and TikTok Shop each have their own classifier.
- Category mappings — the persisted relationship between a Fenicia category and its equivalent in a specific marketplace, with usage metrics and an approval flow.
Base path different from /products
This domain lives under /categories, its own API Gateway resource (fenicia-categories-{stage}) — not a sub-resource of /products. Use https://api.fenicia.io/categories/... directly.
No single envelope
Just like the rest of the Products API, there is no uniform {data, meta} envelope here — each endpoint returns its own shape. We document the actual shape of each one; where it wasn't verified field by field in the audit, we flag it explicitly.
Authentication: Authorization: Bearer fkapi_..., except for the one endpoint explicitly marked as public below (GET /categories/sat/search). The rest require a resolved tenantId (401 if missing) and, on most routes, a specific RBAC permission — see the note on the four exceptions below.
Category tree and search
Returns the complete category tree for your tenant (also accessible at GET /categories, without the suffix)
localestringLanguage of the labels. Default 'es'.
[
{
"id": "cat-ropa",
"name": "Ropa",
"children": [
{ "id": "cat-ropa-playeras", "name": "Playeras", "children": [] }
]
}
]
{ "success": false, "error": "Failed to load category tree", "code": "INTERNAL_ERROR" }
Required permission: products:read
Searches your tenant's categories by text
qstringrequiredSearch term.
localestringLanguage of the labels.
limitnumberResult limit. Default 20.
{ "success": false, "error": "Search query is required", "code": "MISSING_PARAMETER" }
Required permission: products:read
SAT (CFDI) classification
Suggests the appropriate SAT code (product/service key) for electronic invoicing based on a product's title and data.
Searches SAT product/service keys by text
qstringrequiredSearch term.
limitnumberResult limit.
{ "success": false, "error": "Search query is required", "code": "MISSING_PARAMETER" }
Public endpoint — no authentication
Unlike all the other endpoints on this page (and the API in general), GET /categories/sat/search does not require Authorization. It's the only public endpoint in this domain, consistent with the fact that the SAT catalog is public information from the Mexican SAT catalog, not your tenant's data.
Suggests the SAT code for a product based on its title/data
titlestringrequiredProduct title.
{ "title": "Camisa de algodón manga larga" }{
"satCode": "53101600",
"description": "Camisas",
"confidence": 0.92,
"method": "ai",
"processingTimeMs": 340
}
{ "code": "MISSING_PARAMETER", "message": "Product title is required" }
Required permission: products:read
Side effect: emits an event
Every successful call to POST /categories/sat/suggest emits a sat-categorization event to EventBridge — useful if you need to react to SAT classifications from another system, but it also means the endpoint isn't a purely read operation despite not persisting visible changes on the product.
SAT cache and classification metrics
{
"success": true,
"data": {
"service": {
"version": "2.7.0",
"architecture": "global_rag",
"description": "Global RAG - works for ANY product from ANY industry"
},
"cache": {
"enabled": true,
"size": 128,
"maxSize": 5000,
"hitRate": "42.3%"
},
"metrics": {
"enabled": true,
"classification": {
"total": 340,
"successful": 332,
"failed": 8,
"avgConfidence": "88.5%",
"avgProcessingTimeMs": 410
},
"methods": {
"globalRagLlm": 210,
"globalRagOnly": 122
},
"tokens": {
"input": 42000,
"output": 21000,
"estimatedCostUsd": "$0.6300"
}
}
}
}
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:read
Resets SAT metrics and, optionally, clears the classification cache
clearCachebooleanIf `true`, also clears the SAT classification cache.
{ "clearCache": true }{ "success": true, "message": "Stats reset" }
Required permission: settings:manage
T1 / Sears / Sanborns classification
Classifies a product in T1's taxonomy (T1/Sears/Sanborns channel)
titlestringrequiredProduct title and other data relevant to classification.
{ "title": "Camisa de algodón manga larga" }{
"t1CategoryId": "T1-12345",
"t1CategoryPath": "Ropa > Playeras",
"t1CategoryName": "Playeras",
"segment": "ropa",
"segmentName": "Ropa y Accesorios",
"confidence": 0.91,
"method": "global_rag_llm",
"processingTimeMs": 480
}
{ "code": "MISSING_PARAMETER", "message": "Product title is required" }
Required permission: products:read
Side effect: emits an event
Emits a t1-categorization event to EventBridge on every successful classification.
T1 classification metrics
{
"success": true,
"data": {
"service": {
"version": "2.6.0",
"architecture": "global_rag",
"description": "Global RAG - works for ANY product from ANY industry"
},
"cache": {
"enabled": true,
"size": 96,
"maxSize": 5000,
"hitRate": "38.1%"
},
"metrics": {
"enabled": true,
"classification": {
"total": 214,
"successful": 208,
"failed": 6,
"avgConfidence": "86.2%",
"avgProcessingTimeMs": 465
},
"methods": {
"globalRagLlm": 150,
"globalRagOnly": 58
},
"tokens": {
"input": 30000,
"output": 15000,
"estimatedCostUsd": "$0.4500"
}
}
}
}
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:read
Resets T1 metrics, optionally clearing the cache
clearCachebooleanClears the T1 classification cache.
{ "clearCache": true }{ "success": true, "message": "Metrics reset, cache cleared" }
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:manage
Walmart categories and classification
Returns the complete Walmart category catalog (79 categories)
{
"total": 79,
"categories": [
{ "id": "4044", "name": "Athletic Shoes", "segment": "shoes", "segmentName": "Calzado" },
{ "id": "5438", "name": "Shirts", "segment": "clothing", "segmentName": "Ropa y Accesorios" }
]
}
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: products:read
Searches the Walmart category catalog
qstringrequiredSearch term.
limitnumberResult limit.
{ "success": false, "error": "Search query is required", "code": "MISSING_PARAMETER" }
Required permission: products:read
Classifies a product in Walmart's taxonomy (classification engine v5)
titlestringrequiredProduct title and other relevant data.
{ "title": "Tenis Nike Air Max 270" }{
"walmartCategoryId": "4044",
"walmartCategoryName": "Athletic Shoes",
"segment": "shoes",
"segmentName": "Calzado",
"visibleSectionMX": "Tenis deportivos",
"productTypeName": "Athletic Shoes",
"productTypeGroup": "Shoes",
"category": "Clothing, Shoes & Accessories",
"fullPath": "Clothing, Shoes & Accessories > Shoes > Athletic Shoes",
"taxonomyVersion": "v5.0",
"categoryStatus": "active",
"requiredAttributes": ["brand", "size", "color"],
"aiInferableAttributes": ["brand", "color"],
"confidence": 0.89,
"method": "global_rag_llm",
"processingTimeMs": 520
}
{ "code": "MISSING_PARAMETER", "message": "Product title is required" }
Required permission: products:read
Side effect: emits an event
Emits an internal categorization event to EventBridge on every successful classification.
Resolves, with AI, the attribute values required by a Walmart category for a given product
categoryIdstringrequiredID of the target Walmart category.
titlestringrequiredProduct title.
descriptionstringProduct description.
brandstringBrand.
categorystringFenicia category of the product, as additional context.
existingAttributesobjectAlready-known attributes, to avoid resolving them again.
resolveVariantAttributestringName of the variant attribute to resolve specifically (for example, size or color).
allowedValuesarrayRestricts resolution to this set of allowed values.
attributeTypestringType of attribute to resolve.
{
"categoryId": "4044",
"title": "Playera de algodón manga larga",
"description": "Playera 100% algodón, cuello redondo",
"brand": "Fenicia Basics",
"category": "Ropa > Playeras"
}{
"success": true,
"categoryId": "4044",
"resolvedAttributes": {
"brand": { "name": "brand", "value": "Fenicia Basics", "confidence": 0.95, "source": "existing" },
"color": { "name": "color", "value": "Azul", "confidence": 0.8, "source": "ai_inferred" }
},
"missingAttributes": ["size"],
"processingTimeMs": 610
}
{
"success": true,
"categoryId": "4044",
"variantAttributeName": "Talla",
"confidence": 0.93,
"reasoning": "El producto tiene variantes por talla numérica.",
"processingTimeMs": 340
}
{ "code": "MISSING_PARAMETER", "message": "categoryId is required" }
Required permission: products:read
resolvedAttributes is an object, not an array
Unlike POST /categories/generic/resolve-attributes and POST /categories/mercadolibre/resolve-attributes (which return resolvedAttributes as an array), here it is an object of attribute→detail pairs (Record<string, WalmartResolvedAttribute>), indexed by attribute name. source is one of ai_inferred, default, or existing.
Walmart classification metrics
{
"success": true,
"data": {
"service": {
"version": "2.8.0",
"architecture": "global_rag",
"description": "Global RAG - 79 Walmart categories",
"totalCategories": 79
},
"cache": {
"enabled": true,
"size": 210,
"maxSize": 5000,
"hitRate": "51.4%"
},
"metrics": {
"enabled": true,
"classification": {
"total": 512,
"successful": 498,
"failed": 14,
"avgConfidence": "90.1%",
"avgProcessingTimeMs": 530
},
"methods": {
"globalRagLlm": 340,
"globalRagOnly": 158
},
"tokens": {
"input": 68000,
"output": 34000,
"estimatedCostUsd": "$1.0200"
}
}
}
}
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:read
Resets Walmart classification metrics
clearCachebooleanIf `true`, also clears the Walmart classification cache.
{ "clearCache": true }{ "success": true, "message": "Metrics reset, cache cleared" }
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:manage
TikTok Shop classification
Classifies a product in TikTok Shop's taxonomy
titlestringrequiredProduct title and other relevant data.
{ "title": "Blusa floral manga corta" }{
"tiktokCategoryId": "601001",
"tiktokCategoryPath": "Ropa > Blusas y Camisas",
"tiktokCategoryName": "Blusas y Camisas",
"segment": "ropa",
"segmentName": "Ropa y Accesorios",
"confidence": 0.87,
"method": "global_rag_llm",
"processingTimeMs": 455
}
{ "code": "MISSING_PARAMETER", "message": "Product title is required" }
Required permission: products:read
Side effect: emits an event
Emits an internal categorization event to EventBridge on every successful classification.
TikTok Shop classification metrics
{
"success": true,
"data": {
"service": {
"version": "2.12.4",
"architecture": "global_rag",
"description": "Global RAG - ~2,700 TikTok Shop categories",
"totalCategories": 2723
},
"cache": {
"enabled": true,
"size": 74,
"maxSize": 5000,
"hitRate": "29.6%"
},
"metrics": {
"enabled": true,
"classification": {
"total": 130,
"successful": 122,
"failed": 8,
"avgConfidence": "83.4%",
"avgProcessingTimeMs": 490
},
"methods": {
"globalRagLlm": 95,
"globalRagOnly": 27
},
"tokens": {
"input": 19000,
"output": 9500,
"estimatedCostUsd": "$0.2850"
}
}
}
}
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:read
Resets TikTok Shop classification metrics
clearCachebooleanIf `true`, also clears the TikTok Shop classification cache.
{ "clearCache": true }{ "success": true, "message": "Metrics reset, cache cleared" }
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:manage
Unified classification and cache
No explicit RBAC permission
The four endpoints in this section do not call requirePermission() in the audited source code — unlike practically every other route in this domain, they only validate that a resolved tenantId exists (a valid authenticated session), without requiring an additional permission. This isn't an error in this documentation: it's the actual confirmed behavior in the code. If your organization needs to restrict these operations by role, report it to support.
Classifies a product by trying the available classification strategies in cascade (up to 5), until obtaining a result
titlestringrequiredProduct title.
descriptionstringProduct description.
brandstringBrand.
feniciaCategoryIdstringAlready-known Fenicia category of the product, as context.
skustringProduct SKU.
categorystringAdditional context category.
{
"title": "Playera de algodón manga larga",
"description": "Playera 100% algodón, cuello redondo",
"brand": "Fenicia Basics",
"feniciaCategoryId": "cat-ropa-playeras",
"sku": "PLA-001",
"category": "Ropa > Playeras"
}{ "success": false, "error": "Tenant ID required", "code": "UNAUTHORIZED" }
{ "success": false, "error": "Product title is required", "code": "MISSING_PARAMETER" }
Required permission: none explicit — authenticated session with resolved tenantId only.
Looks up an already-cached classification result, without re-running the classifier
titlestringrequiredProduct title.
descriptionstringProduct description.
brandstringBrand.
marketplacestringRestricts the cache lookup to a specific marketplace.
{
"title": "Playera de algodón manga larga",
"description": "Playera 100% algodón, cuello redondo",
"brand": "Fenicia Basics",
"marketplace": "walmart"
}{
"success": true,
"data": {
"found": true,
"classification": {
"marketplace": "walmart",
"categoryId": "4044",
"categoryPath": "Clothing, Shoes & Accessories > Shoes > Athletic Shoes",
"confidence": 0.89,
"method": "global_rag_llm",
"classifiedAt": "2026-07-10T15:22:00.000Z"
}
}
}
{ "success": true, "data": { "found": false } }
{ "success": false, "error": "Tenant ID required", "code": "UNAUTHORIZED" }
{ "success": false, "error": "Product title is required", "code": "MISSING_PARAMETER" }
Required permission: none explicit — authenticated session only.
Classification cache statistics
{
"success": true,
"data": {
"service": {
"version": "2.13.0",
"description": "Persistent classification cache with confidence-based TTL"
},
"stats": {
"totalEntries": 812,
"totalHits": 2140,
"avgHitCount": 2.64,
"methodDistribution": { "binding": 40, "cache": 0, "mapping": 120, "rag": 380, "rag_llm": 272 },
"marketplaceDistribution": { "walmart": 310, "mercadolibre": 260, "tiktok": 180 },
"publicationSuccessRate": 0.94
}
}
}
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: none explicit — authenticated session only.
Invalidates classification cache entries
feniciaCategoryIdstringInvalidates only the entries associated with this Fenicia category. If omitted, applies the `cleanup` criterion.
cleanupbooleanRuns a general cleanup of expired/stale entries.
{ "feniciaCategoryId": "cat-ropa-playeras", "cleanup": false }{ "invalidatedCount": 12, "cleanedCount": 3 }
Required permission: none explicit — authenticated session only.
Category mappings by marketplace
A mapping relates a Fenicia category to its equivalent in a marketplace, with usage metrics (success rate) and an approval flow for automatically generated mappings.
Looks up the existing mapping between a Fenicia category and a marketplace
feniciaCategoryIdstringrequiredFenicia category ID.
marketplacestringrequiredTarget marketplace.
{ "feniciaCategoryId": "cat-ropa-playeras", "marketplace": "walmart" }{
"found": true,
"mapping": {
"feniciaCategoryId": "cat-ropa-playeras",
"marketplace": "walmart",
"marketplaceCategoryId": "5438",
"marketplaceCategoryName": "Shirts",
"confidence": 0.95
}
}
{ "found": false }
{ "success": false, "error": "feniciaCategoryId and marketplace are required", "code": "MISSING_PARAMETER" }
Required permission: products:read
Creates or updates the mapping between a Fenicia category and a marketplace
feniciaCategoryIdstringrequiredFenicia category ID.
feniciaCategoryPathstringReadable path of the Fenicia category.
marketplacestringrequiredTarget marketplace.
marketplaceCategoryIdstringrequiredCategory ID in the marketplace.
marketplaceCategoryNamestringReadable name of the category in the marketplace.
mappingTypestringMapping type (for example, manual vs. AI-generated).
confidencenumberMapping confidence, if it was automatically generated.
sourcestringOrigin of the mapping.
attributeMappingsobjectAssociated attribute mapping.
{
"feniciaCategoryId": "cat-ropa-playeras",
"feniciaCategoryPath": "Ropa > Playeras",
"marketplace": "walmart",
"marketplaceCategoryId": "5438",
"marketplaceCategoryName": "Shirts",
"mappingType": "ai_inferred",
"confidence": 0.95,
"source": "ai",
"attributeMappings": { "color": "COLOR_ATTR", "size": "SIZE_ATTR" }
}{ "success": false, "error": "feniciaCategoryId, marketplace, and marketplaceCategoryId are required", "code": "MISSING_PARAMETER" }
Required permission: products:manage
Records the result of using a mapping (for example, after exporting a product with that category) and returns its updated metrics
feniciaCategoryIdstringrequiredFenicia category ID.
marketplacestringrequiredMarketplace.
successbooleanWhether the mapping usage was successful.
errorCodestringError code, if `success` is `false`.
errorMessagestringError message.
{ "feniciaCategoryId": "cat-ropa-playeras", "marketplace": "walmart", "success": true }{ "status": "active", "usageCount": 34, "successRate": 0.97 }
null
Required permission: products:read
Lists mappings pending approval
marketplacestringFilters by marketplace.
limitnumberResult limit. Default 50.
{
"success": true,
"data": {
"total": 2,
"mappings": [
{
"feniciaCategoryId": "cat-ropa-playeras",
"marketplace": "walmart",
"marketplaceCategoryId": "5438",
"marketplaceCategoryPath": "Shirts",
"mappingType": "ai_inferred",
"confidence": 0.82,
"source": "ai",
"status": "pending",
"stats": { "usageCount": 6, "successRate": 0.83 }
}
]
}
}
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:read
Approves or rejects a pending mapping
feniciaCategoryIdstringrequiredFenicia category ID of the mapping.
marketplacestringrequiredMarketplace of the mapping.
approvedbooleanrequired`true` to approve, `false` to reject.
rejectionReasonstringReason for rejection, when `approved` is `false`.
{ "feniciaCategoryId": "cat-ropa-playeras", "marketplace": "walmart", "approved": true }{
"success": true,
"data": {
"feniciaCategoryId": "cat-ropa-playeras",
"marketplace": "walmart",
"marketplaceCategoryId": "5438",
"marketplaceCategoryPath": "Shirts",
"mappingType": "ai_inferred",
"confidence": 0.82,
"source": "ai",
"status": "validated",
"stats": { "usageCount": 6, "successRate": 0.83 },
"validatedAt": "2026-07-21T18:40:00.000Z",
"validatedBy": "admin"
}
}
{ "success": false, "error": "feniciaCategoryId, marketplace, and approved are required", "code": "MISSING_PARAMETER" }
Required permission: settings:manage
Aggregated mapping statistics
marketplacestringRestricts the statistics to a marketplace.
{
"success": true,
"data": {
"service": {
"version": "2.13.0",
"description": "Bidirectional Fenicia ↔ Marketplace category mappings"
},
"stats": {
"totalMappings": 634,
"byStatus": { "pending": 40, "auto_approved": 210, "validated": 350, "flagged": 12, "rejected": 22 },
"bySource": { "manual": 100, "ai": 480, "community": 0, "api": 40, "publication_feedback": 14 },
"avgConfidence": 0.91,
"avgSuccessRate": 0.96,
"totalUsage": 5820
}
}
}
{ "success": false, "error": "Unknown error", "code": "INTERNAL_ERROR" }
Required permission: settings:read
AI attribute resolution (multi-marketplace)
In addition to POST /categories/walmart/resolve-attributes (documented above), there are two variants: a generic multi-marketplace one and a MercadoLibre-specific one.
Resolves, with AI, attributes required by a category from any supported marketplace, based on a list of requirements
requirementsarrayrequiredList of attribute requirements to resolve.
productobjectrequiredProduct data; `product.title` is required.
channelTypestringChannel/marketplace type, as additional context.
localestringLanguage for the resolution.
{
"requirements": [
{
"id": "MATERIAL_SUELA",
"name": "Material de la suela",
"type": "select",
"required": true,
"allowedValues": [{ "id": "GOMA", "name": "Goma" }, { "id": "CUERO", "name": "Cuero" }]
}
],
"product": {
"title": "Tenis casuales para hombre",
"brand": "Fenicia Basics",
"attributes": [{ "name": "suela", "value": "Goma" }]
},
"channelType": "tiktok",
"locale": "es"
}{
"success": true,
"resolvedAttributes": [
{ "id": "MATERIAL_SUELA", "value": "Goma", "confidence": 0.98, "source": "exact_match" }
],
"unresolvedAttributes": [],
"processingTimeMs": 210
}
{ "code": "MISSING_PARAMETER", "message": "requirements array is required" }
Required permission: products:read
Resolves attributes required by a MercadoLibre category; variant attributes (size/color) are derived from the product's actual variants, not guessed with AI
requirementsarrayrequiredList of attribute requirements.
productobjectrequiredProduct data; `product.title` is required.
categoryIdstringrequiredMercadoLibre category ID.
categoryNamestringCategory name.
hasVariantsbooleanWhether the product has variants.
variantAxesarrayProduct's variant axes (for example, size, color).
{
"product": {
"title": "Tenis Nike Air Max",
"brand": "Nike",
"attributes": [{ "name": "material", "value": "Sintético" }]
},
"requirements": [
{ "id": "BRAND", "name": "Marca", "type": "text", "required": true, "sourceField": "brand" }
],
"categoryId": "MLM1234",
"categoryName": "Tenis",
"hasVariants": true,
"variantAxes": ["Talla", "Color"]
}{
"success": true,
"resolvedAttributes": [
{ "attributeId": "BRAND", "value": "Nike", "valueType": "value_name", "confidence": 0.99, "source": "product_field" }
],
"unresolvedAttributes": [],
"variantAttributes": [
{
"attributeId": "TALLA",
"name": "Talla",
"reason": "allows_variations",
"valueType": "value_id",
"resolvedByVariant": [
{ "sku": "TENIS-001-28", "feniciaValue": "28", "mlValue": { "id": "28MX", "name": "28 MX" }, "confidence": 1, "matched": true }
]
}
],
"stats": {
"totalAttributes": 2,
"resolvedCount": 1,
"variantCount": 1,
"unresolvedCount": 0,
"processingTimeMs": 380,
"aiInferredCount": 0
}
}
{ "code": "MISSING_PARAMETER", "message": "requirements array is required" }
Required permission: products:read
variantAttributes: derived from real data, not AI
Unlike the rest of the attribute resolution endpoints, this endpoint's response includes a variantAttributes field built from the product's actual options/variants (for example, SIZE/COLOR) — it isn't an AI inference, and it includes resolvedByVariant[] with the value already matched by SKU. The rest of the resolved attributes (resolvedAttributes[]) can indeed come from the AI engine (source: 'ai_inferred'), in addition to exact_match, alias_match, default, or product_field.
Example — suggest a product's SAT code
curl -X POST https://api.fenicia.io/categories/sat/suggest \
-H "Authorization: Bearer fkapi_tu_api_key" \
-H "Content-Type: application/json" \
-d '{ "title": "Camisa de algodón manga larga" }'Errors
| Code | Status | Description |
|---|---|---|
MISSING_PARAMETER | 400 | A required parameter is missing (q, title, etc.). |
not-found | 404 | Unknown route. The response includes availableRoutes with the full list of routes supported by this lambda. |
auth:invalid_token | 401 | The API key is invalid, has been revoked, or the tenant couldn't be resolved. |
auth:permission_denied | 403 | The API key doesn't have the required permission. |
See the full error catalog for the rest of the possible codes.
CORS: no PUT/DELETE
This domain only allows GET, POST, and OPTIONS at the CORS level — there are no PUT/DELETE operations on /categories/* (consistent with the fact that all write routes in this domain are POST, never PUT/DELETE).
Next steps
- Collections
- Channel publishing — how marketplace categorization is used when exporting a product.
- Query a product