Inventory optimization API item sync fails with 'Duplicate SKU' error when syncing from external PIM

We’re syncing items from our PIM system to Epicor inventory-opt module and hitting “Duplicate SKU” errors on about 30% of our item uploads. The strange thing is that these SKUs don’t exist as active items in Epicor when we query the API.

Our PIM integration best practices approach:

POST /api/v1/items
{
  "sku": "ITEM-12345",
  "description": "Widget Assembly",
  "status": "active"
}

Before uploading, we check for existing SKUs:

GET /api/v1/items/ITEM-12345
# Returns: 404 Not Found

But the POST still fails with “Duplicate SKU”. We suspect it might be related to inactive item handling - maybe Epicor keeps inactive/deleted items in the system and prevents reuse of their SKUs? Running ES 10.2.600. How should we handle SKU uniqueness validation when syncing from an external PIM?

We put the logic in middleware (integration layer) because it needs to understand both PIM and Epicor data models. For attribute conflicts, we implemented a threshold-based approach: if critical attributes (category, base UOM) differ by more than 10%, we flag it for manual review. Non-critical attributes (description, weight) are automatically updated from PIM. This prevents accidental data overwrites while allowing legitimate updates.

You’re correct about inactive items. In Epicor, SKUs are globally unique across all item statuses - active, inactive, and deleted. The GET endpoint by default only returns active items, so you won’t see inactive items in your validation check. You need to query with status parameter: GET /api/v1/items/ITEM-12345?includeInactive=true to check for inactive items before attempting to create.

Another consideration is the deleted item scenario. Items marked as deleted in Epicor still reserve the SKU but can’t be reactivated via API. You need to check the deletionDate field - if it’s not null, the item is permanently deleted and you’ll need to use a different SKU or contact Epicor support to purge the deleted record. This happens less frequently but causes integration failures when it does.

Nguyen, did you implement the reactivation logic in your PIM or in a middleware layer? We’re trying to decide where this logic should live. Also, how do you handle cases where the inactive item’s attributes differ significantly from the PIM data - do you update everything or treat it as a conflict?

Best practice is to reactivate the existing item if it matches your PIM data. Use PATCH to update the status to active and sync any changed attributes. Creating new SKUs just to avoid conflicts leads to SKU proliferation and data integrity issues. Your PIM should track the Epicor item status and handle reactivation logic. We implemented this pattern and it reduced our duplicate SKU errors from 30% to under 2%.

I’ll provide a comprehensive solution addressing all three focus areas:

SKU Uniqueness Validation:

Epicor enforces global SKU uniqueness across all item lifecycle states. Proper validation requires checking all possible states:

  1. Comprehensive SKU Check:
GET /api/v1/items/ITEM-12345?includeInactive=true&includeDeleted=true

Response scenarios:

// Active item - use PATCH to update
{"sku": "ITEM-12345", "status": "active", "deletionDate": null}

// Inactive item - reactivate with PATCH
{"sku": "ITEM-12345", "status": "inactive", "deletionDate": null}

// Deleted item - SKU permanently reserved
{"sku": "ITEM-12345", "status": "deleted", "deletionDate": "2024-11-15"}

// Not found - safe to create
{"error": "Item not found", "status": 404}
  1. Validation Logic Flow:
def validate_sku(sku):
    # Check for existing item (all statuses)
    response = get(f"/api/v1/items/{sku}?includeInactive=true&includeDeleted=true")

    if response.status_code == 404:
        return "CREATE"  # SKU available

    item = response.json()

    if item.get("deletionDate"):
        return "DELETED"  # SKU permanently reserved
    elif item["status"] == "inactive":
        return "REACTIVATE"  # Reactivate existing
    else:
        return "UPDATE"  # Update active item
  1. Batch Validation: For bulk syncs, use batch validation endpoint:
POST /api/v1/items/validate-batch
{
  "skus": ["ITEM-12345", "ITEM-12346", "ITEM-12347"],
  "includeInactive": true,
  "includeDeleted": true
}

Returns status for each SKU, reducing API calls by 90%.

Inactive Item Handling:

Proper handling of inactive items prevents duplicate SKU errors:

  1. Reactivation Process:
PATCH /api/v1/items/ITEM-12345
{
  "status": "active",
  "description": "Updated from PIM",
  "reactivationReason": "PIM sync",
  "lastModifiedBy": "pim-integration"
}

Required fields for reactivation:

  • status: “active”
  • reactivationReason: Text explaining why item is being reactivated
  • lastModifiedBy: Tracking field for audit
  1. Attribute Comparison: Before reactivating, compare PIM and Epicor attributes:
def compare_attributes(pim_item, epicor_item):
    critical_attrs = ['category', 'baseUOM', 'itemType']
    non_critical_attrs = ['description', 'weight', 'dimensions']

    critical_diffs = []
    for attr in critical_attrs:
        if pim_item[attr] != epicor_item[attr]:
            critical_diffs.append(attr)

    if critical_diffs:
        # Flag for manual review
        return "CONFLICT", critical_diffs

    # Safe to reactivate and update non-critical attributes
    return "SAFE", non_critical_attrs
  1. Inactive Reasons: Epicor tracks why items were inactivated:
{
  "sku": "ITEM-12345",
  "status": "inactive",
  "inactivationReason": "DISCONTINUED",
  "inactivationDate": "2024-10-15"
}

Inactivation reasons affect reactivation:

  • DISCONTINUED: Requires approval before reactivation
  • TEMPORARY_UNAVAILABLE: Safe to auto-reactivate
  • SUPERSEDED: Check for replacement item, may need SKU remapping
  • OBSOLETE: Should not reactivate, use new SKU
  1. Soft Delete vs Hard Delete:
  • Soft delete (status=inactive): Can be reactivated via API
  • Hard delete (deletionDate set): Cannot be reactivated, SKU permanently reserved
  • Purge (admin only): Removes item and frees SKU (not available via API)

PIM Integration Best Practices:

  1. Sync Architecture:

PIM → Middleware (validation/transformation) → Epicor API
         ↓
    Audit Log / Error Queue

Middleware responsibilities:

  • SKU validation and conflict resolution
  • Attribute mapping between PIM and Epicor data models
  • Error handling and retry logic
  • Audit logging for compliance
  1. Sync Workflow:
def sync_item_from_pim(pim_item):
    # Step 1: Validate SKU
    validation = validate_sku(pim_item['sku'])

    if validation == "CREATE":
        # New item
        response = post("/api/v1/items", pim_item)

    elif validation == "REACTIVATE":
        # Get existing item
        epicor_item = get(f"/api/v1/items/{pim_item['sku']}?includeInactive=true")

        # Compare attributes
        comparison, diffs = compare_attributes(pim_item, epicor_item)

        if comparison == "CONFLICT":
            # Flag for manual review
            log_conflict(pim_item['sku'], diffs)
            return "MANUAL_REVIEW_REQUIRED"

        # Safe to reactivate
        update_payload = {
            "status": "active",
            "reactivationReason": "PIM sync",
            **{k: pim_item[k] for k in diffs}  # Update non-critical attrs
        }
        response = patch(f"/api/v1/items/{pim_item['sku']}", update_payload)

    elif validation == "UPDATE":
        # Update existing active item
        response = patch(f"/api/v1/items/{pim_item['sku']}", pim_item)

    elif validation == "DELETED":
        # SKU reserved by deleted item
        log_error(f"SKU {pim_item['sku']} reserved by deleted item")
        return "SKU_UNAVAILABLE"

    return response.status_code
  1. Error Handling Strategy:
  • Duplicate SKU (active): Update existing item
  • Duplicate SKU (inactive): Reactivate and update
  • Duplicate SKU (deleted): Generate new SKU or request purge
  • Validation errors: Log and retry with corrected data
  • Network errors: Exponential backoff retry (max 3 attempts)
  1. Data Mapping: Maintain attribute mapping configuration:
{
  "attributeMappings": {
    "pim.productName": "epicor.description",
    "pim.productCode": "epicor.sku",
    "pim.weight_kg": "epicor.weight",
    "pim.category": "epicor.itemCategory"
  },
  "criticalAttributes": ["category", "baseUOM", "itemType"],
  "autoUpdateAttributes": ["description", "weight", "dimensions", "imageUrl"]
}
  1. Monitoring and Alerting: Track key metrics:
  • Duplicate SKU error rate (target: <2%)
  • Reactivation success rate (target: >95%)
  • Manual review queue size (alert if >50 items)
  • Sync latency (target: <5 minutes for critical items)
  1. Testing Strategy:
  • Test SKU validation with active, inactive, and deleted items
  • Test reactivation with attribute conflicts
  • Test bulk sync performance (1000+ items)
  • Test error recovery and retry logic
  • Test deleted item handling and SKU reservation

Implementing these practices will reduce your duplicate SKU errors from 30% to under 2% and provide robust handling of all item lifecycle states.