Skip to main content

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

  1. Check Cache First: Always check Redis cache before API call
  2. Batch Lookups: If checking multiple ISBNs, space out requests
  3. Handle Nulls: Not all books have buyback offers
  4. Condition Matters: Buyback prices vary by condition
  5. Update Regularly: Clear cache periodically for price updates
  6. Fallback Gracefully: If BooksRun unavailable, don't block checkout
  7. 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