BooksRun Integration
BooksRun is a used book marketplace that provides buyback pricing. BookWish integrates with BooksRun to get real-time buyback offers for used books.
Overview
BookWish uses BooksRun for:
- Buyback Pricing: What BooksRun will pay for used books
- Sourcing Comparison: Compare buyback vs. wholesale pricing
- Trade-In Valuation: Value books customers want to trade in
- Market Pricing: Used book market rates
- Condition-Based Pricing: Pricing for different book conditions
Implementation
Location: /backend/src/integrations/booksrun.ts
Configuration
Required environment variables:
BOOKSRUN_API_KEY=your_api_key
The integration uses Redis for caching to minimize API calls.
Features
1. Get Buyback Price
Get the price BooksRun will pay for a used book.
Single ISBN Lookup
import { getBuybackPrice } from '../integrations/booksrun';
const pricing = await getBuybackPrice('9780743273565');
if (pricing) {
console.log('BooksRun will pay:', pricing.buyPriceCents / 100);
console.log('BooksRun sells for:', pricing.sellPriceCents / 100);
console.log('Condition:', pricing.condition);
}
// Returns:
// {
// isbn: '9780743273565',
// buyPriceCents: 450, // BooksRun pays $4.50
// sellPriceCents: 899, // BooksRun sells for $8.99
// condition: 'good',
// lastUpdated: Date(...)
// }
Get Sell Price Only
import { getSellPrice } from '../integrations/booksrun';
const sellPriceCents = await getSellPrice('9780743273565');
// Returns 899 (cents) or null if not available
2. Caching
The integration automatically caches results in Redis:
- Cache Duration: 24 hours
- Negative Cache: 1 hour for books not found
- Cache Key Format:
booksrun:{isbn}
Clear Cache
import { clearCache } from '../integrations/booksrun';
await clearCache('9780743273565');
// Forces fresh lookup on next request
Data Structure
BooksRunPricing
interface BooksRunPricing {
isbn: string;
buyPriceCents: number; // What BooksRun pays
sellPriceCents: number; // What BooksRun sells for
condition: string; // 'good', 'very_good', 'acceptable'
lastUpdated: Date;
}
Use Cases
1. Trade-In Valuation
When customers want to trade in books:
async function valuateTradeIn(isbn: string) {
const pricing = await getBuybackPrice(isbn);
if (!pricing || pricing.buyPriceCents === 0) {
return { accepted: false, reason: 'Book not accepted' };
}
// Offer 80% of BooksRun's buyback price
const offerCents = Math.round(pricing.buyPriceCents * 0.8);
return {
accepted: true,
offerCents,
condition: pricing.condition
};
}
2. Sourcing Comparison
Compare buyback pricing vs. wholesale:
async function compareSourcingOptions(isbn: string) {
const [buyback, wholesale] = await Promise.all([
getBuybackPrice(isbn),
ingram.checkAvailability(isbn)
]);
if (!buyback && !wholesale) {
return { available: false };
}
return {
available: true,
buybackPrice: buyback?.buyPriceCents || null,
wholesalePrice: wholesale?.price || null,
recommendation: buyback && (!wholesale || buyback.buyPriceCents < wholesale.price)
? 'buyback'
: 'wholesale'
};
}
3. Market Price Intelligence
Use BooksRun sell prices as market indicators:
async function getMarketPrice(isbn: string) {
const pricing = await getBuybackPrice(isbn);
if (pricing && pricing.sellPriceCents > 0) {
// BooksRun sell price indicates used market value
return {
marketPriceCents: pricing.sellPriceCents,
source: 'booksrun_used_market'
};
}
return null;
}
API Details
Endpoint
GET https://booksrun.com/api/v3/price/buy/{isbn}
Authentication
Bearer token authentication:
Authorization: Bearer {BOOKSRUN_API_KEY}
Content-Type: application/json
Response Format
Example response from BooksRun API:
{
"buyPrice": 4.50,
"sellPrice": 8.99,
"condition": "good"
}
Error Responses
- 404: ISBN not found (book not accepted for buyback)
- 429: Rate limit exceeded
- 401: Invalid API key
Caching Strategy
Cache Duration
const CACHE_TTL = 24 * 60 * 60; // 24 hours
Buyback prices change slowly, so 24-hour caching is appropriate.
Cache Implementation
// Check cache
const cacheKey = `booksrun:${isbn}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Fetch from API
const data = await fetch(/* ... */);
// Store in cache
await redis.setex(cacheKey, CACHE_TTL, JSON.stringify(data));
Negative Caching
Books not found are cached for 1 hour to prevent repeated failed lookups:
if (response.status === 404) {
await redis.setex(cacheKey, 3600, JSON.stringify(null));
return null;
}
Error Handling
API Errors
try {
const pricing = await getBuybackPrice(isbn);
} catch (error) {
// API errors are logged but don't throw
// Returns null on error
}
Rate Limiting
BooksRun API has rate limits. Handle 429 responses:
if (response.status === 429) {
// Rate limit exceeded
// Wait and retry, or return cached data
}
Best Practices
- Check Cache First: Always check Redis cache before API call
- Batch Lookups: If checking multiple ISBNs, space out requests
- Handle Nulls: Not all books have buyback offers
- Condition Matters: Buyback prices vary by condition
- Update Regularly: Clear cache periodically for price updates
- Fallback Gracefully: If BooksRun unavailable, don't block checkout
- Monitor Usage: Track API call volume against plan limits
Pricing Logic
Buyback Offer Calculation
BookWish typically offers less than BooksRun's buyback price:
const bookwishOffer = pricing.buyPriceCents * 0.8; // 80% of BooksRun
This allows margin for:
- Processing costs
- Condition uncertainty
- Risk of price changes
Margin Calculation
BooksRun's margin (sell price - buy price):
const booksrunMargin = pricing.sellPriceCents - pricing.buyPriceCents;
const marginPercent = (booksrunMargin / pricing.sellPriceCents) * 100;
// Typical margin: 40-60%
Condition Guidelines
BooksRun accepts books in these conditions:
Good
- Minor wear, all pages intact
- May have markings or writing
- Cover shows use
Very Good
- Minimal wear
- Minor markings if any
- Clean copy
Acceptable
- Heavy wear acceptable
- Pages intact and readable
- Heavy markings okay
Integration with Trade-In Flow
Example trade-in controller usage:
// In trade-in controller
const pricing = await getBuybackPrice(isbn);
if (!pricing || pricing.buyPriceCents < 50) {
// Reject books worth less than $0.50
return res.status(400).json({
error: 'Book value too low for trade-in'
});
}
// Calculate trade-in credit
const creditCents = Math.round(pricing.buyPriceCents * 0.8);
await createTradeInCredit({
userId,
isbn,
creditCents,
condition: pricing.condition
});
Limitations
- US Market: Primarily US book market
- Limited Selection: Not all books accepted
- Condition Dependent: Pricing assumes stated condition
- Price Volatility: Prices can change with market demand
- API Rate Limits: Subject to plan limits
- No Real-Time Inventory: Doesn't guarantee they'll accept the book
Monitoring
Important metrics to track:
// Track cache hit rate
const cacheHits = await redis.get('metrics:booksrun:cache_hits');
const cacheMisses = await redis.get('metrics:booksrun:cache_misses');
const hitRate = cacheHits / (cacheHits + cacheMisses);
// Track API errors
const apiErrors = await redis.get('metrics:booksrun:api_errors');
// Track average prices
const avgBuyPrice = await calculateAverageBuyPrice();
Alternative Use: Price Discovery
BooksRun can help with price discovery:
async function suggestRetailPrice(isbn: string) {
const [new_price, used_price] = await Promise.all([
ingram.checkAvailability(isbn),
getBuybackPrice(isbn)
]);
if (new_price?.listPrice) {
// Use publisher MSRP
return new_price.listPrice;
} else if (used_price?.sellPriceCents) {
// Mark up BooksRun sell price by 20%
return Math.round(used_price.sellPriceCents * 1.2);
}
return null; // No price data available
}
Testing
Test Mode
BooksRun may provide test API keys. Check documentation for test endpoints.
Mock Data
For development without API key:
// Mock pricing data
const mockPricing: BooksRunPricing = {
isbn: '9780743273565',
buyPriceCents: 450,
sellPriceCents: 899,
condition: 'good',
lastUpdated: new Date()
};
Cache Testing
Test cache behavior:
// Clear cache before test
await clearCache(isbn);
// First call hits API
const result1 = await getBuybackPrice(isbn);
// Second call hits cache
const result2 = await getBuybackPrice(isbn);
// Verify cache hit
const cached = await redis.get(`booksrun:${isbn}`);
expect(cached).not.toBeNull();
Additional Resources
- BooksRun API Documentation: Contact BooksRun for partner API docs
- BooksRun Website: booksrun.com
- Used Book Market Reports: Industry pricing trends