Finance
labx-api - Claude MCP Skill
LabX Seller API integration reference for American Laboratory Trading. Use when working on LabX sync, listings, inquiries, or orders. Trigger words include "labx", "LabX", "labx sync", "labx api", "listings upload", "labx orders", "labx inquiries".
SEO Guide: Enhance your AI agent with the labx-api tool. This Model Context Protocol (MCP) server allows Claude Desktop and other LLMs to labx seller api integration reference for american laboratory trading. use when working on labx sync... Download and configure this skill to unlock new capabilities for your AI workflow.
Documentation
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:**
```bash
# 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**:
```json
{
"success": true,
"data": {
"categories": [
{
"id": 0,
"name": "string"
}
]
}
}
```
**Example cURL**:
```bash
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**:
```json
{
"success": true,
"data": {
"countries": [
{
"code": "US",
"name": "string",
"regions": [
{
"id": 0,
"name": "string"
}
]
}
]
}
}
```
**Example cURL**:
```bash
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**:
```json
{
"success": true,
"data": {
"manufacturers": [
{
"id": 0,
"name": "string"
}
]
}
}
```
**Example cURL**:
```bash
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**:
```json
{
"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**:
```bash
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**:
```json
{
"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**:
```json
{
"success": true,
"data": {
"testMode": false,
"validListingCount": 145,
"invalidListingCount": 2,
"invalidListingDetails": [
{
"unique_id": "string",
"errors": ["error message"]
}
]
}
}
```
**Example cURL**:
```bash
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**:
```json
{
"success": true,
"data": [
{
"inquiryId": 0,
"messageId": 0,
"type": "sent",
"messageAt": "string"
}
]
}
```
**Example cURL**:
```bash
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**:
```json
{
"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**:
```bash
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**:
```json
{
"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**:
```bash
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**:
```json
{
"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**:
```bash
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**:
```json
{
"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**:
```bash
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**:
```json
{
"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**:
```bash
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**:
```json
{
"success": false,
"message": "string",
"context": {}
}
```
**429 Too Many Requests**:
```json
{
"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`:
```ruby
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:
```bash
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
```bash
# 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
```ruby
# 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
```bash
# 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
```ruby
# 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 documentedSignals
Information
- Repository
- arlenagreer/claude_configuration_docs
- Author
- arlenagreer
- Last Sync
- 3/12/2026
- Repo Updated
- 3/11/2026
- Created
- 3/9/2026
Reviews (0)
No reviews yet. Be the first to review this skill!
Related Skills
upgrade-nodejs
Upgrading Bun's Self-Reported Node.js Version
cursorrules
CrewAI Development Rules
cn-check
Install and run the Continue CLI (`cn`) to execute AI agent checks on local code changes. Use when asked to "run checks", "lint with AI", "review my changes with cn", or set up Continue CI locally.
CLAUDE
CLAUDE.md
Related Guides
Bear Notes Claude Skill: Your AI-Powered Note-Taking Assistant
Learn how to use the bear-notes Claude skill. Complete guide with installation instructions and examples.
Mastering tmux with Claude: A Complete Guide to the tmux Claude Skill
Learn how to use the tmux Claude skill. Complete guide with installation instructions and examples.
OpenAI Whisper API Claude Skill: Complete Guide to AI-Powered Audio Transcription
Learn how to use the openai-whisper-api Claude skill. Complete guide with installation instructions and examples.