sound-tank
v2.0.0
Published
A library for interacting with the Reverb Marketplace API
Readme
Quick Start
npm install sound-tank
# or
yarn add sound-tank
# or
pnpm add sound-tankimport Reverb from 'sound-tank';
const reverb = new Reverb({ apiKey: process.env.REVERB_API_KEY });
const { data } = await reverb.listings.getMy({ perPage: 10, state: 'live' });
data.listings.forEach(listing => {
console.log(`${listing.title}: ${listing.price.display}`);
});Features
- ✅ Full TypeScript Support - Complete type definitions for all API entities
- ✅ Automatic Pagination - Built-in helpers to fetch all results seamlessly
- ✅ Configuration Management - Easy setup for currency, locale, and shipping preferences
- ✅ Comprehensive Coverage - Listings, orders, and arbitrary endpoint access
- ✅ HTTP Client Abstraction - Clean architecture with testable mock implementations
- ✅ Dual Module Support - Both CommonJS and ESM builds included
- ✅ Well Tested - Extensive unit and integration test coverage
- ✅ Minimal Dependencies - Only requires axios
Table of Contents
- Installation
- Getting Started
- Configuration
- API Methods
- TypeScript Usage
- Advanced Features
- Examples
- Development
- Testing
- Contributing
- License
Installation
Install via your preferred package manager:
npm install sound-tank
# or
yarn add sound-tank
# or
pnpm add sound-tankGetting a Reverb API Key
- Visit Reverb API Settings
- Generate a new API token
- Set the appropriate scopes for your use case
- Store securely in environment variables
# .env
REVERB_API_KEY=your_api_key_hereYou can use the provided .env.example file as a template:
cp .env.example .env
# Edit .env and add your REVERB_API_KEYGetting Started
Initialize the Client
import Reverb from 'sound-tank';
const reverb = new Reverb({
apiKey: process.env.REVERB_API_KEY,
displayCurrency: 'USD', // optional
locale: 'en', // optional
version: '3.0', // optional
});Fetch Your Listings
const response = await reverb.listings.getMy({
perPage: 25,
page: 1,
state: 'live',
query: 'Gibson Les Paul'
});
console.log(response.data.listings);Error Handling
try {
const response = await reverb.listings.getMy({ state: 'live' });
console.log(`Found ${response.data.listings.length} listings`);
} catch (error) {
console.error('Failed to fetch listings:', error.message);
}Configuration
Constructor Options
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| apiKey | string | ✅ Yes | - | Your Reverb API key |
| version | string | No | '3.0' | API version to use |
| rootEndpoint | string | No | 'https://api.reverb.com/api' | API base URL |
| displayCurrency | string | No | 'USD' | Currency for price display |
| locale | string | No | 'en' | Language locale (e.g., 'en', 'fr', 'de') |
| shippingRegion | string | No | undefined | Shipping region code (e.g., 'US', 'EU') |
Runtime Configuration
You can update configuration after initialization using setters:
reverb.displayCurrency = 'EUR';
reverb.locale = 'fr';
reverb.shippingRegion = 'FR';
reverb.version = '3.0';These changes automatically update the internal headers and configuration for subsequent API requests.
API Methods
listings.getMy(options?)
Fetch a paginated list of your listings.
Parameters:
perPage?: number- Items per pagepage?: number- Page number (starts at 1)query?: string- Search query to filter listingsstate?: string- Filter by state:'live','sold','draft', or'all'
Returns: Promise<AxiosResponse<PaginatedReverbResponse<{ listings: Listing[] }>>>
Example:
const response = await reverb.listings.getMy({
perPage: 50,
page: 1,
state: 'live',
query: 'Fender Stratocaster'
});
const { listings } = response.data;
console.log(`Page 1: ${listings.length} listings`);listings.getAllMy(options?)
Automatically fetches all listings across all pages using automatic pagination.
Parameters:
query?: string- Search query to filter listingsstate?: ListingStates- Filter by state:ListingStates.LIVE,ListingStates.SOLD, orListingStates.DRAFT
Returns: Promise<AxiosResponse<Listing[]>>
Example:
const response = await reverb.listings.getAllMy({ state: 'live' });
const allListings = response.data; // All listings from all pages
console.log(`Total listings: ${allListings.length}`);Note: This method automatically handles pagination by fetching all pages sequentially until all results are retrieved.
listings.getOne(options)
Fetch a single listing by ID.
Parameters:
id: string- Listing ID (required)
Returns: Promise<AxiosResponse<Listing>>
Example:
const response = await reverb.listings.getOne({ id: '12345' });
const listing = response.data;
console.log(`${listing.title} - ${listing.price.display}`);
console.log(`Condition: ${listing.condition.displayName}`);orders.getMy(options?)
Fetch your orders with pagination.
Parameters:
page?: number- Page number (starts at 1)perPage?: number- Items per page
Returns: Promise<AxiosResponse<PaginatedReverbResponse<{ orders: Order[] }>>>
Example:
const response = await reverb.orders.getMy({
page: 1,
perPage: 25
});
const { orders } = response.data;
orders.forEach(order => {
console.log(`Order ${order.order_number}: ${order.status}`);
});_getArbitraryEndpoint(url, params?)
Escape hatch to call any Reverb endpoint not yet wrapped by a resource. The _ prefix indicates this is not part of the stable public API but is intentionally supported.
Parameters:
url: string- Endpoint URL (absolute or relative to root endpoint)params?: object- Query parameters
Returns: Promise<AxiosResponse<unknown>>
Example:
// Fetch categories
const categories = await reverb._getArbitraryEndpoint('/categories/flat');
// Fetch listing conditions
const conditions = await reverb._getArbitraryEndpoint('/listing_conditions');
console.log(categories.data);TypeScript Usage
Sound Tank is written in TypeScript and provides comprehensive type definitions for the entire Reverb API.
Importing Types
import Reverb, {
Listing,
Order,
Price,
ListingStates,
ListingCondition,
ShippingRate,
Category,
ReverbOptions
} from 'sound-tank';Working with Typed Responses
const response = await reverb.listings.getMy();
const listings: Listing[] = response.data.listings;
listings.forEach((listing: Listing) => {
const price: Price = listing.price;
const condition: ListingCondition = listing.condition;
console.log(`${listing.title}`);
console.log(` Price: ${price.display} (${price.currency})`);
console.log(` Condition: ${condition.displayName}`);
console.log(` Year: ${listing.year}`);
});Available Types
The SDK exports comprehensive TypeScript types including:
Listing Types:
Listing- Complete listing data with make, model, price, condition, shipping, etc.ListingState- Listing status informationListingStates- Enum for state values (LIVE, SOLD, DRAFT)ListingCondition- Item condition with UUID and display nameListingShipping- Shipping information and ratesListingStats- View and watch counts
Order Types:
Order- Complete order information with buyer, seller, shipping, pricing detailsOrderStatus- Order status informationShippingAddress- Complete address information
Pricing Types:
Price- Currency-aware price with amount, currency, symbol, and formatted displayShippingRate- Regional shipping costs
Other Types:
Category- Product categorizationLink- HATEOAS navigation linksReverbOptions- SDK configuration options
Advanced Features
Automatic Pagination
The getAllMyListings method demonstrates automatic pagination, fetching all results across multiple API pages:
// Manually paginate
let page = 1;
let allListings = [];
let response;
do {
response = await reverb.listings.getMy({ page, perPage: 50 });
allListings = allListings.concat(response.data.listings);
page++;
} while (response.data.listings.length === 50);
// Or use the built-in helper
const autoResponse = await reverb.listings.getAllMy();
const listings = autoResponse.data; // Same result, simpler codeHTTP Client Abstraction
Sound Tank uses an HTTP client abstraction for testability:
import { MockHttpClient } from 'sound-tank/http';
// Use mock client for testing
const mockClient = new MockHttpClient();
// Your tests hereConfiguration Access
Access the internal configuration and headers:
const config = reverb.config;
console.log(config.rootEndpoint); // 'https://api.reverb.com/api'
console.log(config.displayCurrency); // 'USD'
console.log(config.locale); // 'en'
const headers = reverb.headers;
console.log(headers['Authorization']); // 'Bearer your_api_key'
console.log(headers['X-Display-Currency']); // 'USD'Examples
Find All Guitars Under $1000
const reverb = new Reverb({ apiKey: process.env.REVERB_API_KEY });
const response = await reverb.listings.getAllMy({ query: 'guitar' });
const affordable = response.data.filter(listing =>
listing.price.amount_cents < 100000 // $1000 = 100,000 cents
);
console.log(`Found ${affordable.length} guitars under $1000`);
affordable.forEach(listing => {
console.log(` ${listing.title}: ${listing.price.display}`);
});Multi-Currency Price Display
const reverb = new Reverb({ apiKey: process.env.REVERB_API_KEY });
// Get prices in USD
reverb.displayCurrency = 'USD';
const usdResponse = await reverb.listings.getMy({ perPage: 5 });
console.log('USD Prices:');
usdResponse.data.listings.forEach(l =>
console.log(` ${l.title}: ${l.price.display}`)
);
// Switch to EUR
reverb.displayCurrency = 'EUR';
const eurResponse = await reverb.listings.getMy({ perPage: 5 });
console.log('\nEUR Prices:');
eurResponse.data.listings.forEach(l =>
console.log(` ${l.title}: ${l.price.display}`)
);Export Listings to CSV
const response = await reverb.listings.getAllMy({ state: 'live' });
const csvHeader = 'ID,Title,Price,Currency,Condition,Year,State\n';
const csvRows = response.data.map(listing =>
`${listing.id},"${listing.title}",${listing.price.amount},${listing.price.currency},${listing.condition.displayName},${listing.year},${listing.state.slug}`
).join('\n');
const csv = csvHeader + csvRows;
console.log(csv);
// Save to file or send via APISearch and Filter
// Search for specific items
const response = await reverb.listings.getMy({
query: 'Les Paul',
state: 'live',
perPage: 100
});
// Filter by condition
const excellent = response.data.listings.filter(
listing => listing.condition.displayName === 'Excellent'
);
// Filter by year
const vintage = response.data.listings.filter(
listing => parseInt(listing.year) < 1980
);
console.log(`Found ${excellent.length} in excellent condition`);
console.log(`Found ${vintage.length} vintage items`);Development
Prerequisites
- Node.js 18 or higher
- Yarn package manager
Setup
# Clone the repository
git clone https://github.com/ZacharyEggert/sound-tank.git
cd sound-tank
# Install dependencies
yarn install
# Copy environment template
cp .env.example .env
# Edit .env and add your REVERB_API_KEYProject Structure
sound-tank/
├── src/
│ ├── Reverb.ts # Main SDK class
│ ├── index.ts # Entry point
│ ├── types.ts # TypeScript type definitions
│ ├── config/
│ │ └── ReverbConfig.ts # Configuration management
│ ├── http/
│ │ ├── HttpClient.ts # HTTP client interface
│ │ ├── AxiosHttpClient.ts # Axios implementation
│ │ └── MockHttpClient.ts # Mock for testing
│ ├── methods/
│ │ ├── listings/ # Listing operations (pure functions)
│ │ └── orders/ # Order operations (pure functions)
│ ├── resources/
│ │ ├── ListingsResource.ts # Listings resource class
│ │ └── OrdersResource.ts # Orders resource class
│ └── utils/ # Helper utilities (pagination, url/query builders, logger)
├── tests/ # Test files
├── dist/ # Build output (git-ignored)
├── package.json
├── tsconfig.json # TypeScript configuration
├── tsup.config.ts # Build configuration
└── vite.config.mts # Test configurationDebugging
Set SOUNDTANK_LOG_LEVEL to enable SDK logging:
SOUNDTANK_LOG_LEVEL=DEBUG yarn devValid values: ERROR | WARN | INFO | DEBUG | TRACE. Silent by default.
Available Scripts
| Command | Description |
|---------|-------------|
| yarn dev | Run tests in watch mode during development |
| yarn test | Run all tests once |
| yarn build | Build production bundles (CJS + ESM + declarations) |
| yarn lint | Type-check code with TypeScript compiler |
| yarn ci | Run full CI pipeline (install, lint, test, build) |
| yarn release | Build and publish to npm (uses changesets) |
Build System
Sound Tank uses tsup for building:
- Dual Output: CommonJS (
dist/index.js) and ESM (dist/index.mjs) - Type Declarations: Full TypeScript
.d.tsfiles - Source Maps: Included for debugging
- Tree-shaking: Optimized bundles
- External Dependencies:
axiosmarked as external (not bundled)
Build outputs:
dist/
├── index.js # CommonJS
├── index.mjs # ES Modules
├── index.d.ts # TypeScript declarations
└── *.map # Source mapsTesting
Sound Tank uses Vitest for testing with comprehensive coverage.
Run Tests
# Run all tests once
yarn test
# Run tests in watch mode (for development)
yarn dev
# Run tests with coverage report
yarn test --coverageTest Structure
- Unit Tests: Test individual functions in isolation
- Integration Tests: Test real API interactions (require
REVERB_API_KEYin.env)
Writing Tests
Integration tests require a valid API key:
import { config } from 'dotenv';
config(); // Load .env
const reverb = new Reverb({ apiKey: process.env.REVERB_API_KEY });
// Your tests hereTests are located in the tests/ directory and mirror the src/ structure.
Contributing
Contributions are welcome! Here's how to get started:
Process
- Fork the repository on GitHub
- Clone your fork locally
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes with clear, focused commits
- Test thoroughly:
yarn test - Lint your code:
yarn lint - Commit with descriptive messages
- Push to your fork:
git push origin feature/amazing-feature - Open a Pull Request
Guidelines
- Write TypeScript with strict types
- Add tests for new features
- Update documentation as needed
- Follow existing code style (Prettier configured)
- Ensure all tests pass (
yarn test) - Keep commits atomic and well-described
- Run full CI locally before pushing:
yarn ci
Code Style
This project uses:
- TypeScript strict mode
- Prettier for formatting (run automatically)
- ESLint for linting
- 2-space indentation
- Single quotes for strings
- Trailing commas
Changesets
This project uses Changesets for version management:
# After making changes, create a changeset
npx changeset
# Follow prompts to describe your changes
# Commit the generated changeset fileRelease Process
Releases are automated via GitHub Actions:
- Create and commit a changeset for your changes
- Push to
mainbranch (after PR approval) - Changesets GitHub Action creates a "Version Packages" PR
- Review and merge the Version Packages PR
- Package automatically publishes to npm with provenance
The project uses OIDC trusted publishing for npm, so no NPM_TOKEN is needed.
Links & Resources
- npm Package: sound-tank on npm
- GitHub Repository: ZacharyEggert/sound-tank
- Report Issues: GitHub Issues
- Reverb API Docs: reverb.com/page/api
- Reverb Marketplace: reverb.com
License
MIT © Zachary Eggert
See LICENSE for details.
