labx-api

SKILL.md

LabX Seller API Integration Skill

Overview

LabX Seller API provides programmatic access for managing product listings, inquiries, and orders on the LabX marketplace. This skill documents the complete API surface, integration patterns, and ALT-specific implementation details.

Base URL: https://admin.labx.com Development URL: http://localhost (not typically used) API Version: v1 Path Prefix: /api/v1/ (use this prefix for all endpoints)

All authenticated endpoints require a Bearer token via the seller-api-token header. The token is stored in ENV['LABX_API_TOKEN'] and is active for the duration of ALT's listing program (not time-limited).

Authentication

Bearer Token Authentication

All authenticated endpoints require the seller-api-token header:

Authorization: Bearer <seller-api-token>

Token Management:

  • 1Password: Stored in Development vault as "LabX Seller API Token" (credential field)
  • Retrieval: op item get "LabX Seller API Token" --vault="Development" --fields credential
  • ENV variable: The application reads from ENV['LABX_API_TOKEN'] at runtime
  • Token lifetime: Active for length of ALT listing program (no expiration)
  • Token persistence: Same token survives program renewals
  • Token rotation: Contact Jesse Brito at LabX to rotate if compromised
  • Token validation: If no active program, token will not work

Retrieving the token when needed:

# Get the token value from 1Password
op item get "LabX Seller API Token" --vault="Development" --fields credential

# Set it as an environment variable for local development
export LABX_API_TOKEN=$(op item get "LabX Seller API Token" --vault="Development" --fields credential)

# Use in a one-off command
LABX_API_TOKEN=$(op read "op://Development/LabX Seller API Token/credential") rails console

No OAuth Flow Required: Token is provisioned by LabX directly. No client credentials or OAuth dance needed.

API Endpoints Reference

Lookup Endpoints (No Authentication Required)

These endpoints provide reference data for categories, countries, and manufacturers. No seller-api-token required per Swagger documentation.

GET /api/v1/categories

Retrieve all available product categories.

HTTP Method: GET Path: /api/v1/categories Authentication: None required Query Parameters: None

Response 200:

{
  "success": true,
  "data": {
    "categories": [
      {
        "id": 0,
        "name": "string"
      }
    ]
  }
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/categories" \
  -H "accept: application/json"

GET /api/v1/countries

Retrieve all available countries with regions.

HTTP Method: GET Path: /api/v1/countries Authentication: None required Query Parameters: None

Response 200:

{
  "success": true,
  "data": {
    "countries": [
      {
        "code": "US",
        "name": "string",
        "regions": [
          {
            "id": 0,
            "name": "string"
          }
        ]
      }
    ]
  }
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/countries" \
  -H "accept: application/json"

GET /api/v1/manufacturers

Retrieve all available manufacturers.

HTTP Method: GET Path: /api/v1/manufacturers Authentication: None required Query Parameters: None

Response 200:

{
  "success": true,
  "data": {
    "manufacturers": [
      {
        "id": 0,
        "name": "string"
      }
    ]
  }
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/manufacturers" \
  -H "accept: application/json"

Product Endpoints (Authentication Required)

GET /api/v1/listings

Retrieve paginated list of product listings with filtering options.

HTTP Method: GET Path: /api/v1/listings Authentication: Required (seller-api-token header)

Query Parameters:

Parameter Type Default Description
page integer 1 Page number for pagination
page_length integer 1000 Number of items per page (default 1000, max undefined but >1000 may timeout)
show_active boolean - Filter for active listings
show_inactive boolean - Filter for inactive listings
search string - Search term for filtering listings

Response 200:

{
  "success": true,
  "data": {
    "meta": {
      "page": 1,
      "page_length": 1000,
      "total_pages": 5,
      "total_records": 4532,
      "filters": {
        "show_active": true,
        "show_inactive": false,
        "search": "string"
      }
    },
    "items": [
      {
        "unique_id": 0,
        "sku": "string",
        "name": "string",
        "manufacturer": "string",
        "model": "string",
        "description": "string",
        "listing_active": 0,
        "condition": "Refurbished",
        "warranty_offered": 1,
        "price": 0,
        "currency": "USD",
        "qty": 0,
        "min_order_qty": 0,
        "weight": 0,
        "weight_units": "KG",
        "height": 0,
        "width": 0,
        "length": 0,
        "size_units": "INCHES",
        "show_quote_button": 1,
        "show_buy_now_button": 1,
        "external_link_url": "string",
        "external_link_text": "string",
        "categories": [],
        "location": {},
        "shipping": [],
        "images": [],
        "options": {},
        "variations": {}
      }
    ]
  }
}

Field Type Notes:

  • unique_id: Returned as integer (not string)
  • listing_active: Returned as integer (0 or 1, not boolean)
  • condition: Returned in Titlecase (e.g., "Refurbished", "New", "Used", "Parts")

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/listings?page=1&page_length=1000&show_active=true" \
  -H "accept: application/json" \
  -H "seller-api-token: Bearer ${LABX_API_TOKEN}"

POST /api/v1/listings/upload

Batch create or update product listings. Matching by unique_id: if it exists, the listing is updated; if new, a listing is created.

HTTP Method: POST Path: /api/v1/listings/upload Authentication: Required (seller-api-token header) Content-Type: application/json

Request Body:

{
  "items": [
    {
      "unique_id": "string",
      "name": "string",
      "manufacturer": "string",
      "model": "string",
      "description": "string",
      "condition": "REFURBISHED",
      "warranty_offered": true,
      "price": 0,
      "currency": "USD",
      "qty": 0,
      "min_order_qty": 0,
      "weight": 0,
      "weight_units": "KG",
      "height": 0,
      "width": 0,
      "length": 0,
      "size_units": "INCHES",
      "show_quote_button": true,
      "show_buy_now_button": true,
      "external_link_url": "string",
      "external_link_text": "string",
      "categories": [],
      "location": {},
      "shipping": [],
      "images": []
    }
  ]
}

Required Fields:

  • unique_id (string, 1-100 characters)
  • name (string, 3-500 characters)

Field Enums and Constraints:

Field Type Values Notes
condition string NEW, USED, PARTS, REFURBISHED UPPERCASE, case-sensitive
currency string USD, CAD, GBP, EUR
price decimal 0 - 99,999,999.99
weight_units string LBS, KG
size_units string INCHES, CM
shipping.carrier string FEDEX, UPS, USPS, FLAT, PICKUP
shipping.type string CALCULATED, FLAT

Important POST vs GET Differences:

  • POST unique_id: string (GET returns integer)
  • POST listing_active: boolean (GET returns 0/1 integer)
  • POST condition: UPPERCASE (GET returns Titlecase)

Response 200:

{
  "success": true,
  "data": {
    "testMode": false,
    "validListingCount": 145,
    "invalidListingCount": 2,
    "invalidListingDetails": [
      {
        "unique_id": "string",
        "errors": ["error message"]
      }
    ]
  }
}

Example cURL:

curl -X POST "https://admin.labx.com/api/v1/listings/upload" \
  -H "accept: application/json" \
  -H "seller-api-token: Bearer ${LABX_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "unique_id": "ALT-12345",
        "name": "Thermo Fisher Centrifuge",
        "condition": "REFURBISHED",
        "price": 2500.00,
        "currency": "USD"
      }
    ]
  }'

ALT Implementation Note: The actual ALT codebase wraps the array in a "listings" key instead of "items", which may be legacy behavior. Swagger documentation uses "items".


Inquiry Endpoints (Authentication Required)

GET /api/v1/inquiries

Retrieve list of inquiries. Use after_id parameter for incremental fetching.

HTTP Method: GET Path: /api/v1/inquiries Authentication: Required (seller-api-token header)

Query Parameters:

Parameter Type Description
after_id integer Highest known messageId. Returns only inquiries received after this ID.

Response 200:

{
  "success": true,
  "data": [
    {
      "inquiryId": 0,
      "messageId": 0,
      "type": "sent",
      "messageAt": "string"
    }
  ]
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/inquiries?after_id=1000" \
  -H "accept: application/json" \
  -H "seller-api-token: Bearer ${LABX_API_TOKEN}"

GET /api/v1/inquiries/{inquiryId}

Retrieve full details for a specific inquiry including customer info and listing details.

HTTP Method: GET Path: /api/v1/inquiries/{inquiryId} Authentication: Required (seller-api-token header)

Path Parameters:

Parameter Type Required Description
inquiryId integer Yes Inquiry ID to retrieve

Response 200:

{
  "success": true,
  "data": {
    "quoteRequestId": 0,
    "sellerId": 0,
    "sellerName": "string",
    "customer": {
      "email": "string",
      "phone": "string",
      "name": "string",
      "company": "string",
      "street": "string",
      "city": "string",
      "region": "string",
      "countryCode": "US",
      "postalCode": "string"
    },
    "listing": {
      "id": 0,
      "sellerItemNumber": "string",
      "sku": "string",
      "name": "string",
      "manufacturer": "string",
      "model": "string",
      "price": 0,
      "variation": [{}],
      "link": "string",
      "image": "string"
    },
    "category": "string",
    "quotePreferences": {
      "timeline": "Info only",
      "equipmentType": "Complete System",
      "includeNew": true,
      "includeUsed": true
    }
  },
  "context": {
    "quoteId": 0
  }
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/inquiries/12345" \
  -H "accept: application/json" \
  -H "seller-api-token: Bearer ${LABX_API_TOKEN}"

Order Endpoints - Sales (Authentication Required)

GET /api/v1/orders/sales

Retrieve paginated list of sales orders (items sold by ALT).

HTTP Method: GET Path: /api/v1/orders/sales Authentication: Required (seller-api-token header)

Query Parameters:

Parameter Type Description
page integer Page number for pagination
orders_since string ISO 8601 datetime. Return orders created after this timestamp.
orders_after_id integer Return orders with ID greater than this value

Response 200:

{
  "success": true,
  "data": [
    {
      "order_id": 0,
      "created_at": "string",
      "customer_email": "string",
      "num_skus": 0,
      "num_items": 0,
      "subtotal": 0,
      "tax": 0,
      "total": 0,
      "skus": "string"
    }
  ],
  "context": {
    "page": 1,
    "pageSize": 1,
    "totalPages": 0,
    "totalOrders": 0,
    "filters": {
      "orders_since": "2024-01-01T00:00:00Z",
      "orders_after_id": 0
    }
  }
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/orders/sales?page=1&orders_since=2024-01-01T00:00:00Z" \
  -H "accept: application/json" \
  -H "seller-api-token: Bearer ${LABX_API_TOKEN}"

GET /api/v1/orders/sales/{orderId}

Retrieve full details for a specific sales order.

HTTP Method: GET Path: /api/v1/orders/sales/{orderId} Authentication: Required (seller-api-token header)

Path Parameters:

Parameter Type Required Description
orderId integer Yes Sales order ID to retrieve

Response 200:

{
  "success": true,
  "data": {
    "orderId": 0,
    "orderedAt": "string",
    "total": 0,
    "paymentMethod": "string",
    "customer": {
      "email": "string",
      "phone": "string",
      "name": "string",
      "company": "string",
      "street": "string",
      "city": "string",
      "postalcode": "string",
      "region": "string",
      "country": "US"
    },
    "items": [
      {
        "sku": "string",
        "name": "string",
        "variation": {},
        "price": 0,
        "qty": 0,
        "subtotal": 0,
        "shipMethod": "string",
        "shipPrice": 0,
        "tax": 0,
        "total": 0
      }
    ]
  }
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/orders/sales/12345" \
  -H "accept: application/json" \
  -H "seller-api-token: Bearer ${LABX_API_TOKEN}"

Order Endpoints - Purchases (Authentication Required)

GET /api/v1/orders/purchases

Retrieve paginated list of purchase orders (items purchased by ALT from other sellers).

HTTP Method: GET Path: /api/v1/orders/purchases Authentication: Required (seller-api-token header)

Query Parameters:

Parameter Type Description
page integer Page number for pagination
orders_since string ISO 8601 datetime. Return orders created after this timestamp.
orders_after_id integer Return orders with ID greater than this value

Response 200:

{
  "success": true,
  "data": [
    {
      "order_id": 0,
      "created_at": "string",
      "num_sellers": 0,
      "num_skus": 0,
      "num_items": 0,
      "subtotal": 0,
      "tax": 0,
      "total": 0,
      "skus": "string",
      "sellers": "string"
    }
  ],
  "context": {
    "page": 1,
    "pageSize": 1,
    "totalPages": 0,
    "totalOrders": 0,
    "filters": {
      "orders_since": "2024-01-01T00:00:00Z",
      "orders_after_id": 0
    }
  }
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/orders/purchases?page=1&orders_since=2024-01-01T00:00:00Z" \
  -H "accept: application/json" \
  -H "seller-api-token: Bearer ${LABX_API_TOKEN}"

GET /api/v1/orders/purchases/{orderId}

Retrieve full details for a specific purchase order.

HTTP Method: GET Path: /api/v1/orders/purchases/{orderId} Authentication: Required (seller-api-token header)

Path Parameters:

Parameter Type Required Description
orderId integer Yes Purchase order ID to retrieve

Response 200:

{
  "success": true,
  "data": {
    "orderId": 0,
    "orderedAt": "string",
    "total": 0,
    "paymentMethod": "string",
    "sellers": [
      {
        "seller": "string",
        "email": "string",
        "phone": "string",
        "location": "string",
        "items": [
          {
            "sku": "string",
            "name": "string",
            "variation": {},
            "price": 0,
            "qty": 0,
            "subtotal": 0,
            "shipMethod": "string",
            "shipPrice": 0,
            "tax": 0,
            "total": 0
          }
        ]
      }
    ]
  }
}

Example cURL:

curl -X GET "https://admin.labx.com/api/v1/orders/purchases/12345" \
  -H "accept: application/json" \
  -H "seller-api-token: Bearer ${LABX_API_TOKEN}"

Error Responses (All Authenticated Endpoints)

401 Unauthorized:

{
  "success": false,
  "message": "string",
  "context": {}
}

429 Too Many Requests:

{
  "success": false,
  "message": "string",
  "context": {}
}

Important API Behaviors (Jesse Brito Clarifications, Feb 2026)

URL Path

  • Always use /api/v1/ prefix (documented path) going forward
  • Legacy code may have used different paths but standardize on this

Token Lifecycle

  • Active for the length of ALT's listing program
  • Same token survives program renewals
  • Will not work if ALT has no active program
  • Contact Jesse Brito to rotate if compromised

Upload Behavior

  • unique_id match → updates existing listing
  • New unique_id → creates new listing
  • Test mode (testMode: true) not fully supported yet — use production with caution

Field Type Handling

  • GET returns unique_id as integer, POST accepts string
  • GET returns listing_active as 0/1, POST expects boolean
  • Condition is CASE-SENSITIVE: GET returns Titlecase (e.g., "Refurbished"), POST expects UPPERCASE (e.g., "REFURBISHED")

Rate Limits

  • Package listing limit enforced over 7-day rolling window (15,000 listings for LabVista tier)
  • No per-request rate limits documented
  • For large bulk uploads, notify LabX team in advance

Categories and Manufacturers

  • Use lookup endpoints (GET /api/v1/categories, GET /api/v1/manufacturers) instead of hardcoding values
  • Category and manufacturer IDs/names may change — always fetch fresh data

Deletion

  • No permanent delete via API
  • Only deactivation supported via listing_active: false (POST) or active: false in request body

Pricing

  • No LabX-side price limit enforced
  • "Request a quote" button is optional strategy for high-value items (controlled via show_quote_button field)

Shipping

  • CALCULATED shipping works with FedEx/UPS/USPS carriers based on weight
  • Shipping carrier rates configured in LabX Control Panel > Settings > Shipping
  • FLAT shipping requires manual price specification

GET page_length

  • No hard maximum documented
  • Values >1000 may timeout on large result sets
  • Default is 1000 which is reasonable for most use cases

Listing Versioning (Discovered Feb 2026)

  • LabX retains multiple historical versions of every listing — this is a critical architectural behavior
  • For ALT's ~15,947 catalog numbers, LabX stores ~28,819 total listing records but only ~16,069 unique unique_id values
  • That means ~12,750 listings are stale duplicates (5,075 catalog numbers have 2–82 copies each)
  • 82 listings have a blank unique_id (likely test data, including one labeled "API UPDATE TEST")
  • When fetching via GET /api/v1/listings, all versions are returned, not just the current one
  • Deduplication strategy: When comparing or analyzing listings, prefer the active listing per unique_id, then fall back to the highest SKU as a tiebreaker
  • Consider requesting LabX API enhancement from Jesse Brito — a way to query only the current/latest version per listing would reduce API calls and improve accuracy

show_quote_button vs show_buy_now_button Behavior

  • show_quote_button: true if price is $1–$10,500 OR if the item is in stock; false if out of stock AND price is $0/nil or exceeds $10,500
  • show_buy_now_button: true if price > $0 and price <= $10,500 AND in stock; false otherwise
  • When a listing is deactivated (listing_active=false), LabX preserves the old button states — it does not reset them
  • This means inactive listings may show stale show_quote_button=true values from when items were in stock — this is cosmetically harmless since the listings are not visible on the marketplace

Manufacturer Name Mapping

  • ALT uses Manufacturer#corresponding_labx_manufacturer_name to map internal manufacturer names to LabX-expected names
  • As of Feb 2026: 2,200 of 2,230 manufacturers have LabX name mappings configured
  • Remaining mismatches on LabX are stale data from before mappings were set or before a sync pushed corrected names
  • Common stale patterns: "Thermo Fisher Scientific" (LabX) vs "Thermo Scientific" (ALT sends), "HP" vs "Hewlett Packard", "Dynamax" vs "Rainin"
  • A full sync would resolve all stale manufacturer names in one pass, but rate limits (15,000/week) currently prevent this

ALT Codebase Integration Map

Component File Path Purpose
Core sync methods app/models/model.rb Primary LabX integration logic
Sync controller app/controllers/api/labx_sync_controller.rb Internal API for triggering sync
Background job app/jobs/sync_labx_job.rb Async sync execution with locking
Execution tracking app/models/labx_sync_execution.rb Sync run status and metrics
Lambda trigger lambda/labx_sync_trigger.py CloudWatch scheduled event handler
Rake tasks lib/tasks/labx_sync.rake Manual CLI operations
Manufacturer mapper lib/manufacturer_labx_updater.rb CSV-based manufacturer name mapping

Core Methods in app/models/model.rb

Method Purpose Parameters
synchronize_labx_listings Two-phase sync orchestrator (upload + deactivate) listing_mode, deactivation_mode, days_back, batch_delay
update_labx_listings Format and POST to API with retry/backoff models (array)
fetch_labx_listings GET all listings with pagination None
labx_variance_report Compare local vs LabX listings (with dedup) exclude_fields:, ignore_truncations:
test_labx_sync Diagnostic testing for sync operations models, dry_run
format_as_hash_for_labx_api_post Instance method: map ALT model to LabX schema None (instance method)
calculate_shipping_cost_for_labx Shipping fee calculation logic None (instance method)
show_quote_button_on_labx Determine if quote button should show None (instance method)
show_buy_now_button_on_labx Determine if buy now button should show None (instance method)
labx_image_url Generate LabX-compatible image URL None (instance method)
labx_item_url Generate LabX listing URL None (instance method)

Internal API Endpoints (app/controllers/api/labx_sync_controller.rb)

Endpoint Method Auth Purpose
/api/labx_sync/trigger POST HMAC-SHA256 Trigger SyncLabxJob with mode parameters
/api/labx_sync/status GET None Last sync status + 30-day execution statistics

Background Job Modes (app/jobs/sync_labx_job.rb)

Mode Description
daily Recent in-stock + recent out-of-stock (legacy)
hybrid ALL in-stock + recent out-of-stock (RECOMMENDED)
full ALL in-stock + ALL out-of-stock
weekly Alias for full
custom Configurable days_back parameter

Job Features:

  • SuckerPunch async processing
  • Redis + file-based distributed locking
  • HMAC-SHA256 authentication for trigger endpoint
  • Execution tracking via LabxSyncExecution model

Field Mapping (ALT to LabX)

ALT Field LabX Field Transformation Notes
alt_catalog_number unique_id Direct Primary key for matching existing listings
manufacturer.name manufacturer Uses corresponding_labx_manufacturer_name if set Association lookup
name model Direct Model name
manufacturer.name + ' ' + name name Concatenation Combined display name
description description Direct Full text description
current_price price Direct (if >0 and <=10500) Price capped at $10,500 internally
shipping_weight_in_grams weight ÷ 1000 Convert grams to kilograms
(hardcoded) condition Always "REFURBISHED" Uppercase for POST
(hardcoded) warranty_offered Always true Boolean
(hardcoded) currency Always "USD" String
(hardcoded) weight_units Always "KG" String
(hardcoded) size_units Always "INCHES" String
subcategory.name categories[0] Uses corresponding_labx_subcategory_name if set Category mapping
(computed) shipping.carrier Always "FLAT" Flat rate shipping
(computed) shipping.type Always "Flat" Flat rate type
(computed) shipping.country Always "US" US-only shipping
(computed) shipping.price See shipping calculation Dynamic based on weight/type

Shipping Price Calculation Logic

Implemented in calculate_shipping_cost_for_labx:

def calculate_shipping_cost_for_labx
  return 5.50 if accessories?     # Accessories: $5.50
  return 8.95 if heavy_item?      # Heavy items: $8.95

  # Standard: 4.9% of price, minimum $0.39
  [(current_price * 0.049).round(2), 0.39].max
end

Button Display Logic

Quote Button (show_quote_button_on_labx):

  • Show if price > $10,500 or price == 0
  • Otherwise hide

Buy Now Button (show_buy_now_button_on_labx):

  • Show if price > 0 and price <= $10,500
  • Otherwise hide

Database Columns for LabX Mapping

Table Column Purpose
categories corresponding_labx_category_name Override default category name mapping
subcategories corresponding_labx_subcategory_name Override default subcategory name mapping
manufacturers corresponding_labx_manufacturer_name Override default manufacturer name mapping

These columns allow manual mapping overrides when ALT's naming doesn't match LabX's expected values.


Environment Variables

Variable Purpose Required Example
LABX_API_TOKEN Bearer token for API auth Yes 1Password: op://Development/LabX Seller API Token/credential
LABX_API_ENABLED Feature flag for sync Yes 'true' or 'false'
LABX_SYNC_SECRET HMAC shared secret for trigger endpoint Yes Random 32+ char string
NOTIFICATION_EMAIL Email for sync error notifications Yes Email address
REDIS_URL Distributed lock coordination Yes Redis connection URL

Security Note: NEVER include actual token values in code, logs, or documentation. Always reference via ENV['LABX_API_TOKEN'].

1Password Retrieval: The production token is stored in the Development vault as "LabX Seller API Token". Retrieve with:

op item get "LabX Seller API Token" --vault="Development" --fields credential

Sync Modes Explained

Daily Mode (Legacy)

  • In-stock: Recent items only (configurable days_back)
  • Out-of-stock: Recent items only (configurable days_back)
  • Use case: Daily incremental sync for active inventory changes

Hybrid Mode (RECOMMENDED)

  • In-stock: ALL items (comprehensive)
  • Out-of-stock: Recent items only (configurable days_back, typically 7-30 days)
  • Use case: Default daily sync - ensures all in-stock items are visible while cleaning up recent deactivations
  • Rationale: Best balance of completeness and performance

Full/Weekly Mode

  • In-stock: ALL items
  • Out-of-stock: ALL items
  • Use case: Weekly comprehensive sync or recovery from sync issues
  • Caution: Can be slow and may hit rate limits on large catalogs

Custom Mode

  • In-stock: Configurable days_back
  • Out-of-stock: Configurable days_back
  • Use case: Specific operational needs or testing

Common Operations Quick Reference

Manual Sync Operations

# Trigger daily sync (recent changes)
rails labx_sync:run

# Trigger hybrid sync (all in-stock + recent out-of-stock)
rails labx_sync:sync_async[hybrid,30]

# Trigger full sync (all items)
rails labx_sync:sync_all

# Check last sync status
curl http://localhost:3000/api/labx_sync/status

Testing and Diagnostics

# Test sync connection (dry run)
Model.test_labx_sync(Model.where(in_stock: true).limit(10), dry_run: true)

# Fetch current LabX listings
labx_listings = Model.fetch_labx_listings
puts "Total LabX listings: #{labx_listings.dig('data', 'meta', 'total_records')}"

# Run variance report (compare local vs LabX)
variance = Model.labx_variance_report
puts "Local only: #{variance[:local_only].count}"
puts "LabX only: #{variance[:labx_only].count}"
puts "Price mismatches: #{variance[:price_mismatches].count}"

Triggering Sync via Internal API

# Generate HMAC signature
TIMESTAMP=$(date +%s)
MESSAGE="POST/api/labx_sync/trigger${TIMESTAMP}"
SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$LABX_SYNC_SECRET" | awk '{print $2}')

# Trigger sync
curl -X POST "http://localhost:3000/api/labx_sync/trigger" \
  -H "Content-Type: application/json" \
  -H "X-Signature: $SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -d '{"mode": "hybrid", "days_back": 30}'

Checking Sync Execution History

# Last 10 sync executions
LabxSyncExecution.order(created_at: :desc).limit(10).each do |exec|
  puts "#{exec.created_at}: #{exec.status} - #{exec.message}"
end

# Stats for last 30 days
recent = LabxSyncExecution.where('created_at > ?', 30.days.ago)
puts "Total executions: #{recent.count}"
puts "Completed: #{recent.where(status: 'completed').count}"
puts "Failed: #{recent.where(status: 'failed').count}"

Best Practices

  1. Use Hybrid Mode for Daily Sync: Best balance of completeness and performance
  2. Monitor Execution Status: Check /api/labx_sync/status regularly for failures
  3. Respect Rate Limits: Package limit is 15,000 over 7-day window for LabVista tier
  4. Map Manufacturer Names: Use corresponding_labx_manufacturer_name for name discrepancies
  5. Test Before Production Upload: Use test_labx_sync with dry_run: true
  6. Never Hard-Delete: Only deactivate listings via listing_active: false
  7. Case-Sensitive Condition: Always use UPPERCASE for POST (e.g., "REFURBISHED")
  8. Monitor Variance Reports: Regularly check for discrepancies between local and LabX
  9. Coordinate Large Uploads: Notify LabX team before uploading >1000 items at once
  10. Secure Token Storage: Never commit LABX_API_TOKEN to version control

Troubleshooting

Authentication Failures (401)

  • Verify ENV['LABX_API_TOKEN'] is set correctly
  • Check if ALT listing program is active
  • Contact Jesse Brito if token rotation needed

Rate Limit Errors (429)

  • Check 7-day listing upload count
  • Contact LabX for tier upgrade if hitting limits regularly
  • Spread large uploads across multiple days

Condition Validation Errors

  • Ensure condition is UPPERCASE: "REFURBISHED", not "Refurbished"
  • Valid values: NEW, USED, PARTS, REFURBISHED

Sync Job Not Running

  • Check Redis connection: redis-cli ping
  • Verify LABX_API_ENABLED='true' in environment
  • Check SuckerPunch queue: SuckerPunch::Queue.stats

Variance Report Mismatches

  • Run variance report: Model.labx_variance_report(exclude_fields: [...], ignore_truncations: true)
  • Deduplication is built in: The report automatically deduplicates LabX listings (prefers active, then highest SKU)
  • Use EXCLUDE_FIELDS env var for rake task: EXCLUDE_FIELDS=weight,price,description rake labx:variance_report
  • Use IGNORE_TRUNCATIONS=1 to skip string truncation differences
  • Boolean fields treat nil and false as equivalent
  • Check corresponding_labx_*_name mappings in database for manufacturer/category mismatches
  • Most name/model/manufacturer mismatches are stale LabX data — a full sync resolves them
  • show_quote_button mismatches on inactive listings are cosmetically harmless — no action needed

Large Page Length Timeouts

  • Reduce page_length to 500 or less
  • Implement pagination for large result sets
  • Use show_active or show_inactive filters to reduce result size

Version History

v1.1.0 (February 23, 2026)

  • CRITICAL DISCOVERY: Documented LabX listing versioning behavior — LabX retains multiple historical versions per unique_id, causing ~12,750 stale duplicates across ~28,819 total records for ALT's ~15,947 catalog numbers
  • Added deduplication strategy guidance (prefer active listing, then highest SKU)
  • Documented show_quote_button vs show_buy_now_button behavior and stale state preservation on inactive listings
  • Documented manufacturer name mapping status (2,200/2,230 configured) and common stale mismatch patterns
  • Updated labx_variance_report method documentation with new parameters: exclude_fields:, ignore_truncations:
  • Enhanced variance report troubleshooting with filtering options and interpretation guidance
  • Full variance report results saved at: docs/labx-variance-report-2026-02-23.md

v1.0.0 (February 2026)

  • Initial skill creation
  • All 11 endpoints documented
  • Jesse Brito clarifications integrated (Feb 2026)
  • ALT codebase integration map complete
  • Field mappings and transformations documented
Weekly Installs
1
GitHub Stars
3
First Seen
6 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1