# Harmony - API and Interface Analysis ## API Architecture Harmony is a **web UI-first application** built on the Fresh framework. It does not provide a traditional REST API or JSON endpoints. All interactions occur through server-side rendered HTML pages with embedded data. ### Framework: Fresh 1.6.8 Fresh is a Deno-native web framework with: - **Server-side rendering (SSR)**: All pages rendered on server - **Islands architecture**: Selective client-side interactivity - **File-based routing**: Routes defined by file structure - **Zero config**: No build step required for development ## Route Structure ### Main Application Routes | Route | File | Method | Purpose | |-------|------|--------|---------| | `/` | `routes/index.tsx` | GET | Landing page with documentation | | `/release` | `routes/release.tsx` | GET | Main lookup and comparison interface | | `/release/actions` | `routes/release/actions.tsx` | GET | ISRC/cover submission for existing MB releases | | `/about` | `routes/about.tsx` | GET | Provider documentation and feature matrix | | `/settings` | `routes/settings.tsx` | GET/POST | User preferences (stored in cookies) | ### Static Assets | Route | Purpose | |-------|---------| | `/static/*` | CSS, JavaScript, images | | `/favicon.ico` | Site favicon | ## Primary Route: `/release` The main interface for metadata lookup and harmonization. ### Query Parameters #### Core Lookup Parameters | Parameter | Type | Required | Description | Example | |-----------|------|----------|-------------|---------| | `gtin` | string | No* | Global Trade Item Number (barcode) | `0602537347377` | | `url` | string[] | No* | Provider URL(s), supports multiple | `https://open.spotify.com/album/xyz` | *At least one of `gtin` or `url` must be provided. #### Provider-Specific Parameters | Parameter | Type | Description | Example | |-----------|------|-------------|---------| | `[provider_name]` | string | Provider-specific ID or GTIN lookup | `spotify=3DiDSNVBRYVzccLn2yqhMJ` | | `[provider_name]!` | empty | Template mode for provider | `musicbrainz!` | **Supported Provider Names**: - `spotify` - `deezer` - `itunes` - `tidal` - `bandcamp` - `beatport` - `musicbrainz` - `mora` - `ototoy` #### Filtering Parameters | Parameter | Type | Default | Description | Values | |-----------|------|---------|-------------|--------| | `region` | string[] | `GB,US,DE,JP` | Market regions for lookup | ISO 3166-1 alpha-2 codes | | `category` | string | `default` | Provider category filter | `all`, `default`, `preferred` | #### Permalink Parameters | Parameter | Type | Description | Example | |-----------|------|-------------|---------| | `ts` | number | Unix timestamp for cache replay | `1704067200` | ### Request Examples #### GTIN Lookup (Default Regions) ``` GET /release?gtin=0602537347377 ``` Queries all GTIN-supporting providers in default regions (GB, US, DE, JP). #### GTIN Lookup (Specific Regions) ``` GET /release?gtin=0602537347377®ion=JP,US ``` Queries only Japan and US regions. #### URL Lookup (Single Provider) ``` GET /release?url=https://open.spotify.com/album/3DiDSNVBRYVzccLn2yqhMJ ``` Queries only Spotify using the provided URL. #### URL Lookup (Multiple Providers) ``` GET /release?url=https://open.spotify.com/album/3DiDSNVBRYVzccLn2yqhMJ&url=https://www.deezer.com/album/123456 ``` Queries both Spotify and Deezer. #### Provider-Specific ID Lookup ``` GET /release?spotify=3DiDSNVBRYVzccLn2yqhMJ&deezer=123456 ``` Queries Spotify and Deezer using their native IDs. #### Template Mode (MusicBrainz) ``` GET /release?gtin=0602537347377&musicbrainz! ``` Uses MusicBrainz as template provider (reference data for merge). #### Category Filtering ``` GET /release?gtin=0602537347377&category=preferred ``` Queries only preferred providers (Spotify, Tidal, MusicBrainz). #### Permalink (Cache Replay) ``` GET /release?gtin=0602537347377&ts=1704067200 ``` Replays cached lookup from timestamp 1704067200. ### Response Format The `/release` route returns an **HTML page** with embedded data, not JSON. #### Response Sections 1. **Release Header** - Title - Artist credit - Release date - GTIN (if available) 2. **Provider Comparison Table** - Side-by-side comparison of all providers - Color-coded compatibility indicators - Feature quality ratings 3. **Harmonized Metadata Display** - Merged release information - Track listing with ISRCs - Label and catalog number information - Cover art images - Copyright and availability info 4. **MusicBrainz Seeder Form** - Pre-filled form for MB import - Edit note with provider URLs - Annotation with extra data - Copy-to-clipboard functionality 5. **Warnings and Messages** - Compatibility conflicts - Provider errors - Missing data indicators - Duplicate detection warnings 6. **Permalink** - Timestamp-based URL for reproducibility - Share button #### Example Response Structure (HTML) ```html Album Title - Artist Name | Harmony

Album Title

Artist Name

2014-11-24

GTIN: 0602537347377

Property Spotify Deezer iTunes Merged
``` ### Error Handling Errors are displayed inline in the HTML response: #### Provider Errors ```html
Spotify: Rate limit exceeded. Retry after 60 seconds.
``` #### Lookup Errors ```html
Error: No providers found for GTIN 0602537347377 in region CN.
``` #### Compatibility Warnings ```html
Warning: Release date conflict: Using Spotify value (higher preference).
``` ## Secondary Routes ### `/` - Landing Page **Purpose**: Introduction and quick start guide **Content**: - Project description - Supported providers - Usage examples - Link to `/about` for detailed documentation **No query parameters** ### `/release/actions` - ISRC/Cover Submission **Purpose**: Submit ISRCs or cover art for existing MusicBrainz releases **Query Parameters**: | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `mbid` | string | Yes | MusicBrainz release ID | | `action` | string | Yes | `isrc` or `cover` | **Example**: ``` GET /release/actions?mbid=12345678-1234-1234-1234-123456789012&action=isrc ``` **Response**: Form for submitting ISRCs or cover art to MusicBrainz ### `/about` - Provider Documentation **Purpose**: Detailed provider information and feature comparison **Content**: - Provider descriptions - Feature quality matrix - Rate limits and authentication requirements - Supported regions - Known limitations **No query parameters** **Feature Quality Matrix Example**: | Provider | GTIN | Title | Artists | Date | Labels | Tracks | ISRC | Images | Copyright | |----------|------|-------|---------|------|--------|--------|------|--------|-----------| | Spotify | ✓ | ✓ | ✓ | ✓ | ~ | ✓ | ✓ | 2000px | ~ | | Deezer | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 1400px | ✓ | | iTunes | ✓ | ✓ | ✓ | ✓ | ~ | ✓ | ~ | Varies | ~ | | Tidal | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 1280px | ✓ | | Bandcamp | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | 3000px | ✓ | Legend: - ✓ = GOOD quality - ~ = PRESENT quality - ✗ = MISSING ### `/settings` - User Preferences **Purpose**: Configure user preferences **Method**: GET (display form), POST (save preferences) **Preferences**: | Setting | Type | Default | Description | |---------|------|---------|-------------| | `defaultRegions` | string[] | `['GB','US','DE','JP']` | Default regions for lookup | | `defaultCategory` | string | `default` | Default provider category | | `providerPreferences` | string[] | Custom order | Provider preference order for merge | | `showCompatibilityWarnings` | boolean | `true` | Display compatibility warnings | | `cacheStrategy` | string | `24h` | Cache duration | **Storage**: Preferences stored in cookies (no server-side storage) **Example Cookie**: ``` harmony_prefs={"defaultRegions":["JP","US"],"defaultCategory":"preferred","providerPreferences":["spotify","tidal","deezer"]}; Max-Age=31536000; Path=/ ``` ## Islands (Client-Side Interactivity) Fresh's islands architecture enables selective client-side interactivity. ### Island Components #### 1. LookupForm Island **File**: `islands/LookupForm.tsx` **Purpose**: Dynamic lookup form with validation **Features**: - Real-time GTIN validation - URL parsing and provider detection - Region multi-select - Category radio buttons - Form submission with loading state **Client-Side Logic**: ```typescript // Conceptual function LookupForm() { const [gtin, setGtin] = useState(''); const [urls, setUrls] = useState([]); const [regions, setRegions] = useState(['GB', 'US', 'DE', 'JP']); const validateGtin = (value: string) => { // GTIN-13 validation return /^\d{13}$/.test(value); }; const handleSubmit = async (e: Event) => { e.preventDefault(); // Navigate to /release with query params const params = new URLSearchParams(); if (gtin) params.set('gtin', gtin); urls.forEach(url => params.append('url', url)); params.set('region', regions.join(',')); window.location.href = `/release?${params}`; }; return (
{/* Form fields */}
); } ``` #### 2. ProviderSelector Island **File**: `islands/ProviderSelector.tsx` **Purpose**: Provider category filtering **Features**: - Category selection (all/default/preferred) - Individual provider checkboxes - Real-time URL update #### 3. RegionSelector Island **File**: `islands/RegionSelector.tsx` **Purpose**: Multi-region selection **Features**: - Checkbox list of supported regions - Select all / deselect all - Common region presets (US+GB, Japan, Europe) #### 4. PermalinkGenerator Island **File**: `islands/PermalinkGenerator.tsx` **Purpose**: Generate timestamp-based permalink **Features**: - Current timestamp capture - URL generation with `ts` parameter - Copy to clipboard - Share button **Client-Side Logic**: ```typescript function PermalinkGenerator({ currentUrl }: { currentUrl: string }) { const [permalink, setPermalink] = useState(''); const generatePermalink = () => { const url = new URL(currentUrl); url.searchParams.set('ts', Math.floor(Date.now() / 1000).toString()); setPermalink(url.toString()); }; const copyToClipboard = () => { navigator.clipboard.writeText(permalink); }; return (
{permalink && ( <> )}
); } ``` #### 5. SeederForm Island **File**: `islands/SeederForm.tsx` **Purpose**: MusicBrainz import form with copy functionality **Features**: - Pre-filled form fields - Copy individual fields to clipboard - Copy entire form as JSON - Open MusicBrainz seeder in new tab **Client-Side Logic**: ```typescript function SeederForm({ release }: { release: MergedHarmonyRelease }) { const copyField = (field: string, value: string) => { navigator.clipboard.writeText(value); }; const openSeeder = () => { const mbUrl = `https://musicbrainz.org/release/add`; const form = document.createElement('form'); form.method = 'POST'; form.action = mbUrl; form.target = '_blank'; // Add form fields Object.entries(release).forEach(([key, value]) => { const input = document.createElement('input'); input.type = 'hidden'; input.name = key; input.value = JSON.stringify(value); form.appendChild(input); }); document.body.appendChild(form); form.submit(); document.body.removeChild(form); }; return (
{/* Form fields with copy buttons */}
); } ``` ## No REST API Harmony **does not provide a REST API** or JSON endpoints. Key implications: ### No JSON Responses All routes return HTML. There is no `Accept: application/json` support. **Request**: ``` GET /release?gtin=0602537347377 Accept: application/json ``` **Response**: ``` HTTP/1.1 200 OK Content-Type: text/html ``` ### No Programmatic Access Clients cannot fetch data programmatically without HTML parsing. **Workaround** (not officially supported): 1. Fetch HTML response 2. Parse HTML with DOM parser 3. Extract data from structured elements **Example** (conceptual): ```typescript const response = await fetch('/release?gtin=0602537347377'); const html = await response.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); const title = doc.querySelector('.release-header h1')?.textContent; ``` ### No API Authentication No API keys, no OAuth2 for API access (OAuth2 only used for provider authentication). ### No Rate Limiting on Server Server does not enforce rate limits (providers have their own limits). ## Request/Response Flow ### Typical Request Flow ``` 1. User submits lookup form ↓ 2. Browser sends GET /release?gtin=...®ion=... ↓ 3. Fresh router matches route to routes/release.tsx ↓ 4. Route handler executes: a. Parse query parameters b. Call CombinedReleaseLookup c. Parallel provider queries d. Harmonize responses e. Merge releases f. Generate MusicBrainz seeding data ↓ 5. Server-side rendering: a. Render components with data b. Generate HTML c. Inject island hydration scripts ↓ 6. HTTP response sent to browser ↓ 7. Browser renders HTML ↓ 8. Island hydration: a. Load island JavaScript modules b. Attach event listeners c. Enable client-side interactivity ``` ### Caching Strategy #### Server-Side Caching - **snap_storage**: Caches HTTP responses from providers - **Cache key**: URL + query parameters - **Cache duration**: 24 hours (configurable) - **Cache storage**: SQLite database (`snaps.db`) + file directory (`snaps/`) #### Client-Side Caching - **Browser cache**: Standard HTTP caching headers - **localStorage**: OAuth2 tokens, MBID mappings (dev mode) - **sessionStorage**: MBID mappings (production mode) - **Cookies**: User preferences #### Permalink Caching The `ts` parameter enables cache replay: 1. User performs lookup at timestamp T 2. Responses cached with timestamp T 3. Permalink generated: `/release?gtin=...&ts=T` 4. Future requests with `ts=T` replay cached responses 5. Ensures reproducible results even if provider data changes **Cache Lookup Logic**: ```typescript async function getCachedResponse(url: string, timestamp?: number): Promise { if (timestamp) { // Permalink mode: lookup by timestamp return await cache.getByTimestamp(url, timestamp); } else { // Normal mode: lookup by recency return await cache.getRecent(url, MAX_AGE); } } ``` ## Error Responses ### HTTP Status Codes | Status | Scenario | |--------|----------| | 200 | Success (even with partial provider failures) | | 400 | Invalid query parameters | | 404 | Route not found | | 500 | Server error (unhandled exception) | ### Error Display Errors displayed inline in HTML, not as HTTP error codes. **Example**: All providers fail, but response is still 200 OK with error messages in HTML. ## Performance Considerations ### Parallel Provider Queries All provider lookups execute in parallel via `Promise.allSettled`: ```typescript const lookups = providers.map(p => p.lookup(input)); const results = await Promise.allSettled(lookups); ``` **Benefits**: - Faster total response time - Graceful degradation (partial results) **Typical Response Times**: - Single provider: 200-500ms - Multiple providers (parallel): 500-1500ms - Cached response: <50ms ### Server-Side Rendering Overhead Fresh SSR adds minimal overhead: - Component rendering: 10-50ms - HTML generation: 5-20ms - Total SSR overhead: <100ms ### Island Hydration Islands load asynchronously after initial page render: - Initial HTML render: Immediate - Island JavaScript load: 100-300ms - Island hydration: 50-100ms **User experience**: Page is interactive immediately, islands enhance progressively. ## Integration Patterns ### Embedding in Other Applications Since Harmony has no REST API, integration requires: 1. **iFrame embedding**: Embed `/release` route in iFrame 2. **Redirect**: Redirect users to Harmony for lookup 3. **HTML parsing**: Fetch and parse HTML responses (fragile) **iFrame Example**: ```html ``` ### MusicBrainz Integration Harmony integrates with MusicBrainz via: 1. **Seeder form**: Pre-filled form for MB import 2. **Edit notes**: Include provider URLs and permalink 3. **Annotations**: Extra metadata not in main form 4. **MBID resolution**: Batch URL lookup to detect duplicates **Workflow**: ``` 1. User performs lookup in Harmony ↓ 2. Harmony displays harmonized release ↓ 3. User clicks "Open in MusicBrainz" ↓ 4. Seeder form opens in new tab ↓ 5. User reviews and submits to MusicBrainz ``` ## Summary Harmony's API design prioritizes: 1. **Web UI first**: No REST API, HTML-only responses 2. **Server-side rendering**: Fast initial load, SEO-friendly 3. **Islands architecture**: Selective client-side interactivity 4. **Permalink system**: Reproducible results via timestamp caching 5. **Graceful degradation**: Partial results on provider failures 6. **MusicBrainz integration**: Seamless seeding workflow This design is optimized for human users (MusicBrainz editors) rather than programmatic API consumers. For a metadata aggregation system targeting API consumers, a REST API layer would need to be added.