[{"data":1,"prerenderedAt":10732},["ShallowReactive",2],{"navigation":3,"/blog/music-data/understanding-the-api-first-approach-post":734,"/blog/music-data/understanding-the-api-first-approach-surround":1077,"/blog/music-data/understanding-the-api-first-approach-related":1082},[4,70,207,312,721],{"title":5,"path":6,"stem":7,"children":8,"page":69},"Case Study","/blog/case-study","blog/case-study",[9,13,17,21,25,29,33,37,41,45,49,53,57,61,65],{"title":10,"path":11,"stem":12},"Ambistream – Multi-Layer Streaming Platform","/blog/case-study/ambistream-building-a-multi-layer-streaming-platform-from-a-spark-of-an-idea","blog/case-study/ambistream-building-a-multi-layer-streaming-platform-from-a-spark-of-an-idea",{"title":14,"path":15,"stem":16},"Custom Chromecast & AirPlay Casting App","/blog/case-study/chromecast-airplay-casting-app-case-study","blog/case-study/chromecast-airplay-casting-app-case-study",{"title":18,"path":19,"stem":20},"Direct Music Licensing Platform Case Study","/blog/case-study/dlm-music-catalog-case-study","blog/case-study/dlm-music-catalog-case-study",{"title":22,"path":23,"stem":24},"Assembly Instructions App - Galeco Mobile App","/blog/case-study/galeco-mobile-app-case-study","blog/case-study/galeco-mobile-app-case-study",{"title":26,"path":27,"stem":28},"MemoSonic: Building an Audio Memory Game","/blog/case-study/how-we-built-memosonic-accessible-audio-memory-game-flutter","blog/case-study/how-we-built-memosonic-accessible-audio-memory-game-flutter",{"title":30,"path":31,"stem":32},"Loyalty Program App for Shopping Mall","/blog/case-study/loyalty-program-application-case-study","blog/case-study/loyalty-program-application-case-study",{"title":34,"path":35,"stem":36},"Mobile Application for Music Catalogs","/blog/case-study/mobile-app-for-music-catalog","blog/case-study/mobile-app-for-music-catalog",{"title":38,"path":39,"stem":40},"Universal Music Data Parser for 20+ Platforms","/blog/case-study/musicdata-lab-universal-music-data-parser-case-study","blog/case-study/musicdata-lab-universal-music-data-parser-case-study",{"title":42,"path":43,"stem":44},"Panther ML/AI Pricing Recommendation Tool","/blog/case-study/panther-pricing-recommendation-tool-case-study","blog/case-study/panther-pricing-recommendation-tool-case-study",{"title":46,"path":47,"stem":48},"4D Grupa Roofing Wholesalers Platform","/blog/case-study/roofing-wholesalers-website-case-study","blog/case-study/roofing-wholesalers-website-case-study",{"title":50,"path":51,"stem":52},"Talent Alpha HR Frontend Platform","/blog/case-study/talent-alpha-hr-platform-case-study","blog/case-study/talent-alpha-hr-platform-case-study",{"title":54,"path":55,"stem":56},"Ticketing & Events Platform Development","/blog/case-study/ticketing-events-platform-case-study","blog/case-study/ticketing-events-platform-case-study",{"title":58,"path":59,"stem":60},"Turn Fans into Superfans — Roadie.co","/blog/case-study/turn-fans-into-superfans-roadie-co","blog/case-study/turn-fans-into-superfans-roadie-co",{"title":62,"path":63,"stem":64},"Walkative 2.0 Global Booking Engine","/blog/case-study/walkative-2-booking-platform-case-study","blog/case-study/walkative-2-booking-platform-case-study",{"title":66,"path":67,"stem":68},"Why Merch Is the New Royalty Check, and How Tech Is Closing the Gap","/blog/case-study/why-merch-is-the-new-royalty-check","blog/case-study/why-merch-is-the-new-royalty-check",false,{"title":71,"path":72,"stem":73,"children":74,"page":69},"Music Data","/blog/music-data","blog/music-data",[75,79,83,87,91,95,99,103,107,111,115,119,123,127,131,135,139,143,147,151,155,159,163,167,171,175,179,183,187,191,195,199,203],{"title":76,"path":77,"stem":78},"13 Distributors, 5 File Formats, Zero Standards -The Reality of Music Royalty Data","/blog/music-data/13-distributors-5-file-formats-zero-standards-the-reality-of-music-royalty-data","blog/music-data/13-distributors-5-file-formats-zero-standards-the-reality-of-music-royalty-data",{"title":80,"path":81,"stem":82},"830 Ways to Say Spotify - Normalizing Music Streaming Data","/blog/music-data/830-ways-to-say-spotify-normalizing-music-streaming-data","blog/music-data/830-ways-to-say-spotify-normalizing-music-streaming-data",{"title":84,"path":85,"stem":86},"Why Music Companies Need AI-Powered Analytics (And How We Built One)","/blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama","blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama",{"title":88,"path":89,"stem":90},"AI Rehearsal: Spaced Repetition for Your Musical Ideas","/blog/music-data/ai-rehearsal-spaced-repetition-for-musical-ideas","blog/music-data/ai-rehearsal-spaced-repetition-for-musical-ideas",{"title":92,"path":93,"stem":94},"Audio Project Organization Is a Mess — Here's Why","/blog/music-data/audio-project-organization-mess","blog/music-data/audio-project-organization-mess",{"title":96,"path":97,"stem":98},"Why Audio Search Is Still Broken and How to Fix It with Embeddings","/blog/music-data/audio-search-broken-fix-with-embeddings","blog/music-data/audio-search-broken-fix-with-embeddings",{"title":100,"path":101,"stem":102},"AI Song Structure Analysis: Intro, Verse, Chorus","/blog/music-data/automatic-song-structure-analysis-how-ai-detects-intro-verse-chorus","blog/music-data/automatic-song-structure-analysis-how-ai-detects-intro-verse-chorus",{"title":104,"path":105,"stem":106},"The Broken Feedback Loop in Music Collaboration","/blog/music-data/broken-feedback-loop-music-collaboration","blog/music-data/broken-feedback-loop-music-collaboration",{"title":108,"path":109,"stem":110},"Building a Claude Skill for DDEX Validation: Automate Music Metadata Checks with AI","/blog/music-data/building-a-claude-skill-for-ddex-validation-music-metadata","blog/music-data/building-a-claude-skill-for-ddex-validation-music-metadata",{"title":112,"path":113,"stem":114},"Building a Custom Music Delivery Platform on the Revelator API","/blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api","blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api",{"title":116,"path":117,"stem":118},"Building a Suno AI Remix App with Nuxt & Firebase","/blog/music-data/building-a-suno-remix-app-with-nuxt-and-firebase","blog/music-data/building-a-suno-remix-app-with-nuxt-and-firebase",{"title":120,"path":121,"stem":122},"C2PA & DDEX: Authenticity Meets Rights in the Age of AI Music","/blog/music-data/c2pa-and-ddex-authenticity-meets-rights-in-the-age-of-ai-music","blog/music-data/c2pa-and-ddex-authenticity-meets-rights-in-the-age-of-ai-music",{"title":124,"path":125,"stem":126},"C2PA in Music: A Claude MCP for Reading Content Provenance","/blog/music-data/c2pa-in-music-mcp","blog/music-data/c2pa-in-music-mcp",{"title":128,"path":129,"stem":130},"Data Modeling in MongoDB Using Design Patterns","/blog/music-data/data-modeling-in-mongodb-with-the-usage-of-design-patterns","blog/music-data/data-modeling-in-mongodb-with-the-usage-of-design-patterns",{"title":132,"path":133,"stem":134},"Office Hours with MusicTech Lab's DDEX Expert","/blog/music-data/ddex-office-hours-musictech","blog/music-data/ddex-office-hours-musictech",{"title":136,"path":137,"stem":138},"DDEX Open Source Projects Review","/blog/music-data/ddex-open-source-projects-review","blog/music-data/ddex-open-source-projects-review",{"title":140,"path":141,"stem":142},"Extracting Data from Ableton .als and .asd Files","/blog/music-data/extracting-data-from-ableton-als-asd-files","blog/music-data/extracting-data-from-ableton-als-asd-files",{"title":144,"path":145,"stem":146},"Maciej Dulski on Sound Connections Podcast","/blog/music-data/from-startups-to-musictech-maciej-dulski-on-sound-connections-podcast","blog/music-data/from-startups-to-musictech-maciej-dulski-on-sound-connections-podcast",{"title":148,"path":149,"stem":150},"How to Transcribe Video to Text Using OpenAI Whisper","/blog/music-data/how-to-transcribe-video-to-text-using-whisper","blog/music-data/how-to-transcribe-video-to-text-using-whisper",{"title":152,"path":153,"stem":154},"Epidemic Sound MCP with Claude for Devs","/blog/music-data/how-to-use-epidemic-sound-mcp-with-claude","blog/music-data/how-to-use-epidemic-sound-mcp-with-claude",{"title":156,"path":157,"stem":158},"Hybrid Database Model in Django for Speed","/blog/music-data/hybrid-database-model-in-django-as-a-performance-booster","blog/music-data/hybrid-database-model-in-django-as-a-performance-booster",{"title":160,"path":161,"stem":162},"Introduction to generating DDEX file using Python","/blog/music-data/introduction-to-generating-ddex-file-using-python","blog/music-data/introduction-to-generating-ddex-file-using-python",{"title":164,"path":165,"stem":166},"Maintaining Music Tech Tools: The SLA Dilemma for Small Teams","/blog/music-data/maintaining-music-tech-tools-the-sla-dilemma-for-small-teams","blog/music-data/maintaining-music-tech-tools-the-sla-dilemma-for-small-teams",{"title":168,"path":169,"stem":170},"Querying Bandcamp Revenue Reports with Natural Language — Meet mtl-bandcamp-mcp","/blog/music-data/mtl-bandcamp-mcp-open-source-revenue-dashboard","blog/music-data/mtl-bandcamp-mcp-open-source-revenue-dashboard",{"title":172,"path":173,"stem":174},"mtl-metadata-mcp: Open Source Audio Metadata Embedding for Claude Code","/blog/music-data/mtl-metadata-mcp-open-source-audio-metadata-embedding","blog/music-data/mtl-metadata-mcp-open-source-audio-metadata-embedding",{"title":176,"path":177,"stem":178},"MusicTech Resources for Builders","/blog/music-data/musictech-resources-curated-insights-for-the-musictech-builders","blog/music-data/musictech-resources-curated-insights-for-the-musictech-builders",{"title":180,"path":181,"stem":182},"Poland's Creative Tech and MusicTech Rise","/blog/music-data/polands-creative-tech-sector-is-on-the-rise-and-musictech-is-part-of-it","blog/music-data/polands-creative-tech-sector-is-on-the-rise-and-musictech-is-part-of-it",{"title":184,"path":185,"stem":186},"Batch ISRC Enrichment That Turns Messy Catalogs Into Clean Data","/blog/music-data/scout-isrc-metadata-enrichment-spotify-musicbrainz","blog/music-data/scout-isrc-metadata-enrichment-spotify-musicbrainz",{"title":188,"path":189,"stem":190},"Music Self-Publishing: The Emuze.me Story","/blog/music-data/self-publishing-in-the-music-industry-a-tale-of-emuze-me","blog/music-data/self-publishing-in-the-music-industry-a-tale-of-emuze-me",{"title":192,"path":193,"stem":194},"Understanding the API First Approach","/blog/music-data/understanding-the-api-first-approach","blog/music-data/understanding-the-api-first-approach",{"title":196,"path":197,"stem":198},"The Voice Memo Graveyard Problem","/blog/music-data/voice-memo-graveyard-problem","blog/music-data/voice-memo-graveyard-problem",{"title":200,"path":201,"stem":202},"Which Database for Music Data? Redshift vs BigQuery vs ClickHouse and When to Use Each","/blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse","blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse",{"title":204,"path":205,"stem":206},"Why we decided to use wavesurfer.js","/blog/music-data/why-we-decided-to-use-wavesurfer","blog/music-data/why-we-decided-to-use-wavesurfer",{"title":208,"path":209,"stem":210,"children":211,"page":69},"Newsletter","/blog/newsletter","blog/newsletter",[212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296,300,304,308],{"title":213,"path":214,"stem":215},"Music Industry Tech Openings (April 2024 Update)","/blog/newsletter/music-industry-tech-openings-april-2024-update","blog/newsletter/music-industry-tech-openings-april-2024-update",{"title":217,"path":218,"stem":219},"Music Industry Tech Openings (April 2025 Update)","/blog/newsletter/music-industry-tech-openings-april-2025-update","blog/newsletter/music-industry-tech-openings-april-2025-update",{"title":221,"path":222,"stem":223},"Music Industry Tech Openings (August 2024 Update)","/blog/newsletter/music-industry-tech-openings-august-2024-update","blog/newsletter/music-industry-tech-openings-august-2024-update",{"title":225,"path":226,"stem":227},"Music Industry Tech Openings (December 2024 Update)","/blog/newsletter/music-industry-tech-openings-december-2024-update","blog/newsletter/music-industry-tech-openings-december-2024-update",{"title":229,"path":230,"stem":231},"Music Industry Tech Openings (February 2025 Update)","/blog/newsletter/music-industry-tech-openings-february-2025-update","blog/newsletter/music-industry-tech-openings-february-2025-update",{"title":233,"path":234,"stem":235},"Music Industry Tech Openings (January 2025 Update)","/blog/newsletter/music-industry-tech-openings-january-2025-update","blog/newsletter/music-industry-tech-openings-january-2025-update",{"title":237,"path":238,"stem":239},"Music Industry Tech Openings (July 2024 Update)","/blog/newsletter/music-industry-tech-openings-july-2024-update","blog/newsletter/music-industry-tech-openings-july-2024-update",{"title":241,"path":242,"stem":243},"Music Industry Tech Openings (June 2024 Update)","/blog/newsletter/music-industry-tech-openings-june-2024-update","blog/newsletter/music-industry-tech-openings-june-2024-update",{"title":245,"path":246,"stem":247},"Music Industry Tech Openings (March 2025 Update)","/blog/newsletter/music-industry-tech-openings-march-2025-update","blog/newsletter/music-industry-tech-openings-march-2025-update",{"title":249,"path":250,"stem":251},"Music Industry Tech Openings (May 2024 Update)","/blog/newsletter/music-industry-tech-openings-may-2024-update","blog/newsletter/music-industry-tech-openings-may-2024-update",{"title":253,"path":254,"stem":255},"Music Industry Tech Openings (May 2025 Update)","/blog/newsletter/music-industry-tech-openings-may-2025","blog/newsletter/music-industry-tech-openings-may-2025",{"title":257,"path":258,"stem":259},"Music Industry Tech Openings (November 2024 Update)","/blog/newsletter/music-industry-tech-openings-november-2024-update","blog/newsletter/music-industry-tech-openings-november-2024-update",{"title":261,"path":262,"stem":263},"Music Industry Tech Openings (October 2024 Update)","/blog/newsletter/music-industry-tech-openings-october-2024-update","blog/newsletter/music-industry-tech-openings-october-2024-update",{"title":265,"path":266,"stem":267},"Music Industry Tech Openings (September 2024 Update)","/blog/newsletter/music-industry-tech-openings-september-2024-update","blog/newsletter/music-industry-tech-openings-september-2024-update",{"title":269,"path":270,"stem":271},"MusicTech Insights #1 by Maciej Dulski","/blog/newsletter/musictech-insights-1-curated-by-maciej-dulski","blog/newsletter/musictech-insights-1-curated-by-maciej-dulski",{"title":273,"path":274,"stem":275},"Provenance, Not Detection, Is the Durable Answer","/blog/newsletter/musictech-insights-10-curated-by-jim-anderson","blog/newsletter/musictech-insights-10-curated-by-jim-anderson",{"title":277,"path":278,"stem":279},"What CMOs can teach us about innovation in uncertain times","/blog/newsletter/musictech-insights-10-curated-by-joanna-kurkowska","blog/newsletter/musictech-insights-10-curated-by-joanna-kurkowska",{"title":281,"path":282,"stem":283},"Feeling the MusicTech Momentum","/blog/newsletter/musictech-insights-2-curated-by-maciej-dulski","blog/newsletter/musictech-insights-2-curated-by-maciej-dulski",{"title":285,"path":286,"stem":287},"AI in Music: Hype, Hope, and a Human Touch","/blog/newsletter/musictech-insights-3-curated-by-drew-thurlow","blog/newsletter/musictech-insights-3-curated-by-drew-thurlow",{"title":289,"path":290,"stem":291},"The Music Metadata Conundrum","/blog/newsletter/musictech-insights-4-curated-by-amanda-schupf","blog/newsletter/musictech-insights-4-curated-by-amanda-schupf",{"title":293,"path":294,"stem":295},"7 Rounds in the First 10 Days of November 2025","/blog/newsletter/musictech-insights-5-curated-by-maciej-dulski","blog/newsletter/musictech-insights-5-curated-by-maciej-dulski",{"title":297,"path":298,"stem":299},"The End of an Era: It's All About to Crash","/blog/newsletter/musictech-insights-6-curated-by-sigurdur-arnason","blog/newsletter/musictech-insights-6-curated-by-sigurdur-arnason",{"title":301,"path":302,"stem":303},"Low-Code Magic Won't Solve MusicTech Reality","/blog/newsletter/musictech-insights-7-curated-by-mariusz-smenzyk","blog/newsletter/musictech-insights-7-curated-by-mariusz-smenzyk",{"title":305,"path":306,"stem":307},"The New Economics of Game Music","/blog/newsletter/musictech-insights-8-curated-by-kenny-vaughan","blog/newsletter/musictech-insights-8-curated-by-kenny-vaughan",{"title":309,"path":310,"stem":311},"Music Business Meets Direct-to-Fan","/blog/newsletter/musictech-insights-9-curated-by-yaw-asamani","blog/newsletter/musictech-insights-9-curated-by-yaw-asamani",{"title":313,"path":314,"stem":315,"children":316,"page":69},"Software Development","/blog/software-development","blog/software-development",[317,321,325,329,333,337,341,345,349,353,357,361,365,369,373,377,381,385,389,393,397,401,405,409,413,417,421,425,429,433,437,441,445,449,453,457,461,465,469,473,477,481,485,489,493,497,501,505,509,513,517,521,525,529,533,537,541,545,549,553,557,561,565,569,573,577,581,585,589,593,597,601,605,609,613,617,621,625,629,633,637,641,645,649,653,657,661,665,669,673,677,681,685,689,693,697,701,705,709,713,717],{"title":318,"path":319,"stem":320},"Benefits of Outsourcing Software Development","/blog/software-development/10-benefits-of-outsourcing-software-development-services","blog/software-development/10-benefits-of-outsourcing-software-development-services",{"title":322,"path":323,"stem":324},"10 Steps to Find the Best MVP Developers","/blog/software-development/10-steps-to-find-the-best-mvp-developers-for-your-startup-idea","blog/software-development/10-steps-to-find-the-best-mvp-developers-for-your-startup-idea",{"title":326,"path":327,"stem":328},"1,200 Looms Later: How Async Video Became My Development Superpower","/blog/software-development/1200-looms-how-async-video-became-our-development-superpower","blog/software-development/1200-looms-how-async-video-became-our-development-superpower",{"title":330,"path":331,"stem":332},"Communication Strategy in Outsourcing Projects","/blog/software-development/5-steps-to-implement-an-effective-communication-strategy-in-outsourcing-software-development-project","blog/software-development/5-steps-to-implement-an-effective-communication-strategy-in-outsourcing-software-development-project",{"title":334,"path":335,"stem":336},"7 Best Practices for Outsourcing Software Development","/blog/software-development/7-best-practices-for-outsourcing-software-development","blog/software-development/7-best-practices-for-outsourcing-software-development",{"title":338,"path":339,"stem":340},"9 Reasons Why Saleor.io Is Best for eCommerce","/blog/software-development/9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website","blog/software-development/9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website",{"title":342,"path":343,"stem":344},"A Look at Bravelab.io’s Clutch 2021 Year In Review","/blog/software-development/a-look-at-bravelab-ios-clutch-2021-year-in-review","blog/software-development/a-look-at-bravelab-ios-clutch-2021-year-in-review",{"title":346,"path":347,"stem":348},"A quick introduction to profit sharing implementation","/blog/software-development/a-quick-introduction-to-profit-sharing-implementation","blog/software-development/a-quick-introduction-to-profit-sharing-implementation",{"title":350,"path":351,"stem":352},"AI Audio Similarity Search: The Future of Sound Library Discovery","/blog/software-development/ai-audio-similarity-search-for-sound-libraries","blog/software-development/ai-audio-similarity-search-for-sound-libraries",{"title":354,"path":355,"stem":356},"Automate Repetitive Tasks for Better Results","/blog/software-development/automate-repetitive-tasks-to-improve-your-business-performance","blog/software-development/automate-repetitive-tasks-to-improve-your-business-performance",{"title":358,"path":359,"stem":360},"Automating Success: The Art of Unified Documentation","/blog/software-development/automating-success-the-art-of-unified-documentation","blog/software-development/automating-success-the-art-of-unified-documentation",{"title":362,"path":363,"stem":364},"Brave 3.0 Website Redesign, Part 2: Solution","/blog/software-development/brave-3-0-how-we-conducted-website-redesign-part-2-solution","blog/software-development/brave-3-0-how-we-conducted-website-redesign-part-2-solution",{"title":366,"path":367,"stem":368},"Brave 3.0, Part 4: Tech Stack and Recap","/blog/software-development/brave-3-0-part-4-technologies-behind-and-final-series-recap","blog/software-development/brave-3-0-part-4-technologies-behind-and-final-series-recap",{"title":370,"path":371,"stem":372},"Brave 3.0 – redesign process part 1. The Challenge","/blog/software-development/brave-3-0-redesign-process-part-1-challenge","blog/software-development/brave-3-0-redesign-process-part-1-challenge",{"title":374,"path":375,"stem":376},"Brave 3.0 – redesign process, part 3. Lesson learned","/blog/software-development/brave-3-0-redesign-process-part-3-lesson-learned","blog/software-development/brave-3-0-redesign-process-part-3-lesson-learned",{"title":378,"path":379,"stem":380},"Bravelab.io: Top Software Developer by Clutch","/blog/software-development/bravelab-io-is-recognized-as-a-top-custom-software-developer-by-clutch","blog/software-development/bravelab-io-is-recognized-as-a-top-custom-software-developer-by-clutch",{"title":382,"path":383,"stem":384},"Bravelab.io: Top Developer in Poland by Clutch","/blog/software-development/bravelab-io-named-top-software-developer-in-poland-by-clutch","blog/software-development/bravelab-io-named-top-software-developer-in-poland-by-clutch",{"title":386,"path":387,"stem":388},"MusicTech Lab Partners with LALAL.AI","/blog/software-development/bravelab-partners-with-the-audio-lalal-ai","blog/software-development/bravelab-partners-with-the-audio-lalal-ai",{"title":390,"path":391,"stem":392},"MusicTech Lab Partners with The Audio Programmer","/blog/software-development/bravelab-partners-with-the-audio-programmer","blog/software-development/bravelab-partners-with-the-audio-programmer",{"title":394,"path":395,"stem":396},"Bravelab's team about productivity","/blog/software-development/bravelabs-team-about-productivity","blog/software-development/bravelabs-team-about-productivity",{"title":398,"path":399,"stem":400},"Braveloper","/blog/software-development/braveloper","blog/software-development/braveloper",{"title":402,"path":403,"stem":404},"Bravely App: Boost Productivity with Django","/blog/software-development/bravely-app-how-to-be-more-productive-with-django-quick","blog/software-development/bravely-app-how-to-be-more-productive-with-django-quick",{"title":406,"path":407,"stem":408},"DIY MIDI Controller for Ableton with Arduino","/blog/software-development/building-a-diy-midi-controller-for-ableton-live-with-arduino","blog/software-development/building-a-diy-midi-controller-for-ableton-live-with-arduino",{"title":410,"path":411,"stem":412},"C2PA in Ableton: Making AI Music Provenance Visible Inside Your DAW","/blog/software-development/c2pa-in-ableton-max-for-live","blog/software-development/c2pa-in-ableton-max-for-live",{"title":414,"path":415,"stem":416},"Change Detection mechanism in Angular","/blog/software-development/change-detection-mechanism-in-angular","blog/software-development/change-detection-mechanism-in-angular",{"title":418,"path":419,"stem":420},"Communication Channels in Remote Work","/blog/software-development/comparison-of-the-communication-channels-in-remote-work","blog/software-development/comparison-of-the-communication-channels-in-remote-work",{"title":422,"path":423,"stem":424},"Connecting Your Max for Live Device to a Cloud API","/blog/software-development/connecting-your-max-for-live-device-to-a-cloud-api","blog/software-development/connecting-your-max-for-live-device-to-a-cloud-api",{"title":426,"path":427,"stem":428},"From Voice Memo to Studio: The Cross-Platform Problem for Creators","/blog/software-development/cross-platform-problem-for-creators","blog/software-development/cross-platform-problem-for-creators",{"title":430,"path":431,"stem":432},"Cultural transformation through the pandemic era","/blog/software-development/cultural-transformation-through-the-pandemic-era","blog/software-development/cultural-transformation-through-the-pandemic-era",{"title":434,"path":435,"stem":436},"D-Commerce Decoded: Cutting Through the Hype","/blog/software-development/d-commerce-decoded-cutting-through-the-hype","blog/software-development/d-commerce-decoded-cutting-through-the-hype",{"title":438,"path":439,"stem":440},"Dev Meeting 002: Intro to DDD","/blog/software-development/dev-meeting-002-introduction-to-domain-driven-design-ddd","blog/software-development/dev-meeting-002-introduction-to-domain-driven-design-ddd",{"title":442,"path":443,"stem":444},"Dev Meeting 003: Web3 Primer","/blog/software-development/dev-meeting-003-web3-primer","blog/software-development/dev-meeting-003-web3-primer",{"title":446,"path":447,"stem":448},"Dev Meeting 004: Introduction to Event Storming","/blog/software-development/dev-meeting-004-introduction-to-event-storming","blog/software-development/dev-meeting-004-introduction-to-event-storming",{"title":450,"path":451,"stem":452},"Dev Meeting 001: Kubernetes is a Framework","/blog/software-development/dev-meeting-kubernetes-is-a-framework","blog/software-development/dev-meeting-kubernetes-is-a-framework",{"title":454,"path":455,"stem":456},"Did You Know? 10 Developer Tips from Real Codebases","/blog/software-development/did-you-know-dev-tips-part-1","blog/software-development/did-you-know-dev-tips-part-1",{"title":458,"path":459,"stem":460},"10 Surprising MusicTech Facts (Part 2)","/blog/software-development/did-you-know-musictech-facts-part-2","blog/software-development/did-you-know-musictech-facts-part-2",{"title":462,"path":463,"stem":464},"Django-cms and GraphQL","/blog/software-development/django-cms-and-graphql","blog/software-development/django-cms-and-graphql",{"title":466,"path":467,"stem":468},"Does Zappa make it super easy?","/blog/software-development/does-zappa-make-it-super-easy","blog/software-development/does-zappa-make-it-super-easy",{"title":470,"path":471,"stem":472},"Establishing cooperation between Netlify and Bravelab","/blog/software-development/establishing-cooperation-between-netlify-and-bravelab","blog/software-development/establishing-cooperation-between-netlify-and-bravelab",{"title":474,"path":475,"stem":476},"Export Ableton Locators to JSON via Max for Live","/blog/software-development/exporting-ableton-live-locators-to-json-with-max-for-live","blog/software-development/exporting-ableton-live-locators-to-json-with-max-for-live",{"title":478,"path":479,"stem":480},"IT Outsourcing: Success and Failure Factors","/blog/software-development/factors-that-contribute-to-the-success-or-failure-of-an-it-outsourcing-project","blog/software-development/factors-that-contribute-to-the-success-or-failure-of-an-it-outsourcing-project",{"title":482,"path":483,"stem":484},"Flutter 2022 Strategy: Analyzing the Roadmap","/blog/software-development/flutter-strategy-for-2022-analyzing-the-new-flutter-roadmap","blog/software-development/flutter-strategy-for-2022-analyzing-the-new-flutter-roadmap",{"title":486,"path":487,"stem":488},"Git Better #1 — Commit Message Convention","/blog/software-development/git-better-1-see-more-with-a-commit-message-convention","blog/software-development/git-better-1-see-more-with-a-commit-message-convention",{"title":490,"path":491,"stem":492},"Hasura in action. How to use it with Django","/blog/software-development/hasura-in-action","blog/software-development/hasura-in-action",{"title":494,"path":495,"stem":496},"Holacracy why and where we are","/blog/software-development/holacracy-why-and-where-we-are","blog/software-development/holacracy-why-and-where-we-are",{"title":498,"path":499,"stem":500},"How does JavaScript work","/blog/software-development/how-does-javascript-work","blog/software-development/how-does-javascript-work",{"title":502,"path":503,"stem":504},"How important is good UX/UI design?","/blog/software-development/how-important-is-good-ux-ui-design","blog/software-development/how-important-is-good-ux-ui-design",{"title":506,"path":507,"stem":508},"How repetitive tasks impact your business","/blog/software-development/how-repetitive-tasks-impact-your-business","blog/software-development/how-repetitive-tasks-impact-your-business",{"title":510,"path":511,"stem":512},"Becoming a Vue.js Dev: Do Paid Trials Work?","/blog/software-development/how-to-become-a-vue-js-developer-and-whether-paid-trials-in-it-work-out","blog/software-development/how-to-become-a-vue-js-developer-and-whether-paid-trials-in-it-work-out",{"title":514,"path":515,"stem":516},"How to Build an MVP in 6 Steps","/blog/software-development/how-to-build-a-minimum-viable-product-mvp-in-6-steps","blog/software-development/how-to-build-a-minimum-viable-product-mvp-in-6-steps",{"title":518,"path":519,"stem":520},"How to conduct workshops for creative industry?","/blog/software-development/how-to-conduct-workshops-for-creative-industry","blog/software-development/how-to-conduct-workshops-for-creative-industry",{"title":522,"path":523,"stem":524},"How to easily create form in Angular","/blog/software-development/how-to-easily-create-form-in-angular","blog/software-development/how-to-easily-create-form-in-angular",{"title":526,"path":527,"stem":528},"How to export orders in Saleor.io to XLSX file","/blog/software-development/how-to-export-orders-in-saleor-io-to-xlsx-file","blog/software-development/how-to-export-orders-in-saleor-io-to-xlsx-file",{"title":530,"path":531,"stem":532},"Handling High Loads on E-Commerce Platforms","/blog/software-development/how-to-handle-high-loads-on-e-commerce-platform-with-ease","blog/software-development/how-to-handle-high-loads-on-e-commerce-platform-with-ease",{"title":534,"path":535,"stem":536},"How to launch Saleor.io shop instance within 40h","/blog/software-development/how-to-launch-saleor-io-shop-instance-within-40h","blog/software-development/how-to-launch-saleor-io-shop-instance-within-40h",{"title":538,"path":539,"stem":540},"First Steps to Build a Business Relationship","/blog/software-development/how-to-make-the-first-step-to-establish-a-business-relationship","blog/software-development/how-to-make-the-first-step-to-establish-a-business-relationship",{"title":542,"path":543,"stem":544},"Multi-Tenant Apps with Django and Saleor.io","/blog/software-development/how-to-manage-tenants-in-the-multitenant-app-based-on-django-tenants-and-saleor-io-platform","blog/software-development/how-to-manage-tenants-in-the-multitenant-app-based-on-django-tenants-and-saleor-io-platform",{"title":546,"path":547,"stem":548},"Notion Backup Tool Built in 3 Days with Python","/blog/software-development/how-we-built-a-notion-backup-tool-in-3-days-with-pythonvue-and-why","blog/software-development/how-we-built-a-notion-backup-tool-in-3-days-with-pythonvue-and-why",{"title":550,"path":551,"stem":552},"Important new features in Python 3.8","/blog/software-development/important-new-features-in-python-3-8","blog/software-development/important-new-features-in-python-3-8",{"title":554,"path":555,"stem":556},"Installing Proxmox on dedicated server from OVH","/blog/software-development/installing-proxmox-on-dedicated-server-from-ovh","blog/software-development/installing-proxmox-on-dedicated-server-from-ovh",{"title":558,"path":559,"stem":560},"Integrating SignNow E-Signatures into Your Django Application","/blog/software-development/integrating-signnow-e-signatures-into-your-django-application","blog/software-development/integrating-signnow-e-signatures-into-your-django-application",{"title":562,"path":563,"stem":564},"Tempus Metronome and GetSongBPM API","/blog/software-development/integrating-tempus-metronome-with-the-getsongbpm-api-what-bpm-really-means-and-how-to-use-it","blog/software-development/integrating-tempus-metronome-with-the-getsongbpm-api-what-bpm-really-means-and-how-to-use-it",{"title":566,"path":567,"stem":568},"Introducing MusicTech Poland","/blog/software-development/introducing-musictech-poland","blog/software-development/introducing-musictech-poland",{"title":570,"path":571,"stem":572},"Vue.js as a Frontend for Saleor.io","/blog/software-development/is-it-possible-to-use-vue-js-as-a-frontend-for-saleor-io-platform","blog/software-development/is-it-possible-to-use-vue-js-as-a-frontend-for-saleor-io-platform",{"title":574,"path":575,"stem":576},"Is your business ready for the cashless era?","/blog/software-development/is-your-business-ready-for-the-cashless-era","blog/software-development/is-your-business-ready-for-the-cashless-era",{"title":578,"path":579,"stem":580},"Is your face ready to buy?","/blog/software-development/is-your-face-ready-to-buy","blog/software-development/is-your-face-ready-to-buy",{"title":582,"path":583,"stem":584},"JS Frameworks: Trends and Opportunities","/blog/software-development/javascript-trending-frameworks-and-market-opportunities","blog/software-development/javascript-trending-frameworks-and-market-opportunities",{"title":586,"path":587,"stem":588},"Kanban Board: Boost Your Team Productivity","/blog/software-development/kanban-board-methodology-hack-your-companys-productivity","blog/software-development/kanban-board-methodology-hack-your-companys-productivity",{"title":590,"path":591,"stem":592},"Verified Human Cert MCP Server: Prove Your Music Is Human-Made, Right from the Terminal","/blog/software-development/mcp-verified-human-cert-open-source","blog/software-development/mcp-verified-human-cert-open-source",{"title":594,"path":595,"stem":596},"Migrating from TravisCI to Github Actions","/blog/software-development/migrating-from-travisci-to-github-actions","blog/software-development/migrating-from-travisci-to-github-actions",{"title":598,"path":599,"stem":600},"MusicTech Lab: Top Software Developer by Clutch","/blog/software-development/musictechlab-is-recognized-as-a-top-custom-software-developer-by-clutch","blog/software-development/musictechlab-is-recognized-as-a-top-custom-software-developer-by-clutch",{"title":602,"path":603,"stem":604},"MusicTech Lab x Verified Human: Building a Trust Layer for Human-Made Music","/blog/software-development/musictechlab_blog_verified_human_partnership","blog/software-development/musictechlab_blog_verified_human_partnership",{"title":606,"path":607,"stem":608},"MusicXML: Standard for Music Notation","/blog/software-development/musicxml-standard-for-music-notation-and-education","blog/software-development/musicxml-standard-for-music-notation-and-education",{"title":610,"path":611,"stem":612},"Only a few books but dozens of ideas","/blog/software-development/only-a-few-books-but-dozens-of-ideas","blog/software-development/only-a-few-books-but-dozens-of-ideas",{"title":614,"path":615,"stem":616},"Overdue Invoices and Issue Tracker Integration","/blog/software-development/overdue-invoices-integration-with-the-issue-tracking-system","blog/software-development/overdue-invoices-integration-with-the-issue-tracking-system",{"title":618,"path":619,"stem":620},"Performing SAML SSO using JWT in Django","/blog/software-development/performing-saml-sso-using-jwt-in-django","blog/software-development/performing-saml-sso-using-jwt-in-django",{"title":622,"path":623,"stem":624},"Progressive Web Apps for Mobile Development","/blog/software-development/progressive-web-apps-a-new-way-of-creating-mobile-application","blog/software-development/progressive-web-apps-a-new-way-of-creating-mobile-application",{"title":626,"path":627,"stem":628},"Recruitment System: Gmail, Jira, and CRM","/blog/software-development/recruitment-system-integrating-gmail-bravely-jira-slack-and-copper-crm","blog/software-development/recruitment-system-integrating-gmail-bravely-jira-slack-and-copper-crm",{"title":630,"path":631,"stem":632},"Scratch Me: Chrome Extension for Leads","/blog/software-development/scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity","blog/software-development/scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity",{"title":634,"path":635,"stem":636},"Scratch Me – integration with the Copper CRM","/blog/software-development/scratch-me-integration-with-the-copper-crm","blog/software-development/scratch-me-integration-with-the-copper-crm",{"title":638,"path":639,"stem":640},"SignNow MCP Server: E-Signatures Straight from Claude Code","/blog/software-development/signnow-mcp-server-e-signatures-from-claude-code","blog/software-development/signnow-mcp-server-e-signatures-from-claude-code",{"title":642,"path":643,"stem":644},"Music Industry Tech Openings (March 2024 Update)","/blog/software-development/technical-job-opportunities-in-the-music-industry","blog/software-development/technical-job-opportunities-in-the-music-industry",{"title":646,"path":647,"stem":648},"Thanks app – a Management 3.0 solution","/blog/software-development/thanks-app-a-management-3-0-solution","blog/software-development/thanks-app-a-management-3-0-solution",{"title":650,"path":651,"stem":652},"Colonial Pipeline Case: 7 Security Reminders","/blog/software-development/the-case-of-colonial-pipeline-and-7-security-reminders","blog/software-development/the-case-of-colonial-pipeline-and-7-security-reminders",{"title":654,"path":655,"stem":656},"The Evolution and Future of E-commerce Platforms","/blog/software-development/the-evolution-and-future-of-e-commerce-platforms","blog/software-development/the-evolution-and-future-of-e-commerce-platforms",{"title":658,"path":659,"stem":660},"The Gender Gap in the Tech Industry","/blog/software-development/the-gender-gap-in-the-tech-industry","blog/software-development/the-gender-gap-in-the-tech-industry",{"title":662,"path":663,"stem":664},"First Attempt to Implement 4DX at Bravelab.io","/blog/software-development/the-very-first-attempt-to-implement-4dx-in-bravelab-io","blog/software-development/the-very-first-attempt-to-implement-4dx-in-bravelab-io",{"title":666,"path":667,"stem":668},"The WTF Scale: IT Project Complexity","/blog/software-development/the-wtf-programming-scale-measuring-it-project-complexity","blog/software-development/the-wtf-programming-scale-measuring-it-project-complexity",{"title":670,"path":671,"stem":672},"Top 10 articles through the eyes of our developers","/blog/software-development/top-10-articles-through-the-eyes-of-our-developers","blog/software-development/top-10-articles-through-the-eyes-of-our-developers",{"title":674,"path":675,"stem":676},"Top 6 apps made with Flutter","/blog/software-development/top-6-apps-made-with-flutter","blog/software-development/top-6-apps-made-with-flutter",{"title":678,"path":679,"stem":680},"Uber 101: How Uber Made It to the Top","/blog/software-development/uber-101-how-this-ride-sharing-behemoth-made-it-to-the-top","blog/software-development/uber-101-how-this-ride-sharing-behemoth-made-it-to-the-top",{"title":682,"path":683,"stem":684},"MusicTech Lab Partners with Music Glue","/blog/software-development/unifying-artists-and-audiences-exploring-music-glue","blog/software-development/unifying-artists-and-audiences-exploring-music-glue",{"title":686,"path":687,"stem":688},"Why AI Will Defeat Traditional HR","/blog/software-development/warning-why-artificial-intelligence-will-defeat-traditional-hr","blog/software-development/warning-why-artificial-intelligence-will-defeat-traditional-hr",{"title":690,"path":691,"stem":692},"What is a Discovery Document?","/blog/software-development/what-is-discovery-document","blog/software-development/what-is-discovery-document",{"title":694,"path":695,"stem":696},"What is Flutter, and Why is it Worth Considering?","/blog/software-development/what-is-flutter-and-why-is-it-worth-considering","blog/software-development/what-is-flutter-and-why-is-it-worth-considering",{"title":698,"path":699,"stem":700},"What is a Watermarked Song?","/blog/software-development/what-is-watermarked-song","blog/software-development/what-is-watermarked-song",{"title":702,"path":703,"stem":704},"Choosing a Frontend Framework for the Web","/blog/software-development/which-framework-should-you-choose-for-the-frontend-web-platform-development","blog/software-development/which-framework-should-you-choose-for-the-frontend-web-platform-development",{"title":706,"path":707,"stem":708},"Why DAWs Are the Wrong Tool for Starting a Song","/blog/software-development/why-daws-wrong-tool-for-starting-song","blog/software-development/why-daws-wrong-tool-for-starting-song",{"title":710,"path":711,"stem":712},"Why the Programming World Loves Python","/blog/software-development/why-the-programming-world-loves-python","blog/software-development/why-the-programming-world-loves-python",{"title":714,"path":715,"stem":716},"Why We Don't Build Chat From Scratch (And Neither Should You)","/blog/software-development/why-we-dont-build-chat-from-scratch","blog/software-development/why-we-dont-build-chat-from-scratch",{"title":718,"path":719,"stem":720},"Why we use Sanity.io","/blog/software-development/why-we-use-sanity-io","blog/software-development/why-we-use-sanity-io",{"title":722,"path":723,"stem":724,"children":725,"page":69},"Sportstech","/blog/sportstech","blog/sportstech",[726,730],{"title":727,"path":728,"stem":729},"BeatBuddy Replay: Video Analysis App Challenges","/blog/sportstech/beatbuddy-replay-video-analysis-app-for-swimmers-flutter","blog/sportstech/beatbuddy-replay-video-analysis-app-for-swimmers-flutter",{"title":731,"path":732,"stem":733},"How to Create a Watch Face App for Garmin Watch","/blog/sportstech/how-to-create-watch-face-app-for-garmin-watch","blog/sportstech/how-to-create-watch-face-app-for-garmin-watch",{"id":735,"title":192,"authors":736,"badge":741,"body":742,"category":1051,"client":741,"date":1052,"description":1053,"extension":1054,"faq":741,"featured":69,"featuredOrder":741,"hidden":69,"image":1055,"keyTakeaways":1057,"meta":1070,"navigation":1058,"path":193,"seo":1071,"status":741,"stem":194,"tags":1072,"teaser":741,"__hash__":1076},"posts/blog/music-data/understanding-the-api-first-approach.md",[737],{"name":738,"avatar":739},"Adam Golański",{"src":740},"/images/people/adam-golanski.webp",null,{"type":743,"value":744,"toc":1032},"minimark",[745,749,752,756,759,762,767,792,808,812,826,829,833,851,865,869,872,890,894,917,921,924,940,944,967,976,980,994,997,1001,1004,1027],[746,747,748],"p",{},"In e-commerce, virtual shops, payment gateways, delivery services, and CRMs all need to interact seamlessly. The API First approach puts those connectors at the forefront of development — designing APIs before writing the application code itself.",[746,750,751],{},"This article explores why API First matters, how leading e-commerce platforms use it, and what it means for your business.",[753,754,192],"h2",{"id":755},"understanding-the-api-first-approach",[746,757,758],{},"APIs are the glue connecting software applications. They define how systems communicate — sets of rules and protocols for sharing data and functionality. In traditional development, APIs were an afterthought, retrofitted into existing systems. The API First approach flips this: you design the API contract (methods, data formats, endpoints) before writing any application code.",[746,760,761],{},"This ensures the API is a stable foundation, not a bolt-on. It's especially valuable for e-commerce platforms that need to support many integrations and interaction models.",[763,764,766],"h3",{"id":765},"why-api-first-shines-in-e-commerce","Why API First shines in e-commerce",[768,769,776,782,787],"div",{"className":770},[771,772,773,774,775],"grid","grid-cols-1","md:grid-cols-3","gap-4","my-8",[777,778],"spotlight-card",{"description":779,"icon":780,"title":781},"Frontend delivers rich UX while backend focuses on business logic. Modular, loosely coupled systems evolve independently.","i-lucide-layers","Decoupled Architecture",[777,783],{"description":784,"icon":785,"title":786},"Payment gateways, CRM systems, logistics providers, and third-party services connect effortlessly through well-defined APIs.","i-lucide-plug","Seamless Integrations",[777,788],{"description":789,"icon":790,"title":791},"Mobile apps, social media platforms, IoT devices — APIs enable consistent experiences across every touchpoint.","i-lucide-smartphone","Multi-Channel Ready",[746,793,794,795,799,800,803,804,807],{},"Several leading e-commerce platforms have embraced the API First approach: ",[796,797,798],"strong",{},"Saleor",", ",[796,801,802],{},"Reaction Commerce"," (now Mailchimp Open Commerce), and ",[796,805,806],{},"Magento",". Each illustrates how designing with an API First mindset leads to extensible and scalable solutions.",[753,809,811],{"id":810},"reaction-commerce-powering-custom-e-commerce-experiences","Reaction Commerce: Powering Custom E-commerce Experiences",[768,813,816,821],{"className":814},[771,772,815,774,775],"md:grid-cols-2",[777,817],{"description":818,"icon":819,"title":820},"Built entirely with JavaScript (Node.js + React), Reaction Commerce delivers highly reactive, customizable e-commerce experiences with real-time data updates.","i-lucide-zap","Real-Time Reactive Platform",[777,822],{"description":823,"icon":824,"title":825},"Clients request exactly the data they need — no over-fetching. Complex nested structures in a single call simplify client-side code.","i-lucide-braces","GraphQL-Powered API",[746,827,828],{},"JavaScript's asynchronous nature and vast ecosystem of libraries make Reaction Commerce a highly productive environment. Developers work in a language they already know, contributing to faster development and interactive real-time experiences.",[763,830,832],{"id":831},"real-world-applications","Real-world applications",[768,834,836,841,846],{"className":835},[771,772,773,774,775],[777,837],{"description":838,"icon":839,"title":840},"LA-based design studio leveraging real-time inventory management. API First enables seamless CRM and logistics integration for streamlined order fulfillment.","i-lucide-sofa","Stephen Kenn",[777,842],{"description":843,"icon":844,"title":845},"Built a Fortune 10 company's internal software provisioning tool. Integrated Microsoft Active Directory, MongoDB sync, and custom checkout — all via Reaction Commerce APIs.","i-lucide-building-2","Project Ricochet",[777,847],{"description":848,"icon":849,"title":850},"Digital agency praising Reaction Commerce's modular architecture. Created boilerplate plugins demonstrating how internal modules become standalone NPM packages.","i-lucide-blocks","DemandCluster",[852,853,854],"note",{},[746,855,856,857,860,861,864],{},"The value of API First was validated when ",[796,858,859],{},"Mailchimp acquired Reaction Commerce"," in April 2020 for $16.1 million. The platform is now known as ",[796,862,863],{},"Mailchimp Open Commerce",", integrated into Mailchimp's developer-focused product suite.",[753,866,868],{"id":867},"magento-the-enterprise-api-first-solution","Magento: The Enterprise API First Solution",[746,870,871],{},"Built on PHP with Zend Framework and Symfony components, Magento is a cornerstone of enterprise e-commerce. Under Adobe's ownership since 2018, it integrates with Adobe's full suite of business tools. In 2021, Magento became a global leader in the Gartner Magic Quadrant for Digital Commerce — and API First was a key reason.",[768,873,875,880,885],{"className":874},[771,772,773,774,775],[777,876],{"description":877,"icon":878,"title":879},"Extensive REST APIs for CRM, ERP, payment gateways, product management, and customer data — all built API First.","i-lucide-shield-check","Enterprise-Grade APIs",[777,881],{"description":882,"icon":883,"title":884},"Supports businesses from startups to international operations. Flexible architecture allows adding, modifying, and expanding functionality.","i-lucide-trending-up","Built to Scale",[777,886],{"description":887,"icon":888,"title":889},"Named a global leader in the 2021 Magic Quadrant for Digital Commerce, powered by its API First architecture.","i-lucide-award","Gartner Leader",[763,891,893],{"id":892},"who-uses-magento","Who uses Magento?",[768,895,897,902,907,912],{"className":896},[771,772,815,774,775],[777,898],{"description":899,"icon":900,"title":901},"Powers Ford's website and global dealer network for original accessories. Personalization features let visitors filter products by vehicle model. Revenue: $127B (2020).","i-lucide-car","Ford Motor Company",[777,903],{"description":904,"icon":905,"title":906},"Runs a Magento 2 store for bottles, apparel, and collectibles. Customers personalize products with their names and earn rewards through vending machine purchases.","i-lucide-cup-soda","Coca-Cola",[777,908],{"description":909,"icon":910,"title":911},"B2B supplier portal powered by Magento's company accounts, shared catalogs, custom pricing, and ERP integrations. Revenue: $71B (2020).","i-lucide-factory","Procter & Gamble",[777,913],{"description":914,"icon":915,"title":916},"Click & collect shopping across India, omnichannel experiences in China and Hong Kong, plus customer rewards programs — all on Magento 2.","i-lucide-monitor","HP Inc.",[753,918,920],{"id":919},"saleor-empowering-e-commerce-with-an-api-first-mindset","Saleor: Empowering E-commerce with an API First Mindset",[746,922,923],{},"Saleor is an open-source platform crafted with Python and Django, designed for extensibility, scalability, and customization. It uses GraphQL for efficient data queries — clients request exactly what they need, reducing network overhead and improving performance.",[768,925,927,931,935],{"className":926},[771,772,773,774,775],[777,928],{"description":929,"icon":849,"title":930},"Independent modules communicate via well-defined interfaces. Add features, swap services, or change business logic without disrupting the system.","Modular Architecture",[777,932],{"description":933,"icon":824,"title":934},"Efficient data fetching, rapid feature development, and easy integration with external systems — all through a single, flexible API layer.","GraphQL API",[777,936],{"description":937,"icon":938,"title":939},"Comprehensive, well-structured docs covering data types, mutations, queries, and endpoints. Reduces onboarding time significantly.","i-lucide-book-open","Documentation",[763,941,943],{"id":942},"who-uses-saleor","Who uses Saleor?",[768,945,947,952,957,962],{"className":946},[771,772,815,774,775],[777,948],{"description":949,"icon":950,"title":951},"The creators of Saleor use their own platform — a responsive, user-friendly site showcasing the platform's flexibility and adaptability.","i-lucide-code-2","Mirumee Software",[777,953],{"description":954,"icon":955,"title":956},"Relaunched website and app with Saleor, placing digital ethics at the core. Real-time updates, complex product data, and seamless user experience at scale.","i-lucide-leaf","Lush Labs",[777,958],{"description":959,"icon":960,"title":961},"Swiss/Liechtenstein marketplace connecting farmers, artisans, and small businesses with customers in a niche regional products market.","i-lucide-wheat","Hofkorb",[777,963],{"description":964,"icon":965,"title":966},"Saudi Arabian platform connecting fashion designers, celebrities, and buyers with listing, promotion, and affiliate marketing capabilities.","i-lucide-shirt","Wecre8",[968,969,970],"tip",{},[746,971,972,975],{},[796,973,974],{},"Colophon Foundry",", a renowned international type foundry, showcases Saleor in a niche market — leveraging the GraphQL API for real-time inventory management of complex typeface product data.",[753,977,979],{"id":978},"comparing-api-first-e-commerce-solutions","Comparing API First E-commerce Solutions",[768,981,983,987,990],{"className":982},[771,772,773,774,775],[777,984],{"description":985,"icon":986,"title":798},"Python + Django, GraphQL API. Modular plugin architecture. Best for complex product variants and high UI customisation.","i-lucide-egg",[777,988],{"description":989,"icon":819,"title":802},"Node.js + React, GraphQL API. Real-time reactive architecture. Best for applications demanding instant updates during high-volume sales.",[777,991],{"description":992,"icon":993,"title":806},"PHP + Zend/Symfony, REST APIs. Mature enterprise stack with Adobe integrations. Comprehensive catalog, CRM, and B2B features.","i-lucide-building",[746,995,996],{},"All three share the API First approach as a common denominator. The key differences lie in the technology stack, developer experience, and where each platform excels.",[753,998,1000],{"id":999},"overcoming-challenges-and-future-considerations","Overcoming Challenges and Future Considerations",[746,1002,1003],{},"The API First approach offers significant advantages but presents challenges that need careful attention.",[768,1005,1007,1012,1017,1022],{"className":1006},[771,772,815,774,775],[777,1008],{"description":1009,"icon":1010,"title":1011},"Implement OAuth/JWT for authentication, role-based access control for authorization, and SSL/TLS for encryption. Schedule regular security audits and penetration testing.","i-lucide-shield","Prioritize Security",[777,1013],{"description":1014,"icon":1015,"title":1016},"Use semantic versioning and clear deprecation strategies. URI or media type versioning helps maintain backward compatibility as APIs evolve.","i-lucide-git-branch","Manage API Lifecycle",[777,1018],{"description":1019,"icon":1020,"title":1021},"Use Swagger or Postman to auto-generate and maintain API docs. Comprehensive documentation is a hallmark of the API First approach.","i-lucide-file-text","Invest in Documentation",[777,1023],{"description":1024,"icon":1025,"title":1026},"Headless commerce decouples frontend from backend. Microservices promote scalability and resilience with independent deployment.","i-lucide-boxes","Embrace Modern Architecture",[968,1028,1029],{},[746,1030,1031],{},"The API First approach is not just a technical decision — it's a business strategy. By designing APIs before code, you create a stable foundation that supports multiple channels, integrations, and future growth.",{"title":1033,"searchDepth":1034,"depth":1034,"links":1035},"",2,[1036,1040,1043,1046,1049,1050],{"id":755,"depth":1034,"text":192,"children":1037},[1038],{"id":765,"depth":1039,"text":766},3,{"id":810,"depth":1034,"text":811,"children":1041},[1042],{"id":831,"depth":1039,"text":832},{"id":867,"depth":1034,"text":868,"children":1044},[1045],{"id":892,"depth":1039,"text":893},{"id":919,"depth":1034,"text":920,"children":1047},[1048],{"id":942,"depth":1039,"text":943},{"id":978,"depth":1034,"text":979},{"id":999,"depth":1034,"text":1000},"music-data","2024-01-29T00:00:00.000Z","Why the API First approach matters for e-commerce. Learn how designing APIs before code leads to scalable, maintainable, integration-friendly platforms.","md",{"src":1056},"/images/blog/musictechlab_blog_understanding-the-api-first-approach.webp",{"enabled":1058,"items":1059},true,[1060,1063,1065,1068],{"text":1061,"icon":1062},"API First means designing the API contract before writing any application code.","i-lucide-code",{"text":1064,"icon":849},"Saleor, Reaction Commerce, and Magento all built their e-commerce platforms API First.",{"text":1066,"icon":1067},"Mailchimp acquired Reaction Commerce for $16.1M, validating the API First approach.","i-lucide-dollar-sign",{"text":1069,"icon":819},"GraphQL reduces over-fetching by letting clients request exactly the data they need.",{},{"title":192,"description":1053},[1073,1074,1075],"development","musictech","API","WN99ZqOypx_ZYXzZCCthV-dVe_3CRO7zM6Qj8V7tM94",[1078,1080],{"title":188,"path":189,"stem":190,"description":1079,"children":-1},"How self-publishing evolved in the music industry, shifting control from record labels to independent artists, and how Emuze.me empowers creators today.",{"title":196,"path":197,"stem":198,"description":1081,"children":-1},"Most musical ideas end up as unsearchable, scattered recordings that creators never revisit. Why does this happen and what can we do about it?",[1083,3457,8229,10054],{"id":1084,"title":112,"authors":1085,"badge":1091,"body":1094,"category":1051,"client":741,"date":3436,"description":3437,"extension":1054,"faq":741,"featured":69,"featuredOrder":741,"hidden":69,"image":3438,"keyTakeaways":3440,"meta":3452,"navigation":1058,"path":113,"seo":3453,"status":741,"stem":114,"tags":3454,"teaser":741,"__hash__":3456,"score":1370},"posts/blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api.md",[1086],{"name":1087,"to":1088,"avatar":1089},"Mariusz Smenżyk","https://www.linkedin.com/in/mariusz-smenzyk/",{"src":1090},"/images/people/mariusz-smenzyk2.webp",{"label":1092,"color":1093},"Distribution","#0ea5e9",{"type":743,"value":1095,"toc":3404},[1096,1104,1107,1111,1114,1174,1177,1179,1183,1186,1194,1203,1210,1212,1216,1219,1222,1226,1246,1249,1278,1282,1293,1297,1308,1312,1323,1332,1334,1338,1341,1486,1490,1554,1558,1610,1614,1617,2387,2400,2402,2406,2409,2412,2416,2419,2649,2653,2656,2834,2838,2845,3041,3065,3069,3075,3093,3100,3102,3106,3109,3117,3121,3124,3128,3131,3187,3190,3194,3197,3201,3204,3206,3210,3213,3233,3236,3290,3299,3301,3305,3308,3334,3343,3345,3349,3352,3355,3357,3361,3400],[746,1097,1098,1099,1103],{},"Music distribution has never been more accessible. Platforms like DistroKid, TuneCore, and CD Baby democratized access to DSPs (Digital Service Providers) for independent artists. But for companies operating as distributors - managing ",[1100,1101,1102],"a",{"href":77},"multiple labels, complex territory restrictions",", and custom royalty splits - the generic UI and rigid workflows of consumer-facing tools quickly become a bottleneck.",[1105,1106],"hr",{},[753,1108,1110],{"id":1109},"why-distributors-outgrow-off-the-shelf-platforms","Why Distributors Outgrow Off-the-Shelf Platforms",[746,1112,1113],{},"The friction points are predictable:",[1115,1116,1117,1130],"table",{},[1118,1119,1120],"thead",{},[1121,1122,1123,1127],"tr",{},[1124,1125,1126],"th",{},"Pain Point",[1124,1128,1129],{},"What Happens",[1131,1132,1133,1144,1154,1164],"tbody",{},[1121,1134,1135,1141],{},[1136,1137,1138],"td",{},[796,1139,1140],{},"Territory restrictions",[1136,1142,1143],{},"Many platforms only support album-level territory settings, forcing workarounds when individual tracks have different rights across markets",[1121,1145,1146,1151],{},[1136,1147,1148],{},[796,1149,1150],{},"Metadata flexibility",[1136,1152,1153],{},"Contributor roles, localized titles, and DSP-specific artist IDs often require manual overrides the UI doesn't expose",[1121,1155,1156,1161],{},[1136,1157,1158],{},[796,1159,1160],{},"Reporting",[1136,1162,1163],{},"Off-the-shelf dashboards rarely match the operational needs of a multi-label distributor",[1121,1165,1166,1171],{},[1136,1167,1168],{},[796,1169,1170],{},"Delivery control",[1136,1172,1173],{},"When a platform handles delivery as a black box, debugging ingestion failures becomes a support ticket game",[746,1175,1176],{},"This is where the build-vs-buy decision becomes real.",[1105,1178],{},[753,1180,1182],{"id":1181},"the-two-paths-frontend-only-vs-full-stack","The Two Paths: Frontend-Only vs. Full Stack",[746,1184,1185],{},"Distributors who've outgrown their current setup face a fundamental architectural choice:",[852,1187,1188],{},[746,1189,1190,1193],{},[796,1191,1192],{},"Option A: Custom Frontend + Revelator API as Backend","\nBuild your own user-facing layer - UX, workflows, internal tools, reporting - and use Revelator's API purely for catalog management, delivery to DSPs, and royalty ingestion.",[1195,1196,1197],"warning",{},[746,1198,1199,1202],{},[796,1200,1201],{},"Option B: Full-Stack Distribution Platform","\nReplace the platform entirely. Build metadata management, DDEX (Digital Data Exchange) generation, delivery pipelines, DSP integrations, royalty ingestion, reporting, and payouts from scratch.",[746,1204,1205,1206,1209],{},"This article focuses on ",[796,1207,1208],{},"Option A",": what it looks like in practice, where it excels, and where it hits structural limits.",[1105,1211],{},[753,1213,1215],{"id":1214},"what-the-revelator-api-actually-offers","What the Revelator API Actually Offers",[746,1217,1218],{},"Revelator positions itself as an \"end-to-end operating system\" for independent music businesses. Unlike consumer-facing distributors, it's B2B infrastructure - designed for labels, aggregators, and distributors who want to run their own branded operation.",[746,1220,1221],{},"The API is RESTful, JSON-based, and covers five core modules:",[763,1223,1225],{"id":1224},"catalog-management","Catalog Management",[1227,1228,1229,1237,1240,1243],"ul",{},[1230,1231,1232,1233],"li",{},"Create and edit releases via ",[1234,1235,1236],"code",{},"POST /content/release/save",[1230,1238,1239],{},"Upload audio (WAV/FLAC, minimum 16-bit, 44.1 kHz stereo) and cover art (minimum 1400x1400px JPG/RGB)",[1230,1241,1242],{},"Manage ISRCs (International Standard Recording Codes), UPCs (Universal Product Codes), and external DSP artist IDs",[1230,1244,1245],{},"Support for multi-disc releases and localized metadata",[763,1247,1092],{"id":1248},"distribution",[1227,1250,1251,1257,1260,1266,1269,1275],{},[1230,1252,1253,1254],{},"Pre-delivery validation: ",[1234,1255,1256],{},"POST /distribution/release/{releaseId}/validate",[1230,1258,1259],{},"Set DSP targets, release dates, and territories per release",[1230,1261,1262,1263],{},"Queue releases for delivery: ",[1234,1264,1265],{},"POST /distribution/release/addtoqueue",[1230,1267,1268],{},"Track delivery status (statuses range from -20 to 100; 50+ means delivered)",[1230,1270,1271,1272],{},"Takedown support: ",[1234,1273,1274],{},"POST /distribution/release/takedown",[1230,1276,1277],{},"Webhook callbacks for delivery status updates",[763,1279,1281],{"id":1280},"rights-and-royalties","Rights and Royalties",[1227,1283,1284,1287,1290],{},[1230,1285,1286],{},"Contract definition with configurable splits and recoupables",[1230,1288,1289],{},"DSP statement import and reconciliation",[1230,1291,1292],{},"Multi-currency payout automation via Tipalti and PayPal",[763,1294,1296],{"id":1295},"analytics","Analytics",[1227,1298,1299,1302,1305],{},[1230,1300,1301],{},"Streaming and revenue data queryable by track, release, region, or DSP",[1230,1303,1304],{},"Playlist performance tracking",[1230,1306,1307],{},"CSV export and raw data sync",[763,1309,1311],{"id":1310},"account-management","Account Management",[1227,1313,1314,1317,1320],{},[1230,1315,1316],{},"Parent-child account hierarchy (your enterprise is the parent; each label or artist is a child)",[1230,1318,1319],{},"Full visibility into child account assets",[1230,1321,1322],{},"Mandatory approval step before content reaches DSPs",[968,1324,1325],{},[746,1326,1327,1328,1331],{},"Revelator claims ",[796,1329,1330],{},"100+ DSP integrations",", including Spotify, Apple Music, Amazon, YouTube Music, YouTube Content ID, TikTok, and Deezer. DSP access is configurable per child account.",[1105,1333],{},[753,1335,1337],{"id":1336},"architecture-of-the-hybrid-solution","Architecture of the Hybrid Solution",[746,1339,1340],{},"The hybrid approach layers your custom platform on top of Revelator's delivery infrastructure:",[1342,1343,1347],"pre",{"className":1344,"code":1345,"language":1346,"meta":1033,"style":1033},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","graph TD\n    subgraph Frontend[Your Custom Frontend]\n        AP[Artist Portal]\n        LD[Label Dashboard]\n        AT[Admin Tools]\n        AP & LD & AT --> BE[Your API / Backend]\n    end\n    subgraph Revelator[Revelator API Layer]\n        CAT[Catalog Mgmt API]\n        DEL[Delivery API]\n        ROY[Royalty Ingestion]\n    end\n    BE --> CAT & DEL & ROY\n    subgraph DSPs[Digital Service Providers]\n        SP[Spotify]\n        AM[Apple Music]\n        YT[YouTube]\n        MORE[100+ more]\n    end\n    DEL --> SP & AM & YT & MORE\n    style Frontend fill:#0f172a,stroke:#38bdf8,color:#f8fafc\n    style Revelator fill:#1e293b,stroke:#0ea5e9,color:#f8fafc\n    style DSPs fill:#0f172a,stroke:#334155,color:#f8fafc\n","mermaid",[1234,1348,1349,1358,1363,1368,1374,1380,1386,1392,1398,1404,1410,1416,1421,1427,1433,1439,1445,1451,1457,1462,1468,1474,1480],{"__ignoreMap":1033},[1350,1351,1354],"span",{"class":1352,"line":1353},"line",1,[1350,1355,1357],{"class":1356},"sTEyZ","graph TD\n",[1350,1359,1360],{"class":1352,"line":1034},[1350,1361,1362],{"class":1356},"    subgraph Frontend[Your Custom Frontend]\n",[1350,1364,1365],{"class":1352,"line":1039},[1350,1366,1367],{"class":1356},"        AP[Artist Portal]\n",[1350,1369,1371],{"class":1352,"line":1370},4,[1350,1372,1373],{"class":1356},"        LD[Label Dashboard]\n",[1350,1375,1377],{"class":1352,"line":1376},5,[1350,1378,1379],{"class":1356},"        AT[Admin Tools]\n",[1350,1381,1383],{"class":1352,"line":1382},6,[1350,1384,1385],{"class":1356},"        AP & LD & AT --> BE[Your API / Backend]\n",[1350,1387,1389],{"class":1352,"line":1388},7,[1350,1390,1391],{"class":1356},"    end\n",[1350,1393,1395],{"class":1352,"line":1394},8,[1350,1396,1397],{"class":1356},"    subgraph Revelator[Revelator API Layer]\n",[1350,1399,1401],{"class":1352,"line":1400},9,[1350,1402,1403],{"class":1356},"        CAT[Catalog Mgmt API]\n",[1350,1405,1407],{"class":1352,"line":1406},10,[1350,1408,1409],{"class":1356},"        DEL[Delivery API]\n",[1350,1411,1413],{"class":1352,"line":1412},11,[1350,1414,1415],{"class":1356},"        ROY[Royalty Ingestion]\n",[1350,1417,1419],{"class":1352,"line":1418},12,[1350,1420,1391],{"class":1356},[1350,1422,1424],{"class":1352,"line":1423},13,[1350,1425,1426],{"class":1356},"    BE --> CAT & DEL & ROY\n",[1350,1428,1430],{"class":1352,"line":1429},14,[1350,1431,1432],{"class":1356},"    subgraph DSPs[Digital Service Providers]\n",[1350,1434,1436],{"class":1352,"line":1435},15,[1350,1437,1438],{"class":1356},"        SP[Spotify]\n",[1350,1440,1442],{"class":1352,"line":1441},16,[1350,1443,1444],{"class":1356},"        AM[Apple Music]\n",[1350,1446,1448],{"class":1352,"line":1447},17,[1350,1449,1450],{"class":1356},"        YT[YouTube]\n",[1350,1452,1454],{"class":1352,"line":1453},18,[1350,1455,1456],{"class":1356},"        MORE[100+ more]\n",[1350,1458,1460],{"class":1352,"line":1459},19,[1350,1461,1391],{"class":1356},[1350,1463,1465],{"class":1352,"line":1464},20,[1350,1466,1467],{"class":1356},"    DEL --> SP & AM & YT & MORE\n",[1350,1469,1471],{"class":1352,"line":1470},21,[1350,1472,1473],{"class":1356},"    style Frontend fill:#0f172a,stroke:#38bdf8,color:#f8fafc\n",[1350,1475,1477],{"class":1352,"line":1476},22,[1350,1478,1479],{"class":1356},"    style Revelator fill:#1e293b,stroke:#0ea5e9,color:#f8fafc\n",[1350,1481,1483],{"class":1352,"line":1482},23,[1350,1484,1485],{"class":1356},"    style DSPs fill:#0f172a,stroke:#334155,color:#f8fafc\n",[763,1487,1489],{"id":1488},"what-you-build","What You Build",[1115,1491,1492,1502],{},[1118,1493,1494],{},[1121,1495,1496,1499],{},[1124,1497,1498],{},"Layer",[1124,1500,1501],{},"Responsibility",[1131,1503,1504,1514,1524,1534,1544],{},[1121,1505,1506,1511],{},[1136,1507,1508],{},[796,1509,1510],{},"Artist & label portals",[1136,1512,1513],{},"Branded onboarding, release submission, approval workflows",[1121,1515,1516,1521],{},[1136,1517,1518],{},[796,1519,1520],{},"Metadata layer",[1136,1522,1523],{},"Your own database of releases, tracks, contributors, and rights - synced to Revelator via API",[1121,1525,1526,1531],{},[1136,1527,1528],{},[796,1529,1530],{},"Territory & rights engine",[1136,1532,1533],{},"Business logic for track-level territory restrictions, split sheets, and embargo rules",[1121,1535,1536,1541],{},[1136,1537,1538],{},[796,1539,1540],{},"Reporting dashboards",[1136,1542,1543],{},"Custom views pulling from Revelator's analytics API plus your own data",[1121,1545,1546,1551],{},[1136,1547,1548],{},[796,1549,1550],{},"Internal tools",[1136,1552,1553],{},"Approval queues, QC checklists, bulk operations, CRM integration",[763,1555,1557],{"id":1556},"what-revelator-handles","What Revelator Handles",[1115,1559,1560,1568],{},[1118,1561,1562],{},[1121,1563,1564,1566],{},[1124,1565,1498],{},[1124,1567,1501],{},[1131,1569,1570,1580,1590,1600],{},[1121,1571,1572,1577],{},[1136,1573,1574],{},[796,1575,1576],{},"DDEX generation",[1136,1578,1579],{},"Generates ERN (Electronic Release Notification) XML packages from catalog data you push via API",[1121,1581,1582,1587],{},[1136,1583,1584],{},[796,1585,1586],{},"DSP delivery",[1136,1588,1589],{},"SFTP/API delivery to all connected DSPs, including retry logic and status tracking",[1121,1591,1592,1597],{},[1136,1593,1594],{},[796,1595,1596],{},"Royalty ingestion",[1136,1598,1599],{},"Parsing DSP statements and normalizing revenue data",[1121,1601,1602,1607],{},[1136,1603,1604],{},[796,1605,1606],{},"Payout infrastructure",[1136,1608,1609],{},"Payment rail integrations for artist payouts",[763,1611,1613],{"id":1612},"quick-example-creating-a-release-via-revelator-api","Quick Example: Creating a Release via Revelator API",[746,1615,1616],{},"Here's a minimal example of how your backend would create and distribute a release through Revelator's API:",[1342,1618,1622],{"className":1619,"code":1620,"language":1621,"meta":1033,"style":1033},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import httpx\n\nREVELATOR_API = \"https://api.revelator.com\"\n\n# 1. Authenticate\nauth = httpx.post(f\"{REVELATOR_API}/partner/account/login\", json={\n    \"email\": \"your@email.com\",\n    \"password\": \"your-password\"\n})\ntoken = auth.json()[\"token\"]\nheaders = {\"Authorization\": f\"Bearer {token}\"}\n\n# 2. Create a release\nrelease = httpx.post(f\"{REVELATOR_API}/content/release/save\", headers=headers, json={\n    \"title\": \"Delilah - Summer Version\",\n    \"releaseType\": \"Single\",\n    \"artists\": [\n        {\"name\": \"MIKOLAS\", \"role\": \"MainArtist\"},\n        {\"name\": \"Mark Neve\", \"role\": \"MainArtist\"}\n    ],\n    \"genre\": \"Pop\",\n    \"releaseDate\": \"2026-04-01\",\n    \"territories\": {\n        \"include\": \"Worldwide\",\n        \"exclude\": [\"JP\", \"DE\", \"AT\", \"CH\", \"NL\", \"AU\", \"NZ\"]\n    }\n})\nrelease_id = release.json()[\"id\"]\n\n# 3. Validate before delivery\nvalidation = httpx.post(\n    f\"{REVELATOR_API}/distribution/release/{release_id}/validate\",\n    headers=headers\n)\n\n# 4. Queue for delivery to DSPs\nif validation.json()[\"valid\"]:\n    httpx.post(f\"{REVELATOR_API}/distribution/release/addtoqueue\",\n        headers=headers,\n        json={\"releaseId\": release_id}\n    )\n","python",[1234,1623,1624,1633,1638,1657,1661,1667,1717,1740,1758,1763,1791,1827,1831,1836,1880,1900,1920,1934,1976,2013,2018,2038,2058,2072,2094,2172,2178,2183,2209,2214,2220,2237,2266,2277,2283,2288,2294,2319,2346,2358,2381],{"__ignoreMap":1033},[1350,1625,1626,1630],{"class":1352,"line":1353},[1350,1627,1629],{"class":1628},"s7zQu","import",[1350,1631,1632],{"class":1356}," httpx\n",[1350,1634,1635],{"class":1352,"line":1034},[1350,1636,1637],{"emptyLinePlaceholder":1058},"\n",[1350,1639,1640,1643,1647,1650,1654],{"class":1352,"line":1039},[1350,1641,1642],{"class":1356},"REVELATOR_API ",[1350,1644,1646],{"class":1645},"sMK4o","=",[1350,1648,1649],{"class":1645}," \"",[1350,1651,1653],{"class":1652},"sfazB","https://api.revelator.com",[1350,1655,1656],{"class":1645},"\"\n",[1350,1658,1659],{"class":1352,"line":1370},[1350,1660,1637],{"emptyLinePlaceholder":1058},[1350,1662,1663],{"class":1352,"line":1376},[1350,1664,1666],{"class":1665},"sHwdD","# 1. Authenticate\n",[1350,1668,1669,1672,1674,1677,1680,1684,1687,1691,1694,1698,1701,1704,1707,1710,1714],{"class":1352,"line":1382},[1350,1670,1671],{"class":1356},"auth ",[1350,1673,1646],{"class":1645},[1350,1675,1676],{"class":1356}," httpx",[1350,1678,1679],{"class":1645},".",[1350,1681,1683],{"class":1682},"s2Zo4","post",[1350,1685,1686],{"class":1645},"(",[1350,1688,1690],{"class":1689},"spNyl","f",[1350,1692,1693],{"class":1652},"\"",[1350,1695,1697],{"class":1696},"sbssI","{",[1350,1699,1700],{"class":1682},"REVELATOR_API",[1350,1702,1703],{"class":1696},"}",[1350,1705,1706],{"class":1652},"/partner/account/login\"",[1350,1708,1709],{"class":1645},",",[1350,1711,1713],{"class":1712},"sHdIc"," json",[1350,1715,1716],{"class":1645},"={\n",[1350,1718,1719,1722,1725,1727,1730,1732,1735,1737],{"class":1352,"line":1388},[1350,1720,1721],{"class":1645},"    \"",[1350,1723,1724],{"class":1652},"email",[1350,1726,1693],{"class":1645},[1350,1728,1729],{"class":1645},":",[1350,1731,1649],{"class":1645},[1350,1733,1734],{"class":1652},"your@email.com",[1350,1736,1693],{"class":1645},[1350,1738,1739],{"class":1645},",\n",[1350,1741,1742,1744,1747,1749,1751,1753,1756],{"class":1352,"line":1394},[1350,1743,1721],{"class":1645},[1350,1745,1746],{"class":1652},"password",[1350,1748,1693],{"class":1645},[1350,1750,1729],{"class":1645},[1350,1752,1649],{"class":1645},[1350,1754,1755],{"class":1652},"your-password",[1350,1757,1656],{"class":1645},[1350,1759,1760],{"class":1352,"line":1400},[1350,1761,1762],{"class":1645},"})\n",[1350,1764,1765,1768,1770,1773,1775,1778,1781,1783,1786,1788],{"class":1352,"line":1406},[1350,1766,1767],{"class":1356},"token ",[1350,1769,1646],{"class":1645},[1350,1771,1772],{"class":1356}," auth",[1350,1774,1679],{"class":1645},[1350,1776,1777],{"class":1682},"json",[1350,1779,1780],{"class":1645},"()[",[1350,1782,1693],{"class":1645},[1350,1784,1785],{"class":1652},"token",[1350,1787,1693],{"class":1645},[1350,1789,1790],{"class":1645},"]\n",[1350,1792,1793,1796,1798,1801,1803,1806,1808,1810,1813,1816,1818,1820,1822,1824],{"class":1352,"line":1412},[1350,1794,1795],{"class":1356},"headers ",[1350,1797,1646],{"class":1645},[1350,1799,1800],{"class":1645}," {",[1350,1802,1693],{"class":1645},[1350,1804,1805],{"class":1652},"Authorization",[1350,1807,1693],{"class":1645},[1350,1809,1729],{"class":1645},[1350,1811,1812],{"class":1689}," f",[1350,1814,1815],{"class":1652},"\"Bearer ",[1350,1817,1697],{"class":1696},[1350,1819,1785],{"class":1356},[1350,1821,1703],{"class":1696},[1350,1823,1693],{"class":1652},[1350,1825,1826],{"class":1645},"}\n",[1350,1828,1829],{"class":1352,"line":1418},[1350,1830,1637],{"emptyLinePlaceholder":1058},[1350,1832,1833],{"class":1352,"line":1423},[1350,1834,1835],{"class":1665},"# 2. Create a release\n",[1350,1837,1838,1841,1843,1845,1847,1849,1851,1853,1855,1857,1859,1861,1864,1866,1869,1871,1874,1876,1878],{"class":1352,"line":1429},[1350,1839,1840],{"class":1356},"release ",[1350,1842,1646],{"class":1645},[1350,1844,1676],{"class":1356},[1350,1846,1679],{"class":1645},[1350,1848,1683],{"class":1682},[1350,1850,1686],{"class":1645},[1350,1852,1690],{"class":1689},[1350,1854,1693],{"class":1652},[1350,1856,1697],{"class":1696},[1350,1858,1700],{"class":1682},[1350,1860,1703],{"class":1696},[1350,1862,1863],{"class":1652},"/content/release/save\"",[1350,1865,1709],{"class":1645},[1350,1867,1868],{"class":1712}," headers",[1350,1870,1646],{"class":1645},[1350,1872,1873],{"class":1682},"headers",[1350,1875,1709],{"class":1645},[1350,1877,1713],{"class":1712},[1350,1879,1716],{"class":1645},[1350,1881,1882,1884,1887,1889,1891,1893,1896,1898],{"class":1352,"line":1435},[1350,1883,1721],{"class":1645},[1350,1885,1886],{"class":1652},"title",[1350,1888,1693],{"class":1645},[1350,1890,1729],{"class":1645},[1350,1892,1649],{"class":1645},[1350,1894,1895],{"class":1652},"Delilah - Summer Version",[1350,1897,1693],{"class":1645},[1350,1899,1739],{"class":1645},[1350,1901,1902,1904,1907,1909,1911,1913,1916,1918],{"class":1352,"line":1441},[1350,1903,1721],{"class":1645},[1350,1905,1906],{"class":1652},"releaseType",[1350,1908,1693],{"class":1645},[1350,1910,1729],{"class":1645},[1350,1912,1649],{"class":1645},[1350,1914,1915],{"class":1652},"Single",[1350,1917,1693],{"class":1645},[1350,1919,1739],{"class":1645},[1350,1921,1922,1924,1927,1929,1931],{"class":1352,"line":1447},[1350,1923,1721],{"class":1645},[1350,1925,1926],{"class":1652},"artists",[1350,1928,1693],{"class":1645},[1350,1930,1729],{"class":1645},[1350,1932,1933],{"class":1645}," [\n",[1350,1935,1936,1939,1941,1944,1946,1948,1950,1953,1955,1957,1959,1962,1964,1966,1968,1971,1973],{"class":1352,"line":1453},[1350,1937,1938],{"class":1645},"        {",[1350,1940,1693],{"class":1645},[1350,1942,1943],{"class":1652},"name",[1350,1945,1693],{"class":1645},[1350,1947,1729],{"class":1645},[1350,1949,1649],{"class":1645},[1350,1951,1952],{"class":1652},"MIKOLAS",[1350,1954,1693],{"class":1645},[1350,1956,1709],{"class":1645},[1350,1958,1649],{"class":1645},[1350,1960,1961],{"class":1652},"role",[1350,1963,1693],{"class":1645},[1350,1965,1729],{"class":1645},[1350,1967,1649],{"class":1645},[1350,1969,1970],{"class":1652},"MainArtist",[1350,1972,1693],{"class":1645},[1350,1974,1975],{"class":1645},"},\n",[1350,1977,1978,1980,1982,1984,1986,1988,1990,1993,1995,1997,1999,2001,2003,2005,2007,2009,2011],{"class":1352,"line":1459},[1350,1979,1938],{"class":1645},[1350,1981,1693],{"class":1645},[1350,1983,1943],{"class":1652},[1350,1985,1693],{"class":1645},[1350,1987,1729],{"class":1645},[1350,1989,1649],{"class":1645},[1350,1991,1992],{"class":1652},"Mark Neve",[1350,1994,1693],{"class":1645},[1350,1996,1709],{"class":1645},[1350,1998,1649],{"class":1645},[1350,2000,1961],{"class":1652},[1350,2002,1693],{"class":1645},[1350,2004,1729],{"class":1645},[1350,2006,1649],{"class":1645},[1350,2008,1970],{"class":1652},[1350,2010,1693],{"class":1645},[1350,2012,1826],{"class":1645},[1350,2014,2015],{"class":1352,"line":1464},[1350,2016,2017],{"class":1645},"    ],\n",[1350,2019,2020,2022,2025,2027,2029,2031,2034,2036],{"class":1352,"line":1470},[1350,2021,1721],{"class":1645},[1350,2023,2024],{"class":1652},"genre",[1350,2026,1693],{"class":1645},[1350,2028,1729],{"class":1645},[1350,2030,1649],{"class":1645},[1350,2032,2033],{"class":1652},"Pop",[1350,2035,1693],{"class":1645},[1350,2037,1739],{"class":1645},[1350,2039,2040,2042,2045,2047,2049,2051,2054,2056],{"class":1352,"line":1476},[1350,2041,1721],{"class":1645},[1350,2043,2044],{"class":1652},"releaseDate",[1350,2046,1693],{"class":1645},[1350,2048,1729],{"class":1645},[1350,2050,1649],{"class":1645},[1350,2052,2053],{"class":1652},"2026-04-01",[1350,2055,1693],{"class":1645},[1350,2057,1739],{"class":1645},[1350,2059,2060,2062,2065,2067,2069],{"class":1352,"line":1482},[1350,2061,1721],{"class":1645},[1350,2063,2064],{"class":1652},"territories",[1350,2066,1693],{"class":1645},[1350,2068,1729],{"class":1645},[1350,2070,2071],{"class":1645}," {\n",[1350,2073,2075,2078,2081,2083,2085,2087,2090,2092],{"class":1352,"line":2074},24,[1350,2076,2077],{"class":1645},"        \"",[1350,2079,2080],{"class":1652},"include",[1350,2082,1693],{"class":1645},[1350,2084,1729],{"class":1645},[1350,2086,1649],{"class":1645},[1350,2088,2089],{"class":1652},"Worldwide",[1350,2091,1693],{"class":1645},[1350,2093,1739],{"class":1645},[1350,2095,2097,2099,2102,2104,2106,2109,2111,2114,2116,2118,2120,2123,2125,2127,2129,2132,2134,2136,2138,2141,2143,2145,2147,2150,2152,2154,2156,2159,2161,2163,2165,2168,2170],{"class":1352,"line":2096},25,[1350,2098,2077],{"class":1645},[1350,2100,2101],{"class":1652},"exclude",[1350,2103,1693],{"class":1645},[1350,2105,1729],{"class":1645},[1350,2107,2108],{"class":1645}," [",[1350,2110,1693],{"class":1645},[1350,2112,2113],{"class":1652},"JP",[1350,2115,1693],{"class":1645},[1350,2117,1709],{"class":1645},[1350,2119,1649],{"class":1645},[1350,2121,2122],{"class":1652},"DE",[1350,2124,1693],{"class":1645},[1350,2126,1709],{"class":1645},[1350,2128,1649],{"class":1645},[1350,2130,2131],{"class":1652},"AT",[1350,2133,1693],{"class":1645},[1350,2135,1709],{"class":1645},[1350,2137,1649],{"class":1645},[1350,2139,2140],{"class":1652},"CH",[1350,2142,1693],{"class":1645},[1350,2144,1709],{"class":1645},[1350,2146,1649],{"class":1645},[1350,2148,2149],{"class":1652},"NL",[1350,2151,1693],{"class":1645},[1350,2153,1709],{"class":1645},[1350,2155,1649],{"class":1645},[1350,2157,2158],{"class":1652},"AU",[1350,2160,1693],{"class":1645},[1350,2162,1709],{"class":1645},[1350,2164,1649],{"class":1645},[1350,2166,2167],{"class":1652},"NZ",[1350,2169,1693],{"class":1645},[1350,2171,1790],{"class":1645},[1350,2173,2175],{"class":1352,"line":2174},26,[1350,2176,2177],{"class":1645},"    }\n",[1350,2179,2181],{"class":1352,"line":2180},27,[1350,2182,1762],{"class":1645},[1350,2184,2186,2189,2191,2194,2196,2198,2200,2202,2205,2207],{"class":1352,"line":2185},28,[1350,2187,2188],{"class":1356},"release_id ",[1350,2190,1646],{"class":1645},[1350,2192,2193],{"class":1356}," release",[1350,2195,1679],{"class":1645},[1350,2197,1777],{"class":1682},[1350,2199,1780],{"class":1645},[1350,2201,1693],{"class":1645},[1350,2203,2204],{"class":1652},"id",[1350,2206,1693],{"class":1645},[1350,2208,1790],{"class":1645},[1350,2210,2212],{"class":1352,"line":2211},29,[1350,2213,1637],{"emptyLinePlaceholder":1058},[1350,2215,2217],{"class":1352,"line":2216},30,[1350,2218,2219],{"class":1665},"# 3. Validate before delivery\n",[1350,2221,2223,2226,2228,2230,2232,2234],{"class":1352,"line":2222},31,[1350,2224,2225],{"class":1356},"validation ",[1350,2227,1646],{"class":1645},[1350,2229,1676],{"class":1356},[1350,2231,1679],{"class":1645},[1350,2233,1683],{"class":1682},[1350,2235,2236],{"class":1645},"(\n",[1350,2238,2240,2243,2245,2247,2249,2251,2254,2256,2259,2261,2264],{"class":1352,"line":2239},32,[1350,2241,2242],{"class":1689},"    f",[1350,2244,1693],{"class":1652},[1350,2246,1697],{"class":1696},[1350,2248,1700],{"class":1682},[1350,2250,1703],{"class":1696},[1350,2252,2253],{"class":1652},"/distribution/release/",[1350,2255,1697],{"class":1696},[1350,2257,2258],{"class":1682},"release_id",[1350,2260,1703],{"class":1696},[1350,2262,2263],{"class":1652},"/validate\"",[1350,2265,1739],{"class":1645},[1350,2267,2269,2272,2274],{"class":1352,"line":2268},33,[1350,2270,2271],{"class":1712},"    headers",[1350,2273,1646],{"class":1645},[1350,2275,2276],{"class":1682},"headers\n",[1350,2278,2280],{"class":1352,"line":2279},34,[1350,2281,2282],{"class":1645},")\n",[1350,2284,2286],{"class":1352,"line":2285},35,[1350,2287,1637],{"emptyLinePlaceholder":1058},[1350,2289,2291],{"class":1352,"line":2290},36,[1350,2292,2293],{"class":1665},"# 4. Queue for delivery to DSPs\n",[1350,2295,2297,2300,2303,2305,2307,2309,2311,2314,2316],{"class":1352,"line":2296},37,[1350,2298,2299],{"class":1628},"if",[1350,2301,2302],{"class":1356}," validation",[1350,2304,1679],{"class":1645},[1350,2306,1777],{"class":1682},[1350,2308,1780],{"class":1645},[1350,2310,1693],{"class":1645},[1350,2312,2313],{"class":1652},"valid",[1350,2315,1693],{"class":1645},[1350,2317,2318],{"class":1645},"]:\n",[1350,2320,2322,2325,2327,2329,2331,2333,2335,2337,2339,2341,2344],{"class":1352,"line":2321},38,[1350,2323,2324],{"class":1356},"    httpx",[1350,2326,1679],{"class":1645},[1350,2328,1683],{"class":1682},[1350,2330,1686],{"class":1645},[1350,2332,1690],{"class":1689},[1350,2334,1693],{"class":1652},[1350,2336,1697],{"class":1696},[1350,2338,1700],{"class":1682},[1350,2340,1703],{"class":1696},[1350,2342,2343],{"class":1652},"/distribution/release/addtoqueue\"",[1350,2345,1739],{"class":1645},[1350,2347,2349,2352,2354,2356],{"class":1352,"line":2348},39,[1350,2350,2351],{"class":1712},"        headers",[1350,2353,1646],{"class":1645},[1350,2355,1873],{"class":1682},[1350,2357,1739],{"class":1645},[1350,2359,2361,2364,2367,2369,2372,2374,2376,2379],{"class":1352,"line":2360},40,[1350,2362,2363],{"class":1712},"        json",[1350,2365,2366],{"class":1645},"={",[1350,2368,1693],{"class":1645},[1350,2370,2371],{"class":1652},"releaseId",[1350,2373,1693],{"class":1645},[1350,2375,1729],{"class":1645},[1350,2377,2378],{"class":1682}," release_id",[1350,2380,1826],{"class":1645},[1350,2382,2384],{"class":1352,"line":2383},41,[1350,2385,2386],{"class":1645},"    )\n",[852,2388,2389],{},[746,2390,2391,2392,2395,2396,2399],{},"This is a simplified example. In production, you'd also upload audio files via ",[1234,2393,2394],{},"/media/audio/upload",", cover art via ",[1234,2397,2398],{},"/media/image/upload",", and handle webhook callbacks for delivery status updates.",[1105,2401],{},[753,2403,2405],{"id":2404},"ddex-as-the-backbone","DDEX as the Backbone",[746,2407,2408],{},"Under the hood, every delivery to a DSP is a DDEX ERN message - an XML package describing the release, its resources (audio, artwork), and the commercial terms (deals) under which DSPs can exploit the content.",[746,2410,2411],{},"An ERN message has three critical sections:",[763,2413,2415],{"id":2414},"resourcelist","ResourceList",[746,2417,2418],{},"Defines the audio files, cover art, and their metadata. Each sound recording includes an ISRC, title, artist credits, and territory-specific details:",[1342,2420,2424],{"className":2421,"code":2422,"language":2423,"meta":1033,"style":1033},"language-xml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003CSoundRecording>\n  \u003CSoundRecordingId>\n    \u003CISRC>SKXXX2500001\u003C/ISRC>\n  \u003C/SoundRecordingId>\n  \u003CResourceReference>A1\u003C/ResourceReference>\n  \u003CSoundRecordingDetailsByTerritory>\n    \u003CTerritoryCode>Worldwide\u003C/TerritoryCode>\n    \u003CTitle TitleType=\"FormalTitle\">\n      \u003CTitleText>Delilah\u003C/TitleText>\n    \u003C/Title>\n    \u003CDisplayArtist>\n      \u003CPartyName>\u003CFullName>MIKOLAS\u003C/FullName>\u003C/PartyName>\n      \u003CArtistRole>MainArtist\u003C/ArtistRole>\n    \u003C/DisplayArtist>\n  \u003C/SoundRecordingDetailsByTerritory>\n\u003C/SoundRecording>\n","xml",[1234,2425,2426,2438,2448,2469,2478,2496,2505,2522,2543,2562,2571,2580,2608,2625,2633,2641],{"__ignoreMap":1033},[1350,2427,2428,2431,2435],{"class":1352,"line":1353},[1350,2429,2430],{"class":1645},"\u003C",[1350,2432,2434],{"class":2433},"swJcz","SoundRecording",[1350,2436,2437],{"class":1645},">\n",[1350,2439,2440,2443,2446],{"class":1352,"line":1034},[1350,2441,2442],{"class":1645},"  \u003C",[1350,2444,2445],{"class":2433},"SoundRecordingId",[1350,2447,2437],{"class":1645},[1350,2449,2450,2453,2456,2459,2462,2465,2467],{"class":1352,"line":1039},[1350,2451,2452],{"class":1645},"    \u003C",[1350,2454,2455],{"class":2433},"ISRC",[1350,2457,2458],{"class":1645},">",[1350,2460,2461],{"class":1356},"SKXXX2500001",[1350,2463,2464],{"class":1645},"\u003C/",[1350,2466,2455],{"class":2433},[1350,2468,2437],{"class":1645},[1350,2470,2471,2474,2476],{"class":1352,"line":1370},[1350,2472,2473],{"class":1645},"  \u003C/",[1350,2475,2445],{"class":2433},[1350,2477,2437],{"class":1645},[1350,2479,2480,2482,2485,2487,2490,2492,2494],{"class":1352,"line":1376},[1350,2481,2442],{"class":1645},[1350,2483,2484],{"class":2433},"ResourceReference",[1350,2486,2458],{"class":1645},[1350,2488,2489],{"class":1356},"A1",[1350,2491,2464],{"class":1645},[1350,2493,2484],{"class":2433},[1350,2495,2437],{"class":1645},[1350,2497,2498,2500,2503],{"class":1352,"line":1382},[1350,2499,2442],{"class":1645},[1350,2501,2502],{"class":2433},"SoundRecordingDetailsByTerritory",[1350,2504,2437],{"class":1645},[1350,2506,2507,2509,2512,2514,2516,2518,2520],{"class":1352,"line":1388},[1350,2508,2452],{"class":1645},[1350,2510,2511],{"class":2433},"TerritoryCode",[1350,2513,2458],{"class":1645},[1350,2515,2089],{"class":1356},[1350,2517,2464],{"class":1645},[1350,2519,2511],{"class":2433},[1350,2521,2437],{"class":1645},[1350,2523,2524,2526,2529,2532,2534,2536,2539,2541],{"class":1352,"line":1394},[1350,2525,2452],{"class":1645},[1350,2527,2528],{"class":2433},"Title",[1350,2530,2531],{"class":1689}," TitleType",[1350,2533,1646],{"class":1645},[1350,2535,1693],{"class":1645},[1350,2537,2538],{"class":1652},"FormalTitle",[1350,2540,1693],{"class":1645},[1350,2542,2437],{"class":1645},[1350,2544,2545,2548,2551,2553,2556,2558,2560],{"class":1352,"line":1400},[1350,2546,2547],{"class":1645},"      \u003C",[1350,2549,2550],{"class":2433},"TitleText",[1350,2552,2458],{"class":1645},[1350,2554,2555],{"class":1356},"Delilah",[1350,2557,2464],{"class":1645},[1350,2559,2550],{"class":2433},[1350,2561,2437],{"class":1645},[1350,2563,2564,2567,2569],{"class":1352,"line":1406},[1350,2565,2566],{"class":1645},"    \u003C/",[1350,2568,2528],{"class":2433},[1350,2570,2437],{"class":1645},[1350,2572,2573,2575,2578],{"class":1352,"line":1412},[1350,2574,2452],{"class":1645},[1350,2576,2577],{"class":2433},"DisplayArtist",[1350,2579,2437],{"class":1645},[1350,2581,2582,2584,2587,2590,2593,2595,2597,2599,2601,2604,2606],{"class":1352,"line":1418},[1350,2583,2547],{"class":1645},[1350,2585,2586],{"class":2433},"PartyName",[1350,2588,2589],{"class":1645},">\u003C",[1350,2591,2592],{"class":2433},"FullName",[1350,2594,2458],{"class":1645},[1350,2596,1952],{"class":1356},[1350,2598,2464],{"class":1645},[1350,2600,2592],{"class":2433},[1350,2602,2603],{"class":1645},">\u003C/",[1350,2605,2586],{"class":2433},[1350,2607,2437],{"class":1645},[1350,2609,2610,2612,2615,2617,2619,2621,2623],{"class":1352,"line":1423},[1350,2611,2547],{"class":1645},[1350,2613,2614],{"class":2433},"ArtistRole",[1350,2616,2458],{"class":1645},[1350,2618,1970],{"class":1356},[1350,2620,2464],{"class":1645},[1350,2622,2614],{"class":2433},[1350,2624,2437],{"class":1645},[1350,2626,2627,2629,2631],{"class":1352,"line":1429},[1350,2628,2566],{"class":1645},[1350,2630,2577],{"class":2433},[1350,2632,2437],{"class":1645},[1350,2634,2635,2637,2639],{"class":1352,"line":1435},[1350,2636,2473],{"class":1645},[1350,2638,2502],{"class":2433},[1350,2640,2437],{"class":1645},[1350,2642,2643,2645,2647],{"class":1352,"line":1441},[1350,2644,2464],{"class":1645},[1350,2646,2434],{"class":2433},[1350,2648,2437],{"class":1645},[763,2650,2652],{"id":2651},"releaselist","ReleaseList",[746,2654,2655],{},"Defines the product - the album or single - and links it to its component resources:",[1342,2657,2659],{"className":2421,"code":2658,"language":2423,"meta":1033,"style":1033},"\u003CRelease>\n  \u003CReleaseId>\u003CICPN>0123456789012\u003C/ICPN>\u003C/ReleaseId>\n  \u003CReleaseReference>R0\u003C/ReleaseReference>\n  \u003CReleaseType>Album\u003C/ReleaseType>\n  \u003CReleaseDetailsByTerritory>\n    \u003CTerritoryCode>Worldwide\u003C/TerritoryCode>\n    \u003CDisplayArtistName>MIKOLAS\u003C/DisplayArtistName>\n    \u003CTitle TitleType=\"FormalTitle\">\n      \u003CTitleText>ONE\u003C/TitleText>\n    \u003C/Title>\n  \u003C/ReleaseDetailsByTerritory>\n\u003C/Release>\n",[1234,2660,2661,2670,2697,2715,2733,2742,2758,2775,2793,2810,2818,2826],{"__ignoreMap":1033},[1350,2662,2663,2665,2668],{"class":1352,"line":1353},[1350,2664,2430],{"class":1645},[1350,2666,2667],{"class":2433},"Release",[1350,2669,2437],{"class":1645},[1350,2671,2672,2674,2677,2679,2682,2684,2687,2689,2691,2693,2695],{"class":1352,"line":1034},[1350,2673,2442],{"class":1645},[1350,2675,2676],{"class":2433},"ReleaseId",[1350,2678,2589],{"class":1645},[1350,2680,2681],{"class":2433},"ICPN",[1350,2683,2458],{"class":1645},[1350,2685,2686],{"class":1356},"0123456789012",[1350,2688,2464],{"class":1645},[1350,2690,2681],{"class":2433},[1350,2692,2603],{"class":1645},[1350,2694,2676],{"class":2433},[1350,2696,2437],{"class":1645},[1350,2698,2699,2701,2704,2706,2709,2711,2713],{"class":1352,"line":1039},[1350,2700,2442],{"class":1645},[1350,2702,2703],{"class":2433},"ReleaseReference",[1350,2705,2458],{"class":1645},[1350,2707,2708],{"class":1356},"R0",[1350,2710,2464],{"class":1645},[1350,2712,2703],{"class":2433},[1350,2714,2437],{"class":1645},[1350,2716,2717,2719,2722,2724,2727,2729,2731],{"class":1352,"line":1370},[1350,2718,2442],{"class":1645},[1350,2720,2721],{"class":2433},"ReleaseType",[1350,2723,2458],{"class":1645},[1350,2725,2726],{"class":1356},"Album",[1350,2728,2464],{"class":1645},[1350,2730,2721],{"class":2433},[1350,2732,2437],{"class":1645},[1350,2734,2735,2737,2740],{"class":1352,"line":1376},[1350,2736,2442],{"class":1645},[1350,2738,2739],{"class":2433},"ReleaseDetailsByTerritory",[1350,2741,2437],{"class":1645},[1350,2743,2744,2746,2748,2750,2752,2754,2756],{"class":1352,"line":1382},[1350,2745,2452],{"class":1645},[1350,2747,2511],{"class":2433},[1350,2749,2458],{"class":1645},[1350,2751,2089],{"class":1356},[1350,2753,2464],{"class":1645},[1350,2755,2511],{"class":2433},[1350,2757,2437],{"class":1645},[1350,2759,2760,2762,2765,2767,2769,2771,2773],{"class":1352,"line":1388},[1350,2761,2452],{"class":1645},[1350,2763,2764],{"class":2433},"DisplayArtistName",[1350,2766,2458],{"class":1645},[1350,2768,1952],{"class":1356},[1350,2770,2464],{"class":1645},[1350,2772,2764],{"class":2433},[1350,2774,2437],{"class":1645},[1350,2776,2777,2779,2781,2783,2785,2787,2789,2791],{"class":1352,"line":1394},[1350,2778,2452],{"class":1645},[1350,2780,2528],{"class":2433},[1350,2782,2531],{"class":1689},[1350,2784,1646],{"class":1645},[1350,2786,1693],{"class":1645},[1350,2788,2538],{"class":1652},[1350,2790,1693],{"class":1645},[1350,2792,2437],{"class":1645},[1350,2794,2795,2797,2799,2801,2804,2806,2808],{"class":1352,"line":1400},[1350,2796,2547],{"class":1645},[1350,2798,2550],{"class":2433},[1350,2800,2458],{"class":1645},[1350,2802,2803],{"class":1356},"ONE",[1350,2805,2464],{"class":1645},[1350,2807,2550],{"class":2433},[1350,2809,2437],{"class":1645},[1350,2811,2812,2814,2816],{"class":1352,"line":1406},[1350,2813,2566],{"class":1645},[1350,2815,2528],{"class":2433},[1350,2817,2437],{"class":1645},[1350,2819,2820,2822,2824],{"class":1352,"line":1412},[1350,2821,2473],{"class":1645},[1350,2823,2739],{"class":2433},[1350,2825,2437],{"class":1645},[1350,2827,2828,2830,2832],{"class":1352,"line":1418},[1350,2829,2464],{"class":1645},[1350,2831,2667],{"class":2433},[1350,2833,2437],{"class":1645},[763,2835,2837],{"id":2836},"deallist","DealList",[746,2839,2840,2841,2844],{},"The ",[796,2842,2843],{},"only"," section that grants commercial rights. This is where territory restrictions live:",[1342,2846,2848],{"className":2421,"code":2847,"language":2423,"meta":1033,"style":1033},"\u003CReleaseDeal>\n  \u003CDealReleaseReference>R0\u003C/DealReleaseReference>\n  \u003CDeal>\n    \u003CDealTerms>\n      \u003CTerritoryCode>Worldwide\u003C/TerritoryCode>\n      \u003CExcludedTerritoryCode>JP\u003C/ExcludedTerritoryCode>\n      \u003CCommercialModelType>SubscriptionModel\u003C/CommercialModelType>\n      \u003CUsage>\n        \u003CUseType>OnDemandStream\u003C/UseType>\n      \u003C/Usage>\n      \u003CValidityPeriod>\n        \u003CStartDate>2026-03-15\u003C/StartDate>\n      \u003C/ValidityPeriod>\n    \u003C/DealTerms>\n  \u003C/Deal>\n\u003C/ReleaseDeal>\n",[1234,2849,2850,2859,2876,2885,2894,2910,2927,2945,2954,2973,2982,2991,3009,3017,3025,3033],{"__ignoreMap":1033},[1350,2851,2852,2854,2857],{"class":1352,"line":1353},[1350,2853,2430],{"class":1645},[1350,2855,2856],{"class":2433},"ReleaseDeal",[1350,2858,2437],{"class":1645},[1350,2860,2861,2863,2866,2868,2870,2872,2874],{"class":1352,"line":1034},[1350,2862,2442],{"class":1645},[1350,2864,2865],{"class":2433},"DealReleaseReference",[1350,2867,2458],{"class":1645},[1350,2869,2708],{"class":1356},[1350,2871,2464],{"class":1645},[1350,2873,2865],{"class":2433},[1350,2875,2437],{"class":1645},[1350,2877,2878,2880,2883],{"class":1352,"line":1039},[1350,2879,2442],{"class":1645},[1350,2881,2882],{"class":2433},"Deal",[1350,2884,2437],{"class":1645},[1350,2886,2887,2889,2892],{"class":1352,"line":1370},[1350,2888,2452],{"class":1645},[1350,2890,2891],{"class":2433},"DealTerms",[1350,2893,2437],{"class":1645},[1350,2895,2896,2898,2900,2902,2904,2906,2908],{"class":1352,"line":1376},[1350,2897,2547],{"class":1645},[1350,2899,2511],{"class":2433},[1350,2901,2458],{"class":1645},[1350,2903,2089],{"class":1356},[1350,2905,2464],{"class":1645},[1350,2907,2511],{"class":2433},[1350,2909,2437],{"class":1645},[1350,2911,2912,2914,2917,2919,2921,2923,2925],{"class":1352,"line":1382},[1350,2913,2547],{"class":1645},[1350,2915,2916],{"class":2433},"ExcludedTerritoryCode",[1350,2918,2458],{"class":1645},[1350,2920,2113],{"class":1356},[1350,2922,2464],{"class":1645},[1350,2924,2916],{"class":2433},[1350,2926,2437],{"class":1645},[1350,2928,2929,2931,2934,2936,2939,2941,2943],{"class":1352,"line":1388},[1350,2930,2547],{"class":1645},[1350,2932,2933],{"class":2433},"CommercialModelType",[1350,2935,2458],{"class":1645},[1350,2937,2938],{"class":1356},"SubscriptionModel",[1350,2940,2464],{"class":1645},[1350,2942,2933],{"class":2433},[1350,2944,2437],{"class":1645},[1350,2946,2947,2949,2952],{"class":1352,"line":1394},[1350,2948,2547],{"class":1645},[1350,2950,2951],{"class":2433},"Usage",[1350,2953,2437],{"class":1645},[1350,2955,2956,2959,2962,2964,2967,2969,2971],{"class":1352,"line":1400},[1350,2957,2958],{"class":1645},"        \u003C",[1350,2960,2961],{"class":2433},"UseType",[1350,2963,2458],{"class":1645},[1350,2965,2966],{"class":1356},"OnDemandStream",[1350,2968,2464],{"class":1645},[1350,2970,2961],{"class":2433},[1350,2972,2437],{"class":1645},[1350,2974,2975,2978,2980],{"class":1352,"line":1406},[1350,2976,2977],{"class":1645},"      \u003C/",[1350,2979,2951],{"class":2433},[1350,2981,2437],{"class":1645},[1350,2983,2984,2986,2989],{"class":1352,"line":1412},[1350,2985,2547],{"class":1645},[1350,2987,2988],{"class":2433},"ValidityPeriod",[1350,2990,2437],{"class":1645},[1350,2992,2993,2995,2998,3000,3003,3005,3007],{"class":1352,"line":1418},[1350,2994,2958],{"class":1645},[1350,2996,2997],{"class":2433},"StartDate",[1350,2999,2458],{"class":1645},[1350,3001,3002],{"class":1356},"2026-03-15",[1350,3004,2464],{"class":1645},[1350,3006,2997],{"class":2433},[1350,3008,2437],{"class":1645},[1350,3010,3011,3013,3015],{"class":1352,"line":1423},[1350,3012,2977],{"class":1645},[1350,3014,2988],{"class":2433},[1350,3016,2437],{"class":1645},[1350,3018,3019,3021,3023],{"class":1352,"line":1429},[1350,3020,2566],{"class":1645},[1350,3022,2891],{"class":2433},[1350,3024,2437],{"class":1645},[1350,3026,3027,3029,3031],{"class":1352,"line":1435},[1350,3028,2473],{"class":1645},[1350,3030,2882],{"class":2433},[1350,3032,2437],{"class":1645},[1350,3034,3035,3037,3039],{"class":1352,"line":1441},[1350,3036,2464],{"class":1645},[1350,3038,2856],{"class":2433},[1350,3040,2437],{"class":1645},[1195,3042,3043],{},[746,3044,3045,3048,3049,3052,3053,3057,3058,3060,3061,3064],{},[796,3046,3047],{},"Common DDEX mistake:"," ",[1234,3050,3051],{},"DetailsByTerritory"," in ResourceList and ReleaseList describes ",[3054,3055,3056],"em",{},"how content is presented"," in different markets. The ",[1234,3059,2837],{}," defines ",[3054,3062,3063],{},"where content is available",". Confusing these two - applying territory restrictions in metadata instead of deals - is one of the most frequent implementation errors.",[763,3066,3068],{"id":3067},"territory-restrictions-in-practice","Territory Restrictions in Practice",[746,3070,3071,3072,1729],{},"When a distributor has agreements with partners who hold rights in specific regions, individual tracks must be excluded from those territories. This requires consistent territory entries across ",[796,3073,3074],{},"three XML sections",[3076,3077,3078,3083,3088],"ol",{},[1230,3079,3080,3082],{},[1234,3081,2502],{}," in ResourceList",[1230,3084,3085,3087],{},[1234,3086,2739],{}," in ReleaseList",[1230,3089,3090,3092],{},[1234,3091,2891],{}," in DealList",[746,3094,3095,3096,3099],{},"Platforms like Revelator handle this at the release level through their UI, but ",[796,3097,3098],{},"track-level territory restrictions within an album"," are where the friction begins. This is exactly the kind of limitation that pushes distributors toward a custom frontend - you model the territory logic in your own system and push the correct per-track restrictions through the API.",[1105,3101],{},[753,3103,3105],{"id":3104},"real-world-gotchas","Real-World Gotchas",[746,3107,3108],{},"Having worked with distributors who use Revelator as their delivery backbone, here are the practical challenges we've encountered:",[1195,3110,3111],{},[746,3112,3113,3116],{},[796,3114,3115],{},"1. Mandatory Human Approval Step","\nRevelator requires a parent account to approve distributions before they reach DSPs. There is no fully automated end-to-end delivery without a human clicking \"approve\" in the web UI. For high-volume operations, this creates a bottleneck that API-only workflows cannot bypass.",[763,3118,3120],{"id":3119},"_2-full-object-re-submission","2. Full Object Re-submission",[746,3122,3123],{},"There is no PATCH support for releases. Editing a release requires re-submitting the full release object. If you omit files, they are auto-deleted. This means your backend must always maintain the complete release state.",[763,3125,3127],{"id":3126},"_3-breaking-api-changes","3. Breaking API Changes",[746,3129,3130],{},"Revelator's API has had several breaking changes in 2025-2026:",[1115,3132,3133,3143],{},[1118,3134,3135],{},[1121,3136,3137,3140],{},[1124,3138,3139],{},"Change",[1124,3141,3142],{},"When",[1131,3144,3145,3160,3171,3179],{},[1121,3146,3147,3157],{},[1136,3148,3149,3150,3153,3154],{},"UPC type changed from ",[1234,3151,3152],{},"number"," to ",[1234,3155,3156],{},"string",[1136,3158,3159],{},"Feb 2026",[1121,3161,3162,3168],{},[1136,3163,3164,3165],{},"API base URL migrated to ",[1234,3166,3167],{},"api.revelator.com",[1136,3169,3170],{},"Mar 2026",[1121,3172,3173,3176],{},[1136,3174,3175],{},"Production/engineering credits became mandatory",[1136,3177,3178],{},"Jun 2025",[1121,3180,3181,3184],{},[1136,3182,3183],{},"Zero-sentinel IDs deprecated",[1136,3185,3186],{},"Nov 2025",[746,3188,3189],{},"Your integration layer needs resilience against schema drift.",[763,3191,3193],{"id":3192},"_4-dsp-specific-ddex-interpretation","4. DSP-Specific DDEX Interpretation",[746,3195,3196],{},"Even when Revelator generates valid ERN XML, each DSP interprets certain elements differently. Spotify's ingestion engine, Apple's Content Provider system, and Amazon's pipeline each have proprietary extensions and quirks. When delivery fails, debugging requires understanding both the DDEX standard and the specific DSP's interpretation.",[763,3198,3200],{"id":3199},"_5-locked-distribution-states","5. Locked Distribution States",[746,3202,3203],{},"Tracks in certain distribution statuses (-10, -11, -20, -21) become read-only. If you need to modify metadata on a locked track, you may need to initiate a takedown and redeliver, adding complexity to your workflow engine.",[1105,3205],{},[753,3207,3209],{"id":3208},"when-does-option-a-become-a-dead-end","When Does Option A Become a Dead End?",[746,3211,3212],{},"The hybrid approach works well when:",[1227,3214,3215,3221,3227,3230],{},[1230,3216,3217,3218],{},"Your catalog is under ",[796,3219,3220],{},"50,000 releases",[1230,3222,3223,3224],{},"You have fewer than ",[796,3225,3226],{},"30 DSP targets",[1230,3228,3229],{},"Territory complexity is moderate (album-level restrictions, not per-track-per-DSP)",[1230,3231,3232],{},"Royalty reporting needs are standard (DSP-level aggregates, not sub-publishing splits)",[746,3234,3235],{},"It starts breaking down when:",[1115,3237,3238,3248],{},[1118,3239,3240],{},[1121,3241,3242,3245],{},[1124,3243,3244],{},"Signal",[1124,3246,3247],{},"Why It Matters",[1131,3249,3250,3260,3270,3280],{},[1121,3251,3252,3257],{},[1136,3253,3254],{},[796,3255,3256],{},"Royalty logic becomes the product",[1136,3258,3259],{},"If your competitive advantage is in how you calculate, split, and report royalties - with advances, recoupables, and multi-party splits - you'll outgrow Revelator's royalty engine before its delivery",[1121,3261,3262,3267],{},[1136,3263,3264],{},[796,3265,3266],{},"You need real-time delivery control",[1136,3268,3269],{},"The mandatory approval step and lack of granular delivery status callbacks limit automation at scale",[1121,3271,3272,3277],{},[1136,3273,3274],{},[796,3275,3276],{},"DSP-specific customization matters",[1136,3278,3279],{},"If you need different DDEX profiles per DSP (ERN 3.8.2 for legacy, ERN 4.3 for Spotify), you'll need your own generation pipeline",[1121,3281,3282,3287],{},[1136,3283,3284],{},[796,3285,3286],{},"Vendor risk becomes unacceptable",[1136,3288,3289],{},"Revelator is VC-backed. API changes, pricing shifts, or an acquisition could force migration under pressure",[852,3291,3292],{},[746,3293,3294,3295,3298],{},"The practical tipping point is usually ",[796,3296,3297],{},"2-3 years"," into the hybrid approach, when the workarounds and API limitations start costing more in engineering time than building the replaced components would.",[1105,3300],{},[753,3302,3304],{"id":3303},"the-hybrid-to-independent-migration-path","The Hybrid-to-Independent Migration Path",[746,3306,3307],{},"The smartest architecture for Option A anticipates Option B:",[3076,3309,3310,3316,3322,3328],{},[1230,3311,3312,3315],{},[796,3313,3314],{},"Own your metadata."," Never treat Revelator as the source of truth. Your database is canonical; Revelator is a sync target.",[1230,3317,3318,3321],{},[796,3319,3320],{},"Abstract the delivery layer."," Build an internal delivery interface that Revelator implements today but could be swapped for direct DSP integrations tomorrow.",[1230,3323,3324,3327],{},[796,3325,3326],{},"Build your own rights engine from day one."," Territory restrictions, split sheets, and rights ownership are your core IP. Never delegate this logic to the platform.",[1230,3329,3330,3333],{},[796,3331,3332],{},"Invest in DDEX competency."," Understanding ERN generation and validation - even if Revelator handles it today - is essential knowledge for the eventual transition.",[968,3335,3336],{},[746,3337,3338,3339,3342],{},"This way, when Option A hits its ceiling, you migrate ",[796,3340,3341],{},"component by component"," rather than executing a risky big-bang rewrite.",[1105,3344],{},[753,3346,3348],{"id":3347},"conclusion","Conclusion",[746,3350,3351],{},"For distributors with direct DSP contracts who are hitting the limits of their current platform, the hybrid approach - custom frontend on Revelator's API - is the fastest path to operational control without the multi-year investment of going fully independent.",[746,3353,3354],{},"The key is to build it with migration in mind. Own your metadata, abstract your delivery layer, and invest in DDEX expertise. When the time comes to replace the backend, you'll be swapping a component rather than rebuilding a platform.",[1105,3356],{},[753,3358,3360],{"id":3359},"resources","Resources",[1227,3362,3363,3371,3378,3385,3389,3394],{},[1230,3364,3365],{},[1100,3366,3370],{"href":3367,"rel":3368},"https://api-docs.revelator.com",[3369],"nofollow","Revelator API Documentation",[1230,3372,3373],{},[1100,3374,3377],{"href":3375,"rel":3376},"https://kb.ddex.net/implementing-each-standard/electronic-release-notification-message-suite-(ern)/",[3369],"DDEX ERN Knowledge Base",[1230,3379,3380],{},[1100,3381,3384],{"href":3382,"rel":3383},"https://ddexvalidator.musictechlab.io/",[3369],"MTL DDEX Validator",[1230,3386,3387],{},[1100,3388,136],{"href":137},[1230,3390,3391],{},[1100,3392,3393],{"href":161},"Introduction to Generating DDEX Files Using Python",[1230,3395,3396,3399],{},[1100,3397,3398],{"href":85},"AI-Powered Analytics Dashboard"," - once distribution data flows into your analytics pipeline, make it queryable with natural language",[3401,3402,3403],"style",{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":1033,"searchDepth":1034,"depth":1034,"links":3405},[3406,3407,3408,3415,3420,3426,3432,3433,3434,3435],{"id":1109,"depth":1034,"text":1110},{"id":1181,"depth":1034,"text":1182},{"id":1214,"depth":1034,"text":1215,"children":3409},[3410,3411,3412,3413,3414],{"id":1224,"depth":1039,"text":1225},{"id":1248,"depth":1039,"text":1092},{"id":1280,"depth":1039,"text":1281},{"id":1295,"depth":1039,"text":1296},{"id":1310,"depth":1039,"text":1311},{"id":1336,"depth":1034,"text":1337,"children":3416},[3417,3418,3419],{"id":1488,"depth":1039,"text":1489},{"id":1556,"depth":1039,"text":1557},{"id":1612,"depth":1039,"text":1613},{"id":2404,"depth":1034,"text":2405,"children":3421},[3422,3423,3424,3425],{"id":2414,"depth":1039,"text":2415},{"id":2651,"depth":1039,"text":2652},{"id":2836,"depth":1039,"text":2837},{"id":3067,"depth":1039,"text":3068},{"id":3104,"depth":1034,"text":3105,"children":3427},[3428,3429,3430,3431],{"id":3119,"depth":1039,"text":3120},{"id":3126,"depth":1039,"text":3127},{"id":3192,"depth":1039,"text":3193},{"id":3199,"depth":1039,"text":3200},{"id":3208,"depth":1034,"text":3209},{"id":3303,"depth":1034,"text":3304},{"id":3347,"depth":1034,"text":3348},{"id":3359,"depth":1034,"text":3360},"2026-02-17T00:00:00.000Z","A practical guide for music distributors evaluating a hybrid approach: custom frontend with Revelator's API as the delivery backbone. Covers architecture, DDEX integration, territory handling, and when to go fully independent.",{"src":3439},"/images/blog/musictechlab_blog_building-a-custom-music-delivery-platform-on-the-revelator-api.webp",{"enabled":1058,"items":3441},[3442,3444,3447,3450],{"text":3443,"icon":1062},"Revelator claims 100+ DSP integrations but requires a manual approval step before delivery.",{"text":3445,"icon":3446},"Territory restrictions must be consistent across three XML sections or deliveries fail silently.","i-lucide-alert-triangle",{"text":3448,"icon":3449},"Own your metadata from day one and treat Revelator as a sync target, not the source of truth.","i-lucide-database",{"text":3451,"icon":883},"The hybrid approach typically hits its ceiling after 2-3 years of scaling.",{},{"title":112,"description":3437},[1051,1073,1075,3455],"royalties","j-0HOY7SF2pZKgaTUKG4xI6pYGGOIVWAd9EiQc5LjGU",{"id":3458,"title":152,"authors":3459,"badge":741,"body":3465,"category":1051,"client":741,"date":8208,"description":8209,"extension":1054,"faq":741,"featured":69,"featuredOrder":741,"hidden":69,"image":8210,"keyTakeaways":8212,"meta":8223,"navigation":1058,"path":153,"seo":8224,"status":741,"stem":154,"tags":8225,"teaser":741,"__hash__":8228,"score":1370},"posts/blog/music-data/how-to-use-epidemic-sound-mcp-with-claude.md",[3460],{"name":3461,"to":3462,"avatar":3463},"MusicTech Lab","https://musictechlab.io",{"src":3464},"/images/logo.svg",{"type":743,"value":3466,"toc":8166},[3467,3471,3474,3480,3483,3485,3489,3492,3496,3522,3524,3528,3531,3545,3549,3557,3559,3563,3566,3604,3613,3615,3619,3623,3626,3654,3657,3661,3666,3678,3683,3721,3725,3730,3759,3764,3767,3794,3797,3957,3961,3964,3978,3982,4022,4025,4029,4032,4047,4049,4053,4058,4062,4065,4071,4075,4081,4085,4091,4093,4097,4101,4104,4878,4882,6724,6728,7893,7895,7899,7903,7906,7912,7916,7919,7925,7929,7932,7938,7942,7945,7951,7953,7957,7960,8005,8014,8016,8020,8024,8058,8062,8073,8077,8088,8090,8092,8095,8121,8124,8126,8128,8163],[753,3468,3470],{"id":3469},"introduction","Introduction",[746,3472,3473],{},"Finding the perfect music for your project has always been a time-consuming task. Whether you're developing a mobile game, creating video content, or building an interactive experience, searching through thousands of tracks can take hours.",[746,3475,3476,3479],{},[796,3477,3478],{},"Enter the Epidemic Sound MCP Server"," - a game-changing integration that brings AI-powered music discovery directly into your development workflow. Combined with Claude (Anthropic's AI assistant), you can now search, discover, and curate music using natural language prompts without ever leaving your coding environment.",[746,3481,3482],{},"In this guide, we'll walk you through everything you need to know to set up and use this powerful combination.",[1105,3484],{},[753,3486,3488],{"id":3487},"what-is-mcp-model-context-protocol","What is MCP (Model Context Protocol)?",[746,3490,3491],{},"MCP (Model Context Protocol) is an open standard developed by Anthropic that allows AI assistants like Claude to connect with external tools and services. Think of it as a bridge that lets Claude interact with real-world APIs and databases.",[763,3493,3495],{"id":3494},"key-benefits-of-mcp","Key Benefits of MCP:",[1227,3497,3498,3504,3510,3516],{},[1230,3499,3500,3503],{},[796,3501,3502],{},"Seamless Integration"," - Connect external services directly to your AI workflow",[1230,3505,3506,3509],{},[796,3507,3508],{},"Natural Language Interface"," - No need to learn complex APIs",[1230,3511,3512,3515],{},[796,3513,3514],{},"Context-Aware"," - AI understands your project and makes relevant suggestions",[1230,3517,3518,3521],{},[796,3519,3520],{},"Real-Time Access"," - Get live data from connected services",[1105,3523],{},[753,3525,3527],{"id":3526},"what-is-the-epidemic-sound-mcp-server","What is the Epidemic Sound MCP Server?",[746,3529,3530],{},"Epidemic Sound's MCP Server is their implementation of the Model Context Protocol, designed specifically for music discovery. Instead of manually browsing their catalog of 50,000+ tracks, you can now:",[1227,3532,3533,3536,3539,3542],{},[1230,3534,3535],{},"Search for music using natural language descriptions",[1230,3537,3538],{},"Filter by mood, genre, tempo, and instrumentation",[1230,3540,3541],{},"Get curated suggestions based on your project context",[1230,3543,3544],{},"Find sound effects from their 200,000+ library",[763,3546,3548],{"id":3547},"example-prompts-you-can-use","Example Prompts You Can Use:",[1342,3550,3555],{"className":3551,"code":3553,"language":3554},[3552],"language-text","\"Find upbeat electronic tracks for a mobile puzzle game\"\n\"Search for calm, ambient music with piano, around 80 BPM\"\n\"I need tense orchestral music for a boss battle scene\"\n\"Find short 10-second loops suitable for UI interactions\"\n","text",[1234,3556,3553],{"__ignoreMap":1033},[1105,3558],{},[753,3560,3562],{"id":3561},"prerequisites","Prerequisites",[746,3564,3565],{},"Before you begin, make sure you have:",[3076,3567,3568,3579,3594],{},[1230,3569,3570,3573,3574],{},[796,3571,3572],{},"Node.js"," (LTS version) - ",[1100,3575,3578],{"href":3576,"rel":3577},"https://nodejs.org/",[3369],"Download here",[1230,3580,3581,3584,3585,3588,3589],{},[796,3582,3583],{},"Claude Desktop"," or ",[796,3586,3587],{},"Claude Code CLI"," - ",[1100,3590,3593],{"href":3591,"rel":3592},"https://claude.ai/download",[3369],"Get Claude",[1230,3595,3596,3588,3599],{},[796,3597,3598],{},"Epidemic Sound Account",[1100,3600,3603],{"href":3601,"rel":3602},"https://www.epidemicsound.com/",[3369],"Sign up here",[3605,3606,3607],"blockquote",{},[746,3608,3609,3612],{},[796,3610,3611],{},"Important",": MCP servers require a local environment. They do not work in web browsers or mobile apps.",[1105,3614],{},[753,3616,3618],{"id":3617},"step-by-step-setup-guide","Step-by-Step Setup Guide",[763,3620,3622],{"id":3621},"step-1-install-nodejs","Step 1: Install Node.js",[746,3624,3625],{},"If you don't have Node.js installed, download and install the LTS version:",[1342,3627,3631],{"className":3628,"code":3629,"language":3630,"meta":1033,"style":1033},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# Verify installation\nnode --version\nnpm --version\n","bash",[1234,3632,3633,3638,3647],{"__ignoreMap":1033},[1350,3634,3635],{"class":1352,"line":1353},[1350,3636,3637],{"class":1665},"# Verify installation\n",[1350,3639,3640,3644],{"class":1352,"line":1034},[1350,3641,3643],{"class":3642},"sBMFI","node",[1350,3645,3646],{"class":1652}," --version\n",[1350,3648,3649,3652],{"class":1352,"line":1039},[1350,3650,3651],{"class":3642},"npm",[1350,3653,3646],{"class":1652},[746,3655,3656],{},"You should see version numbers for both commands.",[763,3658,3660],{"id":3659},"step-2-install-claude-desktop-or-claude-code","Step 2: Install Claude Desktop or Claude Code",[746,3662,3663],{},[796,3664,3665],{},"Option A: Claude Desktop (GUI)",[1227,3667,3668,3675],{},[1230,3669,3670,3671],{},"Download from ",[1100,3672,3674],{"href":3591,"rel":3673},[3369],"claude.ai/download",[1230,3676,3677],{},"Install and sign in with your Anthropic account",[746,3679,3680],{},[796,3681,3682],{},"Option B: Claude Code (CLI)",[1342,3684,3686],{"className":3628,"code":3685,"language":3630,"meta":1033,"style":1033},"# Install Claude Code globally\nnpm install -g @anthropic-ai/claude-code\n\n# Verify installation\nclaude --version\n",[1234,3687,3688,3693,3706,3710,3714],{"__ignoreMap":1033},[1350,3689,3690],{"class":1352,"line":1353},[1350,3691,3692],{"class":1665},"# Install Claude Code globally\n",[1350,3694,3695,3697,3700,3703],{"class":1352,"line":1034},[1350,3696,3651],{"class":3642},[1350,3698,3699],{"class":1652}," install",[1350,3701,3702],{"class":1652}," -g",[1350,3704,3705],{"class":1652}," @anthropic-ai/claude-code\n",[1350,3707,3708],{"class":1352,"line":1039},[1350,3709,1637],{"emptyLinePlaceholder":1058},[1350,3711,3712],{"class":1352,"line":1370},[1350,3713,3637],{"class":1665},[1350,3715,3716,3719],{"class":1352,"line":1376},[1350,3717,3718],{"class":3642},"claude",[1350,3720,3646],{"class":1652},[763,3722,3724],{"id":3723},"step-3-add-the-epidemic-sound-mcp-server","Step 3: Add the Epidemic Sound MCP Server",[746,3726,3727],{},[796,3728,3729],{},"Using Claude Code CLI:",[1342,3731,3733],{"className":3628,"code":3732,"language":3630,"meta":1033,"style":1033},"# Add the Epidemic Sound MCP server with user scope\nclaude mcp add epidemicsound --scope user\n",[1234,3734,3735,3740],{"__ignoreMap":1033},[1350,3736,3737],{"class":1352,"line":1353},[1350,3738,3739],{"class":1665},"# Add the Epidemic Sound MCP server with user scope\n",[1350,3741,3742,3744,3747,3750,3753,3756],{"class":1352,"line":1034},[1350,3743,3718],{"class":3642},[1350,3745,3746],{"class":1652}," mcp",[1350,3748,3749],{"class":1652}," add",[1350,3751,3752],{"class":1652}," epidemicsound",[1350,3754,3755],{"class":1652}," --scope",[1350,3757,3758],{"class":1652}," user\n",[746,3760,3761],{},[796,3762,3763],{},"Or manually edit the configuration file:",[746,3765,3766],{},"Find your config file:",[1227,3768,3769,3778,3786],{},[1230,3770,3771,3774,3775],{},[796,3772,3773],{},"macOS",": ",[1234,3776,3777],{},"~/Library/Application Support/Claude/claude_desktop_config.json",[1230,3779,3780,3774,3783],{},[796,3781,3782],{},"Windows",[1234,3784,3785],{},"%APPDATA%\\Claude\\claude_desktop_config.json",[1230,3787,3788,3774,3791],{},[796,3789,3790],{},"Linux",[1234,3792,3793],{},"~/.config/Claude/claude_desktop_config.json",[746,3795,3796],{},"Add the following configuration:",[1342,3798,3801],{"className":3799,"code":3800,"language":1777,"meta":1033,"style":1033},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"mcpServers\": {\n    \"epidemicsound\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@anthropic/mcp-epidemicsound\"],\n      \"env\": {\n        \"EPIDEMIC_SOUND_CLIENT_ID\": \"your-client-id\",\n        \"EPIDEMIC_SOUND_CLIENT_SECRET\": \"your-client-secret\"\n      }\n    }\n  }\n}\n",[1234,3802,3803,3808,3822,3835,3856,3888,3901,3921,3939,3944,3948,3953],{"__ignoreMap":1033},[1350,3804,3805],{"class":1352,"line":1353},[1350,3806,3807],{"class":1645},"{\n",[1350,3809,3810,3813,3816,3818,3820],{"class":1352,"line":1034},[1350,3811,3812],{"class":1645},"  \"",[1350,3814,3815],{"class":1689},"mcpServers",[1350,3817,1693],{"class":1645},[1350,3819,1729],{"class":1645},[1350,3821,2071],{"class":1645},[1350,3823,3824,3826,3829,3831,3833],{"class":1352,"line":1039},[1350,3825,1721],{"class":1645},[1350,3827,3828],{"class":3642},"epidemicsound",[1350,3830,1693],{"class":1645},[1350,3832,1729],{"class":1645},[1350,3834,2071],{"class":1645},[1350,3836,3837,3840,3843,3845,3847,3849,3852,3854],{"class":1352,"line":1370},[1350,3838,3839],{"class":1645},"      \"",[1350,3841,3842],{"class":1696},"command",[1350,3844,1693],{"class":1645},[1350,3846,1729],{"class":1645},[1350,3848,1649],{"class":1645},[1350,3850,3851],{"class":1652},"npx",[1350,3853,1693],{"class":1645},[1350,3855,1739],{"class":1645},[1350,3857,3858,3860,3863,3865,3867,3869,3871,3874,3876,3878,3880,3883,3885],{"class":1352,"line":1376},[1350,3859,3839],{"class":1645},[1350,3861,3862],{"class":1696},"args",[1350,3864,1693],{"class":1645},[1350,3866,1729],{"class":1645},[1350,3868,2108],{"class":1645},[1350,3870,1693],{"class":1645},[1350,3872,3873],{"class":1652},"-y",[1350,3875,1693],{"class":1645},[1350,3877,1709],{"class":1645},[1350,3879,1649],{"class":1645},[1350,3881,3882],{"class":1652},"@anthropic/mcp-epidemicsound",[1350,3884,1693],{"class":1645},[1350,3886,3887],{"class":1645},"],\n",[1350,3889,3890,3892,3895,3897,3899],{"class":1352,"line":1382},[1350,3891,3839],{"class":1645},[1350,3893,3894],{"class":1696},"env",[1350,3896,1693],{"class":1645},[1350,3898,1729],{"class":1645},[1350,3900,2071],{"class":1645},[1350,3902,3903,3905,3908,3910,3912,3914,3917,3919],{"class":1352,"line":1388},[1350,3904,2077],{"class":1645},[1350,3906,3907],{"class":2433},"EPIDEMIC_SOUND_CLIENT_ID",[1350,3909,1693],{"class":1645},[1350,3911,1729],{"class":1645},[1350,3913,1649],{"class":1645},[1350,3915,3916],{"class":1652},"your-client-id",[1350,3918,1693],{"class":1645},[1350,3920,1739],{"class":1645},[1350,3922,3923,3925,3928,3930,3932,3934,3937],{"class":1352,"line":1394},[1350,3924,2077],{"class":1645},[1350,3926,3927],{"class":2433},"EPIDEMIC_SOUND_CLIENT_SECRET",[1350,3929,1693],{"class":1645},[1350,3931,1729],{"class":1645},[1350,3933,1649],{"class":1645},[1350,3935,3936],{"class":1652},"your-client-secret",[1350,3938,1656],{"class":1645},[1350,3940,3941],{"class":1352,"line":1400},[1350,3942,3943],{"class":1645},"      }\n",[1350,3945,3946],{"class":1352,"line":1406},[1350,3947,2177],{"class":1645},[1350,3949,3950],{"class":1352,"line":1412},[1350,3951,3952],{"class":1645},"  }\n",[1350,3954,3955],{"class":1352,"line":1418},[1350,3956,1826],{"class":1645},[763,3958,3960],{"id":3959},"step-4-authenticate-with-epidemic-sound","Step 4: Authenticate with Epidemic Sound",[746,3962,3963],{},"On first use, you'll be prompted to authenticate via OAuth:",[3076,3965,3966,3969,3972,3975],{},[1230,3967,3968],{},"Claude will open a browser window",[1230,3970,3971],{},"Log in to your Epidemic Sound account",[1230,3973,3974],{},"Authorize the MCP connection",[1230,3976,3977],{},"Return to Claude - you're connected!",[763,3979,3981],{"id":3980},"step-5-verify-the-connection","Step 5: Verify the Connection",[1342,3983,3985],{"className":3628,"code":3984,"language":3630,"meta":1033,"style":1033},"# List all configured MCP servers\nclaude mcp list\n\n# Check the Epidemic Sound connection\nclaude mcp get epidemicsound\n",[1234,3986,3987,3992,4001,4005,4010],{"__ignoreMap":1033},[1350,3988,3989],{"class":1352,"line":1353},[1350,3990,3991],{"class":1665},"# List all configured MCP servers\n",[1350,3993,3994,3996,3998],{"class":1352,"line":1034},[1350,3995,3718],{"class":3642},[1350,3997,3746],{"class":1652},[1350,3999,4000],{"class":1652}," list\n",[1350,4002,4003],{"class":1352,"line":1039},[1350,4004,1637],{"emptyLinePlaceholder":1058},[1350,4006,4007],{"class":1352,"line":1370},[1350,4008,4009],{"class":1665},"# Check the Epidemic Sound connection\n",[1350,4011,4012,4014,4016,4019],{"class":1352,"line":1376},[1350,4013,3718],{"class":3642},[1350,4015,3746],{"class":1652},[1350,4017,4018],{"class":1652}," get",[1350,4020,4021],{"class":1652}," epidemicsound\n",[746,4023,4024],{},"You should see the Epidemic Sound server listed and connected.",[763,4026,4028],{"id":4027},"step-6-restart-claude","Step 6: Restart Claude",[746,4030,4031],{},"After configuration, restart Claude Desktop or your Claude Code session:",[1342,4033,4035],{"className":3628,"code":4034,"language":3630,"meta":1033,"style":1033},"# If using Claude Code, simply start a new session\nclaude\n",[1234,4036,4037,4042],{"__ignoreMap":1033},[1350,4038,4039],{"class":1352,"line":1353},[1350,4040,4041],{"class":1665},"# If using Claude Code, simply start a new session\n",[1350,4043,4044],{"class":1352,"line":1034},[1350,4045,4046],{"class":3642},"claude\n",[1105,4048],{},[753,4050,4052],{"id":4051},"using-epidemic-sound-mcp-in-practice","Using Epidemic Sound MCP in Practice",[852,4054,4055],{},[746,4056,4057],{},"The example conversations below show illustrative responses. Actual results from the Epidemic Sound MCP will vary based on the current catalog and your search terms.",[763,4059,4061],{"id":4060},"basic-music-search","Basic Music Search",[746,4063,4064],{},"Once connected, you can search for music using natural language:",[1342,4066,4069],{"className":4067,"code":4068,"language":3554},[3552],"You: Find energetic rock tracks for a racing game\n\nClaude: I found several tracks that would work well for a racing game:\n\n1. \"Full Throttle\" by Horizon Blue\n   - Genre: Rock/Alternative\n   - BPM: 140\n   - Duration: 3:24\n   - Mood: Energetic, Driving\n\n2. \"Speed Demon\" by Electric Avenue\n   - Genre: Hard Rock\n   - BPM: 155\n   - Duration: 2:58\n   - Mood: Intense, Powerful\n\n[Preview links and download options...]\n",[1234,4070,4068],{"__ignoreMap":1033},[763,4072,4074],{"id":4073},"contextual-search-for-game-development","Contextual Search for Game Development",[1342,4076,4079],{"className":4077,"code":4078,"language":3554},[3552],"You: I'm building a memory matching game for kids. I need 8 distinct\nshort musical clips that are easy to remember and differentiate.\nEach should be about 5-10 seconds, family-friendly, and cover\ndifferent musical styles.\n\nClaude: Here are 8 distinct clips perfect for a memory game:\n\n1. Classical Piano Melody - Gentle, recognizable tune\n2. Upbeat Ukulele Strum - Happy, playful feel\n3. Jazz Saxophone Riff - Smooth, memorable hook\n4. Electronic Synth Pulse - Modern, catchy beat\n5. Acoustic Guitar Fingerpicking - Warm, folk-style\n6. Orchestral Strings Swell - Dramatic, cinematic\n7. Reggae Bass Groove - Relaxed, rhythmic pattern\n8. Flute & Harp Fantasy - Magical, whimsical tone\n\nEach clip has been selected for maximum memorability and distinction...\n",[1234,4080,4078],{"__ignoreMap":1033},[763,4082,4084],{"id":4083},"finding-sound-effects","Finding Sound Effects",[1342,4086,4089],{"className":4087,"code":4088,"language":3554},[3552],"You: Search for UI sound effects - button clicks, success chimes,\nand error sounds for a mobile app\n\nClaude: Found these UI sound effects:\n\nButton Clicks:\n- \"Soft Click\" - Subtle, modern feel\n- \"Mechanical Press\" - Tactile feedback sound\n- \"Digital Tap\" - Clean, minimalist\n\nSuccess Sounds:\n- \"Achievement Unlocked\" - Celebratory chime\n- \"Level Complete\" - Uplifting melody\n- \"Coin Collect\" - Classic game feedback\n\nError/Alert Sounds:\n- \"Gentle Warning\" - Non-intrusive alert\n- \"Soft Buzz\" - Subtle error indicator\n- \"Try Again\" - Encouraging tone\n",[1234,4090,4088],{"__ignoreMap":1033},[1105,4092],{},[753,4094,4096],{"id":4095},"integration-examples","Integration Examples",[763,4098,4100],{"id":4099},"example-1-python-backend-with-fastapi","Example 1: Python Backend with FastAPI",[746,4102,4103],{},"After finding tracks via Claude, integrate them into your Python project:",[1342,4105,4107],{"className":1619,"code":4106,"language":1621,"meta":1033,"style":1033},"# music_config.py\n\nfrom dataclasses import dataclass\nfrom datetime import timedelta\nfrom typing import List\n\n\n@dataclass\nclass MusicTrack:\n    id: str\n    title: str\n    artist: str\n    file_path: str\n    duration: timedelta\n\n\n# Tracks discovered via Epidemic Sound MCP\nGAME_MUSIC_TRACKS: List[MusicTrack] = [\n    MusicTrack(\n        id=\"track_001\",\n        title=\"Puzzle Master\",\n        artist=\"Ambient Keys\",\n        file_path=\"assets/sounds/music/puzzle_master.mp3\",\n        duration=timedelta(seconds=12),\n    ),\n    MusicTrack(\n        id=\"track_002\",\n        title=\"Mind Games\",\n        artist=\"Electronic Dreams\",\n        file_path=\"assets/sounds/music/mind_games.mp3\",\n        duration=timedelta(seconds=10),\n    ),\n    # ... more tracks\n]\n\n\n# FastAPI endpoint example\nfrom fastapi import FastAPI\nfrom fastapi.responses import FileResponse\n\napp = FastAPI()\n\n\n@app.get(\"/api/tracks\")\nasync def get_tracks():\n    return [\n        {\n            \"id\": track.id,\n            \"title\": track.title,\n            \"artist\": track.artist,\n            \"duration_seconds\": track.duration.total_seconds(),\n        }\n        for track in GAME_MUSIC_TRACKS\n    ]\n\n\n@app.get(\"/api/tracks/{track_id}/stream\")\nasync def stream_track(track_id: str):\n    track = next((t for t in GAME_MUSIC_TRACKS if t.id == track_id), None)\n    if track:\n        return FileResponse(track.file_path, media_type=\"audio/mpeg\")\n    return {\"error\": \"Track not found\"}\n",[1234,4108,4109,4114,4118,4131,4143,4155,4159,4163,4171,4182,4192,4201,4210,4219,4228,4232,4236,4241,4265,4272,4288,4304,4320,4336,4359,4364,4370,4385,4400,4415,4430,4449,4453,4458,4462,4466,4470,4475,4487,4504,4508,4521,4526,4531,4555,4570,4578,4584,4605,4624,4644,4671,4677,4692,4698,4703,4708,4736,4759,4808,4818,4853],{"__ignoreMap":1033},[1350,4110,4111],{"class":1352,"line":1353},[1350,4112,4113],{"class":1665},"# music_config.py\n",[1350,4115,4116],{"class":1352,"line":1034},[1350,4117,1637],{"emptyLinePlaceholder":1058},[1350,4119,4120,4123,4126,4128],{"class":1352,"line":1039},[1350,4121,4122],{"class":1628},"from",[1350,4124,4125],{"class":1356}," dataclasses ",[1350,4127,1629],{"class":1628},[1350,4129,4130],{"class":1356}," dataclass\n",[1350,4132,4133,4135,4138,4140],{"class":1352,"line":1370},[1350,4134,4122],{"class":1628},[1350,4136,4137],{"class":1356}," datetime ",[1350,4139,1629],{"class":1628},[1350,4141,4142],{"class":1356}," timedelta\n",[1350,4144,4145,4147,4150,4152],{"class":1352,"line":1376},[1350,4146,4122],{"class":1628},[1350,4148,4149],{"class":1356}," typing ",[1350,4151,1629],{"class":1628},[1350,4153,4154],{"class":1356}," List\n",[1350,4156,4157],{"class":1352,"line":1382},[1350,4158,1637],{"emptyLinePlaceholder":1058},[1350,4160,4161],{"class":1352,"line":1388},[1350,4162,1637],{"emptyLinePlaceholder":1058},[1350,4164,4165,4168],{"class":1352,"line":1394},[1350,4166,4167],{"class":1645},"@",[1350,4169,4170],{"class":1682},"dataclass\n",[1350,4172,4173,4176,4179],{"class":1352,"line":1400},[1350,4174,4175],{"class":1689},"class",[1350,4177,4178],{"class":3642}," MusicTrack",[1350,4180,4181],{"class":1645},":\n",[1350,4183,4184,4187,4189],{"class":1352,"line":1406},[1350,4185,4186],{"class":1682},"    id",[1350,4188,1729],{"class":1645},[1350,4190,4191],{"class":3642}," str\n",[1350,4193,4194,4197,4199],{"class":1352,"line":1412},[1350,4195,4196],{"class":1356},"    title",[1350,4198,1729],{"class":1645},[1350,4200,4191],{"class":3642},[1350,4202,4203,4206,4208],{"class":1352,"line":1418},[1350,4204,4205],{"class":1356},"    artist",[1350,4207,1729],{"class":1645},[1350,4209,4191],{"class":3642},[1350,4211,4212,4215,4217],{"class":1352,"line":1423},[1350,4213,4214],{"class":1356},"    file_path",[1350,4216,1729],{"class":1645},[1350,4218,4191],{"class":3642},[1350,4220,4221,4224,4226],{"class":1352,"line":1429},[1350,4222,4223],{"class":1356},"    duration",[1350,4225,1729],{"class":1645},[1350,4227,4142],{"class":1356},[1350,4229,4230],{"class":1352,"line":1435},[1350,4231,1637],{"emptyLinePlaceholder":1058},[1350,4233,4234],{"class":1352,"line":1441},[1350,4235,1637],{"emptyLinePlaceholder":1058},[1350,4237,4238],{"class":1352,"line":1447},[1350,4239,4240],{"class":1665},"# Tracks discovered via Epidemic Sound MCP\n",[1350,4242,4243,4246,4248,4251,4254,4257,4260,4263],{"class":1352,"line":1453},[1350,4244,4245],{"class":1356},"GAME_MUSIC_TRACKS",[1350,4247,1729],{"class":1645},[1350,4249,4250],{"class":1356}," List",[1350,4252,4253],{"class":1645},"[",[1350,4255,4256],{"class":1356},"MusicTrack",[1350,4258,4259],{"class":1645},"]",[1350,4261,4262],{"class":1645}," =",[1350,4264,1933],{"class":1645},[1350,4266,4267,4270],{"class":1352,"line":1459},[1350,4268,4269],{"class":1682},"    MusicTrack",[1350,4271,2236],{"class":1645},[1350,4273,4274,4277,4279,4281,4284,4286],{"class":1352,"line":1464},[1350,4275,4276],{"class":1712},"        id",[1350,4278,1646],{"class":1645},[1350,4280,1693],{"class":1645},[1350,4282,4283],{"class":1652},"track_001",[1350,4285,1693],{"class":1645},[1350,4287,1739],{"class":1645},[1350,4289,4290,4293,4295,4297,4300,4302],{"class":1352,"line":1470},[1350,4291,4292],{"class":1712},"        title",[1350,4294,1646],{"class":1645},[1350,4296,1693],{"class":1645},[1350,4298,4299],{"class":1652},"Puzzle Master",[1350,4301,1693],{"class":1645},[1350,4303,1739],{"class":1645},[1350,4305,4306,4309,4311,4313,4316,4318],{"class":1352,"line":1476},[1350,4307,4308],{"class":1712},"        artist",[1350,4310,1646],{"class":1645},[1350,4312,1693],{"class":1645},[1350,4314,4315],{"class":1652},"Ambient Keys",[1350,4317,1693],{"class":1645},[1350,4319,1739],{"class":1645},[1350,4321,4322,4325,4327,4329,4332,4334],{"class":1352,"line":1482},[1350,4323,4324],{"class":1712},"        file_path",[1350,4326,1646],{"class":1645},[1350,4328,1693],{"class":1645},[1350,4330,4331],{"class":1652},"assets/sounds/music/puzzle_master.mp3",[1350,4333,1693],{"class":1645},[1350,4335,1739],{"class":1645},[1350,4337,4338,4341,4343,4346,4348,4351,4353,4356],{"class":1352,"line":2074},[1350,4339,4340],{"class":1712},"        duration",[1350,4342,1646],{"class":1645},[1350,4344,4345],{"class":1682},"timedelta",[1350,4347,1686],{"class":1645},[1350,4349,4350],{"class":1712},"seconds",[1350,4352,1646],{"class":1645},[1350,4354,4355],{"class":1696},"12",[1350,4357,4358],{"class":1645},"),\n",[1350,4360,4361],{"class":1352,"line":2096},[1350,4362,4363],{"class":1645},"    ),\n",[1350,4365,4366,4368],{"class":1352,"line":2174},[1350,4367,4269],{"class":1682},[1350,4369,2236],{"class":1645},[1350,4371,4372,4374,4376,4378,4381,4383],{"class":1352,"line":2180},[1350,4373,4276],{"class":1712},[1350,4375,1646],{"class":1645},[1350,4377,1693],{"class":1645},[1350,4379,4380],{"class":1652},"track_002",[1350,4382,1693],{"class":1645},[1350,4384,1739],{"class":1645},[1350,4386,4387,4389,4391,4393,4396,4398],{"class":1352,"line":2185},[1350,4388,4292],{"class":1712},[1350,4390,1646],{"class":1645},[1350,4392,1693],{"class":1645},[1350,4394,4395],{"class":1652},"Mind Games",[1350,4397,1693],{"class":1645},[1350,4399,1739],{"class":1645},[1350,4401,4402,4404,4406,4408,4411,4413],{"class":1352,"line":2211},[1350,4403,4308],{"class":1712},[1350,4405,1646],{"class":1645},[1350,4407,1693],{"class":1645},[1350,4409,4410],{"class":1652},"Electronic Dreams",[1350,4412,1693],{"class":1645},[1350,4414,1739],{"class":1645},[1350,4416,4417,4419,4421,4423,4426,4428],{"class":1352,"line":2216},[1350,4418,4324],{"class":1712},[1350,4420,1646],{"class":1645},[1350,4422,1693],{"class":1645},[1350,4424,4425],{"class":1652},"assets/sounds/music/mind_games.mp3",[1350,4427,1693],{"class":1645},[1350,4429,1739],{"class":1645},[1350,4431,4432,4434,4436,4438,4440,4442,4444,4447],{"class":1352,"line":2222},[1350,4433,4340],{"class":1712},[1350,4435,1646],{"class":1645},[1350,4437,4345],{"class":1682},[1350,4439,1686],{"class":1645},[1350,4441,4350],{"class":1712},[1350,4443,1646],{"class":1645},[1350,4445,4446],{"class":1696},"10",[1350,4448,4358],{"class":1645},[1350,4450,4451],{"class":1352,"line":2239},[1350,4452,4363],{"class":1645},[1350,4454,4455],{"class":1352,"line":2268},[1350,4456,4457],{"class":1665},"    # ... more tracks\n",[1350,4459,4460],{"class":1352,"line":2279},[1350,4461,1790],{"class":1645},[1350,4463,4464],{"class":1352,"line":2285},[1350,4465,1637],{"emptyLinePlaceholder":1058},[1350,4467,4468],{"class":1352,"line":2290},[1350,4469,1637],{"emptyLinePlaceholder":1058},[1350,4471,4472],{"class":1352,"line":2296},[1350,4473,4474],{"class":1665},"# FastAPI endpoint example\n",[1350,4476,4477,4479,4482,4484],{"class":1352,"line":2321},[1350,4478,4122],{"class":1628},[1350,4480,4481],{"class":1356}," fastapi ",[1350,4483,1629],{"class":1628},[1350,4485,4486],{"class":1356}," FastAPI\n",[1350,4488,4489,4491,4494,4496,4499,4501],{"class":1352,"line":2348},[1350,4490,4122],{"class":1628},[1350,4492,4493],{"class":1356}," fastapi",[1350,4495,1679],{"class":1645},[1350,4497,4498],{"class":1356},"responses ",[1350,4500,1629],{"class":1628},[1350,4502,4503],{"class":1356}," FileResponse\n",[1350,4505,4506],{"class":1352,"line":2360},[1350,4507,1637],{"emptyLinePlaceholder":1058},[1350,4509,4510,4513,4515,4518],{"class":1352,"line":2383},[1350,4511,4512],{"class":1356},"app ",[1350,4514,1646],{"class":1645},[1350,4516,4517],{"class":1682}," FastAPI",[1350,4519,4520],{"class":1645},"()\n",[1350,4522,4524],{"class":1352,"line":4523},42,[1350,4525,1637],{"emptyLinePlaceholder":1058},[1350,4527,4529],{"class":1352,"line":4528},43,[1350,4530,1637],{"emptyLinePlaceholder":1058},[1350,4532,4534,4536,4539,4541,4544,4546,4548,4551,4553],{"class":1352,"line":4533},44,[1350,4535,4167],{"class":1645},[1350,4537,4538],{"class":1682},"app",[1350,4540,1679],{"class":1645},[1350,4542,4543],{"class":1682},"get",[1350,4545,1686],{"class":1645},[1350,4547,1693],{"class":1645},[1350,4549,4550],{"class":1652},"/api/tracks",[1350,4552,1693],{"class":1645},[1350,4554,2282],{"class":1645},[1350,4556,4558,4561,4564,4567],{"class":1352,"line":4557},45,[1350,4559,4560],{"class":1689},"async",[1350,4562,4563],{"class":1689}," def",[1350,4565,4566],{"class":1682}," get_tracks",[1350,4568,4569],{"class":1645},"():\n",[1350,4571,4573,4576],{"class":1352,"line":4572},46,[1350,4574,4575],{"class":1628},"    return",[1350,4577,1933],{"class":1645},[1350,4579,4581],{"class":1352,"line":4580},47,[1350,4582,4583],{"class":1645},"        {\n",[1350,4585,4587,4590,4592,4594,4596,4599,4601,4603],{"class":1352,"line":4586},48,[1350,4588,4589],{"class":1645},"            \"",[1350,4591,2204],{"class":1652},[1350,4593,1693],{"class":1645},[1350,4595,1729],{"class":1645},[1350,4597,4598],{"class":1356}," track",[1350,4600,1679],{"class":1645},[1350,4602,2204],{"class":2433},[1350,4604,1739],{"class":1645},[1350,4606,4608,4610,4612,4614,4616,4618,4620,4622],{"class":1352,"line":4607},49,[1350,4609,4589],{"class":1645},[1350,4611,1886],{"class":1652},[1350,4613,1693],{"class":1645},[1350,4615,1729],{"class":1645},[1350,4617,4598],{"class":1356},[1350,4619,1679],{"class":1645},[1350,4621,1886],{"class":2433},[1350,4623,1739],{"class":1645},[1350,4625,4627,4629,4632,4634,4636,4638,4640,4642],{"class":1352,"line":4626},50,[1350,4628,4589],{"class":1645},[1350,4630,4631],{"class":1652},"artist",[1350,4633,1693],{"class":1645},[1350,4635,1729],{"class":1645},[1350,4637,4598],{"class":1356},[1350,4639,1679],{"class":1645},[1350,4641,4631],{"class":2433},[1350,4643,1739],{"class":1645},[1350,4645,4647,4649,4652,4654,4656,4658,4660,4663,4665,4668],{"class":1352,"line":4646},51,[1350,4648,4589],{"class":1645},[1350,4650,4651],{"class":1652},"duration_seconds",[1350,4653,1693],{"class":1645},[1350,4655,1729],{"class":1645},[1350,4657,4598],{"class":1356},[1350,4659,1679],{"class":1645},[1350,4661,4662],{"class":2433},"duration",[1350,4664,1679],{"class":1645},[1350,4666,4667],{"class":1682},"total_seconds",[1350,4669,4670],{"class":1645},"(),\n",[1350,4672,4674],{"class":1352,"line":4673},52,[1350,4675,4676],{"class":1645},"        }\n",[1350,4678,4680,4683,4686,4689],{"class":1352,"line":4679},53,[1350,4681,4682],{"class":1628},"        for",[1350,4684,4685],{"class":1356}," track ",[1350,4687,4688],{"class":1628},"in",[1350,4690,4691],{"class":1356}," GAME_MUSIC_TRACKS\n",[1350,4693,4695],{"class":1352,"line":4694},54,[1350,4696,4697],{"class":1645},"    ]\n",[1350,4699,4701],{"class":1352,"line":4700},55,[1350,4702,1637],{"emptyLinePlaceholder":1058},[1350,4704,4706],{"class":1352,"line":4705},56,[1350,4707,1637],{"emptyLinePlaceholder":1058},[1350,4709,4711,4713,4715,4717,4719,4721,4723,4726,4729,4732,4734],{"class":1352,"line":4710},57,[1350,4712,4167],{"class":1645},[1350,4714,4538],{"class":1682},[1350,4716,1679],{"class":1645},[1350,4718,4543],{"class":1682},[1350,4720,1686],{"class":1645},[1350,4722,1693],{"class":1645},[1350,4724,4725],{"class":1652},"/api/tracks/",[1350,4727,4728],{"class":1696},"{track_id}",[1350,4730,4731],{"class":1652},"/stream",[1350,4733,1693],{"class":1645},[1350,4735,2282],{"class":1645},[1350,4737,4739,4741,4743,4746,4748,4751,4753,4756],{"class":1352,"line":4738},58,[1350,4740,4560],{"class":1689},[1350,4742,4563],{"class":1689},[1350,4744,4745],{"class":1682}," stream_track",[1350,4747,1686],{"class":1645},[1350,4749,4750],{"class":1712},"track_id",[1350,4752,1729],{"class":1645},[1350,4754,4755],{"class":3642}," str",[1350,4757,4758],{"class":1645},"):\n",[1350,4760,4762,4765,4767,4770,4773,4776,4779,4782,4784,4787,4789,4792,4794,4796,4799,4802,4805],{"class":1352,"line":4761},59,[1350,4763,4764],{"class":1356},"    track ",[1350,4766,1646],{"class":1645},[1350,4768,4769],{"class":1682}," next",[1350,4771,4772],{"class":1645},"((",[1350,4774,4775],{"class":1682},"t ",[1350,4777,4778],{"class":1628},"for",[1350,4780,4781],{"class":1682}," t ",[1350,4783,4688],{"class":1628},[1350,4785,4786],{"class":1682}," GAME_MUSIC_TRACKS ",[1350,4788,2299],{"class":1628},[1350,4790,4791],{"class":1682}," t",[1350,4793,1679],{"class":1645},[1350,4795,2204],{"class":2433},[1350,4797,4798],{"class":1645}," ==",[1350,4800,4801],{"class":1682}," track_id",[1350,4803,4804],{"class":1645},"),",[1350,4806,4807],{"class":1645}," None)\n",[1350,4809,4811,4814,4816],{"class":1352,"line":4810},60,[1350,4812,4813],{"class":1628},"    if",[1350,4815,4598],{"class":1356},[1350,4817,4181],{"class":1645},[1350,4819,4821,4824,4827,4829,4832,4834,4837,4839,4842,4844,4846,4849,4851],{"class":1352,"line":4820},61,[1350,4822,4823],{"class":1628},"        return",[1350,4825,4826],{"class":1682}," FileResponse",[1350,4828,1686],{"class":1645},[1350,4830,4831],{"class":1682},"track",[1350,4833,1679],{"class":1645},[1350,4835,4836],{"class":2433},"file_path",[1350,4838,1709],{"class":1645},[1350,4840,4841],{"class":1712}," media_type",[1350,4843,1646],{"class":1645},[1350,4845,1693],{"class":1645},[1350,4847,4848],{"class":1652},"audio/mpeg",[1350,4850,1693],{"class":1645},[1350,4852,2282],{"class":1645},[1350,4854,4856,4858,4860,4862,4865,4867,4869,4871,4874,4876],{"class":1352,"line":4855},62,[1350,4857,4575],{"class":1628},[1350,4859,1800],{"class":1645},[1350,4861,1693],{"class":1645},[1350,4863,4864],{"class":1652},"error",[1350,4866,1693],{"class":1645},[1350,4868,1729],{"class":1645},[1350,4870,1649],{"class":1645},[1350,4872,4873],{"class":1652},"Track not found",[1350,4875,1693],{"class":1645},[1350,4877,1826],{"class":1645},[763,4879,4881],{"id":4880},"example-2-vue-3-nuxt-ui-audio-player","Example 2: Vue 3 / Nuxt UI Audio Player",[1342,4883,4887],{"className":4884,"code":4885,"language":4886,"meta":1033,"style":1033},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- components/MusicPlayer.vue -->\n\u003Ctemplate>\n  \u003CUCard>\n    \u003Ctemplate #header>\n      \u003Cdiv class=\"flex items-center justify-between\">\n        \u003Ch3 class=\"text-lg font-semibold\">{{ currentTrack?.title }}\u003C/h3>\n        \u003CUBadge color=\"primary\">{{ currentTrack?.artist }}\u003C/UBadge>\n      \u003C/div>\n    \u003C/template>\n\n    \u003Cdiv class=\"space-y-4\">\n      \u003C!-- Progress bar -->\n      \u003CUProgress :value=\"progress\" />\n\n      \u003C!-- Controls -->\n      \u003Cdiv class=\"flex items-center justify-center gap-4\">\n        \u003CUButton\n          icon=\"i-heroicons-backward\"\n          color=\"gray\"\n          variant=\"ghost\"\n          @click=\"previousTrack\"\n        />\n        \u003CUButton\n          :icon=\"isPlaying ? 'i-heroicons-pause' : 'i-heroicons-play'\"\n          size=\"lg\"\n          @click=\"togglePlay\"\n        />\n        \u003CUButton\n          icon=\"i-heroicons-forward\"\n          color=\"gray\"\n          variant=\"ghost\"\n          @click=\"nextTrack\"\n        />\n      \u003C/div>\n\n      \u003C!-- Track list -->\n      \u003CUDivider />\n      \u003Cdiv class=\"space-y-2\">\n        \u003CUButton\n          v-for=\"track in tracks\"\n          :key=\"track.id\"\n          block\n          :color=\"currentTrack?.id === track.id ? 'primary' : 'gray'\"\n          variant=\"soft\"\n          @click=\"selectTrack(track)\"\n        >\n          {{ track.title }} - {{ track.artist }}\n        \u003C/UButton>\n      \u003C/div>\n    \u003C/div>\n  \u003C/UCard>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\ninterface MusicTrack {\n  id: string\n  title: string\n  artist: string\n  src: string\n  category: string\n  mood: string\n  bpm: number\n}\n\n// Tracks curated via Epidemic Sound MCP\nconst tracks: MusicTrack[] = [\n  {\n    id: 'es_001',\n    title: 'Digital Sunrise',\n    artist: 'Synth Wave Collective',\n    src: '/audio/music/digital_sunrise.mp3',\n    category: 'electronic',\n    mood: 'uplifting',\n    bpm: 120,\n  },\n  {\n    id: 'es_002',\n    title: 'Ocean Breeze',\n    artist: 'Ambient Nature',\n    src: '/audio/music/ocean_breeze.mp3',\n    category: 'ambient',\n    mood: 'calm',\n    bpm: 70,\n  },\n]\n\nconst currentTrack = ref\u003CMusicTrack | null>(tracks[0])\nconst isPlaying = ref(false)\nconst progress = ref(0)\nconst audio = ref\u003CHTMLAudioElement | null>(null)\n\nonMounted(() => {\n  audio.value = new Audio()\n  audio.value.addEventListener('timeupdate', updateProgress)\n  audio.value.addEventListener('ended', nextTrack)\n})\n\nfunction updateProgress() {\n  if (audio.value) {\n    progress.value = (audio.value.currentTime / audio.value.duration) * 100\n  }\n}\n\nfunction selectTrack(track: MusicTrack) {\n  currentTrack.value = track\n  if (audio.value) {\n    audio.value.src = track.src\n    audio.value.play()\n    isPlaying.value = true\n  }\n}\n\nfunction togglePlay() {\n  if (!audio.value || !currentTrack.value) return\n\n  if (isPlaying.value) {\n    audio.value.pause()\n  } else {\n    if (!audio.value.src) {\n      audio.value.src = currentTrack.value.src\n    }\n    audio.value.play()\n  }\n  isPlaying.value = !isPlaying.value\n}\n\nfunction nextTrack() {\n  const currentIndex = tracks.findIndex(t => t.id === currentTrack.value?.id)\n  const nextIndex = (currentIndex + 1) % tracks.length\n  selectTrack(tracks[nextIndex])\n}\n\nfunction previousTrack() {\n  const currentIndex = tracks.findIndex(t => t.id === currentTrack.value?.id)\n  const prevIndex = currentIndex === 0 ? tracks.length - 1 : currentIndex - 1\n  selectTrack(tracks[prevIndex])\n}\n\u003C/script>\n","vue",[1234,4888,4889,4894,4903,4912,4926,4946,4974,5004,5012,5020,5024,5043,5048,5070,5074,5079,5098,5105,5119,5133,5147,5161,5166,5172,5186,5200,5213,5217,5223,5236,5248,5260,5273,5277,5285,5289,5294,5303,5322,5328,5342,5356,5361,5375,5388,5401,5406,5411,5421,5429,5437,5445,5453,5457,5481,5490,5500,5509,5518,5527,5536,5545,5555,5560,5565,5571,5591,5597,5615,5631,5647,5664,5681,5698,5711,5717,5722,5738,5754,5770,5786,5802,5818,5830,5835,5840,5845,5879,5899,5917,5947,5952,5968,5989,6019,6048,6055,6060,6072,6093,6140,6145,6150,6155,6176,6191,6208,6232,6248,6263,6268,6273,6278,6290,6324,6329,6347,6363,6374,6397,6424,6429,6444,6449,6470,6475,6480,6491,6538,6571,6589,6594,6599,6611,6652,6694,6710,6715],{"__ignoreMap":1033},[1350,4890,4891],{"class":1352,"line":1353},[1350,4892,4893],{"class":1665},"\u003C!-- components/MusicPlayer.vue -->\n",[1350,4895,4896,4898,4901],{"class":1352,"line":1034},[1350,4897,2430],{"class":1645},[1350,4899,4900],{"class":2433},"template",[1350,4902,2437],{"class":1645},[1350,4904,4905,4907,4910],{"class":1352,"line":1039},[1350,4906,2442],{"class":1645},[1350,4908,4909],{"class":2433},"UCard",[1350,4911,2437],{"class":1645},[1350,4913,4914,4916,4918,4921,4924],{"class":1352,"line":1370},[1350,4915,2452],{"class":1645},[1350,4917,4900],{"class":2433},[1350,4919,4920],{"class":1645}," #",[1350,4922,4923],{"class":1689},"header",[1350,4925,2437],{"class":1645},[1350,4927,4928,4930,4932,4935,4937,4939,4942,4944],{"class":1352,"line":1376},[1350,4929,2547],{"class":1645},[1350,4931,768],{"class":2433},[1350,4933,4934],{"class":1689}," class",[1350,4936,1646],{"class":1645},[1350,4938,1693],{"class":1645},[1350,4940,4941],{"class":1652},"flex items-center justify-between",[1350,4943,1693],{"class":1645},[1350,4945,2437],{"class":1645},[1350,4947,4948,4950,4952,4954,4956,4958,4961,4963,4965,4968,4970,4972],{"class":1352,"line":1382},[1350,4949,2958],{"class":1645},[1350,4951,763],{"class":2433},[1350,4953,4934],{"class":1689},[1350,4955,1646],{"class":1645},[1350,4957,1693],{"class":1645},[1350,4959,4960],{"class":1652},"text-lg font-semibold",[1350,4962,1693],{"class":1645},[1350,4964,2458],{"class":1645},[1350,4966,4967],{"class":1356},"{{ currentTrack?.title }}",[1350,4969,2464],{"class":1645},[1350,4971,763],{"class":2433},[1350,4973,2437],{"class":1645},[1350,4975,4976,4978,4981,4984,4986,4988,4991,4993,4995,4998,5000,5002],{"class":1352,"line":1388},[1350,4977,2958],{"class":1645},[1350,4979,4980],{"class":2433},"UBadge",[1350,4982,4983],{"class":1689}," color",[1350,4985,1646],{"class":1645},[1350,4987,1693],{"class":1645},[1350,4989,4990],{"class":1652},"primary",[1350,4992,1693],{"class":1645},[1350,4994,2458],{"class":1645},[1350,4996,4997],{"class":1356},"{{ currentTrack?.artist }}",[1350,4999,2464],{"class":1645},[1350,5001,4980],{"class":2433},[1350,5003,2437],{"class":1645},[1350,5005,5006,5008,5010],{"class":1352,"line":1394},[1350,5007,2977],{"class":1645},[1350,5009,768],{"class":2433},[1350,5011,2437],{"class":1645},[1350,5013,5014,5016,5018],{"class":1352,"line":1400},[1350,5015,2566],{"class":1645},[1350,5017,4900],{"class":2433},[1350,5019,2437],{"class":1645},[1350,5021,5022],{"class":1352,"line":1406},[1350,5023,1637],{"emptyLinePlaceholder":1058},[1350,5025,5026,5028,5030,5032,5034,5036,5039,5041],{"class":1352,"line":1412},[1350,5027,2452],{"class":1645},[1350,5029,768],{"class":2433},[1350,5031,4934],{"class":1689},[1350,5033,1646],{"class":1645},[1350,5035,1693],{"class":1645},[1350,5037,5038],{"class":1652},"space-y-4",[1350,5040,1693],{"class":1645},[1350,5042,2437],{"class":1645},[1350,5044,5045],{"class":1352,"line":1418},[1350,5046,5047],{"class":1665},"      \u003C!-- Progress bar -->\n",[1350,5049,5050,5052,5055,5058,5060,5062,5065,5067],{"class":1352,"line":1423},[1350,5051,2547],{"class":1645},[1350,5053,5054],{"class":2433},"UProgress",[1350,5056,5057],{"class":1689}," :value",[1350,5059,1646],{"class":1645},[1350,5061,1693],{"class":1645},[1350,5063,5064],{"class":1652},"progress",[1350,5066,1693],{"class":1645},[1350,5068,5069],{"class":1645}," />\n",[1350,5071,5072],{"class":1352,"line":1429},[1350,5073,1637],{"emptyLinePlaceholder":1058},[1350,5075,5076],{"class":1352,"line":1435},[1350,5077,5078],{"class":1665},"      \u003C!-- Controls -->\n",[1350,5080,5081,5083,5085,5087,5089,5091,5094,5096],{"class":1352,"line":1441},[1350,5082,2547],{"class":1645},[1350,5084,768],{"class":2433},[1350,5086,4934],{"class":1689},[1350,5088,1646],{"class":1645},[1350,5090,1693],{"class":1645},[1350,5092,5093],{"class":1652},"flex items-center justify-center gap-4",[1350,5095,1693],{"class":1645},[1350,5097,2437],{"class":1645},[1350,5099,5100,5102],{"class":1352,"line":1447},[1350,5101,2958],{"class":1645},[1350,5103,5104],{"class":2433},"UButton\n",[1350,5106,5107,5110,5112,5114,5117],{"class":1352,"line":1453},[1350,5108,5109],{"class":1689},"          icon",[1350,5111,1646],{"class":1645},[1350,5113,1693],{"class":1645},[1350,5115,5116],{"class":1652},"i-heroicons-backward",[1350,5118,1656],{"class":1645},[1350,5120,5121,5124,5126,5128,5131],{"class":1352,"line":1459},[1350,5122,5123],{"class":1689},"          color",[1350,5125,1646],{"class":1645},[1350,5127,1693],{"class":1645},[1350,5129,5130],{"class":1652},"gray",[1350,5132,1656],{"class":1645},[1350,5134,5135,5138,5140,5142,5145],{"class":1352,"line":1464},[1350,5136,5137],{"class":1689},"          variant",[1350,5139,1646],{"class":1645},[1350,5141,1693],{"class":1645},[1350,5143,5144],{"class":1652},"ghost",[1350,5146,1656],{"class":1645},[1350,5148,5149,5152,5154,5156,5159],{"class":1352,"line":1470},[1350,5150,5151],{"class":1689},"          @click",[1350,5153,1646],{"class":1645},[1350,5155,1693],{"class":1645},[1350,5157,5158],{"class":1652},"previousTrack",[1350,5160,1656],{"class":1645},[1350,5162,5163],{"class":1352,"line":1476},[1350,5164,5165],{"class":1645},"        />\n",[1350,5167,5168,5170],{"class":1352,"line":1482},[1350,5169,2958],{"class":1645},[1350,5171,5104],{"class":2433},[1350,5173,5174,5177,5179,5181,5184],{"class":1352,"line":2074},[1350,5175,5176],{"class":1689},"          :icon",[1350,5178,1646],{"class":1645},[1350,5180,1693],{"class":1645},[1350,5182,5183],{"class":1652},"isPlaying ? 'i-heroicons-pause' : 'i-heroicons-play'",[1350,5185,1656],{"class":1645},[1350,5187,5188,5191,5193,5195,5198],{"class":1352,"line":2096},[1350,5189,5190],{"class":1689},"          size",[1350,5192,1646],{"class":1645},[1350,5194,1693],{"class":1645},[1350,5196,5197],{"class":1652},"lg",[1350,5199,1656],{"class":1645},[1350,5201,5202,5204,5206,5208,5211],{"class":1352,"line":2174},[1350,5203,5151],{"class":1689},[1350,5205,1646],{"class":1645},[1350,5207,1693],{"class":1645},[1350,5209,5210],{"class":1652},"togglePlay",[1350,5212,1656],{"class":1645},[1350,5214,5215],{"class":1352,"line":2180},[1350,5216,5165],{"class":1645},[1350,5218,5219,5221],{"class":1352,"line":2185},[1350,5220,2958],{"class":1645},[1350,5222,5104],{"class":2433},[1350,5224,5225,5227,5229,5231,5234],{"class":1352,"line":2211},[1350,5226,5109],{"class":1689},[1350,5228,1646],{"class":1645},[1350,5230,1693],{"class":1645},[1350,5232,5233],{"class":1652},"i-heroicons-forward",[1350,5235,1656],{"class":1645},[1350,5237,5238,5240,5242,5244,5246],{"class":1352,"line":2216},[1350,5239,5123],{"class":1689},[1350,5241,1646],{"class":1645},[1350,5243,1693],{"class":1645},[1350,5245,5130],{"class":1652},[1350,5247,1656],{"class":1645},[1350,5249,5250,5252,5254,5256,5258],{"class":1352,"line":2222},[1350,5251,5137],{"class":1689},[1350,5253,1646],{"class":1645},[1350,5255,1693],{"class":1645},[1350,5257,5144],{"class":1652},[1350,5259,1656],{"class":1645},[1350,5261,5262,5264,5266,5268,5271],{"class":1352,"line":2239},[1350,5263,5151],{"class":1689},[1350,5265,1646],{"class":1645},[1350,5267,1693],{"class":1645},[1350,5269,5270],{"class":1652},"nextTrack",[1350,5272,1656],{"class":1645},[1350,5274,5275],{"class":1352,"line":2268},[1350,5276,5165],{"class":1645},[1350,5278,5279,5281,5283],{"class":1352,"line":2279},[1350,5280,2977],{"class":1645},[1350,5282,768],{"class":2433},[1350,5284,2437],{"class":1645},[1350,5286,5287],{"class":1352,"line":2285},[1350,5288,1637],{"emptyLinePlaceholder":1058},[1350,5290,5291],{"class":1352,"line":2290},[1350,5292,5293],{"class":1665},"      \u003C!-- Track list -->\n",[1350,5295,5296,5298,5301],{"class":1352,"line":2296},[1350,5297,2547],{"class":1645},[1350,5299,5300],{"class":2433},"UDivider",[1350,5302,5069],{"class":1645},[1350,5304,5305,5307,5309,5311,5313,5315,5318,5320],{"class":1352,"line":2321},[1350,5306,2547],{"class":1645},[1350,5308,768],{"class":2433},[1350,5310,4934],{"class":1689},[1350,5312,1646],{"class":1645},[1350,5314,1693],{"class":1645},[1350,5316,5317],{"class":1652},"space-y-2",[1350,5319,1693],{"class":1645},[1350,5321,2437],{"class":1645},[1350,5323,5324,5326],{"class":1352,"line":2348},[1350,5325,2958],{"class":1645},[1350,5327,5104],{"class":2433},[1350,5329,5330,5333,5335,5337,5340],{"class":1352,"line":2360},[1350,5331,5332],{"class":1689},"          v-for",[1350,5334,1646],{"class":1645},[1350,5336,1693],{"class":1645},[1350,5338,5339],{"class":1652},"track in tracks",[1350,5341,1656],{"class":1645},[1350,5343,5344,5347,5349,5351,5354],{"class":1352,"line":2383},[1350,5345,5346],{"class":1689},"          :key",[1350,5348,1646],{"class":1645},[1350,5350,1693],{"class":1645},[1350,5352,5353],{"class":1652},"track.id",[1350,5355,1656],{"class":1645},[1350,5357,5358],{"class":1352,"line":4523},[1350,5359,5360],{"class":1689},"          block\n",[1350,5362,5363,5366,5368,5370,5373],{"class":1352,"line":4528},[1350,5364,5365],{"class":1689},"          :color",[1350,5367,1646],{"class":1645},[1350,5369,1693],{"class":1645},[1350,5371,5372],{"class":1652},"currentTrack?.id === track.id ? 'primary' : 'gray'",[1350,5374,1656],{"class":1645},[1350,5376,5377,5379,5381,5383,5386],{"class":1352,"line":4533},[1350,5378,5137],{"class":1689},[1350,5380,1646],{"class":1645},[1350,5382,1693],{"class":1645},[1350,5384,5385],{"class":1652},"soft",[1350,5387,1656],{"class":1645},[1350,5389,5390,5392,5394,5396,5399],{"class":1352,"line":4557},[1350,5391,5151],{"class":1689},[1350,5393,1646],{"class":1645},[1350,5395,1693],{"class":1645},[1350,5397,5398],{"class":1652},"selectTrack(track)",[1350,5400,1656],{"class":1645},[1350,5402,5403],{"class":1352,"line":4572},[1350,5404,5405],{"class":1645},"        >\n",[1350,5407,5408],{"class":1352,"line":4580},[1350,5409,5410],{"class":1356},"          {{ track.title }} - {{ track.artist }}\n",[1350,5412,5413,5416,5419],{"class":1352,"line":4586},[1350,5414,5415],{"class":1645},"        \u003C/",[1350,5417,5418],{"class":2433},"UButton",[1350,5420,2437],{"class":1645},[1350,5422,5423,5425,5427],{"class":1352,"line":4607},[1350,5424,2977],{"class":1645},[1350,5426,768],{"class":2433},[1350,5428,2437],{"class":1645},[1350,5430,5431,5433,5435],{"class":1352,"line":4626},[1350,5432,2566],{"class":1645},[1350,5434,768],{"class":2433},[1350,5436,2437],{"class":1645},[1350,5438,5439,5441,5443],{"class":1352,"line":4646},[1350,5440,2473],{"class":1645},[1350,5442,4909],{"class":2433},[1350,5444,2437],{"class":1645},[1350,5446,5447,5449,5451],{"class":1352,"line":4673},[1350,5448,2464],{"class":1645},[1350,5450,4900],{"class":2433},[1350,5452,2437],{"class":1645},[1350,5454,5455],{"class":1352,"line":4679},[1350,5456,1637],{"emptyLinePlaceholder":1058},[1350,5458,5459,5461,5464,5467,5470,5472,5474,5477,5479],{"class":1352,"line":4694},[1350,5460,2430],{"class":1645},[1350,5462,5463],{"class":2433},"script",[1350,5465,5466],{"class":1689}," setup",[1350,5468,5469],{"class":1689}," lang",[1350,5471,1646],{"class":1645},[1350,5473,1693],{"class":1645},[1350,5475,5476],{"class":1652},"ts",[1350,5478,1693],{"class":1645},[1350,5480,2437],{"class":1645},[1350,5482,5483,5486,5488],{"class":1352,"line":4700},[1350,5484,5485],{"class":1689},"interface",[1350,5487,4178],{"class":3642},[1350,5489,2071],{"class":1645},[1350,5491,5492,5495,5497],{"class":1352,"line":4705},[1350,5493,5494],{"class":2433},"  id",[1350,5496,1729],{"class":1645},[1350,5498,5499],{"class":3642}," string\n",[1350,5501,5502,5505,5507],{"class":1352,"line":4710},[1350,5503,5504],{"class":2433},"  title",[1350,5506,1729],{"class":1645},[1350,5508,5499],{"class":3642},[1350,5510,5511,5514,5516],{"class":1352,"line":4738},[1350,5512,5513],{"class":2433},"  artist",[1350,5515,1729],{"class":1645},[1350,5517,5499],{"class":3642},[1350,5519,5520,5523,5525],{"class":1352,"line":4761},[1350,5521,5522],{"class":2433},"  src",[1350,5524,1729],{"class":1645},[1350,5526,5499],{"class":3642},[1350,5528,5529,5532,5534],{"class":1352,"line":4810},[1350,5530,5531],{"class":2433},"  category",[1350,5533,1729],{"class":1645},[1350,5535,5499],{"class":3642},[1350,5537,5538,5541,5543],{"class":1352,"line":4820},[1350,5539,5540],{"class":2433},"  mood",[1350,5542,1729],{"class":1645},[1350,5544,5499],{"class":3642},[1350,5546,5547,5550,5552],{"class":1352,"line":4855},[1350,5548,5549],{"class":2433},"  bpm",[1350,5551,1729],{"class":1645},[1350,5553,5554],{"class":3642}," number\n",[1350,5556,5558],{"class":1352,"line":5557},63,[1350,5559,1826],{"class":1645},[1350,5561,5563],{"class":1352,"line":5562},64,[1350,5564,1637],{"emptyLinePlaceholder":1058},[1350,5566,5568],{"class":1352,"line":5567},65,[1350,5569,5570],{"class":1665},"// Tracks curated via Epidemic Sound MCP\n",[1350,5572,5574,5577,5580,5582,5584,5587,5589],{"class":1352,"line":5573},66,[1350,5575,5576],{"class":1689},"const",[1350,5578,5579],{"class":1356}," tracks",[1350,5581,1729],{"class":1645},[1350,5583,4178],{"class":3642},[1350,5585,5586],{"class":1356},"[] ",[1350,5588,1646],{"class":1645},[1350,5590,1933],{"class":1356},[1350,5592,5594],{"class":1352,"line":5593},67,[1350,5595,5596],{"class":1645},"  {\n",[1350,5598,5600,5602,5604,5607,5610,5613],{"class":1352,"line":5599},68,[1350,5601,4186],{"class":2433},[1350,5603,1729],{"class":1645},[1350,5605,5606],{"class":1645}," '",[1350,5608,5609],{"class":1652},"es_001",[1350,5611,5612],{"class":1645},"'",[1350,5614,1739],{"class":1645},[1350,5616,5618,5620,5622,5624,5627,5629],{"class":1352,"line":5617},69,[1350,5619,4196],{"class":2433},[1350,5621,1729],{"class":1645},[1350,5623,5606],{"class":1645},[1350,5625,5626],{"class":1652},"Digital Sunrise",[1350,5628,5612],{"class":1645},[1350,5630,1739],{"class":1645},[1350,5632,5634,5636,5638,5640,5643,5645],{"class":1352,"line":5633},70,[1350,5635,4205],{"class":2433},[1350,5637,1729],{"class":1645},[1350,5639,5606],{"class":1645},[1350,5641,5642],{"class":1652},"Synth Wave Collective",[1350,5644,5612],{"class":1645},[1350,5646,1739],{"class":1645},[1350,5648,5650,5653,5655,5657,5660,5662],{"class":1352,"line":5649},71,[1350,5651,5652],{"class":2433},"    src",[1350,5654,1729],{"class":1645},[1350,5656,5606],{"class":1645},[1350,5658,5659],{"class":1652},"/audio/music/digital_sunrise.mp3",[1350,5661,5612],{"class":1645},[1350,5663,1739],{"class":1645},[1350,5665,5667,5670,5672,5674,5677,5679],{"class":1352,"line":5666},72,[1350,5668,5669],{"class":2433},"    category",[1350,5671,1729],{"class":1645},[1350,5673,5606],{"class":1645},[1350,5675,5676],{"class":1652},"electronic",[1350,5678,5612],{"class":1645},[1350,5680,1739],{"class":1645},[1350,5682,5684,5687,5689,5691,5694,5696],{"class":1352,"line":5683},73,[1350,5685,5686],{"class":2433},"    mood",[1350,5688,1729],{"class":1645},[1350,5690,5606],{"class":1645},[1350,5692,5693],{"class":1652},"uplifting",[1350,5695,5612],{"class":1645},[1350,5697,1739],{"class":1645},[1350,5699,5701,5704,5706,5709],{"class":1352,"line":5700},74,[1350,5702,5703],{"class":2433},"    bpm",[1350,5705,1729],{"class":1645},[1350,5707,5708],{"class":1696}," 120",[1350,5710,1739],{"class":1645},[1350,5712,5714],{"class":1352,"line":5713},75,[1350,5715,5716],{"class":1645},"  },\n",[1350,5718,5720],{"class":1352,"line":5719},76,[1350,5721,5596],{"class":1645},[1350,5723,5725,5727,5729,5731,5734,5736],{"class":1352,"line":5724},77,[1350,5726,4186],{"class":2433},[1350,5728,1729],{"class":1645},[1350,5730,5606],{"class":1645},[1350,5732,5733],{"class":1652},"es_002",[1350,5735,5612],{"class":1645},[1350,5737,1739],{"class":1645},[1350,5739,5741,5743,5745,5747,5750,5752],{"class":1352,"line":5740},78,[1350,5742,4196],{"class":2433},[1350,5744,1729],{"class":1645},[1350,5746,5606],{"class":1645},[1350,5748,5749],{"class":1652},"Ocean Breeze",[1350,5751,5612],{"class":1645},[1350,5753,1739],{"class":1645},[1350,5755,5757,5759,5761,5763,5766,5768],{"class":1352,"line":5756},79,[1350,5758,4205],{"class":2433},[1350,5760,1729],{"class":1645},[1350,5762,5606],{"class":1645},[1350,5764,5765],{"class":1652},"Ambient Nature",[1350,5767,5612],{"class":1645},[1350,5769,1739],{"class":1645},[1350,5771,5773,5775,5777,5779,5782,5784],{"class":1352,"line":5772},80,[1350,5774,5652],{"class":2433},[1350,5776,1729],{"class":1645},[1350,5778,5606],{"class":1645},[1350,5780,5781],{"class":1652},"/audio/music/ocean_breeze.mp3",[1350,5783,5612],{"class":1645},[1350,5785,1739],{"class":1645},[1350,5787,5789,5791,5793,5795,5798,5800],{"class":1352,"line":5788},81,[1350,5790,5669],{"class":2433},[1350,5792,1729],{"class":1645},[1350,5794,5606],{"class":1645},[1350,5796,5797],{"class":1652},"ambient",[1350,5799,5612],{"class":1645},[1350,5801,1739],{"class":1645},[1350,5803,5805,5807,5809,5811,5814,5816],{"class":1352,"line":5804},82,[1350,5806,5686],{"class":2433},[1350,5808,1729],{"class":1645},[1350,5810,5606],{"class":1645},[1350,5812,5813],{"class":1652},"calm",[1350,5815,5612],{"class":1645},[1350,5817,1739],{"class":1645},[1350,5819,5821,5823,5825,5828],{"class":1352,"line":5820},83,[1350,5822,5703],{"class":2433},[1350,5824,1729],{"class":1645},[1350,5826,5827],{"class":1696}," 70",[1350,5829,1739],{"class":1645},[1350,5831,5833],{"class":1352,"line":5832},84,[1350,5834,5716],{"class":1645},[1350,5836,5838],{"class":1352,"line":5837},85,[1350,5839,1790],{"class":1356},[1350,5841,5843],{"class":1352,"line":5842},86,[1350,5844,1637],{"emptyLinePlaceholder":1058},[1350,5846,5848,5850,5853,5855,5858,5860,5862,5865,5868,5870,5873,5876],{"class":1352,"line":5847},87,[1350,5849,5576],{"class":1689},[1350,5851,5852],{"class":1356}," currentTrack ",[1350,5854,1646],{"class":1645},[1350,5856,5857],{"class":1682}," ref",[1350,5859,2430],{"class":1645},[1350,5861,4256],{"class":3642},[1350,5863,5864],{"class":1645}," |",[1350,5866,5867],{"class":3642}," null",[1350,5869,2458],{"class":1645},[1350,5871,5872],{"class":1356},"(tracks[",[1350,5874,5875],{"class":1696},"0",[1350,5877,5878],{"class":1356},"])\n",[1350,5880,5882,5884,5887,5889,5891,5893,5897],{"class":1352,"line":5881},88,[1350,5883,5576],{"class":1689},[1350,5885,5886],{"class":1356}," isPlaying ",[1350,5888,1646],{"class":1645},[1350,5890,5857],{"class":1682},[1350,5892,1686],{"class":1356},[1350,5894,5896],{"class":5895},"sfNiH","false",[1350,5898,2282],{"class":1356},[1350,5900,5902,5904,5907,5909,5911,5913,5915],{"class":1352,"line":5901},89,[1350,5903,5576],{"class":1689},[1350,5905,5906],{"class":1356}," progress ",[1350,5908,1646],{"class":1645},[1350,5910,5857],{"class":1682},[1350,5912,1686],{"class":1356},[1350,5914,5875],{"class":1696},[1350,5916,2282],{"class":1356},[1350,5918,5920,5922,5925,5927,5929,5931,5934,5936,5938,5940,5942,5945],{"class":1352,"line":5919},90,[1350,5921,5576],{"class":1689},[1350,5923,5924],{"class":1356}," audio ",[1350,5926,1646],{"class":1645},[1350,5928,5857],{"class":1682},[1350,5930,2430],{"class":1645},[1350,5932,5933],{"class":3642},"HTMLAudioElement",[1350,5935,5864],{"class":1645},[1350,5937,5867],{"class":3642},[1350,5939,2458],{"class":1645},[1350,5941,1686],{"class":1356},[1350,5943,5944],{"class":1645},"null",[1350,5946,2282],{"class":1356},[1350,5948,5950],{"class":1352,"line":5949},91,[1350,5951,1637],{"emptyLinePlaceholder":1058},[1350,5953,5955,5958,5960,5963,5966],{"class":1352,"line":5954},92,[1350,5956,5957],{"class":1682},"onMounted",[1350,5959,1686],{"class":1356},[1350,5961,5962],{"class":1645},"()",[1350,5964,5965],{"class":1689}," =>",[1350,5967,2071],{"class":1645},[1350,5969,5971,5974,5976,5979,5981,5984,5987],{"class":1352,"line":5970},93,[1350,5972,5973],{"class":1356},"  audio",[1350,5975,1679],{"class":1645},[1350,5977,5978],{"class":1356},"value",[1350,5980,4262],{"class":1645},[1350,5982,5983],{"class":1645}," new",[1350,5985,5986],{"class":1682}," Audio",[1350,5988,4520],{"class":2433},[1350,5990,5992,5994,5996,5998,6000,6003,6005,6007,6010,6012,6014,6017],{"class":1352,"line":5991},94,[1350,5993,5973],{"class":1356},[1350,5995,1679],{"class":1645},[1350,5997,5978],{"class":1356},[1350,5999,1679],{"class":1645},[1350,6001,6002],{"class":1682},"addEventListener",[1350,6004,1686],{"class":2433},[1350,6006,5612],{"class":1645},[1350,6008,6009],{"class":1652},"timeupdate",[1350,6011,5612],{"class":1645},[1350,6013,1709],{"class":1645},[1350,6015,6016],{"class":1356}," updateProgress",[1350,6018,2282],{"class":2433},[1350,6020,6022,6024,6026,6028,6030,6032,6034,6036,6039,6041,6043,6046],{"class":1352,"line":6021},95,[1350,6023,5973],{"class":1356},[1350,6025,1679],{"class":1645},[1350,6027,5978],{"class":1356},[1350,6029,1679],{"class":1645},[1350,6031,6002],{"class":1682},[1350,6033,1686],{"class":2433},[1350,6035,5612],{"class":1645},[1350,6037,6038],{"class":1652},"ended",[1350,6040,5612],{"class":1645},[1350,6042,1709],{"class":1645},[1350,6044,6045],{"class":1356}," nextTrack",[1350,6047,2282],{"class":2433},[1350,6049,6051,6053],{"class":1352,"line":6050},96,[1350,6052,1703],{"class":1645},[1350,6054,2282],{"class":1356},[1350,6056,6058],{"class":1352,"line":6057},97,[1350,6059,1637],{"emptyLinePlaceholder":1058},[1350,6061,6063,6066,6068,6070],{"class":1352,"line":6062},98,[1350,6064,6065],{"class":1689},"function",[1350,6067,6016],{"class":1682},[1350,6069,5962],{"class":1645},[1350,6071,2071],{"class":1645},[1350,6073,6075,6078,6081,6084,6086,6088,6091],{"class":1352,"line":6074},99,[1350,6076,6077],{"class":1628},"  if",[1350,6079,6080],{"class":2433}," (",[1350,6082,6083],{"class":1356},"audio",[1350,6085,1679],{"class":1645},[1350,6087,5978],{"class":1356},[1350,6089,6090],{"class":2433},") ",[1350,6092,3807],{"class":1645},[1350,6094,6096,6099,6101,6103,6105,6107,6109,6111,6113,6115,6118,6121,6124,6126,6128,6130,6132,6134,6137],{"class":1352,"line":6095},100,[1350,6097,6098],{"class":1356},"    progress",[1350,6100,1679],{"class":1645},[1350,6102,5978],{"class":1356},[1350,6104,4262],{"class":1645},[1350,6106,6080],{"class":2433},[1350,6108,6083],{"class":1356},[1350,6110,1679],{"class":1645},[1350,6112,5978],{"class":1356},[1350,6114,1679],{"class":1645},[1350,6116,6117],{"class":1356},"currentTime",[1350,6119,6120],{"class":1645}," /",[1350,6122,6123],{"class":1356}," audio",[1350,6125,1679],{"class":1645},[1350,6127,5978],{"class":1356},[1350,6129,1679],{"class":1645},[1350,6131,4662],{"class":1356},[1350,6133,6090],{"class":2433},[1350,6135,6136],{"class":1645},"*",[1350,6138,6139],{"class":1696}," 100\n",[1350,6141,6143],{"class":1352,"line":6142},101,[1350,6144,3952],{"class":1645},[1350,6146,6148],{"class":1352,"line":6147},102,[1350,6149,1826],{"class":1645},[1350,6151,6153],{"class":1352,"line":6152},103,[1350,6154,1637],{"emptyLinePlaceholder":1058},[1350,6156,6158,6160,6163,6165,6167,6169,6171,6174],{"class":1352,"line":6157},104,[1350,6159,6065],{"class":1689},[1350,6161,6162],{"class":1682}," selectTrack",[1350,6164,1686],{"class":1645},[1350,6166,4831],{"class":1712},[1350,6168,1729],{"class":1645},[1350,6170,4178],{"class":3642},[1350,6172,6173],{"class":1645},")",[1350,6175,2071],{"class":1645},[1350,6177,6179,6182,6184,6186,6188],{"class":1352,"line":6178},105,[1350,6180,6181],{"class":1356},"  currentTrack",[1350,6183,1679],{"class":1645},[1350,6185,5978],{"class":1356},[1350,6187,4262],{"class":1645},[1350,6189,6190],{"class":1356}," track\n",[1350,6192,6194,6196,6198,6200,6202,6204,6206],{"class":1352,"line":6193},106,[1350,6195,6077],{"class":1628},[1350,6197,6080],{"class":2433},[1350,6199,6083],{"class":1356},[1350,6201,1679],{"class":1645},[1350,6203,5978],{"class":1356},[1350,6205,6090],{"class":2433},[1350,6207,3807],{"class":1645},[1350,6209,6211,6214,6216,6218,6220,6223,6225,6227,6229],{"class":1352,"line":6210},107,[1350,6212,6213],{"class":1356},"    audio",[1350,6215,1679],{"class":1645},[1350,6217,5978],{"class":1356},[1350,6219,1679],{"class":1645},[1350,6221,6222],{"class":1356},"src",[1350,6224,4262],{"class":1645},[1350,6226,4598],{"class":1356},[1350,6228,1679],{"class":1645},[1350,6230,6231],{"class":1356},"src\n",[1350,6233,6235,6237,6239,6241,6243,6246],{"class":1352,"line":6234},108,[1350,6236,6213],{"class":1356},[1350,6238,1679],{"class":1645},[1350,6240,5978],{"class":1356},[1350,6242,1679],{"class":1645},[1350,6244,6245],{"class":1682},"play",[1350,6247,4520],{"class":2433},[1350,6249,6251,6254,6256,6258,6260],{"class":1352,"line":6250},109,[1350,6252,6253],{"class":1356},"    isPlaying",[1350,6255,1679],{"class":1645},[1350,6257,5978],{"class":1356},[1350,6259,4262],{"class":1645},[1350,6261,6262],{"class":5895}," true\n",[1350,6264,6266],{"class":1352,"line":6265},110,[1350,6267,3952],{"class":1645},[1350,6269,6271],{"class":1352,"line":6270},111,[1350,6272,1826],{"class":1645},[1350,6274,6276],{"class":1352,"line":6275},112,[1350,6277,1637],{"emptyLinePlaceholder":1058},[1350,6279,6281,6283,6286,6288],{"class":1352,"line":6280},113,[1350,6282,6065],{"class":1689},[1350,6284,6285],{"class":1682}," togglePlay",[1350,6287,5962],{"class":1645},[1350,6289,2071],{"class":1645},[1350,6291,6293,6295,6297,6300,6302,6304,6306,6309,6312,6315,6317,6319,6321],{"class":1352,"line":6292},114,[1350,6294,6077],{"class":1628},[1350,6296,6080],{"class":2433},[1350,6298,6299],{"class":1645},"!",[1350,6301,6083],{"class":1356},[1350,6303,1679],{"class":1645},[1350,6305,5978],{"class":1356},[1350,6307,6308],{"class":1645}," ||",[1350,6310,6311],{"class":1645}," !",[1350,6313,6314],{"class":1356},"currentTrack",[1350,6316,1679],{"class":1645},[1350,6318,5978],{"class":1356},[1350,6320,6090],{"class":2433},[1350,6322,6323],{"class":1628},"return\n",[1350,6325,6327],{"class":1352,"line":6326},115,[1350,6328,1637],{"emptyLinePlaceholder":1058},[1350,6330,6332,6334,6336,6339,6341,6343,6345],{"class":1352,"line":6331},116,[1350,6333,6077],{"class":1628},[1350,6335,6080],{"class":2433},[1350,6337,6338],{"class":1356},"isPlaying",[1350,6340,1679],{"class":1645},[1350,6342,5978],{"class":1356},[1350,6344,6090],{"class":2433},[1350,6346,3807],{"class":1645},[1350,6348,6350,6352,6354,6356,6358,6361],{"class":1352,"line":6349},117,[1350,6351,6213],{"class":1356},[1350,6353,1679],{"class":1645},[1350,6355,5978],{"class":1356},[1350,6357,1679],{"class":1645},[1350,6359,6360],{"class":1682},"pause",[1350,6362,4520],{"class":2433},[1350,6364,6366,6369,6372],{"class":1352,"line":6365},118,[1350,6367,6368],{"class":1645},"  }",[1350,6370,6371],{"class":1628}," else",[1350,6373,2071],{"class":1645},[1350,6375,6377,6379,6381,6383,6385,6387,6389,6391,6393,6395],{"class":1352,"line":6376},119,[1350,6378,4813],{"class":1628},[1350,6380,6080],{"class":2433},[1350,6382,6299],{"class":1645},[1350,6384,6083],{"class":1356},[1350,6386,1679],{"class":1645},[1350,6388,5978],{"class":1356},[1350,6390,1679],{"class":1645},[1350,6392,6222],{"class":1356},[1350,6394,6090],{"class":2433},[1350,6396,3807],{"class":1645},[1350,6398,6400,6403,6405,6407,6409,6411,6413,6416,6418,6420,6422],{"class":1352,"line":6399},120,[1350,6401,6402],{"class":1356},"      audio",[1350,6404,1679],{"class":1645},[1350,6406,5978],{"class":1356},[1350,6408,1679],{"class":1645},[1350,6410,6222],{"class":1356},[1350,6412,4262],{"class":1645},[1350,6414,6415],{"class":1356}," currentTrack",[1350,6417,1679],{"class":1645},[1350,6419,5978],{"class":1356},[1350,6421,1679],{"class":1645},[1350,6423,6231],{"class":1356},[1350,6425,6427],{"class":1352,"line":6426},121,[1350,6428,2177],{"class":1645},[1350,6430,6432,6434,6436,6438,6440,6442],{"class":1352,"line":6431},122,[1350,6433,6213],{"class":1356},[1350,6435,1679],{"class":1645},[1350,6437,5978],{"class":1356},[1350,6439,1679],{"class":1645},[1350,6441,6245],{"class":1682},[1350,6443,4520],{"class":2433},[1350,6445,6447],{"class":1352,"line":6446},123,[1350,6448,3952],{"class":1645},[1350,6450,6452,6455,6457,6459,6461,6463,6465,6467],{"class":1352,"line":6451},124,[1350,6453,6454],{"class":1356},"  isPlaying",[1350,6456,1679],{"class":1645},[1350,6458,5978],{"class":1356},[1350,6460,4262],{"class":1645},[1350,6462,6311],{"class":1645},[1350,6464,6338],{"class":1356},[1350,6466,1679],{"class":1645},[1350,6468,6469],{"class":1356},"value\n",[1350,6471,6473],{"class":1352,"line":6472},125,[1350,6474,1826],{"class":1645},[1350,6476,6478],{"class":1352,"line":6477},126,[1350,6479,1637],{"emptyLinePlaceholder":1058},[1350,6481,6483,6485,6487,6489],{"class":1352,"line":6482},127,[1350,6484,6065],{"class":1689},[1350,6486,6045],{"class":1682},[1350,6488,5962],{"class":1645},[1350,6490,2071],{"class":1645},[1350,6492,6494,6497,6500,6502,6504,6506,6509,6511,6514,6516,6518,6520,6522,6525,6527,6529,6531,6534,6536],{"class":1352,"line":6493},128,[1350,6495,6496],{"class":1689},"  const",[1350,6498,6499],{"class":1356}," currentIndex",[1350,6501,4262],{"class":1645},[1350,6503,5579],{"class":1356},[1350,6505,1679],{"class":1645},[1350,6507,6508],{"class":1682},"findIndex",[1350,6510,1686],{"class":2433},[1350,6512,6513],{"class":1712},"t",[1350,6515,5965],{"class":1689},[1350,6517,4791],{"class":1356},[1350,6519,1679],{"class":1645},[1350,6521,2204],{"class":1356},[1350,6523,6524],{"class":1645}," ===",[1350,6526,6415],{"class":1356},[1350,6528,1679],{"class":1645},[1350,6530,5978],{"class":1356},[1350,6532,6533],{"class":1645},"?.",[1350,6535,2204],{"class":1356},[1350,6537,2282],{"class":2433},[1350,6539,6541,6543,6546,6548,6550,6553,6556,6559,6561,6564,6566,6568],{"class":1352,"line":6540},129,[1350,6542,6496],{"class":1689},[1350,6544,6545],{"class":1356}," nextIndex",[1350,6547,4262],{"class":1645},[1350,6549,6080],{"class":2433},[1350,6551,6552],{"class":1356},"currentIndex",[1350,6554,6555],{"class":1645}," +",[1350,6557,6558],{"class":1696}," 1",[1350,6560,6090],{"class":2433},[1350,6562,6563],{"class":1645},"%",[1350,6565,5579],{"class":1356},[1350,6567,1679],{"class":1645},[1350,6569,6570],{"class":1356},"length\n",[1350,6572,6574,6577,6579,6582,6584,6587],{"class":1352,"line":6573},130,[1350,6575,6576],{"class":1682},"  selectTrack",[1350,6578,1686],{"class":2433},[1350,6580,6581],{"class":1356},"tracks",[1350,6583,4253],{"class":2433},[1350,6585,6586],{"class":1356},"nextIndex",[1350,6588,5878],{"class":2433},[1350,6590,6592],{"class":1352,"line":6591},131,[1350,6593,1826],{"class":1645},[1350,6595,6597],{"class":1352,"line":6596},132,[1350,6598,1637],{"emptyLinePlaceholder":1058},[1350,6600,6602,6604,6607,6609],{"class":1352,"line":6601},133,[1350,6603,6065],{"class":1689},[1350,6605,6606],{"class":1682}," previousTrack",[1350,6608,5962],{"class":1645},[1350,6610,2071],{"class":1645},[1350,6612,6614,6616,6618,6620,6622,6624,6626,6628,6630,6632,6634,6636,6638,6640,6642,6644,6646,6648,6650],{"class":1352,"line":6613},134,[1350,6615,6496],{"class":1689},[1350,6617,6499],{"class":1356},[1350,6619,4262],{"class":1645},[1350,6621,5579],{"class":1356},[1350,6623,1679],{"class":1645},[1350,6625,6508],{"class":1682},[1350,6627,1686],{"class":2433},[1350,6629,6513],{"class":1712},[1350,6631,5965],{"class":1689},[1350,6633,4791],{"class":1356},[1350,6635,1679],{"class":1645},[1350,6637,2204],{"class":1356},[1350,6639,6524],{"class":1645},[1350,6641,6415],{"class":1356},[1350,6643,1679],{"class":1645},[1350,6645,5978],{"class":1356},[1350,6647,6533],{"class":1645},[1350,6649,2204],{"class":1356},[1350,6651,2282],{"class":2433},[1350,6653,6655,6657,6660,6662,6664,6666,6669,6672,6674,6676,6679,6682,6684,6687,6689,6691],{"class":1352,"line":6654},135,[1350,6656,6496],{"class":1689},[1350,6658,6659],{"class":1356}," prevIndex",[1350,6661,4262],{"class":1645},[1350,6663,6499],{"class":1356},[1350,6665,6524],{"class":1645},[1350,6667,6668],{"class":1696}," 0",[1350,6670,6671],{"class":1645}," ?",[1350,6673,5579],{"class":1356},[1350,6675,1679],{"class":1645},[1350,6677,6678],{"class":1356},"length",[1350,6680,6681],{"class":1645}," -",[1350,6683,6558],{"class":1696},[1350,6685,6686],{"class":1645}," :",[1350,6688,6499],{"class":1356},[1350,6690,6681],{"class":1645},[1350,6692,6693],{"class":1696}," 1\n",[1350,6695,6697,6699,6701,6703,6705,6708],{"class":1352,"line":6696},136,[1350,6698,6576],{"class":1682},[1350,6700,1686],{"class":2433},[1350,6702,6581],{"class":1356},[1350,6704,4253],{"class":2433},[1350,6706,6707],{"class":1356},"prevIndex",[1350,6709,5878],{"class":2433},[1350,6711,6713],{"class":1352,"line":6712},137,[1350,6714,1826],{"class":1645},[1350,6716,6718,6720,6722],{"class":1352,"line":6717},138,[1350,6719,2464],{"class":1645},[1350,6721,5463],{"class":2433},[1350,6723,2437],{"class":1645},[763,6725,6727],{"id":6726},"example-3-typescript-music-service","Example 3: TypeScript Music Service",[1342,6729,6733],{"className":6730,"code":6731,"language":6732,"meta":1033,"style":1033},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// services/musicService.ts\n\ninterface MusicTrack {\n  id: string\n  title: string\n  artist: string\n  filePath: string\n  mood: string\n  bpm: number\n  duration: number\n}\n\ninterface SearchFilters {\n  mood?: string\n  minBpm?: number\n  maxBpm?: number\n  category?: string\n}\n\nclass MusicService {\n  private tracks: MusicTrack[] = []\n  private audioContext: AudioContext | null = null\n  private currentSource: AudioBufferSourceNode | null = null\n\n  constructor() {\n    // Tracks curated via Epidemic Sound MCP\n    this.tracks = [\n      {\n        id: 'puzzle_001',\n        title: 'Puzzle Master',\n        artist: 'Ambient Keys',\n        filePath: '/audio/puzzle_master.mp3',\n        mood: 'calm',\n        bpm: 85,\n        duration: 120,\n      },\n      {\n        id: 'action_001',\n        title: 'Speed Runner',\n        artist: 'Electric Dreams',\n        filePath: '/audio/speed_runner.mp3',\n        mood: 'energetic',\n        bpm: 140,\n        duration: 180,\n      },\n    ]\n  }\n\n  searchTracks(filters: SearchFilters): MusicTrack[] {\n    return this.tracks.filter(track => {\n      if (filters.mood && track.mood !== filters.mood) return false\n      if (filters.minBpm && track.bpm \u003C filters.minBpm) return false\n      if (filters.maxBpm && track.bpm > filters.maxBpm) return false\n      return true\n    })\n  }\n\n  async playTrack(trackId: string): Promise\u003Cvoid> {\n    const track = this.tracks.find(t => t.id === trackId)\n    if (!track) throw new Error('Track not found')\n\n    if (!this.audioContext) {\n      this.audioContext = new AudioContext()\n    }\n\n    // Stop current playback\n    this.stop()\n\n    const response = await fetch(track.filePath)\n    const arrayBuffer = await response.arrayBuffer()\n    const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer)\n\n    this.currentSource = this.audioContext.createBufferSource()\n    this.currentSource.buffer = audioBuffer\n    this.currentSource.connect(this.audioContext.destination)\n    this.currentSource.start()\n  }\n\n  stop(): void {\n    if (this.currentSource) {\n      this.currentSource.stop()\n      this.currentSource = null\n    }\n  }\n\n  getRandomTrack(mood?: string): MusicTrack {\n    const filtered = mood\n      ? this.tracks.filter(t => t.mood === mood)\n      : this.tracks\n    return filtered[Math.floor(Math.random() * filtered.length)]\n  }\n}\n\nexport const musicService = new MusicService()\n","typescript",[1234,6734,6735,6740,6744,6752,6760,6768,6776,6785,6793,6801,6810,6814,6818,6827,6836,6845,6854,6862,6866,6870,6879,6897,6918,6938,6942,6951,6956,6967,6972,6987,7001,7015,7031,7046,7058,7068,7073,7077,7092,7107,7122,7137,7152,7163,7174,7178,7182,7186,7190,7213,7235,7276,7313,7349,7356,7363,7367,7371,7403,7440,7470,7474,7490,7505,7509,7513,7518,7527,7531,7557,7577,7603,7607,7627,7643,7668,7681,7685,7689,7702,7716,7728,7738,7742,7746,7750,7769,7781,7813,7823,7862,7866,7870,7874],{"__ignoreMap":1033},[1350,6736,6737],{"class":1352,"line":1353},[1350,6738,6739],{"class":1665},"// services/musicService.ts\n",[1350,6741,6742],{"class":1352,"line":1034},[1350,6743,1637],{"emptyLinePlaceholder":1058},[1350,6745,6746,6748,6750],{"class":1352,"line":1039},[1350,6747,5485],{"class":1689},[1350,6749,4178],{"class":3642},[1350,6751,2071],{"class":1645},[1350,6753,6754,6756,6758],{"class":1352,"line":1370},[1350,6755,5494],{"class":2433},[1350,6757,1729],{"class":1645},[1350,6759,5499],{"class":3642},[1350,6761,6762,6764,6766],{"class":1352,"line":1376},[1350,6763,5504],{"class":2433},[1350,6765,1729],{"class":1645},[1350,6767,5499],{"class":3642},[1350,6769,6770,6772,6774],{"class":1352,"line":1382},[1350,6771,5513],{"class":2433},[1350,6773,1729],{"class":1645},[1350,6775,5499],{"class":3642},[1350,6777,6778,6781,6783],{"class":1352,"line":1388},[1350,6779,6780],{"class":2433},"  filePath",[1350,6782,1729],{"class":1645},[1350,6784,5499],{"class":3642},[1350,6786,6787,6789,6791],{"class":1352,"line":1394},[1350,6788,5540],{"class":2433},[1350,6790,1729],{"class":1645},[1350,6792,5499],{"class":3642},[1350,6794,6795,6797,6799],{"class":1352,"line":1400},[1350,6796,5549],{"class":2433},[1350,6798,1729],{"class":1645},[1350,6800,5554],{"class":3642},[1350,6802,6803,6806,6808],{"class":1352,"line":1406},[1350,6804,6805],{"class":2433},"  duration",[1350,6807,1729],{"class":1645},[1350,6809,5554],{"class":3642},[1350,6811,6812],{"class":1352,"line":1412},[1350,6813,1826],{"class":1645},[1350,6815,6816],{"class":1352,"line":1418},[1350,6817,1637],{"emptyLinePlaceholder":1058},[1350,6819,6820,6822,6825],{"class":1352,"line":1423},[1350,6821,5485],{"class":1689},[1350,6823,6824],{"class":3642}," SearchFilters",[1350,6826,2071],{"class":1645},[1350,6828,6829,6831,6834],{"class":1352,"line":1429},[1350,6830,5540],{"class":2433},[1350,6832,6833],{"class":1645},"?:",[1350,6835,5499],{"class":3642},[1350,6837,6838,6841,6843],{"class":1352,"line":1435},[1350,6839,6840],{"class":2433},"  minBpm",[1350,6842,6833],{"class":1645},[1350,6844,5554],{"class":3642},[1350,6846,6847,6850,6852],{"class":1352,"line":1441},[1350,6848,6849],{"class":2433},"  maxBpm",[1350,6851,6833],{"class":1645},[1350,6853,5554],{"class":3642},[1350,6855,6856,6858,6860],{"class":1352,"line":1447},[1350,6857,5531],{"class":2433},[1350,6859,6833],{"class":1645},[1350,6861,5499],{"class":3642},[1350,6863,6864],{"class":1352,"line":1453},[1350,6865,1826],{"class":1645},[1350,6867,6868],{"class":1352,"line":1459},[1350,6869,1637],{"emptyLinePlaceholder":1058},[1350,6871,6872,6874,6877],{"class":1352,"line":1464},[1350,6873,4175],{"class":1689},[1350,6875,6876],{"class":3642}," MusicService",[1350,6878,2071],{"class":1645},[1350,6880,6881,6884,6886,6888,6890,6892,6894],{"class":1352,"line":1470},[1350,6882,6883],{"class":1689},"  private",[1350,6885,5579],{"class":2433},[1350,6887,1729],{"class":1645},[1350,6889,4178],{"class":3642},[1350,6891,5586],{"class":1356},[1350,6893,1646],{"class":1645},[1350,6895,6896],{"class":1356}," []\n",[1350,6898,6899,6901,6904,6906,6909,6911,6913,6915],{"class":1352,"line":1476},[1350,6900,6883],{"class":1689},[1350,6902,6903],{"class":2433}," audioContext",[1350,6905,1729],{"class":1645},[1350,6907,6908],{"class":3642}," AudioContext",[1350,6910,5864],{"class":1645},[1350,6912,5867],{"class":3642},[1350,6914,4262],{"class":1645},[1350,6916,6917],{"class":1645}," null\n",[1350,6919,6920,6922,6925,6927,6930,6932,6934,6936],{"class":1352,"line":1482},[1350,6921,6883],{"class":1689},[1350,6923,6924],{"class":2433}," currentSource",[1350,6926,1729],{"class":1645},[1350,6928,6929],{"class":3642}," AudioBufferSourceNode",[1350,6931,5864],{"class":1645},[1350,6933,5867],{"class":3642},[1350,6935,4262],{"class":1645},[1350,6937,6917],{"class":1645},[1350,6939,6940],{"class":1352,"line":2074},[1350,6941,1637],{"emptyLinePlaceholder":1058},[1350,6943,6944,6947,6949],{"class":1352,"line":2096},[1350,6945,6946],{"class":1689},"  constructor",[1350,6948,5962],{"class":1645},[1350,6950,2071],{"class":1645},[1350,6952,6953],{"class":1352,"line":2174},[1350,6954,6955],{"class":1665},"    // Tracks curated via Epidemic Sound MCP\n",[1350,6957,6958,6961,6963,6965],{"class":1352,"line":2180},[1350,6959,6960],{"class":1645},"    this.",[1350,6962,6581],{"class":1356},[1350,6964,4262],{"class":1645},[1350,6966,1933],{"class":2433},[1350,6968,6969],{"class":1352,"line":2185},[1350,6970,6971],{"class":1645},"      {\n",[1350,6973,6974,6976,6978,6980,6983,6985],{"class":1352,"line":2211},[1350,6975,4276],{"class":2433},[1350,6977,1729],{"class":1645},[1350,6979,5606],{"class":1645},[1350,6981,6982],{"class":1652},"puzzle_001",[1350,6984,5612],{"class":1645},[1350,6986,1739],{"class":1645},[1350,6988,6989,6991,6993,6995,6997,6999],{"class":1352,"line":2216},[1350,6990,4292],{"class":2433},[1350,6992,1729],{"class":1645},[1350,6994,5606],{"class":1645},[1350,6996,4299],{"class":1652},[1350,6998,5612],{"class":1645},[1350,7000,1739],{"class":1645},[1350,7002,7003,7005,7007,7009,7011,7013],{"class":1352,"line":2222},[1350,7004,4308],{"class":2433},[1350,7006,1729],{"class":1645},[1350,7008,5606],{"class":1645},[1350,7010,4315],{"class":1652},[1350,7012,5612],{"class":1645},[1350,7014,1739],{"class":1645},[1350,7016,7017,7020,7022,7024,7027,7029],{"class":1352,"line":2239},[1350,7018,7019],{"class":2433},"        filePath",[1350,7021,1729],{"class":1645},[1350,7023,5606],{"class":1645},[1350,7025,7026],{"class":1652},"/audio/puzzle_master.mp3",[1350,7028,5612],{"class":1645},[1350,7030,1739],{"class":1645},[1350,7032,7033,7036,7038,7040,7042,7044],{"class":1352,"line":2268},[1350,7034,7035],{"class":2433},"        mood",[1350,7037,1729],{"class":1645},[1350,7039,5606],{"class":1645},[1350,7041,5813],{"class":1652},[1350,7043,5612],{"class":1645},[1350,7045,1739],{"class":1645},[1350,7047,7048,7051,7053,7056],{"class":1352,"line":2279},[1350,7049,7050],{"class":2433},"        bpm",[1350,7052,1729],{"class":1645},[1350,7054,7055],{"class":1696}," 85",[1350,7057,1739],{"class":1645},[1350,7059,7060,7062,7064,7066],{"class":1352,"line":2285},[1350,7061,4340],{"class":2433},[1350,7063,1729],{"class":1645},[1350,7065,5708],{"class":1696},[1350,7067,1739],{"class":1645},[1350,7069,7070],{"class":1352,"line":2290},[1350,7071,7072],{"class":1645},"      },\n",[1350,7074,7075],{"class":1352,"line":2296},[1350,7076,6971],{"class":1645},[1350,7078,7079,7081,7083,7085,7088,7090],{"class":1352,"line":2321},[1350,7080,4276],{"class":2433},[1350,7082,1729],{"class":1645},[1350,7084,5606],{"class":1645},[1350,7086,7087],{"class":1652},"action_001",[1350,7089,5612],{"class":1645},[1350,7091,1739],{"class":1645},[1350,7093,7094,7096,7098,7100,7103,7105],{"class":1352,"line":2348},[1350,7095,4292],{"class":2433},[1350,7097,1729],{"class":1645},[1350,7099,5606],{"class":1645},[1350,7101,7102],{"class":1652},"Speed Runner",[1350,7104,5612],{"class":1645},[1350,7106,1739],{"class":1645},[1350,7108,7109,7111,7113,7115,7118,7120],{"class":1352,"line":2360},[1350,7110,4308],{"class":2433},[1350,7112,1729],{"class":1645},[1350,7114,5606],{"class":1645},[1350,7116,7117],{"class":1652},"Electric Dreams",[1350,7119,5612],{"class":1645},[1350,7121,1739],{"class":1645},[1350,7123,7124,7126,7128,7130,7133,7135],{"class":1352,"line":2383},[1350,7125,7019],{"class":2433},[1350,7127,1729],{"class":1645},[1350,7129,5606],{"class":1645},[1350,7131,7132],{"class":1652},"/audio/speed_runner.mp3",[1350,7134,5612],{"class":1645},[1350,7136,1739],{"class":1645},[1350,7138,7139,7141,7143,7145,7148,7150],{"class":1352,"line":4523},[1350,7140,7035],{"class":2433},[1350,7142,1729],{"class":1645},[1350,7144,5606],{"class":1645},[1350,7146,7147],{"class":1652},"energetic",[1350,7149,5612],{"class":1645},[1350,7151,1739],{"class":1645},[1350,7153,7154,7156,7158,7161],{"class":1352,"line":4528},[1350,7155,7050],{"class":2433},[1350,7157,1729],{"class":1645},[1350,7159,7160],{"class":1696}," 140",[1350,7162,1739],{"class":1645},[1350,7164,7165,7167,7169,7172],{"class":1352,"line":4533},[1350,7166,4340],{"class":2433},[1350,7168,1729],{"class":1645},[1350,7170,7171],{"class":1696}," 180",[1350,7173,1739],{"class":1645},[1350,7175,7176],{"class":1352,"line":4557},[1350,7177,7072],{"class":1645},[1350,7179,7180],{"class":1352,"line":4572},[1350,7181,4697],{"class":2433},[1350,7183,7184],{"class":1352,"line":4580},[1350,7185,3952],{"class":1645},[1350,7187,7188],{"class":1352,"line":4586},[1350,7189,1637],{"emptyLinePlaceholder":1058},[1350,7191,7192,7195,7197,7200,7202,7204,7207,7209,7211],{"class":1352,"line":4607},[1350,7193,7194],{"class":2433},"  searchTracks",[1350,7196,1686],{"class":1645},[1350,7198,7199],{"class":1712},"filters",[1350,7201,1729],{"class":1645},[1350,7203,6824],{"class":3642},[1350,7205,7206],{"class":1645},"):",[1350,7208,4178],{"class":3642},[1350,7210,5586],{"class":1356},[1350,7212,3807],{"class":1645},[1350,7214,7215,7217,7220,7222,7224,7227,7229,7231,7233],{"class":1352,"line":4626},[1350,7216,4575],{"class":1628},[1350,7218,7219],{"class":1645}," this.",[1350,7221,6581],{"class":1356},[1350,7223,1679],{"class":1645},[1350,7225,7226],{"class":1682},"filter",[1350,7228,1686],{"class":2433},[1350,7230,4831],{"class":1712},[1350,7232,5965],{"class":1689},[1350,7234,2071],{"class":1645},[1350,7236,7237,7240,7242,7244,7246,7249,7252,7254,7256,7258,7261,7264,7266,7268,7270,7273],{"class":1352,"line":4646},[1350,7238,7239],{"class":1628},"      if",[1350,7241,6080],{"class":2433},[1350,7243,7199],{"class":1356},[1350,7245,1679],{"class":1645},[1350,7247,7248],{"class":1356},"mood",[1350,7250,7251],{"class":1645}," &&",[1350,7253,4598],{"class":1356},[1350,7255,1679],{"class":1645},[1350,7257,7248],{"class":1356},[1350,7259,7260],{"class":1645}," !==",[1350,7262,7263],{"class":1356}," filters",[1350,7265,1679],{"class":1645},[1350,7267,7248],{"class":1356},[1350,7269,6090],{"class":2433},[1350,7271,7272],{"class":1628},"return",[1350,7274,7275],{"class":5895}," false\n",[1350,7277,7278,7280,7282,7284,7286,7289,7291,7293,7295,7298,7301,7303,7305,7307,7309,7311],{"class":1352,"line":4673},[1350,7279,7239],{"class":1628},[1350,7281,6080],{"class":2433},[1350,7283,7199],{"class":1356},[1350,7285,1679],{"class":1645},[1350,7287,7288],{"class":1356},"minBpm",[1350,7290,7251],{"class":1645},[1350,7292,4598],{"class":1356},[1350,7294,1679],{"class":1645},[1350,7296,7297],{"class":1356},"bpm",[1350,7299,7300],{"class":1645}," \u003C",[1350,7302,7263],{"class":1356},[1350,7304,1679],{"class":1645},[1350,7306,7288],{"class":1356},[1350,7308,6090],{"class":2433},[1350,7310,7272],{"class":1628},[1350,7312,7275],{"class":5895},[1350,7314,7315,7317,7319,7321,7323,7326,7328,7330,7332,7334,7337,7339,7341,7343,7345,7347],{"class":1352,"line":4679},[1350,7316,7239],{"class":1628},[1350,7318,6080],{"class":2433},[1350,7320,7199],{"class":1356},[1350,7322,1679],{"class":1645},[1350,7324,7325],{"class":1356},"maxBpm",[1350,7327,7251],{"class":1645},[1350,7329,4598],{"class":1356},[1350,7331,1679],{"class":1645},[1350,7333,7297],{"class":1356},[1350,7335,7336],{"class":1645}," >",[1350,7338,7263],{"class":1356},[1350,7340,1679],{"class":1645},[1350,7342,7325],{"class":1356},[1350,7344,6090],{"class":2433},[1350,7346,7272],{"class":1628},[1350,7348,7275],{"class":5895},[1350,7350,7351,7354],{"class":1352,"line":4694},[1350,7352,7353],{"class":1628},"      return",[1350,7355,6262],{"class":5895},[1350,7357,7358,7361],{"class":1352,"line":4700},[1350,7359,7360],{"class":1645},"    }",[1350,7362,2282],{"class":2433},[1350,7364,7365],{"class":1352,"line":4705},[1350,7366,3952],{"class":1645},[1350,7368,7369],{"class":1352,"line":4710},[1350,7370,1637],{"emptyLinePlaceholder":1058},[1350,7372,7373,7376,7379,7381,7384,7386,7389,7391,7394,7396,7399,7401],{"class":1352,"line":4738},[1350,7374,7375],{"class":1689},"  async",[1350,7377,7378],{"class":2433}," playTrack",[1350,7380,1686],{"class":1645},[1350,7382,7383],{"class":1712},"trackId",[1350,7385,1729],{"class":1645},[1350,7387,7388],{"class":3642}," string",[1350,7390,7206],{"class":1645},[1350,7392,7393],{"class":3642}," Promise",[1350,7395,2430],{"class":1645},[1350,7397,7398],{"class":3642},"void",[1350,7400,2458],{"class":1645},[1350,7402,2071],{"class":1645},[1350,7404,7405,7408,7410,7412,7414,7416,7418,7421,7423,7425,7427,7429,7431,7433,7435,7438],{"class":1352,"line":4761},[1350,7406,7407],{"class":1689},"    const",[1350,7409,4598],{"class":1356},[1350,7411,4262],{"class":1645},[1350,7413,7219],{"class":1645},[1350,7415,6581],{"class":1356},[1350,7417,1679],{"class":1645},[1350,7419,7420],{"class":1682},"find",[1350,7422,1686],{"class":2433},[1350,7424,6513],{"class":1712},[1350,7426,5965],{"class":1689},[1350,7428,4791],{"class":1356},[1350,7430,1679],{"class":1645},[1350,7432,2204],{"class":1356},[1350,7434,6524],{"class":1645},[1350,7436,7437],{"class":1356}," trackId",[1350,7439,2282],{"class":2433},[1350,7441,7442,7444,7446,7448,7450,7452,7455,7457,7460,7462,7464,7466,7468],{"class":1352,"line":4810},[1350,7443,4813],{"class":1628},[1350,7445,6080],{"class":2433},[1350,7447,6299],{"class":1645},[1350,7449,4831],{"class":1356},[1350,7451,6090],{"class":2433},[1350,7453,7454],{"class":1628},"throw",[1350,7456,5983],{"class":1645},[1350,7458,7459],{"class":1682}," Error",[1350,7461,1686],{"class":2433},[1350,7463,5612],{"class":1645},[1350,7465,4873],{"class":1652},[1350,7467,5612],{"class":1645},[1350,7469,2282],{"class":2433},[1350,7471,7472],{"class":1352,"line":4820},[1350,7473,1637],{"emptyLinePlaceholder":1058},[1350,7475,7476,7478,7480,7483,7486,7488],{"class":1352,"line":4855},[1350,7477,4813],{"class":1628},[1350,7479,6080],{"class":2433},[1350,7481,7482],{"class":1645},"!this.",[1350,7484,7485],{"class":1356},"audioContext",[1350,7487,6090],{"class":2433},[1350,7489,3807],{"class":1645},[1350,7491,7492,7495,7497,7499,7501,7503],{"class":1352,"line":5557},[1350,7493,7494],{"class":1645},"      this.",[1350,7496,7485],{"class":1356},[1350,7498,4262],{"class":1645},[1350,7500,5983],{"class":1645},[1350,7502,6908],{"class":1682},[1350,7504,4520],{"class":2433},[1350,7506,7507],{"class":1352,"line":5562},[1350,7508,2177],{"class":1645},[1350,7510,7511],{"class":1352,"line":5567},[1350,7512,1637],{"emptyLinePlaceholder":1058},[1350,7514,7515],{"class":1352,"line":5573},[1350,7516,7517],{"class":1665},"    // Stop current playback\n",[1350,7519,7520,7522,7525],{"class":1352,"line":5593},[1350,7521,6960],{"class":1645},[1350,7523,7524],{"class":1682},"stop",[1350,7526,4520],{"class":2433},[1350,7528,7529],{"class":1352,"line":5599},[1350,7530,1637],{"emptyLinePlaceholder":1058},[1350,7532,7533,7535,7538,7540,7543,7546,7548,7550,7552,7555],{"class":1352,"line":5617},[1350,7534,7407],{"class":1689},[1350,7536,7537],{"class":1356}," response",[1350,7539,4262],{"class":1645},[1350,7541,7542],{"class":1628}," await",[1350,7544,7545],{"class":1682}," fetch",[1350,7547,1686],{"class":2433},[1350,7549,4831],{"class":1356},[1350,7551,1679],{"class":1645},[1350,7553,7554],{"class":1356},"filePath",[1350,7556,2282],{"class":2433},[1350,7558,7559,7561,7564,7566,7568,7570,7572,7575],{"class":1352,"line":5633},[1350,7560,7407],{"class":1689},[1350,7562,7563],{"class":1356}," arrayBuffer",[1350,7565,4262],{"class":1645},[1350,7567,7542],{"class":1628},[1350,7569,7537],{"class":1356},[1350,7571,1679],{"class":1645},[1350,7573,7574],{"class":1682},"arrayBuffer",[1350,7576,4520],{"class":2433},[1350,7578,7579,7581,7584,7586,7588,7590,7592,7594,7597,7599,7601],{"class":1352,"line":5649},[1350,7580,7407],{"class":1689},[1350,7582,7583],{"class":1356}," audioBuffer",[1350,7585,4262],{"class":1645},[1350,7587,7542],{"class":1628},[1350,7589,7219],{"class":1645},[1350,7591,7485],{"class":1356},[1350,7593,1679],{"class":1645},[1350,7595,7596],{"class":1682},"decodeAudioData",[1350,7598,1686],{"class":2433},[1350,7600,7574],{"class":1356},[1350,7602,2282],{"class":2433},[1350,7604,7605],{"class":1352,"line":5666},[1350,7606,1637],{"emptyLinePlaceholder":1058},[1350,7608,7609,7611,7614,7616,7618,7620,7622,7625],{"class":1352,"line":5683},[1350,7610,6960],{"class":1645},[1350,7612,7613],{"class":1356},"currentSource",[1350,7615,4262],{"class":1645},[1350,7617,7219],{"class":1645},[1350,7619,7485],{"class":1356},[1350,7621,1679],{"class":1645},[1350,7623,7624],{"class":1682},"createBufferSource",[1350,7626,4520],{"class":2433},[1350,7628,7629,7631,7633,7635,7638,7640],{"class":1352,"line":5700},[1350,7630,6960],{"class":1645},[1350,7632,7613],{"class":1356},[1350,7634,1679],{"class":1645},[1350,7636,7637],{"class":1356},"buffer",[1350,7639,4262],{"class":1645},[1350,7641,7642],{"class":1356}," audioBuffer\n",[1350,7644,7645,7647,7649,7651,7654,7656,7659,7661,7663,7666],{"class":1352,"line":5713},[1350,7646,6960],{"class":1645},[1350,7648,7613],{"class":1356},[1350,7650,1679],{"class":1645},[1350,7652,7653],{"class":1682},"connect",[1350,7655,1686],{"class":2433},[1350,7657,7658],{"class":1645},"this.",[1350,7660,7485],{"class":1356},[1350,7662,1679],{"class":1645},[1350,7664,7665],{"class":1356},"destination",[1350,7667,2282],{"class":2433},[1350,7669,7670,7672,7674,7676,7679],{"class":1352,"line":5719},[1350,7671,6960],{"class":1645},[1350,7673,7613],{"class":1356},[1350,7675,1679],{"class":1645},[1350,7677,7678],{"class":1682},"start",[1350,7680,4520],{"class":2433},[1350,7682,7683],{"class":1352,"line":5724},[1350,7684,3952],{"class":1645},[1350,7686,7687],{"class":1352,"line":5740},[1350,7688,1637],{"emptyLinePlaceholder":1058},[1350,7690,7691,7694,7697,7700],{"class":1352,"line":5756},[1350,7692,7693],{"class":2433},"  stop",[1350,7695,7696],{"class":1645},"():",[1350,7698,7699],{"class":3642}," void",[1350,7701,2071],{"class":1645},[1350,7703,7704,7706,7708,7710,7712,7714],{"class":1352,"line":5772},[1350,7705,4813],{"class":1628},[1350,7707,6080],{"class":2433},[1350,7709,7658],{"class":1645},[1350,7711,7613],{"class":1356},[1350,7713,6090],{"class":2433},[1350,7715,3807],{"class":1645},[1350,7717,7718,7720,7722,7724,7726],{"class":1352,"line":5788},[1350,7719,7494],{"class":1645},[1350,7721,7613],{"class":1356},[1350,7723,1679],{"class":1645},[1350,7725,7524],{"class":1682},[1350,7727,4520],{"class":2433},[1350,7729,7730,7732,7734,7736],{"class":1352,"line":5804},[1350,7731,7494],{"class":1645},[1350,7733,7613],{"class":1356},[1350,7735,4262],{"class":1645},[1350,7737,6917],{"class":1645},[1350,7739,7740],{"class":1352,"line":5820},[1350,7741,2177],{"class":1645},[1350,7743,7744],{"class":1352,"line":5832},[1350,7745,3952],{"class":1645},[1350,7747,7748],{"class":1352,"line":5837},[1350,7749,1637],{"emptyLinePlaceholder":1058},[1350,7751,7752,7755,7757,7759,7761,7763,7765,7767],{"class":1352,"line":5842},[1350,7753,7754],{"class":2433},"  getRandomTrack",[1350,7756,1686],{"class":1645},[1350,7758,7248],{"class":1712},[1350,7760,6833],{"class":1645},[1350,7762,7388],{"class":3642},[1350,7764,7206],{"class":1645},[1350,7766,4178],{"class":3642},[1350,7768,2071],{"class":1645},[1350,7770,7771,7773,7776,7778],{"class":1352,"line":5847},[1350,7772,7407],{"class":1689},[1350,7774,7775],{"class":1356}," filtered",[1350,7777,4262],{"class":1645},[1350,7779,7780],{"class":1356}," mood\n",[1350,7782,7783,7786,7788,7790,7792,7794,7796,7798,7800,7802,7804,7806,7808,7811],{"class":1352,"line":5881},[1350,7784,7785],{"class":1645},"      ?",[1350,7787,7219],{"class":1645},[1350,7789,6581],{"class":1356},[1350,7791,1679],{"class":1645},[1350,7793,7226],{"class":1682},[1350,7795,1686],{"class":2433},[1350,7797,6513],{"class":1712},[1350,7799,5965],{"class":1689},[1350,7801,4791],{"class":1356},[1350,7803,1679],{"class":1645},[1350,7805,7248],{"class":1356},[1350,7807,6524],{"class":1645},[1350,7809,7810],{"class":1356}," mood",[1350,7812,2282],{"class":2433},[1350,7814,7815,7818,7820],{"class":1352,"line":5901},[1350,7816,7817],{"class":1645},"      :",[1350,7819,7219],{"class":1645},[1350,7821,7822],{"class":1356},"tracks\n",[1350,7824,7825,7827,7829,7831,7834,7836,7839,7841,7843,7845,7848,7851,7853,7855,7857,7859],{"class":1352,"line":5919},[1350,7826,4575],{"class":1628},[1350,7828,7775],{"class":1356},[1350,7830,4253],{"class":2433},[1350,7832,7833],{"class":1356},"Math",[1350,7835,1679],{"class":1645},[1350,7837,7838],{"class":1682},"floor",[1350,7840,1686],{"class":2433},[1350,7842,7833],{"class":1356},[1350,7844,1679],{"class":1645},[1350,7846,7847],{"class":1682},"random",[1350,7849,7850],{"class":2433},"() ",[1350,7852,6136],{"class":1645},[1350,7854,7775],{"class":1356},[1350,7856,1679],{"class":1645},[1350,7858,6678],{"class":1356},[1350,7860,7861],{"class":2433},")]\n",[1350,7863,7864],{"class":1352,"line":5949},[1350,7865,3952],{"class":1645},[1350,7867,7868],{"class":1352,"line":5954},[1350,7869,1826],{"class":1645},[1350,7871,7872],{"class":1352,"line":5970},[1350,7873,1637],{"emptyLinePlaceholder":1058},[1350,7875,7876,7879,7882,7885,7887,7889,7891],{"class":1352,"line":5991},[1350,7877,7878],{"class":1628},"export",[1350,7880,7881],{"class":1689}," const",[1350,7883,7884],{"class":1356}," musicService ",[1350,7886,1646],{"class":1645},[1350,7888,5983],{"class":1645},[1350,7890,6876],{"class":1682},[1350,7892,4520],{"class":1356},[1105,7894],{},[753,7896,7898],{"id":7897},"advanced-tips-tricks","Advanced Tips & Tricks",[763,7900,7902],{"id":7901},"tip-1-be-specific-with-context","Tip 1: Be Specific with Context",[746,7904,7905],{},"The more context you provide, the better results you'll get:",[1342,7907,7910],{"className":7908,"code":7909,"language":3554},[3552],"\"Find music for my game\"\n\n\"Find calm, ambient music for the menu screen of a puzzle game\n targeted at adults. Should feel modern and minimalist,\n around 70-90 BPM, with soft synths or piano.\"\n",[1234,7911,7909],{"__ignoreMap":1033},[763,7913,7915],{"id":7914},"tip-2-use-mood-combinations","Tip 2: Use Mood Combinations",[746,7917,7918],{},"Combine multiple mood descriptors for nuanced results:",[1342,7920,7923],{"className":7921,"code":7922,"language":3554},[3552],"\"Find tracks that are both mysterious AND playful -\nlike exploring a whimsical haunted house\"\n",[1234,7924,7922],{"__ignoreMap":1033},[763,7926,7928],{"id":7927},"tip-3-request-alternatives","Tip 3: Request Alternatives",[746,7930,7931],{},"Ask for variations to build a cohesive soundtrack:",[1342,7933,7936],{"className":7934,"code":7935,"language":3554},[3552],"\"I like this track. Can you find 5 similar tracks\nthat would work well together as a playlist?\"\n",[1234,7937,7935],{"__ignoreMap":1033},[763,7939,7941],{"id":7940},"tip-4-specify-technical-requirements","Tip 4: Specify Technical Requirements",[746,7943,7944],{},"Include technical specs when needed:",[1342,7946,7949],{"className":7947,"code":7948,"language":3554},[3552],"\"Find tracks that loop seamlessly, are exactly 30 seconds long,\nand have no vocals - suitable for background gameplay music\"\n",[1234,7950,7948],{"__ignoreMap":1033},[1105,7952],{},[753,7954,7956],{"id":7955},"licensing-usage-rights","Licensing & Usage Rights",[746,7958,7959],{},"When using Epidemic Sound tracks in your projects, remember:",[1115,7961,7962,7972],{},[1118,7963,7964],{},[1121,7965,7966,7969],{},[1124,7967,7968],{},"Use Case",[1124,7970,7971],{},"License Required",[1131,7973,7974,7982,7990,7998],{},[1121,7975,7976,7979],{},[1136,7977,7978],{},"Personal projects",[1136,7980,7981],{},"Personal subscription",[1121,7983,7984,7987],{},[1136,7985,7986],{},"Commercial games",[1136,7988,7989],{},"Commercial license",[1121,7991,7992,7995],{},[1136,7993,7994],{},"YouTube/Streaming",[1136,7996,7997],{},"Creator subscription",[1121,7999,8000,8003],{},[1136,8001,8002],{},"Client work",[1136,8004,7989],{},[746,8006,8007,8008,8013],{},"Always verify your license covers your intended use case. Visit ",[1100,8009,8012],{"href":8010,"rel":8011},"https://www.epidemicsound.com/pricing/",[3369],"Epidemic Sound Licensing"," for details.",[1105,8015],{},[753,8017,8019],{"id":8018},"troubleshooting","Troubleshooting",[763,8021,8023],{"id":8022},"mcp-server-not-connecting","MCP Server Not Connecting",[1342,8025,8027],{"className":3628,"code":8026,"language":3630,"meta":1033,"style":1033},"# Debug MCP connections\nclaude --mcp-debug\n\n# Check if the server is properly configured\nclaude mcp list\n",[1234,8028,8029,8034,8041,8045,8050],{"__ignoreMap":1033},[1350,8030,8031],{"class":1352,"line":1353},[1350,8032,8033],{"class":1665},"# Debug MCP connections\n",[1350,8035,8036,8038],{"class":1352,"line":1034},[1350,8037,3718],{"class":3642},[1350,8039,8040],{"class":1652}," --mcp-debug\n",[1350,8042,8043],{"class":1352,"line":1039},[1350,8044,1637],{"emptyLinePlaceholder":1058},[1350,8046,8047],{"class":1352,"line":1370},[1350,8048,8049],{"class":1665},"# Check if the server is properly configured\n",[1350,8051,8052,8054,8056],{"class":1352,"line":1376},[1350,8053,3718],{"class":3642},[1350,8055,3746],{"class":1652},[1350,8057,4000],{"class":1652},[763,8059,8061],{"id":8060},"authentication-issues","Authentication Issues",[3076,8063,8064,8067,8070],{},[1230,8065,8066],{},"Clear cached credentials",[1230,8068,8069],{},"Re-authenticate via OAuth",[1230,8071,8072],{},"Check your Epidemic Sound subscription status",[763,8074,8076],{"id":8075},"no-results-returned","No Results Returned",[1227,8078,8079,8082,8085],{},[1230,8080,8081],{},"Verify your search terms aren't too restrictive",[1230,8083,8084],{},"Check your internet connection",[1230,8086,8087],{},"Ensure Epidemic Sound API is accessible",[1105,8089],{},[753,8091,3348],{"id":3347},[746,8093,8094],{},"The Epidemic Sound MCP integration with Claude represents a significant leap forward in creative workflows. By combining AI-powered natural language search with one of the world's largest royalty-free music libraries, developers and creators can:",[1227,8096,8097,8103,8109,8115],{},[1230,8098,8099,8102],{},[796,8100,8101],{},"Save hours"," previously spent browsing catalogs",[1230,8104,8105,8108],{},[796,8106,8107],{},"Discover music"," that perfectly matches their creative vision",[1230,8110,8111,8114],{},[796,8112,8113],{},"Stay in flow"," without context-switching between tools",[1230,8116,8117,8120],{},[796,8118,8119],{},"Build better products"," with professionally curated audio",[746,8122,8123],{},"Whether you're building games, creating content, or developing interactive experiences, this integration streamlines the entire music discovery process.",[1105,8125],{},[753,8127,3360],{"id":3359},[1227,8129,8130,8137,8144,8150,8157],{},[1230,8131,8132],{},[1100,8133,8136],{"href":8134,"rel":8135},"https://www.epidemicsound.com/blog/mcp-server/",[3369],"Epidemic Sound MCP Documentation",[1230,8138,8139],{},[1100,8140,8143],{"href":8141,"rel":8142},"https://modelcontextprotocol.io/",[3369],"Model Context Protocol Specification",[1230,8145,8146],{},[1100,8147,8149],{"href":3591,"rel":8148},[3369],"Claude Desktop Download",[1230,8151,8152],{},[1100,8153,8156],{"href":8154,"rel":8155},"https://www.epidemicsound.com/developers/",[3369],"Epidemic Sound API Documentation",[1230,8158,8159],{},[1100,8160,8162],{"href":3462,"rel":8161},[3369],"MusicTech Lab Projects",[3401,8164,8165],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":1033,"searchDepth":1034,"depth":1034,"links":8167},[8168,8169,8172,8175,8176,8184,8189,8194,8200,8201,8206,8207],{"id":3469,"depth":1034,"text":3470},{"id":3487,"depth":1034,"text":3488,"children":8170},[8171],{"id":3494,"depth":1039,"text":3495},{"id":3526,"depth":1034,"text":3527,"children":8173},[8174],{"id":3547,"depth":1039,"text":3548},{"id":3561,"depth":1034,"text":3562},{"id":3617,"depth":1034,"text":3618,"children":8177},[8178,8179,8180,8181,8182,8183],{"id":3621,"depth":1039,"text":3622},{"id":3659,"depth":1039,"text":3660},{"id":3723,"depth":1039,"text":3724},{"id":3959,"depth":1039,"text":3960},{"id":3980,"depth":1039,"text":3981},{"id":4027,"depth":1039,"text":4028},{"id":4051,"depth":1034,"text":4052,"children":8185},[8186,8187,8188],{"id":4060,"depth":1039,"text":4061},{"id":4073,"depth":1039,"text":4074},{"id":4083,"depth":1039,"text":4084},{"id":4095,"depth":1034,"text":4096,"children":8190},[8191,8192,8193],{"id":4099,"depth":1039,"text":4100},{"id":4880,"depth":1039,"text":4881},{"id":6726,"depth":1039,"text":6727},{"id":7897,"depth":1034,"text":7898,"children":8195},[8196,8197,8198,8199],{"id":7901,"depth":1039,"text":7902},{"id":7914,"depth":1039,"text":7915},{"id":7927,"depth":1039,"text":7928},{"id":7940,"depth":1039,"text":7941},{"id":7955,"depth":1034,"text":7956},{"id":8018,"depth":1034,"text":8019,"children":8202},[8203,8204,8205],{"id":8022,"depth":1039,"text":8023},{"id":8060,"depth":1039,"text":8061},{"id":8075,"depth":1039,"text":8076},{"id":3347,"depth":1034,"text":3348},{"id":3359,"depth":1034,"text":3360},"2025-01-15T00:00:00.000Z","Set up and use the Epidemic Sound MCP Server with Claude to discover music using natural language prompts directly in your development workflow.",{"src":8211},"/images/blog/musictechlab_blog_epidemic-sound-mcp-claude.webp",{"enabled":1058,"items":8213},[8214,8217,8220],{"text":8215,"icon":8216},"Epidemic Sound MCP lets you search 50,000+ tracks using natural language inside Claude.","i-lucide-search",{"text":8218,"icon":8219},"MCP keeps everything local; no web browser or mobile app required.","i-lucide-terminal",{"text":8221,"icon":8222},"Contextual prompts with mood, BPM, and genre produce significantly better search results.","i-lucide-music",{},{"title":152,"description":8209},[8226,1074,1075,8227],"AI","streaming","BTS9sNanqMh3YfKM42iEbg3Waj6rCSBgwJ-9wxzE9gE",{"id":8230,"title":84,"authors":8231,"badge":8234,"body":8236,"category":1051,"client":741,"date":10016,"description":10017,"extension":1054,"faq":10018,"featured":69,"featuredOrder":741,"hidden":69,"image":10031,"keyTakeaways":10033,"meta":10044,"navigation":1058,"path":85,"seo":10045,"status":741,"stem":86,"tags":10048,"teaser":741,"__hash__":10053,"score":1039},"posts/blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama.md",[8232],{"name":1087,"to":1088,"avatar":8233},{"src":1090},{"label":5,"color":8235},"#f59e0b",{"type":743,"value":8237,"toc":10001},[8238,8241,8244,8248,8251,8254,8271,8274,8279,8283,8286,8289,8303,8306,8400,8417,8421,8432,8435,8760,8763,8912,8915,8926,8930,8933,8936,8955,8958,9090,9093,9098,9102,9105,9111,9655,9658,9688,9694,9875,9882,9885,9890,9894,9897,9900,9903,9939,9943,9946,9950,9953,9957,9960,9964,9967,9971,9974,9988,9991,9995,9998],[746,8239,8240],{},"Your data team knows the answer is in the database. Your A&R lead, your finance director, and your label manager do not know how to get it out. This is the gap that costs music companies real money, not in licensing fees or infrastructure, but in decisions delayed, trends missed, and reports that arrive a week too late.",[746,8242,8243],{},"We built an AI-powered analytics dashboard to close that gap. It sits inside MusicData Lab, our royalty analytics platform, and it lets anyone with access type a question in plain English and get a chart back in seconds.",[753,8245,8247],{"id":8246},"the-problem-everybody-talks-about-at-conferences","The problem everybody talks about at conferences",[746,8249,8250],{},"The music industry has a data access problem. Not a data collection problem. Labels and distributors already have millions of rows of streaming data, royalty reports, and territorial breakdowns sitting in their databases. The bottleneck is getting from \"I need to know which retailers drove revenue last quarter\" to an actual answer.",[746,8252,8253],{},"Today, that journey typically looks like this:",[3076,8255,8256,8259,8262,8265,8268],{},[1230,8257,8258],{},"A business stakeholder writes an email or Slack message to the data team",[1230,8260,8261],{},"The data team interprets the request, writes a SQL query, exports the results",[1230,8263,8264],{},"Someone pastes the numbers into a spreadsheet and builds a chart",[1230,8266,8267],{},"The chart goes back to the stakeholder, who asks a follow-up question",[1230,8269,8270],{},"Repeat from step 1",[746,8272,8273],{},"This loop can take hours, sometimes days. Multiply it across every label, every territory, every reporting period, and you start to see the scale of the problem.",[852,8275,8276],{},[746,8277,8278],{},"A 2024 IFPI report noted that the global recorded music market generated $28.6 billion in revenue, with streaming accounting for 67% of that. The volume of data behind those numbers is staggering, and growing every quarter.",[753,8280,8282],{"id":8281},"what-if-business-users-could-just-ask","What if business users could just ask?",[746,8284,8285],{},"That was the design question behind our AI Dashboard. Instead of routing every data request through a technical team, what if the platform could understand a question like \"top 5 artists from the US by income\" and return a bar chart?",[746,8287,8288],{},"The workflow is simple:",[3076,8290,8291,8294,8297,8300],{},[1230,8292,8293],{},"A user types a question in the chat interface",[1230,8295,8296],{},"An LLM translates the question into a ClickHouse SQL query",[1230,8298,8299],{},"The query runs against the analytics database (read-only, with safety guardrails)",[1230,8301,8302],{},"Results come back as an interactive chart and a data table",[746,8304,8305],{},"No SQL knowledge required. No waiting for the data team. No spreadsheets.",[1342,8307,8309],{"className":1344,"code":8308,"language":1346,"meta":1033,"style":1033},"sequenceDiagram\n    participant U as User\n    participant UI as Chat UI\n    participant LLM as LLM (Ollama/Claude)\n    participant SG as SQL Guard\n    participant CH as ClickHouse\n    participant CB as Chart Builder\n\n    U->>UI: \"Top 5 artists by income\"\n    UI->>LLM: System prompt + question\n    LLM-->>UI: Generated SQL query\n    UI->>SG: Validate SQL\n    SG-->>UI: Sanitised query (SELECT only, LIMIT enforced)\n    UI->>CH: Execute (read-only, 10s timeout)\n    CH-->>UI: Result rows + columns\n    UI->>CB: Build chart config\n    CB-->>UI: Chart.js config (type, labels, datasets)\n    UI-->>U: Interactive chart + data table\n",[1234,8310,8311,8316,8321,8326,8331,8336,8341,8346,8350,8355,8360,8365,8370,8375,8380,8385,8390,8395],{"__ignoreMap":1033},[1350,8312,8313],{"class":1352,"line":1353},[1350,8314,8315],{"class":1356},"sequenceDiagram\n",[1350,8317,8318],{"class":1352,"line":1034},[1350,8319,8320],{"class":1356},"    participant U as User\n",[1350,8322,8323],{"class":1352,"line":1039},[1350,8324,8325],{"class":1356},"    participant UI as Chat UI\n",[1350,8327,8328],{"class":1352,"line":1370},[1350,8329,8330],{"class":1356},"    participant LLM as LLM (Ollama/Claude)\n",[1350,8332,8333],{"class":1352,"line":1376},[1350,8334,8335],{"class":1356},"    participant SG as SQL Guard\n",[1350,8337,8338],{"class":1352,"line":1382},[1350,8339,8340],{"class":1356},"    participant CH as ClickHouse\n",[1350,8342,8343],{"class":1352,"line":1388},[1350,8344,8345],{"class":1356},"    participant CB as Chart Builder\n",[1350,8347,8348],{"class":1352,"line":1394},[1350,8349,1637],{"emptyLinePlaceholder":1058},[1350,8351,8352],{"class":1352,"line":1400},[1350,8353,8354],{"class":1356},"    U->>UI: \"Top 5 artists by income\"\n",[1350,8356,8357],{"class":1352,"line":1406},[1350,8358,8359],{"class":1356},"    UI->>LLM: System prompt + question\n",[1350,8361,8362],{"class":1352,"line":1412},[1350,8363,8364],{"class":1356},"    LLM-->>UI: Generated SQL query\n",[1350,8366,8367],{"class":1352,"line":1418},[1350,8368,8369],{"class":1356},"    UI->>SG: Validate SQL\n",[1350,8371,8372],{"class":1352,"line":1423},[1350,8373,8374],{"class":1356},"    SG-->>UI: Sanitised query (SELECT only, LIMIT enforced)\n",[1350,8376,8377],{"class":1352,"line":1429},[1350,8378,8379],{"class":1356},"    UI->>CH: Execute (read-only, 10s timeout)\n",[1350,8381,8382],{"class":1352,"line":1435},[1350,8383,8384],{"class":1356},"    CH-->>UI: Result rows + columns\n",[1350,8386,8387],{"class":1352,"line":1441},[1350,8388,8389],{"class":1356},"    UI->>CB: Build chart config\n",[1350,8391,8392],{"class":1352,"line":1447},[1350,8393,8394],{"class":1356},"    CB-->>UI: Chart.js config (type, labels, datasets)\n",[1350,8396,8397],{"class":1352,"line":1453},[1350,8398,8399],{"class":1356},"    UI-->>U: Interactive chart + data table\n",[768,8401,8403,8408,8412],{"className":8402},[771,772,773,774,775],[777,8404],{"description":8405,"icon":8406,"title":8407},"Ask questions in plain English. No SQL, no training, no onboarding friction.","i-lucide-message-square","Natural Language Input",[777,8409],{"description":8410,"icon":3449,"title":8411},"Columnar storage handles millions of streaming records with sub-second query times.","ClickHouse Analytics",[777,8413],{"description":8414,"icon":8415,"title":8416},"Results render as interactive charts. Bar, line, and doughnut, selected automatically.","i-lucide-bar-chart-3","Instant Visualization",[753,8418,8420],{"id":8419},"why-this-matters-for-music-companies-specifically","Why this matters for music companies specifically",[746,8422,8423,8424,8427,8428,8431],{},"Music royalty data is uniquely complex. A single label might receive reports from ",[1100,8425,8426],{"href":77},"13 different distributors, each with its own file format",", column naming, and date conventions. Once that data is normalised and loaded into an analytics database, the schema reflects that complexity: dozens of fields covering artists, tracks, retailers, territories, currencies, and time periods. Even the ",[1100,8429,8430],{"href":81},"retailer names need normalisation"," before they become queryable.",[746,8433,8434],{},"This is precisely the kind of dataset where AI-assisted querying shines. The system prompt includes the full database schema, domain-specific hints, and few-shot examples that teach the model how to write correct ClickHouse SQL. We build it dynamically from Django model metadata, so the prompt stays in sync with the schema automatically:",[1342,8436,8439],{"className":1619,"code":8437,"filename":8438,"language":1621,"meta":1033,"style":1033},"def build_system_prompt() -> str:\n    fields = []\n    for field in StreamDataCH._meta.get_fields():\n        fields.append(f\"  - {field.name} ({field.__class__.__name__})\")\n\n    schema_block = \"\\n\".join(fields)\n\n    return (\n        \"You are a SQL analyst for a music streaming analytics platform.\\n\"\n        f\"The database is ClickHouse. There is one table: `{TABLE}`.\\n\"\n        f\"\\n## Schema\\n\\n{schema_block}\\n\"\n        \"\\n## Domain hints\\n\\n\"\n        \"- `final_income` is income converted to the target currency\\n\"\n        \"- `retailer_union` is the normalized retailer name\\n\"\n        \"- Always filter out empty strings when grouping by text fields\\n\"\n        \"\\n## Output rules\\n\\n\"\n        \"1. Output ONLY a single SELECT statement\\n\"\n        \"2. Always include a LIMIT clause (max 100)\\n\"\n        \"3. Never use INSERT, UPDATE, DELETE, DROP, or any DDL\\n\"\n        f\"\\n## Examples\\n\\n{examples_block}\"\n    )\n","prompts.py",[1234,8440,8441,8458,8467,8492,8543,8547,8573,8577,8584,8595,8617,8642,8655,8666,8677,8688,8701,8712,8723,8734,8756],{"__ignoreMap":1033},[1350,8442,8443,8446,8449,8451,8454,8456],{"class":1352,"line":1353},[1350,8444,8445],{"class":1689},"def",[1350,8447,8448],{"class":1682}," build_system_prompt",[1350,8450,5962],{"class":1645},[1350,8452,8453],{"class":1645}," ->",[1350,8455,4755],{"class":3642},[1350,8457,4181],{"class":1645},[1350,8459,8460,8463,8465],{"class":1352,"line":1034},[1350,8461,8462],{"class":1356},"    fields ",[1350,8464,1646],{"class":1645},[1350,8466,6896],{"class":1645},[1350,8468,8469,8472,8475,8477,8480,8482,8485,8487,8490],{"class":1352,"line":1039},[1350,8470,8471],{"class":1628},"    for",[1350,8473,8474],{"class":1356}," field ",[1350,8476,4688],{"class":1628},[1350,8478,8479],{"class":1356}," StreamDataCH",[1350,8481,1679],{"class":1645},[1350,8483,8484],{"class":2433},"_meta",[1350,8486,1679],{"class":1645},[1350,8488,8489],{"class":1682},"get_fields",[1350,8491,4569],{"class":1645},[1350,8493,8494,8497,8499,8502,8504,8506,8509,8511,8514,8516,8518,8520,8522,8524,8526,8528,8531,8533,8536,8538,8541],{"class":1352,"line":1370},[1350,8495,8496],{"class":1356},"        fields",[1350,8498,1679],{"class":1645},[1350,8500,8501],{"class":1682},"append",[1350,8503,1686],{"class":1645},[1350,8505,1690],{"class":1689},[1350,8507,8508],{"class":1652},"\"  - ",[1350,8510,1697],{"class":1696},[1350,8512,8513],{"class":1682},"field",[1350,8515,1679],{"class":1645},[1350,8517,1943],{"class":2433},[1350,8519,1703],{"class":1696},[1350,8521,6080],{"class":1652},[1350,8523,1697],{"class":1696},[1350,8525,8513],{"class":1682},[1350,8527,1679],{"class":1645},[1350,8529,8530],{"class":1356},"__class__",[1350,8532,1679],{"class":1645},[1350,8534,8535],{"class":1356},"__name__",[1350,8537,1703],{"class":1696},[1350,8539,8540],{"class":1652},")\"",[1350,8542,2282],{"class":1645},[1350,8544,8545],{"class":1352,"line":1376},[1350,8546,1637],{"emptyLinePlaceholder":1058},[1350,8548,8549,8552,8554,8556,8559,8561,8563,8566,8568,8571],{"class":1352,"line":1382},[1350,8550,8551],{"class":1356},"    schema_block ",[1350,8553,1646],{"class":1645},[1350,8555,1649],{"class":1645},[1350,8557,8558],{"class":1356},"\\n",[1350,8560,1693],{"class":1645},[1350,8562,1679],{"class":1645},[1350,8564,8565],{"class":1682},"join",[1350,8567,1686],{"class":1645},[1350,8569,8570],{"class":1682},"fields",[1350,8572,2282],{"class":1645},[1350,8574,8575],{"class":1352,"line":1388},[1350,8576,1637],{"emptyLinePlaceholder":1058},[1350,8578,8579,8581],{"class":1352,"line":1394},[1350,8580,4575],{"class":1628},[1350,8582,8583],{"class":1645}," (\n",[1350,8585,8586,8588,8591,8593],{"class":1352,"line":1400},[1350,8587,2077],{"class":1645},[1350,8589,8590],{"class":1652},"You are a SQL analyst for a music streaming analytics platform.",[1350,8592,8558],{"class":1356},[1350,8594,1656],{"class":1645},[1350,8596,8597,8600,8603,8605,8608,8610,8613,8615],{"class":1352,"line":1406},[1350,8598,8599],{"class":1689},"        f",[1350,8601,8602],{"class":1652},"\"The database is ClickHouse. There is one table: `",[1350,8604,1697],{"class":1696},[1350,8606,8607],{"class":1356},"TABLE",[1350,8609,1703],{"class":1696},[1350,8611,8612],{"class":1652},"`.",[1350,8614,8558],{"class":1356},[1350,8616,1656],{"class":1652},[1350,8618,8619,8621,8623,8625,8628,8631,8633,8636,8638,8640],{"class":1352,"line":1412},[1350,8620,8599],{"class":1689},[1350,8622,1693],{"class":1652},[1350,8624,8558],{"class":1356},[1350,8626,8627],{"class":1652},"## Schema",[1350,8629,8630],{"class":1356},"\\n\\n",[1350,8632,1697],{"class":1696},[1350,8634,8635],{"class":1356},"schema_block",[1350,8637,1703],{"class":1696},[1350,8639,8558],{"class":1356},[1350,8641,1656],{"class":1652},[1350,8643,8644,8646,8648,8651,8653],{"class":1352,"line":1418},[1350,8645,2077],{"class":1645},[1350,8647,8558],{"class":1356},[1350,8649,8650],{"class":1652},"## Domain hints",[1350,8652,8630],{"class":1356},[1350,8654,1656],{"class":1645},[1350,8656,8657,8659,8662,8664],{"class":1352,"line":1423},[1350,8658,2077],{"class":1645},[1350,8660,8661],{"class":1652},"- `final_income` is income converted to the target currency",[1350,8663,8558],{"class":1356},[1350,8665,1656],{"class":1645},[1350,8667,8668,8670,8673,8675],{"class":1352,"line":1429},[1350,8669,2077],{"class":1645},[1350,8671,8672],{"class":1652},"- `retailer_union` is the normalized retailer name",[1350,8674,8558],{"class":1356},[1350,8676,1656],{"class":1645},[1350,8678,8679,8681,8684,8686],{"class":1352,"line":1435},[1350,8680,2077],{"class":1645},[1350,8682,8683],{"class":1652},"- Always filter out empty strings when grouping by text fields",[1350,8685,8558],{"class":1356},[1350,8687,1656],{"class":1645},[1350,8689,8690,8692,8694,8697,8699],{"class":1352,"line":1441},[1350,8691,2077],{"class":1645},[1350,8693,8558],{"class":1356},[1350,8695,8696],{"class":1652},"## Output rules",[1350,8698,8630],{"class":1356},[1350,8700,1656],{"class":1645},[1350,8702,8703,8705,8708,8710],{"class":1352,"line":1447},[1350,8704,2077],{"class":1645},[1350,8706,8707],{"class":1652},"1. Output ONLY a single SELECT statement",[1350,8709,8558],{"class":1356},[1350,8711,1656],{"class":1645},[1350,8713,8714,8716,8719,8721],{"class":1352,"line":1453},[1350,8715,2077],{"class":1645},[1350,8717,8718],{"class":1652},"2. Always include a LIMIT clause (max 100)",[1350,8720,8558],{"class":1356},[1350,8722,1656],{"class":1645},[1350,8724,8725,8727,8730,8732],{"class":1352,"line":1459},[1350,8726,2077],{"class":1645},[1350,8728,8729],{"class":1652},"3. Never use INSERT, UPDATE, DELETE, DROP, or any DDL",[1350,8731,8558],{"class":1356},[1350,8733,1656],{"class":1645},[1350,8735,8736,8738,8740,8742,8745,8747,8749,8752,8754],{"class":1352,"line":1464},[1350,8737,8599],{"class":1689},[1350,8739,1693],{"class":1652},[1350,8741,8558],{"class":1356},[1350,8743,8744],{"class":1652},"## Examples",[1350,8746,8630],{"class":1356},[1350,8748,1697],{"class":1696},[1350,8750,8751],{"class":1356},"examples_block",[1350,8753,1703],{"class":1696},[1350,8755,1656],{"class":1652},[1350,8757,8758],{"class":1352,"line":1470},[1350,8759,2386],{"class":1645},[746,8761,8762],{},"The few-shot examples are critical. They teach the model patterns like subqueries for hierarchical groupings (\"top 5 artists with their retailers by income\") that a generic LLM would otherwise flatten into a single GROUP BY:",[1342,8764,8768],{"className":8765,"code":8766,"language":8767,"meta":1033,"style":1033},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- Example: top 5 artists with their retailers by income\nSELECT artist, retailer_union, sum(final_income) AS total_income\nFROM analytics_streamdatach\nWHERE artist != '' AND retailer_union != ''\n  AND artist IN (\n    SELECT artist FROM analytics_streamdatach\n    WHERE artist != ''\n    GROUP BY artist ORDER BY sum(final_income) DESC LIMIT 5\n  )\nGROUP BY artist, retailer_union\nORDER BY artist, total_income DESC LIMIT 100\n","sql",[1234,8769,8770,8775,8795,8803,8828,8840,8851,8862,8886,8891,8899],{"__ignoreMap":1033},[1350,8771,8772],{"class":1352,"line":1353},[1350,8773,8774],{"class":1665},"-- Example: top 5 artists with their retailers by income\n",[1350,8776,8777,8780,8783,8786,8789,8792],{"class":1352,"line":1034},[1350,8778,8779],{"class":1696},"SELECT",[1350,8781,8782],{"class":1356}," artist, retailer_union, ",[1350,8784,8785],{"class":1682},"sum",[1350,8787,8788],{"class":1356},"(final_income) ",[1350,8790,8791],{"class":1696},"AS",[1350,8793,8794],{"class":1356}," total_income\n",[1350,8796,8797,8800],{"class":1352,"line":1039},[1350,8798,8799],{"class":1696},"FROM",[1350,8801,8802],{"class":1356}," analytics_streamdatach\n",[1350,8804,8805,8808,8811,8814,8817,8820,8823,8825],{"class":1352,"line":1370},[1350,8806,8807],{"class":1696},"WHERE",[1350,8809,8810],{"class":1356}," artist ",[1350,8812,8813],{"class":1645},"!=",[1350,8815,8816],{"class":1645}," ''",[1350,8818,8819],{"class":1696}," AND",[1350,8821,8822],{"class":1356}," retailer_union ",[1350,8824,8813],{"class":1645},[1350,8826,8827],{"class":1645}," ''\n",[1350,8829,8830,8833,8835,8838],{"class":1352,"line":1376},[1350,8831,8832],{"class":1696},"  AND",[1350,8834,8810],{"class":1356},[1350,8836,8837],{"class":1696},"IN",[1350,8839,8583],{"class":1356},[1350,8841,8842,8845,8847,8849],{"class":1352,"line":1382},[1350,8843,8844],{"class":1696},"    SELECT",[1350,8846,8810],{"class":1356},[1350,8848,8799],{"class":1696},[1350,8850,8802],{"class":1356},[1350,8852,8853,8856,8858,8860],{"class":1352,"line":1388},[1350,8854,8855],{"class":1696},"    WHERE",[1350,8857,8810],{"class":1356},[1350,8859,8813],{"class":1645},[1350,8861,8827],{"class":1645},[1350,8863,8864,8867,8869,8872,8875,8877,8880,8883],{"class":1352,"line":1394},[1350,8865,8866],{"class":1696},"    GROUP BY",[1350,8868,8810],{"class":1356},[1350,8870,8871],{"class":1696},"ORDER BY",[1350,8873,8874],{"class":1682}," sum",[1350,8876,8788],{"class":1356},[1350,8878,8879],{"class":1696},"DESC",[1350,8881,8882],{"class":1696}," LIMIT",[1350,8884,8885],{"class":1696}," 5\n",[1350,8887,8888],{"class":1352,"line":1400},[1350,8889,8890],{"class":1356},"  )\n",[1350,8892,8893,8896],{"class":1352,"line":1406},[1350,8894,8895],{"class":1696},"GROUP BY",[1350,8897,8898],{"class":1356}," artist, retailer_union\n",[1350,8900,8901,8903,8906,8908,8910],{"class":1352,"line":1412},[1350,8902,8871],{"class":1696},[1350,8904,8905],{"class":1356}," artist, total_income ",[1350,8907,8879],{"class":1696},[1350,8909,8882],{"class":1696},[1350,8911,6139],{"class":1696},[746,8913,8914],{},"The result is that even someone who has never seen a database can ask:",[1227,8916,8917,8920,8923],{},[1230,8918,8919],{},"\"Monthly income trend for 2024\" and get a line chart",[1230,8921,8922],{},"\"Income by country\" and get a ranked bar chart",[1230,8924,8925],{},"\"Top 10 tracks by streams\" and get a sortable table with the data",[753,8927,8929],{"id":8928},"data-privacy-keeping-everything-local","Data privacy: keeping everything local",[746,8931,8932],{},"Here is where most \"AI analytics\" solutions stumble. They require sending your data, or at least your queries, to a third-party API. For a music company handling confidential royalty data, artist earnings, and pre-release catalogue information, that is often a non-starter.",[746,8934,8935],{},"Our architecture solves this with a pluggable LLM design:",[768,8937,8939,8947],{"className":8938},[771,772,815,774,775],[777,8940,8944],{"description":8941,"icon":8942,"title":8943},"Open-source LLM runtime running in Docker alongside the application. All inference happens on your infrastructure. Zero data exposure.","i-lucide-server","Ollama (Local, Default)",[746,8945,8946],{},"Recommended for production environments handling sensitive royalty data. Runs models like Mistral 7B locally with 4GB of memory.",[777,8948,8952],{"description":8949,"icon":8950,"title":8951},"Anthropic's Claude API for higher-quality SQL generation. Swap in with a single environment variable change.","i-lucide-cloud","Claude API (Optional)",[746,8953,8954],{},"Useful for development, testing, or environments where data sensitivity is lower and query accuracy is the priority.",[746,8956,8957],{},"Switching between providers is a configuration change, not a code change. The factory pattern reads a single setting and returns the right client:",[1342,8959,8962],{"className":1619,"code":8960,"filename":8961,"language":1621,"meta":1033,"style":1033},"def get_llm_provider() -> LLMProvider:\n    provider = getattr(settings, \"AI_DASHBOARD_PROVIDER\", \"ollama\")\n\n    if provider == \"claude\":\n        from .claude import ClaudeProvider\n        return ClaudeProvider()\n\n    from .ollama import OllamaProvider\n    return OllamaProvider()\n","factory.py",[1234,8963,8964,8980,9015,9019,9037,9053,9062,9066,9081],{"__ignoreMap":1033},[1350,8965,8966,8968,8971,8973,8975,8978],{"class":1352,"line":1353},[1350,8967,8445],{"class":1689},[1350,8969,8970],{"class":1682}," get_llm_provider",[1350,8972,5962],{"class":1645},[1350,8974,8453],{"class":1645},[1350,8976,8977],{"class":1356}," LLMProvider",[1350,8979,4181],{"class":1645},[1350,8981,8982,8985,8987,8990,8992,8995,8997,8999,9002,9004,9006,9008,9011,9013],{"class":1352,"line":1034},[1350,8983,8984],{"class":1356},"    provider ",[1350,8986,1646],{"class":1645},[1350,8988,8989],{"class":1682}," getattr",[1350,8991,1686],{"class":1645},[1350,8993,8994],{"class":1682},"settings",[1350,8996,1709],{"class":1645},[1350,8998,1649],{"class":1645},[1350,9000,9001],{"class":1652},"AI_DASHBOARD_PROVIDER",[1350,9003,1693],{"class":1645},[1350,9005,1709],{"class":1645},[1350,9007,1649],{"class":1645},[1350,9009,9010],{"class":1652},"ollama",[1350,9012,1693],{"class":1645},[1350,9014,2282],{"class":1645},[1350,9016,9017],{"class":1352,"line":1039},[1350,9018,1637],{"emptyLinePlaceholder":1058},[1350,9020,9021,9023,9026,9029,9031,9033,9035],{"class":1352,"line":1370},[1350,9022,4813],{"class":1628},[1350,9024,9025],{"class":1356}," provider ",[1350,9027,9028],{"class":1645},"==",[1350,9030,1649],{"class":1645},[1350,9032,3718],{"class":1652},[1350,9034,1693],{"class":1645},[1350,9036,4181],{"class":1645},[1350,9038,9039,9042,9045,9048,9050],{"class":1352,"line":1376},[1350,9040,9041],{"class":1628},"        from",[1350,9043,9044],{"class":1645}," .",[1350,9046,9047],{"class":1356},"claude ",[1350,9049,1629],{"class":1628},[1350,9051,9052],{"class":1356}," ClaudeProvider\n",[1350,9054,9055,9057,9060],{"class":1352,"line":1382},[1350,9056,4823],{"class":1628},[1350,9058,9059],{"class":1682}," ClaudeProvider",[1350,9061,4520],{"class":1645},[1350,9063,9064],{"class":1352,"line":1388},[1350,9065,1637],{"emptyLinePlaceholder":1058},[1350,9067,9068,9071,9073,9076,9078],{"class":1352,"line":1394},[1350,9069,9070],{"class":1628},"    from",[1350,9072,9044],{"class":1645},[1350,9074,9075],{"class":1356},"ollama ",[1350,9077,1629],{"class":1628},[1350,9079,9080],{"class":1356}," OllamaProvider\n",[1350,9082,9083,9085,9088],{"class":1352,"line":1400},[1350,9084,4575],{"class":1628},[1350,9086,9087],{"class":1682}," OllamaProvider",[1350,9089,4520],{"class":1645},[746,9091,9092],{},"The same application code, the same security layer, the same chart rendering.",[968,9094,9095],{},[746,9096,9097],{},"The pluggable pattern means you can start with Ollama for privacy, evaluate quality, and switch to Claude API for specific use cases without any migration work.",[753,9099,9101],{"id":9100},"security-by-design-not-by-afterthought","Security by design, not by afterthought",[746,9103,9104],{},"Letting an AI write SQL that runs against your production database sounds risky. It is, if you do it naively. Our approach layers multiple security controls:",[746,9106,9107,9110],{},[796,9108,9109],{},"SQL Guard"," validates every query before execution. Here is the core of it:",[1342,9112,9115],{"className":1619,"code":9113,"filename":9114,"language":1621,"meta":1033,"style":1033},"BLOCKED_KEYWORDS = [\n    \"INSERT\", \"UPDATE\", \"DELETE\", \"DROP\", \"ALTER\",\n    \"TRUNCATE\", \"CREATE\", \"GRANT\", \"REVOKE\",\n    \"ATTACH\", \"DETACH\", \"RENAME\", \"OPTIMIZE\", \"KILL\",\n]\n\nclass SQLGuard:\n    @staticmethod\n    def validate(sql: str) -> str:\n        sql = sql.rstrip(\";\").strip()\n\n        if not sql.upper().startswith(\"SELECT\"):\n            raise SQLGuardError(\"Only SELECT queries are allowed\")\n        if \";\" in sql:\n            raise SQLGuardError(\"Multiple statements are not allowed\")\n\n        sql_upper = sql.upper()\n        for keyword in BLOCKED_KEYWORDS:\n            if re.search(rf\"\\b{keyword}\\b\", sql_upper):\n                raise SQLGuardError(f\"Forbidden keyword: {keyword}\")\n\n        # Only allow the analytics table\n        for table in re.findall(r\"(?:FROM|JOIN)\\s+(\\w+)\", sql_upper):\n            if table.lower() != ALLOWED_TABLE:\n                raise SQLGuardError(f\"Table '{table}' is not allowed\")\n\n        # Enforce LIMIT \u003C= 100\n        return _enforce_limit(sql, sql_upper)\n","guard.py",[1234,9116,9117,9126,9173,9211,9258,9262,9266,9275,9283,9307,9339,9343,9374,9393,9410,9427,9431,9446,9460,9498,9522,9526,9531,9583,9605,9629,9633,9638],{"__ignoreMap":1033},[1350,9118,9119,9122,9124],{"class":1352,"line":1353},[1350,9120,9121],{"class":1356},"BLOCKED_KEYWORDS ",[1350,9123,1646],{"class":1645},[1350,9125,1933],{"class":1645},[1350,9127,9128,9130,9133,9135,9137,9139,9142,9144,9146,9148,9151,9153,9155,9157,9160,9162,9164,9166,9169,9171],{"class":1352,"line":1034},[1350,9129,1721],{"class":1645},[1350,9131,9132],{"class":1652},"INSERT",[1350,9134,1693],{"class":1645},[1350,9136,1709],{"class":1645},[1350,9138,1649],{"class":1645},[1350,9140,9141],{"class":1652},"UPDATE",[1350,9143,1693],{"class":1645},[1350,9145,1709],{"class":1645},[1350,9147,1649],{"class":1645},[1350,9149,9150],{"class":1652},"DELETE",[1350,9152,1693],{"class":1645},[1350,9154,1709],{"class":1645},[1350,9156,1649],{"class":1645},[1350,9158,9159],{"class":1652},"DROP",[1350,9161,1693],{"class":1645},[1350,9163,1709],{"class":1645},[1350,9165,1649],{"class":1645},[1350,9167,9168],{"class":1652},"ALTER",[1350,9170,1693],{"class":1645},[1350,9172,1739],{"class":1645},[1350,9174,9175,9177,9180,9182,9184,9186,9189,9191,9193,9195,9198,9200,9202,9204,9207,9209],{"class":1352,"line":1039},[1350,9176,1721],{"class":1645},[1350,9178,9179],{"class":1652},"TRUNCATE",[1350,9181,1693],{"class":1645},[1350,9183,1709],{"class":1645},[1350,9185,1649],{"class":1645},[1350,9187,9188],{"class":1652},"CREATE",[1350,9190,1693],{"class":1645},[1350,9192,1709],{"class":1645},[1350,9194,1649],{"class":1645},[1350,9196,9197],{"class":1652},"GRANT",[1350,9199,1693],{"class":1645},[1350,9201,1709],{"class":1645},[1350,9203,1649],{"class":1645},[1350,9205,9206],{"class":1652},"REVOKE",[1350,9208,1693],{"class":1645},[1350,9210,1739],{"class":1645},[1350,9212,9213,9215,9218,9220,9222,9224,9227,9229,9231,9233,9236,9238,9240,9242,9245,9247,9249,9251,9254,9256],{"class":1352,"line":1370},[1350,9214,1721],{"class":1645},[1350,9216,9217],{"class":1652},"ATTACH",[1350,9219,1693],{"class":1645},[1350,9221,1709],{"class":1645},[1350,9223,1649],{"class":1645},[1350,9225,9226],{"class":1652},"DETACH",[1350,9228,1693],{"class":1645},[1350,9230,1709],{"class":1645},[1350,9232,1649],{"class":1645},[1350,9234,9235],{"class":1652},"RENAME",[1350,9237,1693],{"class":1645},[1350,9239,1709],{"class":1645},[1350,9241,1649],{"class":1645},[1350,9243,9244],{"class":1652},"OPTIMIZE",[1350,9246,1693],{"class":1645},[1350,9248,1709],{"class":1645},[1350,9250,1649],{"class":1645},[1350,9252,9253],{"class":1652},"KILL",[1350,9255,1693],{"class":1645},[1350,9257,1739],{"class":1645},[1350,9259,9260],{"class":1352,"line":1376},[1350,9261,1790],{"class":1645},[1350,9263,9264],{"class":1352,"line":1382},[1350,9265,1637],{"emptyLinePlaceholder":1058},[1350,9267,9268,9270,9273],{"class":1352,"line":1388},[1350,9269,4175],{"class":1689},[1350,9271,9272],{"class":3642}," SQLGuard",[1350,9274,4181],{"class":1645},[1350,9276,9277,9280],{"class":1352,"line":1394},[1350,9278,9279],{"class":1645},"    @",[1350,9281,9282],{"class":3642},"staticmethod\n",[1350,9284,9285,9288,9291,9293,9295,9297,9299,9301,9303,9305],{"class":1352,"line":1400},[1350,9286,9287],{"class":1689},"    def",[1350,9289,9290],{"class":1682}," validate",[1350,9292,1686],{"class":1645},[1350,9294,8767],{"class":1712},[1350,9296,1729],{"class":1645},[1350,9298,4755],{"class":3642},[1350,9300,6173],{"class":1645},[1350,9302,8453],{"class":1645},[1350,9304,4755],{"class":3642},[1350,9306,4181],{"class":1645},[1350,9308,9309,9312,9314,9317,9319,9322,9324,9326,9329,9331,9334,9337],{"class":1352,"line":1406},[1350,9310,9311],{"class":1356},"        sql ",[1350,9313,1646],{"class":1645},[1350,9315,9316],{"class":1356}," sql",[1350,9318,1679],{"class":1645},[1350,9320,9321],{"class":1682},"rstrip",[1350,9323,1686],{"class":1645},[1350,9325,1693],{"class":1645},[1350,9327,9328],{"class":1652},";",[1350,9330,1693],{"class":1645},[1350,9332,9333],{"class":1645},").",[1350,9335,9336],{"class":1682},"strip",[1350,9338,4520],{"class":1645},[1350,9340,9341],{"class":1352,"line":1412},[1350,9342,1637],{"emptyLinePlaceholder":1058},[1350,9344,9345,9348,9351,9353,9355,9358,9361,9364,9366,9368,9370,9372],{"class":1352,"line":1418},[1350,9346,9347],{"class":1628},"        if",[1350,9349,9350],{"class":1645}," not",[1350,9352,9316],{"class":1356},[1350,9354,1679],{"class":1645},[1350,9356,9357],{"class":1682},"upper",[1350,9359,9360],{"class":1645},"().",[1350,9362,9363],{"class":1682},"startswith",[1350,9365,1686],{"class":1645},[1350,9367,1693],{"class":1645},[1350,9369,8779],{"class":1652},[1350,9371,1693],{"class":1645},[1350,9373,4758],{"class":1645},[1350,9375,9376,9379,9382,9384,9386,9389,9391],{"class":1352,"line":1423},[1350,9377,9378],{"class":1628},"            raise",[1350,9380,9381],{"class":1682}," SQLGuardError",[1350,9383,1686],{"class":1645},[1350,9385,1693],{"class":1645},[1350,9387,9388],{"class":1652},"Only SELECT queries are allowed",[1350,9390,1693],{"class":1645},[1350,9392,2282],{"class":1645},[1350,9394,9395,9397,9399,9401,9403,9406,9408],{"class":1352,"line":1429},[1350,9396,9347],{"class":1628},[1350,9398,1649],{"class":1645},[1350,9400,9328],{"class":1652},[1350,9402,1693],{"class":1645},[1350,9404,9405],{"class":1645}," in",[1350,9407,9316],{"class":1356},[1350,9409,4181],{"class":1645},[1350,9411,9412,9414,9416,9418,9420,9423,9425],{"class":1352,"line":1435},[1350,9413,9378],{"class":1628},[1350,9415,9381],{"class":1682},[1350,9417,1686],{"class":1645},[1350,9419,1693],{"class":1645},[1350,9421,9422],{"class":1652},"Multiple statements are not allowed",[1350,9424,1693],{"class":1645},[1350,9426,2282],{"class":1645},[1350,9428,9429],{"class":1352,"line":1441},[1350,9430,1637],{"emptyLinePlaceholder":1058},[1350,9432,9433,9436,9438,9440,9442,9444],{"class":1352,"line":1447},[1350,9434,9435],{"class":1356},"        sql_upper ",[1350,9437,1646],{"class":1645},[1350,9439,9316],{"class":1356},[1350,9441,1679],{"class":1645},[1350,9443,9357],{"class":1682},[1350,9445,4520],{"class":1645},[1350,9447,9448,9450,9453,9455,9458],{"class":1352,"line":1453},[1350,9449,4682],{"class":1628},[1350,9451,9452],{"class":1356}," keyword ",[1350,9454,4688],{"class":1628},[1350,9456,9457],{"class":1356}," BLOCKED_KEYWORDS",[1350,9459,4181],{"class":1645},[1350,9461,9462,9465,9468,9470,9473,9475,9478,9481,9483,9486,9488,9491,9493,9496],{"class":1352,"line":1459},[1350,9463,9464],{"class":1628},"            if",[1350,9466,9467],{"class":1356}," re",[1350,9469,1679],{"class":1645},[1350,9471,9472],{"class":1682},"search",[1350,9474,1686],{"class":1645},[1350,9476,9477],{"class":1689},"rf",[1350,9479,9480],{"class":1652},"\"\\b",[1350,9482,1697],{"class":1696},[1350,9484,9485],{"class":1682},"keyword",[1350,9487,1703],{"class":1696},[1350,9489,9490],{"class":1652},"\\b\"",[1350,9492,1709],{"class":1645},[1350,9494,9495],{"class":1682}," sql_upper",[1350,9497,4758],{"class":1645},[1350,9499,9500,9503,9505,9507,9509,9512,9514,9516,9518,9520],{"class":1352,"line":1464},[1350,9501,9502],{"class":1628},"                raise",[1350,9504,9381],{"class":1682},[1350,9506,1686],{"class":1645},[1350,9508,1690],{"class":1689},[1350,9510,9511],{"class":1652},"\"Forbidden keyword: ",[1350,9513,1697],{"class":1696},[1350,9515,9485],{"class":1682},[1350,9517,1703],{"class":1696},[1350,9519,1693],{"class":1652},[1350,9521,2282],{"class":1645},[1350,9523,9524],{"class":1352,"line":1470},[1350,9525,1637],{"emptyLinePlaceholder":1058},[1350,9527,9528],{"class":1352,"line":1476},[1350,9529,9530],{"class":1665},"        # Only allow the analytics table\n",[1350,9532,9533,9535,9538,9540,9542,9544,9547,9549,9552,9555,9557,9560,9563,9565,9568,9571,9574,9577,9579,9581],{"class":1352,"line":1482},[1350,9534,4682],{"class":1628},[1350,9536,9537],{"class":1356}," table ",[1350,9539,4688],{"class":1628},[1350,9541,9467],{"class":1356},[1350,9543,1679],{"class":1645},[1350,9545,9546],{"class":1682},"findall",[1350,9548,1686],{"class":1645},[1350,9550,9551],{"class":1689},"r",[1350,9553,9554],{"class":1645},"\"(?:",[1350,9556,8799],{"class":1652},[1350,9558,9559],{"class":1645},"|",[1350,9561,9562],{"class":1652},"JOIN",[1350,9564,6173],{"class":1645},[1350,9566,9567],{"class":1652},"\\s",[1350,9569,9570],{"class":1645},"+(",[1350,9572,9573],{"class":1652},"\\w",[1350,9575,9576],{"class":1645},"+)\"",[1350,9578,1709],{"class":1645},[1350,9580,9495],{"class":1682},[1350,9582,4758],{"class":1645},[1350,9584,9585,9587,9590,9592,9595,9597,9600,9603],{"class":1352,"line":2074},[1350,9586,9464],{"class":1628},[1350,9588,9589],{"class":1356}," table",[1350,9591,1679],{"class":1645},[1350,9593,9594],{"class":1682},"lower",[1350,9596,5962],{"class":1645},[1350,9598,9599],{"class":1645}," !=",[1350,9601,9602],{"class":1356}," ALLOWED_TABLE",[1350,9604,4181],{"class":1645},[1350,9606,9607,9609,9611,9613,9615,9618,9620,9622,9624,9627],{"class":1352,"line":2096},[1350,9608,9502],{"class":1628},[1350,9610,9381],{"class":1682},[1350,9612,1686],{"class":1645},[1350,9614,1690],{"class":1689},[1350,9616,9617],{"class":1652},"\"Table '",[1350,9619,1697],{"class":1696},[1350,9621,1115],{"class":1682},[1350,9623,1703],{"class":1696},[1350,9625,9626],{"class":1652},"' is not allowed\"",[1350,9628,2282],{"class":1645},[1350,9630,9631],{"class":1352,"line":2174},[1350,9632,1637],{"emptyLinePlaceholder":1058},[1350,9634,9635],{"class":1352,"line":2180},[1350,9636,9637],{"class":1665},"        # Enforce LIMIT \u003C= 100\n",[1350,9639,9640,9642,9645,9647,9649,9651,9653],{"class":1352,"line":2185},[1350,9641,4823],{"class":1628},[1350,9643,9644],{"class":1682}," _enforce_limit",[1350,9646,1686],{"class":1645},[1350,9648,8767],{"class":1682},[1350,9650,1709],{"class":1645},[1350,9652,9495],{"class":1682},[1350,9654,2282],{"class":1645},[746,9656,9657],{},"Key constraints enforced:",[1227,9659,9660,9675,9678,9685],{},[1230,9661,9662,9663,9665,9666,799,9668,799,9670,799,9672,9674],{},"Only ",[1234,9664,8779],{}," statements are allowed. Any ",[1234,9667,9132],{},[1234,9669,9141],{},[1234,9671,9150],{},[1234,9673,9159],{},", or DDL keyword is blocked",[1230,9676,9677],{},"Queries are restricted to a single analytics table (no access to user accounts, credentials, or other application data)",[1230,9679,9680,9681,9684],{},"Result sets are capped at 100 rows, with ",[1234,9682,9683],{},"LIMIT"," enforced automatically",[1230,9686,9687],{},"Comments, semicolons, and multi-statement queries are stripped or rejected",[746,9689,9690,9693],{},[796,9691,9692],{},"Read-only execution"," provides a second layer. The ClickHouse query runs over HTTP with safety parameters baked into every request:",[1342,9695,9698],{"className":1619,"code":9696,"filename":9697,"language":1621,"meta":1033,"style":1033},"response = httpx.post(\n    f\"http://{host}:{port}\",\n    params={\n        \"query\": f\"{sql} FORMAT JSON\",\n        \"database\": \"default\",\n        \"readonly\": \"1\",\n        \"max_execution_time\": \"10\",\n        \"max_result_rows\": \"100\",\n    },\n    timeout=15,\n)\n","executor.py",[1234,9699,9700,9715,9742,9749,9775,9795,9815,9834,9854,9859,9871],{"__ignoreMap":1033},[1350,9701,9702,9705,9707,9709,9711,9713],{"class":1352,"line":1353},[1350,9703,9704],{"class":1356},"response ",[1350,9706,1646],{"class":1645},[1350,9708,1676],{"class":1356},[1350,9710,1679],{"class":1645},[1350,9712,1683],{"class":1682},[1350,9714,2236],{"class":1645},[1350,9716,9717,9719,9722,9724,9727,9729,9731,9733,9736,9738,9740],{"class":1352,"line":1034},[1350,9718,2242],{"class":1689},[1350,9720,9721],{"class":1652},"\"http://",[1350,9723,1697],{"class":1696},[1350,9725,9726],{"class":1682},"host",[1350,9728,1703],{"class":1696},[1350,9730,1729],{"class":1652},[1350,9732,1697],{"class":1696},[1350,9734,9735],{"class":1682},"port",[1350,9737,1703],{"class":1696},[1350,9739,1693],{"class":1652},[1350,9741,1739],{"class":1645},[1350,9743,9744,9747],{"class":1352,"line":1039},[1350,9745,9746],{"class":1712},"    params",[1350,9748,1716],{"class":1645},[1350,9750,9751,9753,9756,9758,9760,9762,9764,9766,9768,9770,9773],{"class":1352,"line":1370},[1350,9752,2077],{"class":1645},[1350,9754,9755],{"class":1652},"query",[1350,9757,1693],{"class":1645},[1350,9759,1729],{"class":1645},[1350,9761,1812],{"class":1689},[1350,9763,1693],{"class":1652},[1350,9765,1697],{"class":1696},[1350,9767,8767],{"class":1682},[1350,9769,1703],{"class":1696},[1350,9771,9772],{"class":1652}," FORMAT JSON\"",[1350,9774,1739],{"class":1645},[1350,9776,9777,9779,9782,9784,9786,9788,9791,9793],{"class":1352,"line":1376},[1350,9778,2077],{"class":1645},[1350,9780,9781],{"class":1652},"database",[1350,9783,1693],{"class":1645},[1350,9785,1729],{"class":1645},[1350,9787,1649],{"class":1645},[1350,9789,9790],{"class":1652},"default",[1350,9792,1693],{"class":1645},[1350,9794,1739],{"class":1645},[1350,9796,9797,9799,9802,9804,9806,9808,9811,9813],{"class":1352,"line":1382},[1350,9798,2077],{"class":1645},[1350,9800,9801],{"class":1652},"readonly",[1350,9803,1693],{"class":1645},[1350,9805,1729],{"class":1645},[1350,9807,1649],{"class":1645},[1350,9809,9810],{"class":1652},"1",[1350,9812,1693],{"class":1645},[1350,9814,1739],{"class":1645},[1350,9816,9817,9819,9822,9824,9826,9828,9830,9832],{"class":1352,"line":1388},[1350,9818,2077],{"class":1645},[1350,9820,9821],{"class":1652},"max_execution_time",[1350,9823,1693],{"class":1645},[1350,9825,1729],{"class":1645},[1350,9827,1649],{"class":1645},[1350,9829,4446],{"class":1652},[1350,9831,1693],{"class":1645},[1350,9833,1739],{"class":1645},[1350,9835,9836,9838,9841,9843,9845,9847,9850,9852],{"class":1352,"line":1394},[1350,9837,2077],{"class":1645},[1350,9839,9840],{"class":1652},"max_result_rows",[1350,9842,1693],{"class":1645},[1350,9844,1729],{"class":1645},[1350,9846,1649],{"class":1645},[1350,9848,9849],{"class":1652},"100",[1350,9851,1693],{"class":1645},[1350,9853,1739],{"class":1645},[1350,9855,9856],{"class":1352,"line":1400},[1350,9857,9858],{"class":1645},"    },\n",[1350,9860,9861,9864,9866,9869],{"class":1352,"line":1406},[1350,9862,9863],{"class":1712},"    timeout",[1350,9865,1646],{"class":1645},[1350,9867,9868],{"class":1696},"15",[1350,9870,1739],{"class":1645},[1350,9872,9873],{"class":1352,"line":1412},[1350,9874,2282],{"class":1645},[746,9876,9877,9878,9881],{},"Even if SQL Guard missed something, ClickHouse itself would block writes (",[1234,9879,9880],{},"readonly=1","), kill slow queries (10 seconds), and cap the result set.",[746,9883,9884],{},"This belt-and-suspenders approach means that even if the LLM generates a malicious query (unlikely, but possible), it cannot modify data, access unauthorised tables, or run expensive long-running operations.",[1195,9886,9887],{},[746,9888,9889],{},"No AI system should have write access to production data. Always enforce read-only execution at both the application layer and the database layer.",[753,9891,9893],{"id":9892},"why-clickhouse-for-music-analytics","Why ClickHouse for music analytics",[746,9895,9896],{},"We chose ClickHouse as the analytics engine for MusicData Lab because music streaming data is a textbook columnar analytics workload: append-only, time-series, high-volume, and query-heavy.",[746,9898,9899],{},"A typical label might have 10 to 50 million rows of streaming data, partitioned by month. Common queries aggregate by artist, retailer, territory, or time period. ClickHouse handles these in milliseconds where PostgreSQL would take seconds or minutes.",[746,9901,9902],{},"Key advantages for music data:",[1227,9904,9905,9911,9927,9933],{},[1230,9906,9907,9910],{},[796,9908,9909],{},"MergeTree engine"," with monthly partitioning matches the natural cadence of royalty reporting",[1230,9912,9913,9916,9917,799,9920,9922,9923,9926],{},[796,9914,9915],{},"Low-cardinality string optimisation"," is perfect for fields like ",[1234,9918,9919],{},"retailer_union",[1234,9921,4631],{},", and ",[1234,9924,9925],{},"country_code"," that have a bounded set of values",[1230,9928,9929,9932],{},[796,9930,9931],{},"Columnar compression"," keeps storage costs low even as data grows to hundreds of millions of rows",[1230,9934,9935,9938],{},[796,9936,9937],{},"HTTP API"," makes it straightforward to build read-only query interfaces with proper access controls",[753,9940,9942],{"id":9941},"what-we-learned-building-this","What we learned building this",[746,9944,9945],{},"Three insights from the implementation that apply to any company considering AI-powered analytics:",[763,9947,9949],{"id":9948},"_1-the-prompt-is-the-product","1. The prompt is the product",[746,9951,9952],{},"The quality of SQL generation depends almost entirely on the system prompt. Including the full schema, domain-specific hints, and few-shot examples made the difference between \"sometimes works\" and \"reliably useful.\" As shown in the code above, we dynamically build the prompt from Django model metadata, so it stays in sync with schema changes automatically. No manual updates when a column is added or renamed.",[763,9954,9956],{"id":9955},"_2-start-local-scale-to-cloud","2. Start local, scale to cloud",[746,9958,9959],{},"Running Ollama locally for development and testing removed the biggest adoption barrier: \"we can't send data to an external API.\" Once stakeholders see the value, the conversation about using a cloud API for better quality becomes much easier.",[763,9961,9963],{"id":9962},"_3-security-is-a-feature-not-a-constraint","3. Security is a feature, not a constraint",[746,9965,9966],{},"The SQL Guard and read-only execution are not just safety nets. They are what made the business comfortable deploying this. When your CFO asks \"can this AI delete our data?\", the answer needs to be a confident \"no, here's why.\"",[753,9968,9970],{"id":9969},"who-is-this-for","Who is this for?",[746,9972,9973],{},"This is not a product launch. It is a proof of concept that we built for our own platform and for our clients. If you recognise any of these situations, it might be relevant to you:",[1227,9975,9976,9979,9982,9985],{},[1230,9977,9978],{},"Your data team spends too much time answering ad-hoc reporting requests",[1230,9980,9981],{},"Business stakeholders wait days for insights that should take seconds",[1230,9983,9984],{},"You have sensitive royalty or financial data and cannot use cloud-based AI tools",[1230,9986,9987],{},"You already have analytics data in ClickHouse, PostgreSQL, or a similar database and want to make it more accessible",[746,9989,9990],{},"At MusicTech Lab, we build data platforms for the music industry. The AI Dashboard is one piece of a larger system that handles royalty ingestion, normalisation, currency conversion, and reporting. If this resonates, we should talk.",[753,9992,9994],{"id":9993},"what-comes-next","What comes next",[746,9996,9997],{},"This is a v1. The underlying pattern, natural language to SQL to visualisation, is not limited to music data. Any company with a structured analytics database can benefit from making that data conversational. The technology is ready. The question is whether your organisation is ready to let business users ask their own questions.",[3401,9999,10000],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":1033,"searchDepth":1034,"depth":1034,"links":10002},[10003,10004,10005,10006,10007,10008,10009,10014,10015],{"id":8246,"depth":1034,"text":8247},{"id":8281,"depth":1034,"text":8282},{"id":8419,"depth":1034,"text":8420},{"id":8928,"depth":1034,"text":8929},{"id":9100,"depth":1034,"text":9101},{"id":9892,"depth":1034,"text":9893},{"id":9941,"depth":1034,"text":9942,"children":10010},[10011,10012,10013],{"id":9948,"depth":1039,"text":9949},{"id":9955,"depth":1039,"text":9956},{"id":9962,"depth":1039,"text":9963},{"id":9969,"depth":1034,"text":9970},{"id":9993,"depth":1034,"text":9994},"2026-03-24T00:00:00.000Z","Music royalty data is complex. Non-technical stakeholders need insights without SQL. Here's how we built an AI dashboard that turns plain English into charts.",[10019,10022,10025,10028],{"question":10020,"answer":10021},"Can the AI dashboard work without sending data to the cloud?","Yes. The default setup uses Ollama, an open-source local LLM runtime. All queries, data, and model inference stay on your own infrastructure. No streaming data, royalty figures, or artist names ever leave your servers.",{"question":10023,"answer":10024},"What kind of questions can non-technical users ask?","Users can ask plain English questions like 'top 5 artists by income', 'monthly revenue trend for 2024', or 'income by country'. The AI generates a ClickHouse SQL query, executes it safely, and returns a chart with the results.",{"question":10026,"answer":10027},"How does the system prevent dangerous SQL queries?","A dedicated SQL security layer validates every generated query before execution. It enforces SELECT-only access, restricts queries to a single analytics table, caps result sizes, and runs against a read-only ClickHouse connection with query timeouts.",{"question":10029,"answer":10030},"What technology stack powers the AI dashboard?","Django for the web framework, ClickHouse for columnar analytics, Ollama (or Claude API) for LLM inference, and Chart.js for visualization. The architecture is pluggable, so the LLM provider can be swapped without changing application code.",{"src":10032},"/images/blog/musictechlab_blog_ai-powered-analytics-dashboard.webp",{"enabled":1058,"items":10034},[10035,10037,10039,10042],{"text":10036,"icon":8415},"Business users type questions in plain English and get charts back in seconds, no SQL needed.",{"text":10038,"icon":1010},"Ollama runs locally by default so no royalty data ever leaves your servers.",{"text":10040,"icon":10041},"SQL Guard enforces SELECT-only, single-table access with a 100-row limit on every query.","i-lucide-lock",{"text":10043,"icon":819},"ClickHouse handles 10-50M rows with sub-second query times for columnar analytics.",{},{"title":10046,"description":10047},"AI Analytics Dashboard for Music Data | MusicTech Lab","How we built a generative AI dashboard that turns natural language into ClickHouse SQL and charts - with local LLM, zero data exposure.",[10049,1074,10050,10051,1051,10052],"ai-analytics","clickhouse","llm","data-visualization","pS6Y0h5-QTtmmgq3FGyIwCVS8U7L5KQD1FBN31L-WS8",{"id":10055,"title":80,"authors":10056,"badge":10059,"body":10060,"category":1051,"client":741,"date":10700,"description":10701,"extension":1054,"faq":10702,"featured":1058,"featuredOrder":1034,"hidden":69,"image":10712,"keyTakeaways":10714,"meta":10725,"navigation":1058,"path":81,"seo":10726,"status":741,"stem":82,"tags":10729,"teaser":741,"__hash__":10731,"score":1039},"posts/blog/music-data/830-ways-to-say-spotify-normalizing-music-streaming-data.md",[10057],{"name":1087,"to":1088,"avatar":10058},{"src":1090},{"label":5,"color":8235},{"type":743,"value":10061,"toc":10689},[10062,10069,10072,10076,10079,10169,10176,10180,10187,10293,10296,10303,10314,10329,10332,10336,10339,10342,10347,10353,10357,10360,10364,10367,10451,10454,10457,10460,10526,10529,10533,10536,10666,10669,10673,10676,10683,10686],[746,10063,10064,10065,10068],{},"In our ",[1100,10066,10067],{"href":77},"previous article",", we showed what a real label's download folder looks like - 18 distributors, 5 file formats, 500+ files per year. But solving the file format problem is only step one.",[746,10070,10071],{},"Open those files. The data inside is just as messy.",[753,10073,10075],{"id":10074},"the-name-problem","The name problem",[746,10077,10078],{},"Ask 18 distributors what \"Spotify\" is called. You'll get more answers than you expect:",[1115,10080,10081,10091],{},[1118,10082,10083],{},[1121,10084,10085,10088],{},[1124,10086,10087],{},"Distributor",[1124,10089,10090],{},"How they label Spotify",[1131,10092,10093,10103,10113,10123,10133,10141,10150,10159],{},[1121,10094,10095,10098],{},[1136,10096,10097],{},"FUGA",[1136,10099,10100],{},[1234,10101,10102],{},"Spotify",[1121,10104,10105,10108],{},[1136,10106,10107],{},"ADA",[1136,10109,10110],{},[1234,10111,10112],{},"SPOTIFY",[1121,10114,10115,10118],{},[1136,10116,10117],{},"Ingrooves",[1136,10119,10120],{},[1234,10121,10122],{},"spotify",[1121,10124,10125,10128],{},[1136,10126,10127],{},"The Orchard",[1136,10129,10130],{},[1234,10131,10132],{},"Spotify Premium",[1121,10134,10135,10138],{},[1136,10136,10137],{},"Bandcamp",[1136,10139,10140],{},"N/A",[1121,10142,10143,10146],{},[1136,10144,10145],{},"MVD",[1136,10147,10148],{},[1234,10149,10102],{},[1121,10151,10152,10155],{},[1136,10153,10154],{},"Emerald",[1136,10156,10157],{},[1234,10158,10112],{},[1121,10160,10161,10164],{},[1136,10162,10163],{},"SFM",[1136,10165,10166],{},[1234,10167,10168],{},"Spotify AB",[746,10170,10171,10172,10175],{},"That's just one platform. Now multiply across ",[796,10173,10174],{},"every retailer, every label name, and every service type"," in the dataset. The same entity appears under different names, different capitalizations, and different abbreviations depending on which distributor sent the file.",[753,10177,10179],{"id":10178},"_830-values-19-names","830 values, 19 names",[746,10181,10182,10183,10186],{},"The solution is what we call ",[796,10184,10185],{},"unions"," - normalization groups that map many raw values to one canonical name.",[768,10188,10192],{"className":10189},[10190,10191,775],"flex","justify-center",[1342,10193,10195],{"className":1344,"code":10194,"language":1346,"meta":1033,"style":1033},"flowchart LR\n    subgraph raw[\"Raw Values\"]\n        A[\"Spotify\"]\n        B[\"SPOTIFY\"]\n        C[\"spotify\"]\n        D[\"Spotify Premium\"]\n        E[\"Spotify AB\"]\n        F[\"Spotify Ltd\"]\n    end\n\n    subgraph union[\"Union\"]\n        G([\"SPOTIFY\"])\n    end\n\n    A --> G\n    B --> G\n    C --> G\n    D --> G\n    E --> G\n    F --> G\n",[1234,10196,10197,10202,10207,10212,10217,10222,10227,10232,10237,10241,10245,10250,10255,10259,10263,10268,10273,10278,10283,10288],{"__ignoreMap":1033},[1350,10198,10199],{"class":1352,"line":1353},[1350,10200,10201],{"class":1356},"flowchart LR\n",[1350,10203,10204],{"class":1352,"line":1034},[1350,10205,10206],{"class":1356},"    subgraph raw[\"Raw Values\"]\n",[1350,10208,10209],{"class":1352,"line":1039},[1350,10210,10211],{"class":1356},"        A[\"Spotify\"]\n",[1350,10213,10214],{"class":1352,"line":1370},[1350,10215,10216],{"class":1356},"        B[\"SPOTIFY\"]\n",[1350,10218,10219],{"class":1352,"line":1376},[1350,10220,10221],{"class":1356},"        C[\"spotify\"]\n",[1350,10223,10224],{"class":1352,"line":1382},[1350,10225,10226],{"class":1356},"        D[\"Spotify Premium\"]\n",[1350,10228,10229],{"class":1352,"line":1388},[1350,10230,10231],{"class":1356},"        E[\"Spotify AB\"]\n",[1350,10233,10234],{"class":1352,"line":1394},[1350,10235,10236],{"class":1356},"        F[\"Spotify Ltd\"]\n",[1350,10238,10239],{"class":1352,"line":1400},[1350,10240,1391],{"class":1356},[1350,10242,10243],{"class":1352,"line":1406},[1350,10244,1637],{"emptyLinePlaceholder":1058},[1350,10246,10247],{"class":1352,"line":1412},[1350,10248,10249],{"class":1356},"    subgraph union[\"Union\"]\n",[1350,10251,10252],{"class":1352,"line":1418},[1350,10253,10254],{"class":1356},"        G([\"SPOTIFY\"])\n",[1350,10256,10257],{"class":1352,"line":1423},[1350,10258,1391],{"class":1356},[1350,10260,10261],{"class":1352,"line":1429},[1350,10262,1637],{"emptyLinePlaceholder":1058},[1350,10264,10265],{"class":1352,"line":1435},[1350,10266,10267],{"class":1356},"    A --> G\n",[1350,10269,10270],{"class":1352,"line":1441},[1350,10271,10272],{"class":1356},"    B --> G\n",[1350,10274,10275],{"class":1352,"line":1447},[1350,10276,10277],{"class":1356},"    C --> G\n",[1350,10279,10280],{"class":1352,"line":1453},[1350,10281,10282],{"class":1356},"    D --> G\n",[1350,10284,10285],{"class":1352,"line":1459},[1350,10286,10287],{"class":1356},"    E --> G\n",[1350,10289,10290],{"class":1352,"line":1464},[1350,10291,10292],{"class":1356},"    F --> G\n",[746,10294,10295],{},"Here's what the union management interface looks like in practice - TIDAL alone has 10 variations, SoundCloud over 30:",[746,10297,10298],{},[10299,10300],"img",{"alt":10301,"src":10302},"Union management interface showing raw value mappings","/images/blog/musictechlab_blog_unions-admin-interface.webp",[746,10304,10305,10306,10309,10310,10313],{},"In production, this system maps ",[796,10307,10308],{},"830 raw tag values"," to just ",[796,10311,10312],{},"19 unions"," across three dimensions:",[768,10315,10317,10321,10325],{"className":10316},[771,772,773,774,775],[777,10318],{"description":10319,"title":10320},"Spotify, Amazon, YouTube, Pandora, Facebook, and more. Each with dozens of naming variations across sources.","Retailer unions",[777,10322],{"description":10323,"title":10324},"The label itself can appear under different names, abbreviations, or legal entity variations across distributors.","Label unions",[777,10326],{"description":10327,"title":10328},"Streaming, downloads, physical, sync - each distributor categorizes their services differently.","Service unions",[746,10330,10331],{},"When a file is imported, every raw value is checked against the union mappings. If it matches, the canonical name is stored alongside the original. If it doesn't, the raw value is preserved - and flagged for review.",[753,10333,10335],{"id":10334},"why-not-just-find-and-replace","Why not just find-and-replace?",[746,10337,10338],{},"Because new values appear constantly. A distributor adds a sub-brand. Another changes their internal naming. A third introduces a typo that persists for six months before anyone notices. Static find-and-replace breaks every time the data evolves.",[746,10340,10341],{},"Unions are dynamic. A non-technical user can add a new mapping through an admin interface - no code changes, no redeployment. The next import picks it up automatically.",[852,10343,10344],{},[746,10345,10346],{},"One real example: a distributor renamed their Spotify column from \"Spotify\" to \"Spotify AB\" mid-year. Without union mapping, this would have created a second \"Spotify\" in every report, splitting the data and making quarterly comparisons unreliable.",[746,10348,10349],{},[10299,10350],{"alt":10351,"src":10352},"Currency exchange and data normalization","/images/blog/musictechlab_blog_currency-exchange-data.webp",[753,10354,10356],{"id":10355},"the-cherry-on-top-currencies-and-territories","The cherry on top: currencies and territories",[746,10358,10359],{},"Even after normalizing names, two more problems remain.",[763,10361,10363],{"id":10362},"currencies","Currencies",[746,10365,10366],{},"Every distributor reports income in their own currency. And they don't even agree on what to call the currency column:",[1115,10368,10369,10381],{},[1118,10370,10371],{},[1121,10372,10373,10375,10378],{},[1124,10374,10087],{},[1124,10376,10377],{},"Column name",[1124,10379,10380],{},"Default currency",[1131,10382,10383,10395,10405,10416,10428,10440],{},[1121,10384,10385,10387,10392],{},[1136,10386,10097],{},[1136,10388,10389],{},[1234,10390,10391],{},"Original currency",[1136,10393,10394],{},"varies",[1121,10396,10397,10399,10402],{},[1136,10398,10107],{},[1136,10400,10401],{},"(file-level)",[1136,10403,10404],{},"GBP",[1121,10406,10407,10409,10414],{},[1136,10408,10117],{},[1136,10410,10411],{},[1234,10412,10413],{},"CURRENCY_CODE",[1136,10415,10404],{},[1121,10417,10418,10420,10425],{},[1136,10419,10127],{},[1136,10421,10422],{},[1234,10423,10424],{},"Preferred Currency",[1136,10426,10427],{},"USD",[1121,10429,10430,10433,10438],{},[1136,10431,10432],{},"Merlin",[1136,10434,10435],{},[1234,10436,10437],{},"Payable currency",[1136,10439,10394],{},[1121,10441,10442,10444,10449],{},[1136,10443,10145],{},[1136,10445,10446],{},[1234,10447,10448],{},"currency-code",[1136,10450,10394],{},[746,10452,10453],{},"To compare income across sources, every value needs to be converted to a single target currency using exchange rates tied to specific dates. A $1,000 Orchard payment and a £800 ADA payment aren't comparable until you normalize them.",[763,10455,10456],{"id":2064},"Territories",[746,10458,10459],{},"Countries seem straightforward - until you see how distributors label them:",[1115,10461,10462,10472],{},[1118,10463,10464],{},[1121,10465,10466,10469],{},[1124,10467,10468],{},"The same country",[1124,10470,10471],{},"Variations across sources",[1131,10473,10474,10492,10510],{},[1121,10475,10476,10479],{},[1136,10477,10478],{},"United States",[1136,10480,10481,799,10484,799,10487,799,10490],{},[1234,10482,10483],{},"US",[1234,10485,10486],{},"USA",[1234,10488,10489],{},"UNITED STATES",[1234,10491,10478],{},[1121,10493,10494,10497],{},[1136,10495,10496],{},"United Kingdom",[1136,10498,10499,799,10502,799,10505,799,10508],{},[1234,10500,10501],{},"UK",[1234,10503,10504],{},"GB",[1234,10506,10507],{},"UNITED KINGDOM (GB)",[1234,10509,10496],{},[1121,10511,10512,10515],{},[1136,10513,10514],{},"Czech Republic",[1136,10516,10517,799,10520,799,10523],{},[1234,10518,10519],{},"CZ",[1234,10521,10522],{},"CZECH REPUBLIC",[1234,10524,10525],{},"Czechia",[746,10527,10528],{},"Each variation needs to map to a standard two-letter code. Without this, \"show me all US streams\" misses half the data.",[753,10530,10532],{"id":10531},"three-layers-of-normalization","Three layers of normalization",[746,10534,10535],{},"Here's the full picture - what it actually takes to turn raw distributor data into something usable:",[768,10537,10539],{"className":10538},[10190,10191,775],[1342,10540,10542],{"className":1344,"code":10541,"language":1346,"meta":1033,"style":1033},"flowchart TD\n    subgraph layer1[\"Layer 1: File Formats\"]\n        A[\"18 adapters\"]\n        B[\"5 formats\"]\n        C[\"500+ files/year\"]\n    end\n\n    subgraph layer2[\"Layer 2: Name Normalization\"]\n        D[\"830 raw values\"]\n        E[\"19 unions\"]\n        F[\"3 dimensions\"]\n    end\n\n    subgraph layer3[\"Layer 3: Currency & Territory\"]\n        G[\"Multiple currencies\"]\n        H[\"Exchange rates\"]\n        I[\"Country code mapping\"]\n    end\n\n    subgraph result[\"Result\"]\n        J[(\"One clean dataset\")]\n    end\n\n    layer1 --> layer2\n    layer2 --> layer3\n    layer3 --> result\n",[1234,10543,10544,10549,10554,10559,10564,10569,10573,10577,10582,10587,10592,10597,10601,10605,10610,10615,10620,10625,10629,10633,10638,10643,10647,10651,10656,10661],{"__ignoreMap":1033},[1350,10545,10546],{"class":1352,"line":1353},[1350,10547,10548],{"class":1356},"flowchart TD\n",[1350,10550,10551],{"class":1352,"line":1034},[1350,10552,10553],{"class":1356},"    subgraph layer1[\"Layer 1: File Formats\"]\n",[1350,10555,10556],{"class":1352,"line":1039},[1350,10557,10558],{"class":1356},"        A[\"18 adapters\"]\n",[1350,10560,10561],{"class":1352,"line":1370},[1350,10562,10563],{"class":1356},"        B[\"5 formats\"]\n",[1350,10565,10566],{"class":1352,"line":1376},[1350,10567,10568],{"class":1356},"        C[\"500+ files/year\"]\n",[1350,10570,10571],{"class":1352,"line":1382},[1350,10572,1391],{"class":1356},[1350,10574,10575],{"class":1352,"line":1388},[1350,10576,1637],{"emptyLinePlaceholder":1058},[1350,10578,10579],{"class":1352,"line":1394},[1350,10580,10581],{"class":1356},"    subgraph layer2[\"Layer 2: Name Normalization\"]\n",[1350,10583,10584],{"class":1352,"line":1400},[1350,10585,10586],{"class":1356},"        D[\"830 raw values\"]\n",[1350,10588,10589],{"class":1352,"line":1406},[1350,10590,10591],{"class":1356},"        E[\"19 unions\"]\n",[1350,10593,10594],{"class":1352,"line":1412},[1350,10595,10596],{"class":1356},"        F[\"3 dimensions\"]\n",[1350,10598,10599],{"class":1352,"line":1418},[1350,10600,1391],{"class":1356},[1350,10602,10603],{"class":1352,"line":1423},[1350,10604,1637],{"emptyLinePlaceholder":1058},[1350,10606,10607],{"class":1352,"line":1429},[1350,10608,10609],{"class":1356},"    subgraph layer3[\"Layer 3: Currency & Territory\"]\n",[1350,10611,10612],{"class":1352,"line":1435},[1350,10613,10614],{"class":1356},"        G[\"Multiple currencies\"]\n",[1350,10616,10617],{"class":1352,"line":1441},[1350,10618,10619],{"class":1356},"        H[\"Exchange rates\"]\n",[1350,10621,10622],{"class":1352,"line":1447},[1350,10623,10624],{"class":1356},"        I[\"Country code mapping\"]\n",[1350,10626,10627],{"class":1352,"line":1453},[1350,10628,1391],{"class":1356},[1350,10630,10631],{"class":1352,"line":1459},[1350,10632,1637],{"emptyLinePlaceholder":1058},[1350,10634,10635],{"class":1352,"line":1464},[1350,10636,10637],{"class":1356},"    subgraph result[\"Result\"]\n",[1350,10639,10640],{"class":1352,"line":1470},[1350,10641,10642],{"class":1356},"        J[(\"One clean dataset\")]\n",[1350,10644,10645],{"class":1352,"line":1476},[1350,10646,1391],{"class":1356},[1350,10648,10649],{"class":1352,"line":1482},[1350,10650,1637],{"emptyLinePlaceholder":1058},[1350,10652,10653],{"class":1352,"line":2074},[1350,10654,10655],{"class":1356},"    layer1 --> layer2\n",[1350,10657,10658],{"class":1352,"line":2096},[1350,10659,10660],{"class":1356},"    layer2 --> layer3\n",[1350,10662,10663],{"class":1352,"line":2174},[1350,10664,10665],{"class":1356},"    layer3 --> result\n",[746,10667,10668],{},"Most teams get stuck at Layer 1 and never even reach the name and currency problems. But without all three layers, you can't answer basic questions like \"What were our total Spotify streams in Q3, in GBP?\"",[753,10670,10672],{"id":10671},"the-payoff","The payoff",[746,10674,10675],{},"After all three normalization layers, that question becomes trivial. Filter by retailer union, filter by date range, and every value is already in GBP. One query. One answer. No spreadsheet wrangling.",[746,10677,10678,10679,10682],{},"And once the data is clean, even non-technical users can get answers. We built an ",[1100,10680,10681],{"href":85},"AI-powered analytics dashboard"," that lets anyone type a question in plain English and get a chart back in seconds, no SQL required.",[746,10684,10685],{},"That's the difference between a collection of files and a data platform. We build the latter.",[3401,10687,10688],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":1033,"searchDepth":1034,"depth":1034,"links":10690},[10691,10692,10693,10694,10698,10699],{"id":10074,"depth":1034,"text":10075},{"id":10178,"depth":1034,"text":10179},{"id":10334,"depth":1034,"text":10335},{"id":10355,"depth":1034,"text":10356,"children":10695},[10696,10697],{"id":10362,"depth":1039,"text":10363},{"id":2064,"depth":1039,"text":10456},{"id":10531,"depth":1034,"text":10532},{"id":10671,"depth":1034,"text":10672},"2026-03-03T00:00:00.000Z","After solving the file format problem, the data inside is just as messy. Different names for the same platforms, labels, currencies, and territories. Here's how we normalize it.",[10703,10706,10709],{"question":10704,"answer":10705},"Why is music streaming data so inconsistent?","Every distributor uses their own naming conventions for retailers, labels, services, territories, and currencies. There is no shared standard, so the same platform can appear under dozens of different names across data sources.",{"question":10707,"answer":10708},"What is a data union in the context of music royalties?","A union is a normalization group that maps multiple raw values to one canonical name. For example, all variations of 'Spotify' across 18 distributors get mapped to a single SPOTIFY union, making cross-source analysis possible.",{"question":10710,"answer":10711},"How do currency differences affect music royalty reporting?","Distributors report income in different currencies (USD, GBP, EUR) and even label the currency column differently. To compare or aggregate income across sources, every value must be converted to a single target currency using exchange rates.",{"src":10713},"/images/blog/musictechlab_blog_830-ways-to-say-spotify.webp",{"enabled":1058,"items":10715},[10716,10718,10720,10723],{"text":10717,"icon":3449},"830 raw data values map to just 19 canonical names across retailers, labels, and services.",{"text":10719,"icon":780},"Three normalization layers are needed: file formats, name unions, and currency/territory mapping.",{"text":10721,"icon":10722},"Unions are dynamic and admin-editable, so new naming variations need no code changes.","i-lucide-settings",{"text":10724,"icon":3446},"Without all three layers, even basic queries like total Spotify streams in Q3 are impossible.",{},{"title":10727,"description":10728},"Normalizing Music Streaming Data: 830 Values, 19 Unions | MusicTech Lab","How we normalize 830 raw data values into 19 canonical names across retailers, labels, and services. Real-world music data normalization.",[1051,1074,8227,10730],"metadata","nkhKea-amz1NLgquVd-h3Qvo8Tq2jJzXtEx0Fmt3TeE",1780305241887]