Skip to main content

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:

  1. Department Code: Square categorization (R = Paperback)
  2. 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 price in cents

List Price (MSRP)

Manufacturer's suggested retail price:

  • Publisher's recommended retail price
  • Shown as listPrice in 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

  1. Batch Requests: Use checkBulkAvailability() for multiple ISBNs
  2. Cache Results: Cache for at least 1 hour (inventory changes slowly)
  3. Handle Nulls: Always check for null responses
  4. Timeout Handling: Implement retry logic for timeouts
  5. Rate Limiting: Don't hammer the API (no explicit rate limit documented)
  6. Error Logging: Log all API errors for debugging
  7. 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