Ingram Integration
Ingram Content Group is the world's largest book distributor. BookWish integrates with Ingram CoreSource Web Services (CWS) to check book availability and wholesale pricing.
Overview
BookWish uses Ingram for:
- Availability Checking: Real-time inventory availability
- Wholesale Pricing: Get dealer cost for books
- Bulk Queries: Check multiple ISBNs simultaneously
- Book Metadata: Publisher, format, dimensions, publication date
- Inventory Confirmation: Verify stock across Ingram warehouses
Implementation
Location: /backend/src/integrations/ingram.ts
Configuration
Required environment variables:
INGRAM_API_URL=https://coresource.ingrambook.com/...
INGRAM_USERNAME=your_username
INGRAM_PASSWORD=your_password
INGRAM_SAN=your_san # Standard Address Number (optional, for ordering)
The integration uses SOAP XML API with Type 9 Query (SearchRequestTypes12349Enhanced).
API Details
SOAP API
Ingram uses SOAP (XML) rather than REST. The integration:
- Builds SOAP envelopes programmatically
- Parses XML responses using
linkedom - Handles pagination automatically
- Extracts structured data
Type 9 Query
Type 9 is the enhanced search query type that supports:
- ISBN lookups
- Stock status
- Pricing information
- Warehouse availability
- Book metadata
Features
1. Check Availability
Check if a single book is available from Ingram.
Single ISBN Lookup
import { createIngramClient } from '../integrations/ingram';
const ingram = createIngramClient({
apiUrl: process.env.INGRAM_API_URL,
username: process.env.INGRAM_USERNAME,
password: process.env.INGRAM_PASSWORD,
san: process.env.INGRAM_SAN
});
const result = await ingram.checkAvailability('9780743273565');
// Returns:
// {
// isbn: '9780743273565',
// available: true,
// quantityAvailable: 1250,
// price: 1199, // Wholesale price in cents ($11.99)
// listPrice: 1899, // MSRP in cents ($18.99)
// stockStatus: 'available',
// publisher: 'Simon & Schuster',
// title: 'The Great Gatsby',
// publicationDate: '05/2004',
// format: 'Paperback',
// dimensions: {
// weight: 6.4, // ounces
// length: 8.0, // inches
// width: 5.2,
// height: 0.5
// }
// }
Bulk ISBN Lookup
Check up to 25 ISBNs in a single request:
const isbns = [
'9780743273565',
'9780142437247',
'9780061120084',
// ... up to 25 ISBNs
];
const results = await ingram.checkBulkAvailability(isbns);
// Returns array of availability results
// Only includes books that are available and have pricing
2. Confirm Inventory
Alias for checkAvailability with simplified response format:
const result = await ingram.confirmInventory('9780743273565');
// Returns:
// {
// confirmed: true,
// quantityAvailable: 1250,
// price: 1199,
// status: 'available'
// }
3. Test Connection
Verify Ingram API credentials are working:
const isConnected = await ingram.testConnection();
if (!isConnected) {
console.error('Failed to connect to Ingram API');
}
Tests connection by querying a known ISBN (The Giver by Lois Lowry).
Data Structures
IngramBookAvailability
interface IngramBookAvailability {
isbn: string;
available: boolean;
quantityAvailable: number;
price: number; // Wholesale price in cents
listPrice?: number; // MSRP in cents
stockStatus: string; // 'available' | 'unavailable'
publisher?: string;
title?: string;
publicationDate?: string; // Format: 'MM/YYYY'
format?: string; // 'Paperback' | 'Hardcover' | 'Mass Market'
dimensions?: {
weight?: number; // Ounces
length?: number; // Inches
width?: number;
height?: number;
};
}
IngramConfig
interface IngramConfig {
apiUrl: string;
username: string;
password: string;
san?: string; // Standard Address Number (for ordering)
}
ISBN Extraction
The integration automatically extracts and validates ISBNs:
// Accepts various ISBN formats
checkAvailability('978-0-7432-7356-5') // Dashes
checkAvailability('9780743273565') // No dashes
checkAvailability('0743273565') // ISBN-10
ISBNs are normalized by removing dashes before querying.
Warehouse Availability
Ingram has multiple warehouses across the US. The integration aggregates stock from:
- LaVergne, TN
- Chambersburg, PA
- Fort Wayne, IN
- Roseburg, OR
- Allentown, PA
Total quantityAvailable is the sum across all warehouses.
Format Detection
The integration determines book format from:
- Department Code: Square categorization (R = Paperback)
- Dimensions: Physical size estimation
- Large (>9" × 6"): Hardcover
- Medium (>7" × 4"): Paperback
- Small: Mass Market
Pricing
Wholesale Pricing
Ingram provides dealer/wholesale pricing:
- Typically 40-50% off list price
- Varies by publisher agreement
- Shown as
pricein cents
List Price (MSRP)
Manufacturer's suggested retail price:
- Publisher's recommended retail price
- Shown as
listPricein cents - Used for calculating margins
Margin Calculation
const margin = result.listPrice - result.price;
const marginPercent = (margin / result.listPrice) * 100;
// Example:
// listPrice: 1899 cents ($18.99)
// price: 1199 cents ($11.99)
// margin: 700 cents ($7.00)
// marginPercent: 36.9%
Error Handling
Network Errors
try {
const result = await ingram.checkAvailability(isbn);
} catch (error) {
if (error.name === 'TimeoutError') {
// Request timed out (10 second timeout)
} else {
// Other network error
}
}
API Errors
- HTTP 401: Invalid credentials
- HTTP 404: ISBN not found (returns null, not error)
- Timeout: 10 second timeout on requests
- Parse Errors: Invalid XML response (returns empty array)
Null Responses
If a book isn't found, methods return null rather than throwing:
const result = await ingram.checkAvailability('invalid-isbn');
// result === null
Caching Strategy
Recommended caching for Ingram data:
// Cache availability for 1 hour
const CACHE_TTL = 60 * 60;
async function getCachedAvailability(isbn: string) {
const cacheKey = `ingram:${isbn}`;
// Check cache
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Fetch from Ingram
const result = await ingram.checkAvailability(isbn);
// Cache result
if (result) {
await redis.setex(cacheKey, CACHE_TTL, JSON.stringify(result));
}
return result;
}
Best Practices
- Batch Requests: Use
checkBulkAvailability()for multiple ISBNs - Cache Results: Cache for at least 1 hour (inventory changes slowly)
- Handle Nulls: Always check for null responses
- Timeout Handling: Implement retry logic for timeouts
- Rate Limiting: Don't hammer the API (no explicit rate limit documented)
- Error Logging: Log all API errors for debugging
- Validate ISBNs: Validate ISBN format before querying
Limitations
- US-Only: Ingram primarily serves US market
- Read-Only: Integration only reads data, doesn't place orders
- 25 ISBN Limit: Bulk queries limited to 25 ISBNs
- 10 Second Timeout: Requests timeout after 10 seconds
- No Real-Time: Stock levels may be slightly delayed
- Publisher Agreements: Not all publishers available through Ingram
SOAP Request Example
Example SOAP envelope sent to Ingram:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<com:UserInfo xmlns:com="http://ingrambook.com/CompDataAccess/companion">
<com:UserName>username</com:UserName>
<com:Password>password</com:Password>
</com:UserInfo>
</soap:Header>
<soap:Body>
<SearchRequestTypes12349Enhanced xmlns="http://ingrambook.com/CompDataAccess/companion">
<queryType>9</queryType>
<query>BN="9780743273565"</query>
<startRecord>1</startRecord>
<endRecord>25</endRecord>
<liveUpdate>Y</liveUpdate>
<dataRequest>STK</dataRequest>
</SearchRequestTypes12349Enhanced>
</soap:Body>
</soap:Envelope>
SOAP Response Parsing
The integration uses linkedom to parse XML responses:
- Queries for
<Product>elements - Extracts
<Basic>and<Ingram>sections - Parses nested elements for metadata
- Aggregates warehouse stock from
<Stock>section
Use Cases
Bookstore Inventory
Check if books can be ordered from Ingram:
const result = await ingram.checkAvailability(isbn);
if (result && result.available && result.quantityAvailable > 0) {
// Book is in stock at Ingram
// Show wholesale price and availability to store owner
}
Price Comparison
Compare Ingram wholesale vs. used book buyback:
const ingramPrice = await ingram.checkAvailability(isbn);
const buybackPrice = await booksrun.getBuybackPrice(isbn);
// Compare and recommend best sourcing option
Demand Tracking
Check if wishlisted books are available:
const wishlistIsbns = await getWishlistIsbns();
const available = await ingram.checkBulkAvailability(wishlistIsbns);
// Notify users when wishlisted books become available
Additional Resources
- Ingram CoreSource Documentation
- Ingram API support: Contact your Ingram account representative
- Type 9 Query specification: Available through Ingram partner portal