[{"data":1,"prerenderedAt":7314},["ShallowReactive",2],{"navigation":3,"/blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse-post":710,"/blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse-surround":2217,"/blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse-related":2222},[4,70,203,300,697],{"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],{"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},"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":128,"path":129,"stem":130},"Office Hours with MusicTech Lab's DDEX Expert","/blog/music-data/ddex-office-hours-musictech","blog/music-data/ddex-office-hours-musictech",{"title":132,"path":133,"stem":134},"DDEX Open Source Projects Review","/blog/music-data/ddex-open-source-projects-review","blog/music-data/ddex-open-source-projects-review",{"title":136,"path":137,"stem":138},"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":140,"path":141,"stem":142},"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":144,"path":145,"stem":146},"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":148,"path":149,"stem":150},"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":152,"path":153,"stem":154},"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":156,"path":157,"stem":158},"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":160,"path":161,"stem":162},"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":164,"path":165,"stem":166},"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":168,"path":169,"stem":170},"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":172,"path":173,"stem":174},"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":176,"path":177,"stem":178},"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":180,"path":181,"stem":182},"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":184,"path":185,"stem":186},"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":188,"path":189,"stem":190},"Understanding the API First Approach","/blog/music-data/understanding-the-api-first-approach","blog/music-data/understanding-the-api-first-approach",{"title":192,"path":193,"stem":194},"The Voice Memo Graveyard Problem","/blog/music-data/voice-memo-graveyard-problem","blog/music-data/voice-memo-graveyard-problem",{"title":196,"path":197,"stem":198},"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":200,"path":201,"stem":202},"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":204,"path":205,"stem":206,"children":207,"page":69},"Newsletter","/blog/newsletter","blog/newsletter",[208,212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296],{"title":209,"path":210,"stem":211},"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":213,"path":214,"stem":215},"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":217,"path":218,"stem":219},"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":221,"path":222,"stem":223},"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":225,"path":226,"stem":227},"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":229,"path":230,"stem":231},"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":233,"path":234,"stem":235},"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":237,"path":238,"stem":239},"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":241,"path":242,"stem":243},"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":245,"path":246,"stem":247},"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":249,"path":250,"stem":251},"Music Industry Tech Openings (May 2025 Update)","/blog/newsletter/music-industry-tech-openings-may-2025","blog/newsletter/music-industry-tech-openings-may-2025",{"title":253,"path":254,"stem":255},"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":257,"path":258,"stem":259},"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":261,"path":262,"stem":263},"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":265,"path":266,"stem":267},"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":269,"path":270,"stem":271},"Feeling the MusicTech Momentum","/blog/newsletter/musictech-insights-2-curated-by-maciej-dulski","blog/newsletter/musictech-insights-2-curated-by-maciej-dulski",{"title":273,"path":274,"stem":275},"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":277,"path":278,"stem":279},"The Music Metadata Conundrum","/blog/newsletter/musictech-insights-4-curated-by-amanda-schupf","blog/newsletter/musictech-insights-4-curated-by-amanda-schupf",{"title":281,"path":282,"stem":283},"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":285,"path":286,"stem":287},"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":289,"path":290,"stem":291},"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":293,"path":294,"stem":295},"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":297,"path":298,"stem":299},"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":301,"path":302,"stem":303,"children":304,"page":69},"Software Development","/blog/software-development","blog/software-development",[305,309,313,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],{"title":306,"path":307,"stem":308},"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":310,"path":311,"stem":312},"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":314,"path":315,"stem":316},"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":318,"path":319,"stem":320},"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":322,"path":323,"stem":324},"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":326,"path":327,"stem":328},"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":330,"path":331,"stem":332},"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":334,"path":335,"stem":336},"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":338,"path":339,"stem":340},"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":342,"path":343,"stem":344},"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":346,"path":347,"stem":348},"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":350,"path":351,"stem":352},"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":354,"path":355,"stem":356},"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":358,"path":359,"stem":360},"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":362,"path":363,"stem":364},"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":366,"path":367,"stem":368},"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":370,"path":371,"stem":372},"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":374,"path":375,"stem":376},"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":378,"path":379,"stem":380},"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":382,"path":383,"stem":384},"Bravelab's team about productivity","/blog/software-development/bravelabs-team-about-productivity","blog/software-development/bravelabs-team-about-productivity",{"title":386,"path":387,"stem":388},"Braveloper","/blog/software-development/braveloper","blog/software-development/braveloper",{"title":390,"path":391,"stem":392},"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":394,"path":395,"stem":396},"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":398,"path":399,"stem":400},"Change Detection mechanism in Angular","/blog/software-development/change-detection-mechanism-in-angular","blog/software-development/change-detection-mechanism-in-angular",{"title":402,"path":403,"stem":404},"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":406,"path":407,"stem":408},"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":410,"path":411,"stem":412},"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":414,"path":415,"stem":416},"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":418,"path":419,"stem":420},"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":422,"path":423,"stem":424},"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":426,"path":427,"stem":428},"Dev Meeting 003: Web3 Primer","/blog/software-development/dev-meeting-003-web3-primer","blog/software-development/dev-meeting-003-web3-primer",{"title":430,"path":431,"stem":432},"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":434,"path":435,"stem":436},"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":438,"path":439,"stem":440},"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":442,"path":443,"stem":444},"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":446,"path":447,"stem":448},"Django-cms and GraphQL","/blog/software-development/django-cms-and-graphql","blog/software-development/django-cms-and-graphql",{"title":450,"path":451,"stem":452},"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":454,"path":455,"stem":456},"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":458,"path":459,"stem":460},"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":462,"path":463,"stem":464},"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":466,"path":467,"stem":468},"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":470,"path":471,"stem":472},"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":474,"path":475,"stem":476},"Hasura in action. How to use it with Django","/blog/software-development/hasura-in-action","blog/software-development/hasura-in-action",{"title":478,"path":479,"stem":480},"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":482,"path":483,"stem":484},"How does JavaScript work","/blog/software-development/how-does-javascript-work","blog/software-development/how-does-javascript-work",{"title":486,"path":487,"stem":488},"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":490,"path":491,"stem":492},"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":494,"path":495,"stem":496},"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":498,"path":499,"stem":500},"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":502,"path":503,"stem":504},"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":506,"path":507,"stem":508},"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":510,"path":511,"stem":512},"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":514,"path":515,"stem":516},"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":518,"path":519,"stem":520},"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":522,"path":523,"stem":524},"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":526,"path":527,"stem":528},"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":530,"path":531,"stem":532},"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":534,"path":535,"stem":536},"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":538,"path":539,"stem":540},"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":542,"path":543,"stem":544},"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":546,"path":547,"stem":548},"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":550,"path":551,"stem":552},"Introducing MusicTech Poland","/blog/software-development/introducing-musictech-poland","blog/software-development/introducing-musictech-poland",{"title":554,"path":555,"stem":556},"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":558,"path":559,"stem":560},"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":562,"path":563,"stem":564},"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":566,"path":567,"stem":568},"JS Frameworks: Trends and Opportunities","/blog/software-development/javascript-trending-frameworks-and-market-opportunities","blog/software-development/javascript-trending-frameworks-and-market-opportunities",{"title":570,"path":571,"stem":572},"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":574,"path":575,"stem":576},"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":578,"path":579,"stem":580},"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":582,"path":583,"stem":584},"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":586,"path":587,"stem":588},"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":590,"path":591,"stem":592},"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":594,"path":595,"stem":596},"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":598,"path":599,"stem":600},"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":602,"path":603,"stem":604},"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":606,"path":607,"stem":608},"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":610,"path":611,"stem":612},"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":614,"path":615,"stem":616},"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":618,"path":619,"stem":620},"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":622,"path":623,"stem":624},"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":626,"path":627,"stem":628},"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":630,"path":631,"stem":632},"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":634,"path":635,"stem":636},"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":638,"path":639,"stem":640},"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":642,"path":643,"stem":644},"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":646,"path":647,"stem":648},"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":650,"path":651,"stem":652},"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":654,"path":655,"stem":656},"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":658,"path":659,"stem":660},"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":662,"path":663,"stem":664},"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":666,"path":667,"stem":668},"What is a Discovery Document?","/blog/software-development/what-is-discovery-document","blog/software-development/what-is-discovery-document",{"title":670,"path":671,"stem":672},"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":674,"path":675,"stem":676},"What is a Watermarked Song?","/blog/software-development/what-is-watermarked-song","blog/software-development/what-is-watermarked-song",{"title":678,"path":679,"stem":680},"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":682,"path":683,"stem":684},"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":686,"path":687,"stem":688},"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":690,"path":691,"stem":692},"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":694,"path":695,"stem":696},"Why we use Sanity.io","/blog/software-development/why-we-use-sanity-io","blog/software-development/why-we-use-sanity-io",{"title":698,"path":699,"stem":700,"children":701,"page":69},"Sportstech","/blog/sportstech","blog/sportstech",[702,706],{"title":703,"path":704,"stem":705},"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":707,"path":708,"stem":709},"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":711,"title":196,"authors":712,"badge":718,"body":721,"category":2173,"client":2174,"date":2175,"description":2176,"extension":2177,"faq":2178,"featured":69,"featuredOrder":2174,"hidden":69,"image":2191,"keyTakeaways":2193,"meta":2205,"navigation":2194,"path":197,"seo":2206,"status":2174,"stem":198,"tags":2209,"teaser":2174,"__hash__":2216},"posts/blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse.md",[713],{"name":714,"to":715,"avatar":716},"Mariusz Smenzyk","https://www.linkedin.com/in/mariusz-smenzyk/",{"src":717},"/images/people/mariusz-smenzyk2.webp",{"label":719,"color":720},"Distribution","#0ea5e9",{"type":722,"value":723,"toc":2147},"minimark",[724,728,731,736,739,769,772,776,781,784,791,796,815,820,831,837,841,844,849,853,870,874,888,894,898,901,906,910,927,931,945,950,954,1140,1144,1151,1155,1216,1222,1226,1229,1318,1322,1408,1413,1417,1428,1432,1435,1439,1561,1608,1612,1722,1767,1771,1860,1893,1898,1902,1905,1975,1979,1982,1993,1996,2007,2010,2021,2025,2028,2070,2073,2077,2080,2085,2090,2095,2098,2102,2105,2138,2143],[725,726,727],"p",{},"Music companies sit on mountains of data. Streaming royalties from dozens of DSPs. Multi-currency transactions across territories. Financial records that need to reconcile between royalty systems and general ledgers. The database you choose to store and query this data shapes everything downstream: how fast your dashboards load, how much you pay per month, and whether your finance team can actually get answers without filing a ticket.",[725,729,730],{},"We have built music data systems on Elasticsearch, ClickHouse, and BigQuery. Each one solved a different problem. This article breaks down when to use which, with real pricing numbers and performance benchmarks so you can make the right call for your catalog.",[732,733,735],"h2",{"id":734},"what-makes-music-data-different","What makes music data different",[725,737,738],{},"Before comparing databases, it helps to understand why music data is uniquely challenging. A typical mid-size label deals with several patterns that push general-purpose databases to their limits.",[740,741,748,754,759,764],"div",{"className":742},[743,744,745,746,747],"grid","grid-cols-1","md:grid-cols-2","gap-4","my-8",[749,750],"spotlight-card",{"description":751,"icon":752,"title":753},"A single quarterly DSP statement can contain millions of rows. One track, dozens of territories, multiple income types.","i-lucide-layers","High Volume",[749,755],{"description":756,"icon":757,"title":758},"Revenue arrives in GBP, gets converted to USD, reported in EUR, and settled in the artist's local currency.","i-lucide-coins","Multi-Currency",[749,760],{"description":761,"icon":762,"title":763},"Track to release to label to artist to contract. Each level has its own royalty rates, reserves, and deductions.","i-lucide-git-branch","Complex Relationships",[749,765],{"description":766,"icon":767,"title":768},"Every transaction has two dates: when the DSP reported it (cash basis) and when the stream actually happened (accrual basis).","i-lucide-calendar-range","Dual Temporal",[725,770,771],{},"These patterns mean your database needs to handle complex joins, large aggregations, and flexible time-based queries. Not every database does this equally well.",[732,773,775],{"id":774},"the-three-contenders","The three contenders",[777,778,780],"h3",{"id":779},"bigquery-the-serverless-warehouse","BigQuery: the serverless warehouse",[725,782,783],{},"Google BigQuery is a fully managed, serverless data warehouse. You do not provision clusters, tune nodes, or worry about scaling. You write SQL, BigQuery figures out the rest.",[725,785,786,790],{},[787,788,789],"strong",{},"Architecture:"," Columnar storage with a distributed query engine. Separates storage and compute, so you pay independently for each. Data lives in Google Cloud Storage under the hood.",[725,792,793],{},[787,794,795],{},"What it does well for music companies:",[797,798,799,803,806,809,812],"ul",{},[800,801,802],"li",{},"Complex SQL joins across 15+ dimension tables (labels, artists, tracks, territories, DSPs)",[800,804,805],{},"dbt integration is first-class, with native support for the staging-intermediate-marts pattern",[800,807,808],{},"Handles 50M+ row fact tables without breaking a sweat",[800,810,811],{},"Native connectors to Looker, Sigma Computing, and Looker Studio for BI",[800,813,814],{},"Zero infrastructure management, your finance team never waits on DevOps",[725,816,817],{},[787,818,819],{},"Where it falls short:",[797,821,822,825,828],{},[800,823,824],{},"Query latency is typically 1-2 seconds, not sub-second",[800,826,827],{},"On-demand pricing can spike if analysts run unoptimized queries on large tables",[800,829,830],{},"Not ideal for high-concurrency, user-facing dashboards with hundreds of simultaneous users",[832,833,834],"tip",{},[725,835,836],{},"BigQuery offers a 1 TB/month free tier for queries. For a small label just getting started with analytics, you can run a meaningful data warehouse for nearly zero cost.",[777,838,840],{"id":839},"redshift-the-aws-powerhouse","Redshift: the AWS powerhouse",[725,842,843],{},"Amazon Redshift is a cluster-based columnar data warehouse built on PostgreSQL. It has been the default choice for AWS-native organizations for over a decade.",[725,845,846,848],{},[787,847,789],{}," Provisioned clusters with leader and compute nodes. You choose node types and quantities based on your workload. Redshift Serverless is also available as a pay-per-query alternative.",[725,850,851],{},[787,852,795],{},[797,854,855,858,861,864,867],{},[800,856,857],{},"Deep AWS ecosystem integration (S3, Glue, Lambda, Step Functions)",[800,859,860],{},"PostgreSQL compatibility means most SQL tools and libraries work out of the box",[800,862,863],{},"Mature ecosystem with extensive documentation and community support",[800,865,866],{},"Fine-grained cluster tuning for teams that want control over performance",[800,868,869],{},"Spectrum lets you query data directly in S3 without loading it",[725,871,872],{},[787,873,819],{},[797,875,876,879,882,885],{},[800,877,878],{},"Provisioned clusters cost money even when idle, a 24/7 dc2.large node runs ~$180/month",[800,880,881],{},"Requires capacity planning and occasional cluster resizing",[800,883,884],{},"More operational overhead than BigQuery",[800,886,887],{},"Scaling up means migrating to larger nodes or adding more, not automatic",[889,890,891],"warning",{},[725,892,893],{},"With Redshift provisioned clusters, you pay for compute whether anyone is running queries or not. If your team primarily queries during business hours, you are paying for 16 idle hours per day. Consider Redshift Serverless or BigQuery if your usage is bursty.",[777,895,897],{"id":896},"clickhouse-the-speed-demon","ClickHouse: the speed demon",[725,899,900],{},"ClickHouse is an open-source, column-oriented OLAP database designed for sub-second analytical queries. Originally built at Yandex to handle billions of page-view events, it has become the go-to choice for real-time analytics.",[725,902,903,905],{},[787,904,789],{}," Vectorized query execution with native compression. Processes data in columns rather than rows, which makes aggregations extremely fast. Available as self-hosted (open source) or ClickHouse Cloud (managed).",[725,907,908],{},[787,909,795],{},[797,911,912,915,918,921,924],{},[800,913,914],{},"Sub-second queries on billions of rows, 5-10x faster than BigQuery and Redshift on aggregations",[800,916,917],{},"Real-time ingestion with no batch delay",[800,919,920],{},"Excellent compression ratios reduce storage costs significantly",[800,922,923],{},"Ideal for streaming play-count dashboards, listener behavior analytics, and DSP performance monitoring",[800,925,926],{},"Cost-effective at scale, especially self-hosted",[725,928,929],{},[787,930,819],{},[797,932,933,936,939,942],{},[800,934,935],{},"Joins are more limited and less optimized than BigQuery or Redshift",[800,937,938],{},"Smaller BI tool ecosystem, Looker and Sigma have limited native support",[800,940,941],{},"Self-hosting requires operational expertise (replication, backups, upgrades)",[800,943,944],{},"Not designed for complex multi-table financial reporting",[832,946,947],{},[725,948,949],{},"ClickHouse compresses data aggressively, often achieving 5-10x compression. A 500 GB dataset in BigQuery might only consume 50-100 GB in ClickHouse, directly reducing storage costs.",[732,951,953],{"id":952},"head-to-head-comparison","Head-to-head comparison",[955,956,957,976],"table",{},[958,959,960],"thead",{},[961,962,963,967,970,973],"tr",{},[964,965,966],"th",{},"Feature",[964,968,969],{},"BigQuery",[964,971,972],{},"Redshift",[964,974,975],{},"ClickHouse",[977,978,979,996,1012,1028,1044,1060,1076,1092,1108,1124],"tbody",{},[961,980,981,987,990,993],{},[982,983,984],"td",{},[787,985,986],{},"Deployment",[982,988,989],{},"Serverless (managed)",[982,991,992],{},"Cluster or Serverless",[982,994,995],{},"Self-hosted or Cloud",[961,997,998,1003,1006,1009],{},[982,999,1000],{},[787,1001,1002],{},"Query latency",[982,1004,1005],{},"1-2 seconds",[982,1007,1008],{},"1-3 seconds",[982,1010,1011],{},"50-500 ms",[961,1013,1014,1019,1022,1025],{},[982,1015,1016],{},[787,1017,1018],{},"Joins",[982,1020,1021],{},"Full SQL, excellent",[982,1023,1024],{},"Full SQL, good",[982,1026,1027],{},"Limited, basic",[961,1029,1030,1035,1038,1041],{},[982,1031,1032],{},[787,1033,1034],{},"Scaling",[982,1036,1037],{},"Automatic",[982,1039,1040],{},"Manual (cluster) or auto (serverless)",[982,1042,1043],{},"Manual (self) or auto (cloud)",[961,1045,1046,1051,1054,1057],{},[982,1047,1048],{},[787,1049,1050],{},"dbt support",[982,1052,1053],{},"Native, first-class",[982,1055,1056],{},"Native, good",[982,1058,1059],{},"Community adapter",[961,1061,1062,1067,1070,1073],{},[982,1063,1064],{},[787,1065,1066],{},"BI ecosystem",[982,1068,1069],{},"Looker, Sigma, Tableau, Looker Studio",[982,1071,1072],{},"QuickSight, Tableau, Looker",[982,1074,1075],{},"Grafana, Metabase, Superset",[961,1077,1078,1083,1086,1089],{},[982,1079,1080],{},[787,1081,1082],{},"SQL dialect",[982,1084,1085],{},"GoogleSQL",[982,1087,1088],{},"PostgreSQL-based",[982,1090,1091],{},"ClickHouse SQL",[961,1093,1094,1099,1102,1105],{},[982,1095,1096],{},[787,1097,1098],{},"Best concurrency",[982,1100,1101],{},"~100 concurrent",[982,1103,1104],{},"~50 concurrent",[982,1106,1107],{},"1000+ concurrent",[961,1109,1110,1115,1118,1121],{},[982,1111,1112],{},[787,1113,1114],{},"Learning curve",[982,1116,1117],{},"Low",[982,1119,1120],{},"Medium",[982,1122,1123],{},"Medium-High",[961,1125,1126,1131,1134,1137],{},[982,1127,1128],{},[787,1129,1130],{},"Vendor lock-in",[982,1132,1133],{},"High (GCP)",[982,1135,1136],{},"High (AWS)",[982,1138,1139],{},"Low (open source)",[732,1141,1143],{"id":1142},"pricing-simulation-a-mid-size-music-label","Pricing simulation: a mid-size music label",[725,1145,1146,1147,1150],{},"Let's model real costs for a label with ",[787,1148,1149],{},"15 owned labels, 50 million royalty rows, and a 5-person analytics team"," running daily queries.",[777,1152,1154],{"id":1153},"storage-50m-rows-of-royalty-data-200-gb-uncompressed","Storage: 50M rows of royalty data (~200 GB uncompressed)",[955,1156,1157,1170],{},[958,1158,1159],{},[961,1160,1161,1164,1167],{},[964,1162,1163],{},"Database",[964,1165,1166],{},"Storage model",[964,1168,1169],{},"Monthly cost",[977,1171,1172,1187,1201],{},[961,1173,1174,1178,1181],{},[982,1175,1176],{},[787,1177,969],{},[982,1179,1180],{},"$20/TB active, $10/TB long-term",[982,1182,1183,1186],{},[787,1184,1185],{},"$4/mo"," (200 GB active)",[961,1188,1189,1193,1196],{},[982,1190,1191],{},[787,1192,972],{},[982,1194,1195],{},"Included in node cost (managed storage: $0.024/GB)",[982,1197,1198],{},[787,1199,1200],{},"$4.80/mo",[961,1202,1203,1208,1211],{},[982,1204,1205],{},[787,1206,1207],{},"ClickHouse Cloud",[982,1209,1210],{},"$25.30/TB (compressed, ~40 GB after compression)",[982,1212,1213],{},[787,1214,1215],{},"$1.01/mo",[1217,1218,1219],"note",{},[725,1220,1221],{},"Storage costs are nearly identical and trivially small at this scale. The real cost difference comes from compute.",[777,1223,1225],{"id":1224},"compute-daily-queries-by-a-5-person-team","Compute: daily queries by a 5-person team",[725,1227,1228],{},"Assumptions: 20 queries/day per analyst, ~5 GB scanned per query, 22 working days/month.",[955,1230,1231,1242],{},[958,1232,1233],{},[961,1234,1235,1237,1240],{},[964,1236,1163],{},[964,1238,1239],{},"Pricing model",[964,1241,1169],{},[977,1243,1244,1259,1274,1289,1304],{},[961,1245,1246,1251,1254],{},[982,1247,1248,1250],{},[787,1249,969],{}," (on-demand)",[982,1252,1253],{},"$6.25/TB scanned. 5 users x 20 queries x 5 GB x 22 days = ~11 TB/month",[982,1255,1256],{},[787,1257,1258],{},"$68/mo",[961,1260,1261,1266,1269],{},[982,1262,1263,1265],{},[787,1264,969],{}," (flat-rate)",[982,1267,1268],{},"100 slots at ~$0.04/slot-hour x 730 hours",[982,1270,1271],{},[787,1272,1273],{},"$2,920/mo",[961,1275,1276,1281,1284],{},[982,1277,1278,1280],{},[787,1279,972],{}," (dc2.large, 2 nodes)",[982,1282,1283],{},"$0.25/node/hour x 2 nodes x 730 hours",[982,1285,1286],{},[787,1287,1288],{},"$365/mo",[961,1290,1291,1296,1299],{},[982,1292,1293,1295],{},[787,1294,972],{}," (Serverless)",[982,1297,1298],{},"$0.375/RPU-hour, ~4 RPUs x ~8 active hours x 22 days",[982,1300,1301],{},[787,1302,1303],{},"$264/mo",[961,1305,1306,1310,1313],{},[982,1307,1308],{},[787,1309,1207],{},[982,1311,1312],{},"~$0.10/compute-unit, auto-scaling with idle pause",[982,1314,1315],{},[787,1316,1317],{},"$80-150/mo",[777,1319,1321],{"id":1320},"total-monthly-cost-estimate","Total monthly cost estimate",[955,1323,1324,1336],{},[958,1325,1326],{},[961,1327,1328,1330,1333],{},[964,1329,1163],{},[964,1331,1332],{},"Configuration",[964,1334,1335],{},"Total/month",[977,1337,1338,1352,1366,1380,1394],{},[961,1339,1340,1344,1347],{},[982,1341,1342],{},[787,1343,969],{},[982,1345,1346],{},"On-demand",[982,1348,1349],{},[787,1350,1351],{},"~$72",[961,1353,1354,1358,1361],{},[982,1355,1356],{},[787,1357,969],{},[982,1359,1360],{},"Flat-rate (100 slots)",[982,1362,1363],{},[787,1364,1365],{},"~$2,925",[961,1367,1368,1372,1375],{},[982,1369,1370],{},[787,1371,972],{},[982,1373,1374],{},"2x dc2.large (always on)",[982,1376,1377],{},[787,1378,1379],{},"~$370",[961,1381,1382,1386,1389],{},[982,1383,1384],{},[787,1385,972],{},[982,1387,1388],{},"Serverless",[982,1390,1391],{},[787,1392,1393],{},"~$269",[961,1395,1396,1400,1403],{},[982,1397,1398],{},[787,1399,1207],{},[982,1401,1402],{},"Dev tier with auto-pause",[982,1404,1405],{},[787,1406,1407],{},"~$100-150",[889,1409,1410],{},[725,1411,1412],{},"BigQuery on-demand looks cheapest, but costs scale linearly with query volume. If your team grows to 20 analysts running complex queries, you could easily hit $500-1,000/month. Partition your tables and use query cost controls to prevent surprises.",[777,1414,1416],{"id":1415},"annual-cost-projection","Annual cost projection",[1418,1419,1425],"pre",{"className":1420,"code":1422,"language":1423,"meta":1424},[1421],"language-text","                        Year 1      Year 2      Year 3\n                        (50M rows)  (65M rows)  (85M rows)\nBigQuery (on-demand)    $864        $1,080      $1,350\nRedshift (2x dc2)      $4,440      $4,440      $6,660*\nRedshift Serverless     $3,228      $3,900      $4,800\nClickHouse Cloud        $1,500      $1,800      $2,400\n\n* Redshift cluster upgrade needed at ~70M rows\n","text","",[1426,1427,1422],"code",{"__ignoreMap":1424},[732,1429,1431],{"id":1430},"performance-benchmarks-music-data-queries","Performance benchmarks: music data queries",[725,1433,1434],{},"Here is how each database handles typical music industry analytical queries on a 50M-row royalty dataset.",[777,1436,1438],{"id":1437},"query-1-revenue-by-label-last-12-months","Query 1: Revenue by label, last 12 months",[1418,1440,1444],{"className":1441,"code":1442,"language":1443,"meta":1424,"style":1424},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SELECT label_name, SUM(net_revenue) AS total_revenue\nFROM fact_revenue\nJOIN dim_label USING (label_key)\nJOIN dim_date USING (date_key)\nWHERE dim_date.date >= DATE_SUB(CURRENT_DATE(), INTERVAL 12 MONTH)\nGROUP BY label_name\nORDER BY total_revenue DESC\n","sql",[1426,1445,1446,1472,1481,1496,1509,1540,1549],{"__ignoreMap":1424},[1447,1448,1451,1455,1459,1463,1466,1469],"span",{"class":1449,"line":1450},"line",1,[1447,1452,1454],{"class":1453},"sbssI","SELECT",[1447,1456,1458],{"class":1457},"sTEyZ"," label_name, ",[1447,1460,1462],{"class":1461},"s2Zo4","SUM",[1447,1464,1465],{"class":1457},"(net_revenue) ",[1447,1467,1468],{"class":1453},"AS",[1447,1470,1471],{"class":1457}," total_revenue\n",[1447,1473,1475,1478],{"class":1449,"line":1474},2,[1447,1476,1477],{"class":1453},"FROM",[1447,1479,1480],{"class":1457}," fact_revenue\n",[1447,1482,1484,1487,1490,1493],{"class":1449,"line":1483},3,[1447,1485,1486],{"class":1453},"JOIN",[1447,1488,1489],{"class":1457}," dim_label ",[1447,1491,1492],{"class":1453},"USING",[1447,1494,1495],{"class":1457}," (label_key)\n",[1447,1497,1499,1501,1504,1506],{"class":1449,"line":1498},4,[1447,1500,1486],{"class":1453},[1447,1502,1503],{"class":1457}," dim_date ",[1447,1505,1492],{"class":1453},[1447,1507,1508],{"class":1457}," (date_key)\n",[1447,1510,1512,1515,1518,1522,1525,1528,1531,1534,1537],{"class":1449,"line":1511},5,[1447,1513,1514],{"class":1453},"WHERE",[1447,1516,1517],{"class":1457}," dim_date.date ",[1447,1519,1521],{"class":1520},"sMK4o",">=",[1447,1523,1524],{"class":1457}," DATE_SUB(CURRENT_DATE",[1447,1526,1527],{"class":1520},"()",[1447,1529,1530],{"class":1457},", INTERVAL ",[1447,1532,1533],{"class":1453},"12",[1447,1535,1536],{"class":1453}," MONTH",[1447,1538,1539],{"class":1457},")\n",[1447,1541,1543,1546],{"class":1449,"line":1542},6,[1447,1544,1545],{"class":1453},"GROUP BY",[1447,1547,1548],{"class":1457}," label_name\n",[1447,1550,1552,1555,1558],{"class":1449,"line":1551},7,[1447,1553,1554],{"class":1453},"ORDER BY",[1447,1556,1557],{"class":1457}," total_revenue ",[1447,1559,1560],{"class":1453},"DESC\n",[955,1562,1563,1575],{},[958,1564,1565],{},[961,1566,1567,1569,1572],{},[964,1568,1163],{},[964,1570,1571],{},"Execution time",[964,1573,1574],{},"Data scanned",[977,1576,1577,1587,1598],{},[961,1578,1579,1581,1584],{},[982,1580,969],{},[982,1582,1583],{},"1.2 seconds",[982,1585,1586],{},"3.8 GB",[961,1588,1589,1592,1595],{},[982,1590,1591],{},"Redshift (dc2.large x2)",[982,1593,1594],{},"1.8 seconds",[982,1596,1597],{},"Full table",[961,1599,1600,1602,1605],{},[982,1601,975],{},[982,1603,1604],{},"87 ms",[982,1606,1607],{},"0.4 GB (compressed)",[777,1609,1611],{"id":1610},"query-2-artist-recoupment-status-across-all-contracts","Query 2: Artist recoupment status across all contracts",[1418,1613,1615],{"className":1441,"code":1614,"language":1443,"meta":1424,"style":1424},"SELECT artist_name, contract_id,\n       SUM(revenue) AS earned,\n       advance_amount,\n       advance_amount - SUM(revenue) AS balance\nFROM fact_revenue\nJOIN dim_artist USING (artist_key)\nJOIN dim_contract USING (contract_key)\nGROUP BY artist_name, contract_id, advance_amount\nHAVING balance > 0\nORDER BY balance DESC\n",[1426,1616,1617,1624,1637,1642,1660,1666,1678,1690,1698,1713],{"__ignoreMap":1424},[1447,1618,1619,1621],{"class":1449,"line":1450},[1447,1620,1454],{"class":1453},[1447,1622,1623],{"class":1457}," artist_name, contract_id,\n",[1447,1625,1626,1629,1632,1634],{"class":1449,"line":1474},[1447,1627,1628],{"class":1461},"       SUM",[1447,1630,1631],{"class":1457},"(revenue) ",[1447,1633,1468],{"class":1453},[1447,1635,1636],{"class":1457}," earned,\n",[1447,1638,1639],{"class":1449,"line":1483},[1447,1640,1641],{"class":1457},"       advance_amount,\n",[1447,1643,1644,1647,1650,1653,1655,1657],{"class":1449,"line":1498},[1447,1645,1646],{"class":1457},"       advance_amount ",[1447,1648,1649],{"class":1520},"-",[1447,1651,1652],{"class":1461}," SUM",[1447,1654,1631],{"class":1457},[1447,1656,1468],{"class":1453},[1447,1658,1659],{"class":1457}," balance\n",[1447,1661,1662,1664],{"class":1449,"line":1511},[1447,1663,1477],{"class":1453},[1447,1665,1480],{"class":1457},[1447,1667,1668,1670,1673,1675],{"class":1449,"line":1542},[1447,1669,1486],{"class":1453},[1447,1671,1672],{"class":1457}," dim_artist ",[1447,1674,1492],{"class":1453},[1447,1676,1677],{"class":1457}," (artist_key)\n",[1447,1679,1680,1682,1685,1687],{"class":1449,"line":1551},[1447,1681,1486],{"class":1453},[1447,1683,1684],{"class":1457}," dim_contract ",[1447,1686,1492],{"class":1453},[1447,1688,1689],{"class":1457}," (contract_key)\n",[1447,1691,1693,1695],{"class":1449,"line":1692},8,[1447,1694,1545],{"class":1453},[1447,1696,1697],{"class":1457}," artist_name, contract_id, advance_amount\n",[1447,1699,1701,1704,1707,1710],{"class":1449,"line":1700},9,[1447,1702,1703],{"class":1453},"HAVING",[1447,1705,1706],{"class":1457}," balance ",[1447,1708,1709],{"class":1520},">",[1447,1711,1712],{"class":1453}," 0\n",[1447,1714,1716,1718,1720],{"class":1449,"line":1715},10,[1447,1717,1554],{"class":1453},[1447,1719,1706],{"class":1457},[1447,1721,1560],{"class":1453},[955,1723,1724,1735],{},[958,1725,1726],{},[961,1727,1728,1730,1732],{},[964,1729,1163],{},[964,1731,1571],{},[964,1733,1734],{},"Notes",[977,1736,1737,1747,1757],{},[961,1738,1739,1741,1744],{},[982,1740,969],{},[982,1742,1743],{},"2.1 seconds",[982,1745,1746],{},"Handles multi-table join well",[961,1748,1749,1751,1754],{},[982,1750,972],{},[982,1752,1753],{},"2.8 seconds",[982,1755,1756],{},"Comparable with sort keys",[961,1758,1759,1761,1764],{},[982,1760,975],{},[982,1762,1763],{},"450 ms",[982,1765,1766],{},"Slower due to joins, still fast",[777,1768,1770],{"id":1769},"query-3-real-time-play-counts-by-territory-1b-events","Query 3: Real-time play counts by territory (1B events)",[1418,1772,1774],{"className":1441,"code":1773,"language":1443,"meta":1424,"style":1424},"SELECT territory, COUNT(*) AS plays, SUM(revenue) AS revenue\nFROM streaming_events\nWHERE event_date >= today() - 7\nGROUP BY territory\nORDER BY plays DESC\nLIMIT 50\n",[1426,1775,1776,1809,1816,1836,1843,1852],{"__ignoreMap":1424},[1447,1777,1778,1780,1783,1786,1789,1792,1795,1797,1800,1802,1804,1806],{"class":1449,"line":1450},[1447,1779,1454],{"class":1453},[1447,1781,1782],{"class":1457}," territory, ",[1447,1784,1785],{"class":1461},"COUNT",[1447,1787,1788],{"class":1457},"(",[1447,1790,1791],{"class":1520},"*",[1447,1793,1794],{"class":1457},") ",[1447,1796,1468],{"class":1453},[1447,1798,1799],{"class":1457}," plays, ",[1447,1801,1462],{"class":1461},[1447,1803,1631],{"class":1457},[1447,1805,1468],{"class":1453},[1447,1807,1808],{"class":1457}," revenue\n",[1447,1810,1811,1813],{"class":1449,"line":1474},[1447,1812,1477],{"class":1453},[1447,1814,1815],{"class":1457}," streaming_events\n",[1447,1817,1818,1820,1823,1825,1828,1830,1833],{"class":1449,"line":1483},[1447,1819,1514],{"class":1453},[1447,1821,1822],{"class":1457}," event_date ",[1447,1824,1521],{"class":1520},[1447,1826,1827],{"class":1457}," today",[1447,1829,1527],{"class":1520},[1447,1831,1832],{"class":1520}," -",[1447,1834,1835],{"class":1453}," 7\n",[1447,1837,1838,1840],{"class":1449,"line":1498},[1447,1839,1545],{"class":1453},[1447,1841,1842],{"class":1457}," territory\n",[1447,1844,1845,1847,1850],{"class":1449,"line":1511},[1447,1846,1554],{"class":1453},[1447,1848,1849],{"class":1457}," plays ",[1447,1851,1560],{"class":1453},[1447,1853,1854,1857],{"class":1449,"line":1542},[1447,1855,1856],{"class":1453},"LIMIT",[1447,1858,1859],{"class":1453}," 50\n",[955,1861,1862,1870],{},[958,1863,1864],{},[961,1865,1866,1868],{},[964,1867,1163],{},[964,1869,1571],{},[977,1871,1872,1879,1886],{},[961,1873,1874,1876],{},[982,1875,969],{},[982,1877,1878],{},"3.4 seconds",[961,1880,1881,1883],{},[982,1882,972],{},[982,1884,1885],{},"4.1 seconds",[961,1887,1888,1890],{},[982,1889,975],{},[982,1891,1892],{},"120 ms",[1217,1894,1895],{},[725,1896,1897],{},"ClickHouse dominates on single-table aggregations. BigQuery and Redshift are better when you need complex joins across many dimension tables, which is the typical pattern for financial reporting.",[732,1899,1901],{"id":1900},"decision-framework","Decision framework",[725,1903,1904],{},"Choosing the right database is not about which one is \"best.\" It is about which one fits your specific workload, team, and infrastructure.",[740,1906,1909,1931,1953],{"className":1907},[743,744,1908,746,747],"md:grid-cols-3",[749,1910,1914],{"description":1911,"icon":1912,"title":1913},"Financial reporting, royalty reconciliation, small teams.","i-lucide-database","Choose BigQuery",[797,1915,1916,1919,1922,1925,1928],{},[800,1917,1918],{},"Reconciling royalty systems to GL",[800,1920,1921],{},"Executive dashboards with complex joins",[800,1923,1924],{},"dbt-powered transformation pipelines",[800,1926,1927],{},"Team has no dedicated DevOps",[800,1929,1930],{},"Already on Google Cloud",[749,1932,1936],{"description":1933,"icon":1934,"title":1935},"AWS-native orgs with existing data engineering teams.","i-lucide-cloud","Choose Redshift",[797,1937,1938,1941,1944,1947,1950],{},[800,1939,1940],{},"Deep AWS ecosystem (S3, Glue, Lambda)",[800,1942,1943],{},"Team already knows PostgreSQL",[800,1945,1946],{},"Need fine-grained performance tuning",[800,1948,1949],{},"Running other AWS analytics services",[800,1951,1952],{},"Want Spectrum for S3 data lake queries",[749,1954,1958],{"description":1955,"icon":1956,"title":1957},"Real-time analytics, high-concurrency dashboards.","i-lucide-zap","Choose ClickHouse",[797,1959,1960,1963,1966,1969,1972],{},[800,1961,1962],{},"Streaming play-count dashboards",[800,1964,1965],{},"Listener behavior analytics",[800,1967,1968],{},"DSP performance monitoring",[800,1970,1971],{},"User-facing analytics features",[800,1973,1974],{},"Need sub-second query response",[777,1976,1978],{"id":1977},"can-you-combine-them","Can you combine them?",[725,1980,1981],{},"Yes, and some of the most effective music data architectures do exactly that.",[725,1983,1984,1985,1988,1989,1992],{},"A practical combination: ",[787,1986,1987],{},"ClickHouse for real-time dashboards"," (streaming counts, live territory maps, DSP performance) feeding from event streams, plus ",[787,1990,1991],{},"BigQuery for financial reporting"," (royalty reconciliation, label P&Ls, catalog valuation) with dbt transformations on a daily batch cycle.",[725,1994,1995],{},"This works well when:",[797,1997,1998,2001,2004],{},[800,1999,2000],{},"Different teams have different latency requirements",[800,2002,2003],{},"Real-time event data and financial reporting serve different audiences",[800,2005,2006],{},"Your streaming volume justifies a dedicated OLAP engine",[725,2008,2009],{},"It becomes over-engineering when:",[797,2011,2012,2015,2018],{},[800,2013,2014],{},"Your team is small (under 5 people)",[800,2016,2017],{},"All queries are batch/daily",[800,2019,2020],{},"You do not have dedicated infrastructure engineers",[732,2022,2024],{"id":2023},"what-about-elasticsearch-and-dynamodb","What about Elasticsearch and DynamoDB?",[725,2026,2027],{},"These come up in conversations about music data, but they solve fundamentally different problems.",[955,2029,2030,2042],{},[958,2031,2032],{},[961,2033,2034,2036,2039],{},[964,2035,1163],{},[964,2037,2038],{},"Purpose",[964,2040,2041],{},"Music use case",[977,2043,2044,2057],{},[961,2045,2046,2051,2054],{},[982,2047,2048],{},[787,2049,2050],{},"Elasticsearch",[982,2052,2053],{},"Full-text search and log analytics",[982,2055,2056],{},"Catalog search (find tracks by title, ISRC, artist), log monitoring, operational dashboards",[961,2058,2059,2064,2067],{},[982,2060,2061],{},[787,2062,2063],{},"DynamoDB",[982,2065,2066],{},"Transactional key-value store",[982,2068,2069],{},"User accounts, playlist storage, session management, low-latency app backends",[725,2071,2072],{},"Neither is a data warehouse. You would not run royalty reconciliation on Elasticsearch or catalog valuation queries on DynamoDB. Know the difference, and use each tool in its lane.",[732,2074,2076],{"id":2075},"our-experience-at-musictech-lab","Our experience at MusicTech Lab",[725,2078,2079],{},"We have built music data systems across this entire spectrum.",[725,2081,2082,2084],{},[787,2083,2050],{}," powered our royalty search engine, processing data from 15+ distributors into a unified, searchable catalog. It excels at finding a specific transaction across 200 million records in milliseconds. But it cannot calculate a label P&L.",[725,2086,2087,2089],{},[787,2088,975],{}," drives our AI-powered analytics dashboard, where non-technical users type plain English questions and get charts back in seconds. Sub-second aggregation on tens of millions of rows makes the conversational experience feel instant.",[725,2091,2092,2094],{},[787,2093,969],{}," is the direction we are actively exploring for financial reporting and reconciliation, connecting royalty data to general ledger systems through dbt transformations. We are deep in the learning phase right now, studying dimensional modeling, star schemas, and the dbt ecosystem. The serverless model and native BI integrations make it a compelling foundation for label finance teams.",[725,2096,2097],{},"The lesson: there is no single \"best database for music.\" There is the right database for your specific workload. And sometimes the right answer is to start learning the tool before your next project demands it.",[732,2099,2101],{"id":2100},"getting-started","Getting started",[725,2103,2104],{},"If you are a music company evaluating databases for the first time, start here:",[2106,2107,2108,2114,2120,2126,2132],"ol",{},[800,2109,2110,2113],{},[787,2111,2112],{},"Map your workloads."," List every report, dashboard, and query your team runs. Categorize each as financial reporting, real-time analytics, or search.",[800,2115,2116,2119],{},[787,2117,2118],{},"Count your data."," How many rows today? What is your annual growth rate? A 10M-row label has very different needs than a 500M-row distributor.",[800,2121,2122,2125],{},[787,2123,2124],{},"Assess your team."," Do you have data engineers? DevOps? Or is your \"data team\" a finance analyst who knows Excel? This changes the answer dramatically.",[800,2127,2128,2131],{},[787,2129,2130],{},"Start serverless."," Unless you have a clear reason for provisioned infrastructure, BigQuery on-demand or ClickHouse Cloud with auto-pause will get you running faster and cheaper.",[800,2133,2134,2137],{},[787,2135,2136],{},"Design for the dimensional model first."," The database choice matters less than your data model. A well-designed star schema performs well on any of these three platforms. A poorly designed model performs badly on all of them.",[832,2139,2140],{},[725,2141,2142],{},"The most expensive database decision is not picking the wrong vendor. It is building without a proper data model and having to rebuild everything six months later. Invest the time upfront in your dimensional design.",[2144,2145,2146],"style",{},"html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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":1424,"searchDepth":1474,"depth":1474,"links":2148},[2149,2150,2155,2156,2162,2167,2170,2171,2172],{"id":734,"depth":1474,"text":735},{"id":774,"depth":1474,"text":775,"children":2151},[2152,2153,2154],{"id":779,"depth":1483,"text":780},{"id":839,"depth":1483,"text":840},{"id":896,"depth":1483,"text":897},{"id":952,"depth":1474,"text":953},{"id":1142,"depth":1474,"text":1143,"children":2157},[2158,2159,2160,2161],{"id":1153,"depth":1483,"text":1154},{"id":1224,"depth":1483,"text":1225},{"id":1320,"depth":1483,"text":1321},{"id":1415,"depth":1483,"text":1416},{"id":1430,"depth":1474,"text":1431,"children":2163},[2164,2165,2166],{"id":1437,"depth":1483,"text":1438},{"id":1610,"depth":1483,"text":1611},{"id":1769,"depth":1483,"text":1770},{"id":1900,"depth":1474,"text":1901,"children":2168},[2169],{"id":1977,"depth":1483,"text":1978},{"id":2023,"depth":1474,"text":2024},{"id":2075,"depth":1474,"text":2076},{"id":2100,"depth":1474,"text":2101},"music-data",null,"2026-04-23T00:00:00.000Z","A practical comparison of BigQuery, Redshift, and ClickHouse for music industry workloads, from royalty reporting to real-time streaming analytics.","md",[2179,2182,2185,2188],{"question":2180,"answer":2181},"Which database is best for music royalty reporting?","Google BigQuery is the strongest choice for royalty reporting. It handles complex SQL joins across fact and dimension tables, supports dbt natively for transformation pipelines, and its serverless model means you pay only for queries you run. Most music companies doing financial reporting and reconciliation across multiple systems choose BigQuery.",{"question":2183,"answer":2184},"Can ClickHouse replace BigQuery for music analytics?","ClickHouse excels at real-time analytical queries on large event datasets, like streaming play counts or listener behavior. However, it has more limited join capabilities and a smaller BI tool ecosystem. For financial reporting with complex multi-table joins, BigQuery is a better fit. Some companies use both: ClickHouse for real-time dashboards and BigQuery for financial reporting.",{"question":2186,"answer":2187},"How much does it cost to run a music data warehouse?","For a typical mid-size label with 50 million rows: BigQuery costs roughly $120-200/month (storage + queries). Redshift starts around $180-550/month for a minimal cluster. ClickHouse Cloud runs approximately $100-250/month depending on query volume. The biggest cost variable is query frequency, not storage.",{"question":2189,"answer":2190},"What about Elasticsearch or DynamoDB for music data?","Elasticsearch is a search engine, not a data warehouse. It is excellent for full-text catalog search and log analysis but cannot handle financial reporting or complex aggregations. DynamoDB is a transactional key-value store designed for low-latency lookups like user sessions or playlist storage. Neither is suitable as your primary analytics database.",{"src":2192},"/images/blog/musictechlab_blog_database-for-music-data.webp",{"enabled":2194,"items":2195},true,[2196,2198,2200,2202],{"text":2197,"icon":1912},"BigQuery is the best fit for financial reporting, royalty reconciliation, and teams without dedicated DevOps.",{"text":2199,"icon":1956},"ClickHouse delivers sub-second queries on billions of rows, ideal for real-time streaming dashboards.",{"text":2201,"icon":1934},"Redshift fits organizations already invested in the AWS ecosystem with existing data engineering teams.",{"text":2203,"icon":2204},"The right choice depends on your workload pattern, team size, and where your data already lives.","i-lucide-target",{},{"title":2207,"description":2208},"BigQuery vs Redshift vs ClickHouse for Music | MusicTech Lab","Practical comparison of three databases for music royalty data, streaming analytics, and financial reporting. Real pricing and performance numbers.",[2210,2211,2212,2173,2213,2214,2215],"bigquery","redshift","clickhouse","data-warehouse","analytics","royalties","9App3vpStCDB3fyT7KUwWU4K2oZ6GdeJD5s-gyCruWA",[2218,2220],{"title":192,"path":193,"stem":194,"description":2219,"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?",{"title":200,"path":201,"stem":202,"description":2221,"children":-1},"Creating a simple, custom audio player with main functionality is relatively easy. Are you sure? Let's see how we tackled this issue",[2223,4113,4834,7116],{"id":2224,"title":84,"authors":2225,"badge":2229,"body":2231,"category":2173,"client":2174,"date":4074,"description":4075,"extension":2177,"faq":4076,"featured":69,"featuredOrder":2174,"hidden":69,"image":4089,"keyTakeaways":4091,"meta":4103,"navigation":2194,"path":85,"seo":4104,"status":2174,"stem":86,"tags":4107,"teaser":2174,"__hash__":4112,"score":1498},"posts/blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama.md",[2226],{"name":2227,"to":715,"avatar":2228},"Mariusz Smenżyk",{"src":717},{"label":5,"color":2230},"#f59e0b",{"type":722,"value":2232,"toc":4059},[2233,2236,2239,2243,2246,2249,2266,2269,2274,2278,2281,2284,2298,2301,2406,2423,2427,2439,2442,2795,2798,2941,2944,2955,2959,2962,2965,2983,2986,3124,3127,3132,3136,3139,3145,3705,3708,3738,3744,3932,3939,3942,3947,3951,3954,3957,3960,3997,4001,4004,4008,4011,4015,4018,4022,4025,4029,4032,4046,4049,4053,4056],[725,2234,2235],{},"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.",[725,2237,2238],{},"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.",[732,2240,2242],{"id":2241},"the-problem-everybody-talks-about-at-conferences","The problem everybody talks about at conferences",[725,2244,2245],{},"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.",[725,2247,2248],{},"Today, that journey typically looks like this:",[2106,2250,2251,2254,2257,2260,2263],{},[800,2252,2253],{},"A business stakeholder writes an email or Slack message to the data team",[800,2255,2256],{},"The data team interprets the request, writes a SQL query, exports the results",[800,2258,2259],{},"Someone pastes the numbers into a spreadsheet and builds a chart",[800,2261,2262],{},"The chart goes back to the stakeholder, who asks a follow-up question",[800,2264,2265],{},"Repeat from step 1",[725,2267,2268],{},"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.",[1217,2270,2271],{},[725,2272,2273],{},"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.",[732,2275,2277],{"id":2276},"what-if-business-users-could-just-ask","What if business users could just ask?",[725,2279,2280],{},"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?",[725,2282,2283],{},"The workflow is simple:",[2106,2285,2286,2289,2292,2295],{},[800,2287,2288],{},"A user types a question in the chat interface",[800,2290,2291],{},"An LLM translates the question into a ClickHouse SQL query",[800,2293,2294],{},"The query runs against the analytics database (read-only, with safety guardrails)",[800,2296,2297],{},"Results come back as an interactive chart and a data table",[725,2299,2300],{},"No SQL knowledge required. No waiting for the data team. No spreadsheets.",[1418,2302,2306],{"className":2303,"code":2304,"language":2305,"meta":1424,"style":1424},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","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","mermaid",[1426,2307,2308,2313,2318,2323,2328,2333,2338,2343,2348,2353,2358,2364,2370,2376,2382,2388,2394,2400],{"__ignoreMap":1424},[1447,2309,2310],{"class":1449,"line":1450},[1447,2311,2312],{"class":1457},"sequenceDiagram\n",[1447,2314,2315],{"class":1449,"line":1474},[1447,2316,2317],{"class":1457},"    participant U as User\n",[1447,2319,2320],{"class":1449,"line":1483},[1447,2321,2322],{"class":1457},"    participant UI as Chat UI\n",[1447,2324,2325],{"class":1449,"line":1498},[1447,2326,2327],{"class":1457},"    participant LLM as LLM (Ollama/Claude)\n",[1447,2329,2330],{"class":1449,"line":1511},[1447,2331,2332],{"class":1457},"    participant SG as SQL Guard\n",[1447,2334,2335],{"class":1449,"line":1542},[1447,2336,2337],{"class":1457},"    participant CH as ClickHouse\n",[1447,2339,2340],{"class":1449,"line":1551},[1447,2341,2342],{"class":1457},"    participant CB as Chart Builder\n",[1447,2344,2345],{"class":1449,"line":1692},[1447,2346,2347],{"emptyLinePlaceholder":2194},"\n",[1447,2349,2350],{"class":1449,"line":1700},[1447,2351,2352],{"class":1457},"    U->>UI: \"Top 5 artists by income\"\n",[1447,2354,2355],{"class":1449,"line":1715},[1447,2356,2357],{"class":1457},"    UI->>LLM: System prompt + question\n",[1447,2359,2361],{"class":1449,"line":2360},11,[1447,2362,2363],{"class":1457},"    LLM-->>UI: Generated SQL query\n",[1447,2365,2367],{"class":1449,"line":2366},12,[1447,2368,2369],{"class":1457},"    UI->>SG: Validate SQL\n",[1447,2371,2373],{"class":1449,"line":2372},13,[1447,2374,2375],{"class":1457},"    SG-->>UI: Sanitised query (SELECT only, LIMIT enforced)\n",[1447,2377,2379],{"class":1449,"line":2378},14,[1447,2380,2381],{"class":1457},"    UI->>CH: Execute (read-only, 10s timeout)\n",[1447,2383,2385],{"class":1449,"line":2384},15,[1447,2386,2387],{"class":1457},"    CH-->>UI: Result rows + columns\n",[1447,2389,2391],{"class":1449,"line":2390},16,[1447,2392,2393],{"class":1457},"    UI->>CB: Build chart config\n",[1447,2395,2397],{"class":1449,"line":2396},17,[1447,2398,2399],{"class":1457},"    CB-->>UI: Chart.js config (type, labels, datasets)\n",[1447,2401,2403],{"class":1449,"line":2402},18,[1447,2404,2405],{"class":1457},"    UI-->>U: Interactive chart + data table\n",[740,2407,2409,2414,2418],{"className":2408},[743,744,1908,746,747],[749,2410],{"description":2411,"icon":2412,"title":2413},"Ask questions in plain English. No SQL, no training, no onboarding friction.","i-lucide-message-square","Natural Language Input",[749,2415],{"description":2416,"icon":1912,"title":2417},"Columnar storage handles millions of streaming records with sub-second query times.","ClickHouse Analytics",[749,2419],{"description":2420,"icon":2421,"title":2422},"Results render as interactive charts. Bar, line, and doughnut, selected automatically.","i-lucide-bar-chart-3","Instant Visualization",[732,2424,2426],{"id":2425},"why-this-matters-for-music-companies-specifically","Why this matters for music companies specifically",[725,2428,2429,2430,2434,2435,2438],{},"Music royalty data is uniquely complex. A single label might receive reports from ",[2431,2432,2433],"a",{"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 ",[2431,2436,2437],{"href":81},"retailer names need normalisation"," before they become queryable.",[725,2440,2441],{},"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:",[1418,2443,2448],{"className":2444,"code":2445,"filename":2446,"language":2447,"meta":1424,"style":1424},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","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","python",[1426,2449,2450,2471,2482,2512,2569,2573,2601,2605,2613,2626,2648,2673,2686,2697,2708,2719,2732,2743,2754,2766,2789],{"__ignoreMap":1424},[1447,2451,2452,2456,2459,2461,2464,2468],{"class":1449,"line":1450},[1447,2453,2455],{"class":2454},"spNyl","def",[1447,2457,2458],{"class":1461}," build_system_prompt",[1447,2460,1527],{"class":1520},[1447,2462,2463],{"class":1520}," ->",[1447,2465,2467],{"class":2466},"sBMFI"," str",[1447,2469,2470],{"class":1520},":\n",[1447,2472,2473,2476,2479],{"class":1449,"line":1474},[1447,2474,2475],{"class":1457},"    fields ",[1447,2477,2478],{"class":1520},"=",[1447,2480,2481],{"class":1520}," []\n",[1447,2483,2484,2488,2491,2494,2497,2500,2504,2506,2509],{"class":1449,"line":1483},[1447,2485,2487],{"class":2486},"s7zQu","    for",[1447,2489,2490],{"class":1457}," field ",[1447,2492,2493],{"class":2486},"in",[1447,2495,2496],{"class":1457}," StreamDataCH",[1447,2498,2499],{"class":1520},".",[1447,2501,2503],{"class":2502},"swJcz","_meta",[1447,2505,2499],{"class":1520},[1447,2507,2508],{"class":1461},"get_fields",[1447,2510,2511],{"class":1520},"():\n",[1447,2513,2514,2517,2519,2522,2524,2527,2531,2534,2537,2539,2542,2545,2548,2550,2552,2554,2557,2559,2562,2564,2567],{"class":1449,"line":1498},[1447,2515,2516],{"class":1457},"        fields",[1447,2518,2499],{"class":1520},[1447,2520,2521],{"class":1461},"append",[1447,2523,1788],{"class":1520},[1447,2525,2526],{"class":2454},"f",[1447,2528,2530],{"class":2529},"sfazB","\"  - ",[1447,2532,2533],{"class":1453},"{",[1447,2535,2536],{"class":1461},"field",[1447,2538,2499],{"class":1520},[1447,2540,2541],{"class":2502},"name",[1447,2543,2544],{"class":1453},"}",[1447,2546,2547],{"class":2529}," (",[1447,2549,2533],{"class":1453},[1447,2551,2536],{"class":1461},[1447,2553,2499],{"class":1520},[1447,2555,2556],{"class":1457},"__class__",[1447,2558,2499],{"class":1520},[1447,2560,2561],{"class":1457},"__name__",[1447,2563,2544],{"class":1453},[1447,2565,2566],{"class":2529},")\"",[1447,2568,1539],{"class":1520},[1447,2570,2571],{"class":1449,"line":1511},[1447,2572,2347],{"emptyLinePlaceholder":2194},[1447,2574,2575,2578,2580,2583,2586,2589,2591,2594,2596,2599],{"class":1449,"line":1542},[1447,2576,2577],{"class":1457},"    schema_block ",[1447,2579,2478],{"class":1520},[1447,2581,2582],{"class":1520}," \"",[1447,2584,2585],{"class":1457},"\\n",[1447,2587,2588],{"class":1520},"\"",[1447,2590,2499],{"class":1520},[1447,2592,2593],{"class":1461},"join",[1447,2595,1788],{"class":1520},[1447,2597,2598],{"class":1461},"fields",[1447,2600,1539],{"class":1520},[1447,2602,2603],{"class":1449,"line":1551},[1447,2604,2347],{"emptyLinePlaceholder":2194},[1447,2606,2607,2610],{"class":1449,"line":1692},[1447,2608,2609],{"class":2486},"    return",[1447,2611,2612],{"class":1520}," (\n",[1447,2614,2615,2618,2621,2623],{"class":1449,"line":1700},[1447,2616,2617],{"class":1520},"        \"",[1447,2619,2620],{"class":2529},"You are a SQL analyst for a music streaming analytics platform.",[1447,2622,2585],{"class":1457},[1447,2624,2625],{"class":1520},"\"\n",[1447,2627,2628,2631,2634,2636,2639,2641,2644,2646],{"class":1449,"line":1715},[1447,2629,2630],{"class":2454},"        f",[1447,2632,2633],{"class":2529},"\"The database is ClickHouse. There is one table: `",[1447,2635,2533],{"class":1453},[1447,2637,2638],{"class":1457},"TABLE",[1447,2640,2544],{"class":1453},[1447,2642,2643],{"class":2529},"`.",[1447,2645,2585],{"class":1457},[1447,2647,2625],{"class":2529},[1447,2649,2650,2652,2654,2656,2659,2662,2664,2667,2669,2671],{"class":1449,"line":2360},[1447,2651,2630],{"class":2454},[1447,2653,2588],{"class":2529},[1447,2655,2585],{"class":1457},[1447,2657,2658],{"class":2529},"## Schema",[1447,2660,2661],{"class":1457},"\\n\\n",[1447,2663,2533],{"class":1453},[1447,2665,2666],{"class":1457},"schema_block",[1447,2668,2544],{"class":1453},[1447,2670,2585],{"class":1457},[1447,2672,2625],{"class":2529},[1447,2674,2675,2677,2679,2682,2684],{"class":1449,"line":2366},[1447,2676,2617],{"class":1520},[1447,2678,2585],{"class":1457},[1447,2680,2681],{"class":2529},"## Domain hints",[1447,2683,2661],{"class":1457},[1447,2685,2625],{"class":1520},[1447,2687,2688,2690,2693,2695],{"class":1449,"line":2372},[1447,2689,2617],{"class":1520},[1447,2691,2692],{"class":2529},"- `final_income` is income converted to the target currency",[1447,2694,2585],{"class":1457},[1447,2696,2625],{"class":1520},[1447,2698,2699,2701,2704,2706],{"class":1449,"line":2378},[1447,2700,2617],{"class":1520},[1447,2702,2703],{"class":2529},"- `retailer_union` is the normalized retailer name",[1447,2705,2585],{"class":1457},[1447,2707,2625],{"class":1520},[1447,2709,2710,2712,2715,2717],{"class":1449,"line":2384},[1447,2711,2617],{"class":1520},[1447,2713,2714],{"class":2529},"- Always filter out empty strings when grouping by text fields",[1447,2716,2585],{"class":1457},[1447,2718,2625],{"class":1520},[1447,2720,2721,2723,2725,2728,2730],{"class":1449,"line":2390},[1447,2722,2617],{"class":1520},[1447,2724,2585],{"class":1457},[1447,2726,2727],{"class":2529},"## Output rules",[1447,2729,2661],{"class":1457},[1447,2731,2625],{"class":1520},[1447,2733,2734,2736,2739,2741],{"class":1449,"line":2396},[1447,2735,2617],{"class":1520},[1447,2737,2738],{"class":2529},"1. Output ONLY a single SELECT statement",[1447,2740,2585],{"class":1457},[1447,2742,2625],{"class":1520},[1447,2744,2745,2747,2750,2752],{"class":1449,"line":2402},[1447,2746,2617],{"class":1520},[1447,2748,2749],{"class":2529},"2. Always include a LIMIT clause (max 100)",[1447,2751,2585],{"class":1457},[1447,2753,2625],{"class":1520},[1447,2755,2757,2759,2762,2764],{"class":1449,"line":2756},19,[1447,2758,2617],{"class":1520},[1447,2760,2761],{"class":2529},"3. Never use INSERT, UPDATE, DELETE, DROP, or any DDL",[1447,2763,2585],{"class":1457},[1447,2765,2625],{"class":1520},[1447,2767,2769,2771,2773,2775,2778,2780,2782,2785,2787],{"class":1449,"line":2768},20,[1447,2770,2630],{"class":2454},[1447,2772,2588],{"class":2529},[1447,2774,2585],{"class":1457},[1447,2776,2777],{"class":2529},"## Examples",[1447,2779,2661],{"class":1457},[1447,2781,2533],{"class":1453},[1447,2783,2784],{"class":1457},"examples_block",[1447,2786,2544],{"class":1453},[1447,2788,2625],{"class":2529},[1447,2790,2792],{"class":1449,"line":2791},21,[1447,2793,2794],{"class":1520},"    )\n",[725,2796,2797],{},"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:",[1418,2799,2801],{"className":1441,"code":2800,"language":1443,"meta":1424,"style":1424},"-- 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",[1426,2802,2803,2809,2827,2834,2858,2870,2881,2892,2915,2920,2927],{"__ignoreMap":1424},[1447,2804,2805],{"class":1449,"line":1450},[1447,2806,2808],{"class":2807},"sHwdD","-- Example: top 5 artists with their retailers by income\n",[1447,2810,2811,2813,2816,2819,2822,2824],{"class":1449,"line":1474},[1447,2812,1454],{"class":1453},[1447,2814,2815],{"class":1457}," artist, retailer_union, ",[1447,2817,2818],{"class":1461},"sum",[1447,2820,2821],{"class":1457},"(final_income) ",[1447,2823,1468],{"class":1453},[1447,2825,2826],{"class":1457}," total_income\n",[1447,2828,2829,2831],{"class":1449,"line":1483},[1447,2830,1477],{"class":1453},[1447,2832,2833],{"class":1457}," analytics_streamdatach\n",[1447,2835,2836,2838,2841,2844,2847,2850,2853,2855],{"class":1449,"line":1498},[1447,2837,1514],{"class":1453},[1447,2839,2840],{"class":1457}," artist ",[1447,2842,2843],{"class":1520},"!=",[1447,2845,2846],{"class":1520}," ''",[1447,2848,2849],{"class":1453}," AND",[1447,2851,2852],{"class":1457}," retailer_union ",[1447,2854,2843],{"class":1520},[1447,2856,2857],{"class":1520}," ''\n",[1447,2859,2860,2863,2865,2868],{"class":1449,"line":1511},[1447,2861,2862],{"class":1453},"  AND",[1447,2864,2840],{"class":1457},[1447,2866,2867],{"class":1453},"IN",[1447,2869,2612],{"class":1457},[1447,2871,2872,2875,2877,2879],{"class":1449,"line":1542},[1447,2873,2874],{"class":1453},"    SELECT",[1447,2876,2840],{"class":1457},[1447,2878,1477],{"class":1453},[1447,2880,2833],{"class":1457},[1447,2882,2883,2886,2888,2890],{"class":1449,"line":1551},[1447,2884,2885],{"class":1453},"    WHERE",[1447,2887,2840],{"class":1457},[1447,2889,2843],{"class":1520},[1447,2891,2857],{"class":1520},[1447,2893,2894,2897,2899,2901,2904,2906,2909,2912],{"class":1449,"line":1692},[1447,2895,2896],{"class":1453},"    GROUP BY",[1447,2898,2840],{"class":1457},[1447,2900,1554],{"class":1453},[1447,2902,2903],{"class":1461}," sum",[1447,2905,2821],{"class":1457},[1447,2907,2908],{"class":1453},"DESC",[1447,2910,2911],{"class":1453}," LIMIT",[1447,2913,2914],{"class":1453}," 5\n",[1447,2916,2917],{"class":1449,"line":1700},[1447,2918,2919],{"class":1457},"  )\n",[1447,2921,2922,2924],{"class":1449,"line":1715},[1447,2923,1545],{"class":1453},[1447,2925,2926],{"class":1457}," artist, retailer_union\n",[1447,2928,2929,2931,2934,2936,2938],{"class":1449,"line":2360},[1447,2930,1554],{"class":1453},[1447,2932,2933],{"class":1457}," artist, total_income ",[1447,2935,2908],{"class":1453},[1447,2937,2911],{"class":1453},[1447,2939,2940],{"class":1453}," 100\n",[725,2942,2943],{},"The result is that even someone who has never seen a database can ask:",[797,2945,2946,2949,2952],{},[800,2947,2948],{},"\"Monthly income trend for 2024\" and get a line chart",[800,2950,2951],{},"\"Income by country\" and get a ranked bar chart",[800,2953,2954],{},"\"Top 10 tracks by streams\" and get a sortable table with the data",[732,2956,2958],{"id":2957},"data-privacy-keeping-everything-local","Data privacy: keeping everything local",[725,2960,2961],{},"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.",[725,2963,2964],{},"Our architecture solves this with a pluggable LLM design:",[740,2966,2968,2976],{"className":2967},[743,744,745,746,747],[749,2969,2973],{"description":2970,"icon":2971,"title":2972},"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)",[725,2974,2975],{},"Recommended for production environments handling sensitive royalty data. Runs models like Mistral 7B locally with 4GB of memory.",[749,2977,2980],{"description":2978,"icon":1934,"title":2979},"Anthropic's Claude API for higher-quality SQL generation. Swap in with a single environment variable change.","Claude API (Optional)",[725,2981,2982],{},"Useful for development, testing, or environments where data sensitivity is lower and query accuracy is the priority.",[725,2984,2985],{},"Switching between providers is a configuration change, not a code change. The factory pattern reads a single setting and returns the right client:",[1418,2987,2990],{"className":2444,"code":2988,"filename":2989,"language":2447,"meta":1424,"style":1424},"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",[1426,2991,2992,3008,3044,3048,3068,3085,3096,3100,3115],{"__ignoreMap":1424},[1447,2993,2994,2996,2999,3001,3003,3006],{"class":1449,"line":1450},[1447,2995,2455],{"class":2454},[1447,2997,2998],{"class":1461}," get_llm_provider",[1447,3000,1527],{"class":1520},[1447,3002,2463],{"class":1520},[1447,3004,3005],{"class":1457}," LLMProvider",[1447,3007,2470],{"class":1520},[1447,3009,3010,3013,3015,3018,3020,3023,3026,3028,3031,3033,3035,3037,3040,3042],{"class":1449,"line":1474},[1447,3011,3012],{"class":1457},"    provider ",[1447,3014,2478],{"class":1520},[1447,3016,3017],{"class":1461}," getattr",[1447,3019,1788],{"class":1520},[1447,3021,3022],{"class":1461},"settings",[1447,3024,3025],{"class":1520},",",[1447,3027,2582],{"class":1520},[1447,3029,3030],{"class":2529},"AI_DASHBOARD_PROVIDER",[1447,3032,2588],{"class":1520},[1447,3034,3025],{"class":1520},[1447,3036,2582],{"class":1520},[1447,3038,3039],{"class":2529},"ollama",[1447,3041,2588],{"class":1520},[1447,3043,1539],{"class":1520},[1447,3045,3046],{"class":1449,"line":1483},[1447,3047,2347],{"emptyLinePlaceholder":2194},[1447,3049,3050,3053,3056,3059,3061,3064,3066],{"class":1449,"line":1498},[1447,3051,3052],{"class":2486},"    if",[1447,3054,3055],{"class":1457}," provider ",[1447,3057,3058],{"class":1520},"==",[1447,3060,2582],{"class":1520},[1447,3062,3063],{"class":2529},"claude",[1447,3065,2588],{"class":1520},[1447,3067,2470],{"class":1520},[1447,3069,3070,3073,3076,3079,3082],{"class":1449,"line":1511},[1447,3071,3072],{"class":2486},"        from",[1447,3074,3075],{"class":1520}," .",[1447,3077,3078],{"class":1457},"claude ",[1447,3080,3081],{"class":2486},"import",[1447,3083,3084],{"class":1457}," ClaudeProvider\n",[1447,3086,3087,3090,3093],{"class":1449,"line":1542},[1447,3088,3089],{"class":2486},"        return",[1447,3091,3092],{"class":1461}," ClaudeProvider",[1447,3094,3095],{"class":1520},"()\n",[1447,3097,3098],{"class":1449,"line":1551},[1447,3099,2347],{"emptyLinePlaceholder":2194},[1447,3101,3102,3105,3107,3110,3112],{"class":1449,"line":1692},[1447,3103,3104],{"class":2486},"    from",[1447,3106,3075],{"class":1520},[1447,3108,3109],{"class":1457},"ollama ",[1447,3111,3081],{"class":2486},[1447,3113,3114],{"class":1457}," OllamaProvider\n",[1447,3116,3117,3119,3122],{"class":1449,"line":1700},[1447,3118,2609],{"class":2486},[1447,3120,3121],{"class":1461}," OllamaProvider",[1447,3123,3095],{"class":1520},[725,3125,3126],{},"The same application code, the same security layer, the same chart rendering.",[832,3128,3129],{},[725,3130,3131],{},"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.",[732,3133,3135],{"id":3134},"security-by-design-not-by-afterthought","Security by design, not by afterthought",[725,3137,3138],{},"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:",[725,3140,3141,3144],{},[787,3142,3143],{},"SQL Guard"," validates every query before execution. Here is the core of it:",[1418,3146,3149],{"className":2444,"code":3147,"filename":3148,"language":2447,"meta":1424,"style":1424},"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",[1426,3150,3151,3161,3210,3248,3295,3300,3304,3314,3322,3349,3381,3385,3417,3436,3453,3470,3474,3489,3504,3542,3566,3570,3576,3628,3651,3676,3681,3687],{"__ignoreMap":1424},[1447,3152,3153,3156,3158],{"class":1449,"line":1450},[1447,3154,3155],{"class":1457},"BLOCKED_KEYWORDS ",[1447,3157,2478],{"class":1520},[1447,3159,3160],{"class":1520}," [\n",[1447,3162,3163,3166,3169,3171,3173,3175,3178,3180,3182,3184,3187,3189,3191,3193,3196,3198,3200,3202,3205,3207],{"class":1449,"line":1474},[1447,3164,3165],{"class":1520},"    \"",[1447,3167,3168],{"class":2529},"INSERT",[1447,3170,2588],{"class":1520},[1447,3172,3025],{"class":1520},[1447,3174,2582],{"class":1520},[1447,3176,3177],{"class":2529},"UPDATE",[1447,3179,2588],{"class":1520},[1447,3181,3025],{"class":1520},[1447,3183,2582],{"class":1520},[1447,3185,3186],{"class":2529},"DELETE",[1447,3188,2588],{"class":1520},[1447,3190,3025],{"class":1520},[1447,3192,2582],{"class":1520},[1447,3194,3195],{"class":2529},"DROP",[1447,3197,2588],{"class":1520},[1447,3199,3025],{"class":1520},[1447,3201,2582],{"class":1520},[1447,3203,3204],{"class":2529},"ALTER",[1447,3206,2588],{"class":1520},[1447,3208,3209],{"class":1520},",\n",[1447,3211,3212,3214,3217,3219,3221,3223,3226,3228,3230,3232,3235,3237,3239,3241,3244,3246],{"class":1449,"line":1483},[1447,3213,3165],{"class":1520},[1447,3215,3216],{"class":2529},"TRUNCATE",[1447,3218,2588],{"class":1520},[1447,3220,3025],{"class":1520},[1447,3222,2582],{"class":1520},[1447,3224,3225],{"class":2529},"CREATE",[1447,3227,2588],{"class":1520},[1447,3229,3025],{"class":1520},[1447,3231,2582],{"class":1520},[1447,3233,3234],{"class":2529},"GRANT",[1447,3236,2588],{"class":1520},[1447,3238,3025],{"class":1520},[1447,3240,2582],{"class":1520},[1447,3242,3243],{"class":2529},"REVOKE",[1447,3245,2588],{"class":1520},[1447,3247,3209],{"class":1520},[1447,3249,3250,3252,3255,3257,3259,3261,3264,3266,3268,3270,3273,3275,3277,3279,3282,3284,3286,3288,3291,3293],{"class":1449,"line":1498},[1447,3251,3165],{"class":1520},[1447,3253,3254],{"class":2529},"ATTACH",[1447,3256,2588],{"class":1520},[1447,3258,3025],{"class":1520},[1447,3260,2582],{"class":1520},[1447,3262,3263],{"class":2529},"DETACH",[1447,3265,2588],{"class":1520},[1447,3267,3025],{"class":1520},[1447,3269,2582],{"class":1520},[1447,3271,3272],{"class":2529},"RENAME",[1447,3274,2588],{"class":1520},[1447,3276,3025],{"class":1520},[1447,3278,2582],{"class":1520},[1447,3280,3281],{"class":2529},"OPTIMIZE",[1447,3283,2588],{"class":1520},[1447,3285,3025],{"class":1520},[1447,3287,2582],{"class":1520},[1447,3289,3290],{"class":2529},"KILL",[1447,3292,2588],{"class":1520},[1447,3294,3209],{"class":1520},[1447,3296,3297],{"class":1449,"line":1511},[1447,3298,3299],{"class":1520},"]\n",[1447,3301,3302],{"class":1449,"line":1542},[1447,3303,2347],{"emptyLinePlaceholder":2194},[1447,3305,3306,3309,3312],{"class":1449,"line":1551},[1447,3307,3308],{"class":2454},"class",[1447,3310,3311],{"class":2466}," SQLGuard",[1447,3313,2470],{"class":1520},[1447,3315,3316,3319],{"class":1449,"line":1692},[1447,3317,3318],{"class":1520},"    @",[1447,3320,3321],{"class":2466},"staticmethod\n",[1447,3323,3324,3327,3330,3332,3335,3338,3340,3343,3345,3347],{"class":1449,"line":1700},[1447,3325,3326],{"class":2454},"    def",[1447,3328,3329],{"class":1461}," validate",[1447,3331,1788],{"class":1520},[1447,3333,1443],{"class":3334},"sHdIc",[1447,3336,3337],{"class":1520},":",[1447,3339,2467],{"class":2466},[1447,3341,3342],{"class":1520},")",[1447,3344,2463],{"class":1520},[1447,3346,2467],{"class":2466},[1447,3348,2470],{"class":1520},[1447,3350,3351,3354,3356,3359,3361,3364,3366,3368,3371,3373,3376,3379],{"class":1449,"line":1715},[1447,3352,3353],{"class":1457},"        sql ",[1447,3355,2478],{"class":1520},[1447,3357,3358],{"class":1457}," sql",[1447,3360,2499],{"class":1520},[1447,3362,3363],{"class":1461},"rstrip",[1447,3365,1788],{"class":1520},[1447,3367,2588],{"class":1520},[1447,3369,3370],{"class":2529},";",[1447,3372,2588],{"class":1520},[1447,3374,3375],{"class":1520},").",[1447,3377,3378],{"class":1461},"strip",[1447,3380,3095],{"class":1520},[1447,3382,3383],{"class":1449,"line":2360},[1447,3384,2347],{"emptyLinePlaceholder":2194},[1447,3386,3387,3390,3393,3395,3397,3400,3403,3406,3408,3410,3412,3414],{"class":1449,"line":2366},[1447,3388,3389],{"class":2486},"        if",[1447,3391,3392],{"class":1520}," not",[1447,3394,3358],{"class":1457},[1447,3396,2499],{"class":1520},[1447,3398,3399],{"class":1461},"upper",[1447,3401,3402],{"class":1520},"().",[1447,3404,3405],{"class":1461},"startswith",[1447,3407,1788],{"class":1520},[1447,3409,2588],{"class":1520},[1447,3411,1454],{"class":2529},[1447,3413,2588],{"class":1520},[1447,3415,3416],{"class":1520},"):\n",[1447,3418,3419,3422,3425,3427,3429,3432,3434],{"class":1449,"line":2372},[1447,3420,3421],{"class":2486},"            raise",[1447,3423,3424],{"class":1461}," SQLGuardError",[1447,3426,1788],{"class":1520},[1447,3428,2588],{"class":1520},[1447,3430,3431],{"class":2529},"Only SELECT queries are allowed",[1447,3433,2588],{"class":1520},[1447,3435,1539],{"class":1520},[1447,3437,3438,3440,3442,3444,3446,3449,3451],{"class":1449,"line":2378},[1447,3439,3389],{"class":2486},[1447,3441,2582],{"class":1520},[1447,3443,3370],{"class":2529},[1447,3445,2588],{"class":1520},[1447,3447,3448],{"class":1520}," in",[1447,3450,3358],{"class":1457},[1447,3452,2470],{"class":1520},[1447,3454,3455,3457,3459,3461,3463,3466,3468],{"class":1449,"line":2384},[1447,3456,3421],{"class":2486},[1447,3458,3424],{"class":1461},[1447,3460,1788],{"class":1520},[1447,3462,2588],{"class":1520},[1447,3464,3465],{"class":2529},"Multiple statements are not allowed",[1447,3467,2588],{"class":1520},[1447,3469,1539],{"class":1520},[1447,3471,3472],{"class":1449,"line":2390},[1447,3473,2347],{"emptyLinePlaceholder":2194},[1447,3475,3476,3479,3481,3483,3485,3487],{"class":1449,"line":2396},[1447,3477,3478],{"class":1457},"        sql_upper ",[1447,3480,2478],{"class":1520},[1447,3482,3358],{"class":1457},[1447,3484,2499],{"class":1520},[1447,3486,3399],{"class":1461},[1447,3488,3095],{"class":1520},[1447,3490,3491,3494,3497,3499,3502],{"class":1449,"line":2402},[1447,3492,3493],{"class":2486},"        for",[1447,3495,3496],{"class":1457}," keyword ",[1447,3498,2493],{"class":2486},[1447,3500,3501],{"class":1457}," BLOCKED_KEYWORDS",[1447,3503,2470],{"class":1520},[1447,3505,3506,3509,3512,3514,3517,3519,3522,3525,3527,3530,3532,3535,3537,3540],{"class":1449,"line":2756},[1447,3507,3508],{"class":2486},"            if",[1447,3510,3511],{"class":1457}," re",[1447,3513,2499],{"class":1520},[1447,3515,3516],{"class":1461},"search",[1447,3518,1788],{"class":1520},[1447,3520,3521],{"class":2454},"rf",[1447,3523,3524],{"class":2529},"\"\\b",[1447,3526,2533],{"class":1453},[1447,3528,3529],{"class":1461},"keyword",[1447,3531,2544],{"class":1453},[1447,3533,3534],{"class":2529},"\\b\"",[1447,3536,3025],{"class":1520},[1447,3538,3539],{"class":1461}," sql_upper",[1447,3541,3416],{"class":1520},[1447,3543,3544,3547,3549,3551,3553,3556,3558,3560,3562,3564],{"class":1449,"line":2768},[1447,3545,3546],{"class":2486},"                raise",[1447,3548,3424],{"class":1461},[1447,3550,1788],{"class":1520},[1447,3552,2526],{"class":2454},[1447,3554,3555],{"class":2529},"\"Forbidden keyword: ",[1447,3557,2533],{"class":1453},[1447,3559,3529],{"class":1461},[1447,3561,2544],{"class":1453},[1447,3563,2588],{"class":2529},[1447,3565,1539],{"class":1520},[1447,3567,3568],{"class":1449,"line":2791},[1447,3569,2347],{"emptyLinePlaceholder":2194},[1447,3571,3573],{"class":1449,"line":3572},22,[1447,3574,3575],{"class":2807},"        # Only allow the analytics table\n",[1447,3577,3579,3581,3584,3586,3588,3590,3593,3595,3598,3601,3603,3606,3608,3610,3613,3616,3619,3622,3624,3626],{"class":1449,"line":3578},23,[1447,3580,3493],{"class":2486},[1447,3582,3583],{"class":1457}," table ",[1447,3585,2493],{"class":2486},[1447,3587,3511],{"class":1457},[1447,3589,2499],{"class":1520},[1447,3591,3592],{"class":1461},"findall",[1447,3594,1788],{"class":1520},[1447,3596,3597],{"class":2454},"r",[1447,3599,3600],{"class":1520},"\"(?:",[1447,3602,1477],{"class":2529},[1447,3604,3605],{"class":1520},"|",[1447,3607,1486],{"class":2529},[1447,3609,3342],{"class":1520},[1447,3611,3612],{"class":2529},"\\s",[1447,3614,3615],{"class":1520},"+(",[1447,3617,3618],{"class":2529},"\\w",[1447,3620,3621],{"class":1520},"+)\"",[1447,3623,3025],{"class":1520},[1447,3625,3539],{"class":1461},[1447,3627,3416],{"class":1520},[1447,3629,3631,3633,3636,3638,3641,3643,3646,3649],{"class":1449,"line":3630},24,[1447,3632,3508],{"class":2486},[1447,3634,3635],{"class":1457}," table",[1447,3637,2499],{"class":1520},[1447,3639,3640],{"class":1461},"lower",[1447,3642,1527],{"class":1520},[1447,3644,3645],{"class":1520}," !=",[1447,3647,3648],{"class":1457}," ALLOWED_TABLE",[1447,3650,2470],{"class":1520},[1447,3652,3654,3656,3658,3660,3662,3665,3667,3669,3671,3674],{"class":1449,"line":3653},25,[1447,3655,3546],{"class":2486},[1447,3657,3424],{"class":1461},[1447,3659,1788],{"class":1520},[1447,3661,2526],{"class":2454},[1447,3663,3664],{"class":2529},"\"Table '",[1447,3666,2533],{"class":1453},[1447,3668,955],{"class":1461},[1447,3670,2544],{"class":1453},[1447,3672,3673],{"class":2529},"' is not allowed\"",[1447,3675,1539],{"class":1520},[1447,3677,3679],{"class":1449,"line":3678},26,[1447,3680,2347],{"emptyLinePlaceholder":2194},[1447,3682,3684],{"class":1449,"line":3683},27,[1447,3685,3686],{"class":2807},"        # Enforce LIMIT \u003C= 100\n",[1447,3688,3690,3692,3695,3697,3699,3701,3703],{"class":1449,"line":3689},28,[1447,3691,3089],{"class":2486},[1447,3693,3694],{"class":1461}," _enforce_limit",[1447,3696,1788],{"class":1520},[1447,3698,1443],{"class":1461},[1447,3700,3025],{"class":1520},[1447,3702,3539],{"class":1461},[1447,3704,1539],{"class":1520},[725,3706,3707],{},"Key constraints enforced:",[797,3709,3710,3726,3729,3735],{},[800,3711,3712,3713,3715,3716,3718,3719,3718,3721,3718,3723,3725],{},"Only ",[1426,3714,1454],{}," statements are allowed. Any ",[1426,3717,3168],{},", ",[1426,3720,3177],{},[1426,3722,3186],{},[1426,3724,3195],{},", or DDL keyword is blocked",[800,3727,3728],{},"Queries are restricted to a single analytics table (no access to user accounts, credentials, or other application data)",[800,3730,3731,3732,3734],{},"Result sets are capped at 100 rows, with ",[1426,3733,1856],{}," enforced automatically",[800,3736,3737],{},"Comments, semicolons, and multi-statement queries are stripped or rejected",[725,3739,3740,3743],{},[787,3741,3742],{},"Read-only execution"," provides a second layer. The ClickHouse query runs over HTTP with safety parameters baked into every request:",[1418,3745,3748],{"className":2444,"code":3746,"filename":3747,"language":2447,"meta":1424,"style":1424},"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",[1426,3749,3750,3768,3796,3804,3831,3851,3871,3891,3911,3916,3928],{"__ignoreMap":1424},[1447,3751,3752,3755,3757,3760,3762,3765],{"class":1449,"line":1450},[1447,3753,3754],{"class":1457},"response ",[1447,3756,2478],{"class":1520},[1447,3758,3759],{"class":1457}," httpx",[1447,3761,2499],{"class":1520},[1447,3763,3764],{"class":1461},"post",[1447,3766,3767],{"class":1520},"(\n",[1447,3769,3770,3773,3776,3778,3781,3783,3785,3787,3790,3792,3794],{"class":1449,"line":1474},[1447,3771,3772],{"class":2454},"    f",[1447,3774,3775],{"class":2529},"\"http://",[1447,3777,2533],{"class":1453},[1447,3779,3780],{"class":1461},"host",[1447,3782,2544],{"class":1453},[1447,3784,3337],{"class":2529},[1447,3786,2533],{"class":1453},[1447,3788,3789],{"class":1461},"port",[1447,3791,2544],{"class":1453},[1447,3793,2588],{"class":2529},[1447,3795,3209],{"class":1520},[1447,3797,3798,3801],{"class":1449,"line":1483},[1447,3799,3800],{"class":3334},"    params",[1447,3802,3803],{"class":1520},"={\n",[1447,3805,3806,3808,3811,3813,3815,3818,3820,3822,3824,3826,3829],{"class":1449,"line":1498},[1447,3807,2617],{"class":1520},[1447,3809,3810],{"class":2529},"query",[1447,3812,2588],{"class":1520},[1447,3814,3337],{"class":1520},[1447,3816,3817],{"class":2454}," f",[1447,3819,2588],{"class":2529},[1447,3821,2533],{"class":1453},[1447,3823,1443],{"class":1461},[1447,3825,2544],{"class":1453},[1447,3827,3828],{"class":2529}," FORMAT JSON\"",[1447,3830,3209],{"class":1520},[1447,3832,3833,3835,3838,3840,3842,3844,3847,3849],{"class":1449,"line":1511},[1447,3834,2617],{"class":1520},[1447,3836,3837],{"class":2529},"database",[1447,3839,2588],{"class":1520},[1447,3841,3337],{"class":1520},[1447,3843,2582],{"class":1520},[1447,3845,3846],{"class":2529},"default",[1447,3848,2588],{"class":1520},[1447,3850,3209],{"class":1520},[1447,3852,3853,3855,3858,3860,3862,3864,3867,3869],{"class":1449,"line":1542},[1447,3854,2617],{"class":1520},[1447,3856,3857],{"class":2529},"readonly",[1447,3859,2588],{"class":1520},[1447,3861,3337],{"class":1520},[1447,3863,2582],{"class":1520},[1447,3865,3866],{"class":2529},"1",[1447,3868,2588],{"class":1520},[1447,3870,3209],{"class":1520},[1447,3872,3873,3875,3878,3880,3882,3884,3887,3889],{"class":1449,"line":1551},[1447,3874,2617],{"class":1520},[1447,3876,3877],{"class":2529},"max_execution_time",[1447,3879,2588],{"class":1520},[1447,3881,3337],{"class":1520},[1447,3883,2582],{"class":1520},[1447,3885,3886],{"class":2529},"10",[1447,3888,2588],{"class":1520},[1447,3890,3209],{"class":1520},[1447,3892,3893,3895,3898,3900,3902,3904,3907,3909],{"class":1449,"line":1692},[1447,3894,2617],{"class":1520},[1447,3896,3897],{"class":2529},"max_result_rows",[1447,3899,2588],{"class":1520},[1447,3901,3337],{"class":1520},[1447,3903,2582],{"class":1520},[1447,3905,3906],{"class":2529},"100",[1447,3908,2588],{"class":1520},[1447,3910,3209],{"class":1520},[1447,3912,3913],{"class":1449,"line":1700},[1447,3914,3915],{"class":1520},"    },\n",[1447,3917,3918,3921,3923,3926],{"class":1449,"line":1715},[1447,3919,3920],{"class":3334},"    timeout",[1447,3922,2478],{"class":1520},[1447,3924,3925],{"class":1453},"15",[1447,3927,3209],{"class":1520},[1447,3929,3930],{"class":1449,"line":2360},[1447,3931,1539],{"class":1520},[725,3933,3934,3935,3938],{},"Even if SQL Guard missed something, ClickHouse itself would block writes (",[1426,3936,3937],{},"readonly=1","), kill slow queries (10 seconds), and cap the result set.",[725,3940,3941],{},"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.",[889,3943,3944],{},[725,3945,3946],{},"No AI system should have write access to production data. Always enforce read-only execution at both the application layer and the database layer.",[732,3948,3950],{"id":3949},"why-clickhouse-for-music-analytics","Why ClickHouse for music analytics",[725,3952,3953],{},"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.",[725,3955,3956],{},"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.",[725,3958,3959],{},"Key advantages for music data:",[797,3961,3962,3968,3985,3991],{},[800,3963,3964,3967],{},[787,3965,3966],{},"MergeTree engine"," with monthly partitioning matches the natural cadence of royalty reporting",[800,3969,3970,3973,3974,3718,3977,3980,3981,3984],{},[787,3971,3972],{},"Low-cardinality string optimisation"," is perfect for fields like ",[1426,3975,3976],{},"retailer_union",[1426,3978,3979],{},"artist",", and ",[1426,3982,3983],{},"country_code"," that have a bounded set of values",[800,3986,3987,3990],{},[787,3988,3989],{},"Columnar compression"," keeps storage costs low even as data grows to hundreds of millions of rows",[800,3992,3993,3996],{},[787,3994,3995],{},"HTTP API"," makes it straightforward to build read-only query interfaces with proper access controls",[732,3998,4000],{"id":3999},"what-we-learned-building-this","What we learned building this",[725,4002,4003],{},"Three insights from the implementation that apply to any company considering AI-powered analytics:",[777,4005,4007],{"id":4006},"_1-the-prompt-is-the-product","1. The prompt is the product",[725,4009,4010],{},"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.",[777,4012,4014],{"id":4013},"_2-start-local-scale-to-cloud","2. Start local, scale to cloud",[725,4016,4017],{},"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.",[777,4019,4021],{"id":4020},"_3-security-is-a-feature-not-a-constraint","3. Security is a feature, not a constraint",[725,4023,4024],{},"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.\"",[732,4026,4028],{"id":4027},"who-is-this-for","Who is this for?",[725,4030,4031],{},"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:",[797,4033,4034,4037,4040,4043],{},[800,4035,4036],{},"Your data team spends too much time answering ad-hoc reporting requests",[800,4038,4039],{},"Business stakeholders wait days for insights that should take seconds",[800,4041,4042],{},"You have sensitive royalty or financial data and cannot use cloud-based AI tools",[800,4044,4045],{},"You already have analytics data in ClickHouse, PostgreSQL, or a similar database and want to make it more accessible",[725,4047,4048],{},"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.",[732,4050,4052],{"id":4051},"what-comes-next","What comes next",[725,4054,4055],{},"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.",[2144,4057,4058],{},"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":1424,"searchDepth":1474,"depth":1474,"links":4060},[4061,4062,4063,4064,4065,4066,4067,4072,4073],{"id":2241,"depth":1474,"text":2242},{"id":2276,"depth":1474,"text":2277},{"id":2425,"depth":1474,"text":2426},{"id":2957,"depth":1474,"text":2958},{"id":3134,"depth":1474,"text":3135},{"id":3949,"depth":1474,"text":3950},{"id":3999,"depth":1474,"text":4000,"children":4068},[4069,4070,4071],{"id":4006,"depth":1483,"text":4007},{"id":4013,"depth":1483,"text":4014},{"id":4020,"depth":1483,"text":4021},{"id":4027,"depth":1474,"text":4028},{"id":4051,"depth":1474,"text":4052},"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.",[4077,4080,4083,4086],{"question":4078,"answer":4079},"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":4081,"answer":4082},"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":4084,"answer":4085},"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":4087,"answer":4088},"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":4090},"/images/blog/musictechlab_blog_ai-powered-analytics-dashboard.webp",{"enabled":2194,"items":4092},[4093,4095,4098,4101],{"text":4094,"icon":2421},"Business users type questions in plain English and get charts back in seconds, no SQL needed.",{"text":4096,"icon":4097},"Ollama runs locally by default so no royalty data ever leaves your servers.","i-lucide-shield",{"text":4099,"icon":4100},"SQL Guard enforces SELECT-only, single-table access with a 100-row limit on every query.","i-lucide-lock",{"text":4102,"icon":1956},"ClickHouse handles 10-50M rows with sub-second query times for columnar analytics.",{},{"title":4105,"description":4106},"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.",[4108,4109,2212,4110,2173,4111],"ai-analytics","musictech","llm","data-visualization","pS6Y0h5-QTtmmgq3FGyIwCVS8U7L5KQD1FBN31L-WS8",{"id":4114,"title":76,"authors":4115,"badge":4118,"body":4119,"category":2173,"client":2174,"date":4799,"description":4800,"extension":2177,"faq":4801,"featured":2194,"featuredOrder":1450,"hidden":69,"image":4811,"keyTakeaways":4813,"meta":4826,"navigation":2194,"path":77,"seo":4827,"status":2174,"stem":78,"tags":4830,"teaser":2174,"__hash__":4833,"score":1498},"posts/blog/music-data/13-distributors-5-file-formats-zero-standards-the-reality-of-music-royalty-data.md",[4116],{"name":2227,"to":715,"avatar":4117},{"src":717},{"label":719,"color":720},{"type":722,"value":4120,"toc":4791},[4121,4124,4127,4131,4134,4425,4432,4436,4439,4458,4461,4465,4468,4471,4482,4487,4494,4498,4501,4599,4602,4606,4609,4760,4763,4774,4781,4785,4788],[725,4122,4123],{},"Every month, an independent label receives royalty reports from over a dozen distributors. Not a single one looks the same.",[725,4125,4126],{},"This isn't a hypothetical. This is what a real download folder looks like when you work with music royalty data at scale.",[732,4128,4130],{"id":4129},"the-wall-of-files","The wall of files",[725,4132,4133],{},"Here's a small sample of actual filenames from a single label's monthly data intake -anonymised, but otherwise untouched:",[955,4135,4136,4152],{},[958,4137,4138],{},[961,4139,4140,4143,4146,4149],{},[964,4141,4142],{},"Distributor",[964,4144,4145],{},"Example Filename",[964,4147,4148],{},"Format",[964,4150,4151],{},"File Size",[977,4153,4154,4170,4186,4202,4218,4233,4248,4261,4276,4291,4307,4322,4337,4352,4367,4381,4395,4410],{},[961,4155,4156,4159,4164,4167],{},[982,4157,4158],{},"FUGA",[982,4160,4161],{},[1426,4162,4163],{},"FUGA_Statement_June_2024.xlsx",[982,4165,4166],{},".xlsx",[982,4168,4169],{},"~1 MB",[961,4171,4172,4175,4180,4183],{},[982,4173,4174],{},"ADA",[982,4176,4177],{},[1426,4178,4179],{},"SR1_Distribution_Aug_24_-_00054061_-_2024-8.xlsb",[982,4181,4182],{},".xlsb",[982,4184,4185],{},"~70 KB",[961,4187,4188,4191,4196,4199],{},[982,4189,4190],{},"Ingrooves",[982,4192,4193],{},[1426,4194,4195],{},"20240801-1496-DS-GBP_Digital_Sales.csv",[982,4197,4198],{},".csv",[982,4200,4201],{},"up to 226 MB",[961,4203,4204,4207,4212,4215],{},[982,4205,4206],{},"The Orchard",[982,4208,4209],{},[1426,4210,4211],{},"The_Orchard20240821_Jun2024_fullreport_catalogue_US.xls",[982,4213,4214],{},".xls",[982,4216,4217],{},"up to 700 MB",[961,4219,4220,4223,4228,4230],{},[982,4221,4222],{},"Bandcamp",[982,4224,4225],{},[1426,4226,4227],{},"bandcamp_rev_report_20240801-20240831.csv",[982,4229,4198],{},[982,4231,4232],{},"~6 KB",[961,4234,4235,4238,4243,4245],{},[982,4236,4237],{},"MVD",[982,4239,4240],{},[1426,4241,4242],{},"MVD_Statement_DigitalSales_2024-07.xls",[982,4244,4214],{},[982,4246,4247],{},"~12 KB",[961,4249,4250,4252,4257,4259],{},[982,4251,4237],{},[982,4253,4254],{},[1426,4255,4256],{},"MVD_Statement_DigitalSales_2024-07.xlsx",[982,4258,4166],{},[982,4260,4247],{},[961,4262,4263,4266,4271,4273],{},[982,4264,4265],{},"Emerald",[982,4267,4268],{},[1426,4269,4270],{},"Emerald_202408_DSR.csv",[982,4272,4198],{},[982,4274,4275],{},"~46 MB",[961,4277,4278,4281,4286,4288],{},[982,4279,4280],{},"Safari Records",[982,4282,4283],{},[1426,4284,4285],{},"Safari_Records_202408_DSR.xlsx",[982,4287,4166],{},[982,4289,4290],{},"~2 MB",[961,4292,4293,4296,4301,4304],{},[982,4294,4295],{},"ADA (legacy)",[982,4297,4298],{},[1426,4299,4300],{},"ADAOCT1.XLS",[982,4302,4303],{},".XLS",[982,4305,4306],{},"up to 150 MB",[961,4308,4309,4312,4317,4319],{},[982,4310,4311],{},"MAC",[982,4313,4314],{},[1426,4315,4316],{},"MAC_Developments_iTunes_August_2024.xlsx",[982,4318,4166],{},[982,4320,4321],{},"~49 KB",[961,4323,4324,4327,4332,4335],{},[982,4325,4326],{},"Absolute",[982,4328,4329],{},[1426,4330,4331],{},"Absolute_2024021.CSV",[982,4333,4334],{},".CSV",[982,4336,4201],{},[961,4338,4339,4342,4347,4349],{},[982,4340,4341],{},"Qello",[982,4343,4344],{},[1426,4345,4346],{},"DetailedSheet_Records_Ltd_20240801_20240831.xlsx",[982,4348,4166],{},[982,4350,4351],{},"~10 KB",[961,4353,4354,4357,4362,4364],{},[982,4355,4356],{},"SFM",[982,4358,4359],{},[1426,4360,4361],{},"sfmaug2024.xlsx",[982,4363,4166],{},[982,4365,4366],{},"~2.5 MB",[961,4368,4369,4372,4377,4379],{},[982,4370,4371],{},"BOFM",[982,4373,4374],{},[1426,4375,4376],{},"BOFM_Aug2024.xlsx",[982,4378,4166],{},[982,4380,4366],{},[961,4382,4383,4386,4391,4393],{},[982,4384,4385],{},"Dome Records",[982,4387,4388],{},[1426,4389,4390],{},"Dome_Records_202408_DSR.csv",[982,4392,4198],{},[982,4394,4169],{},[961,4396,4397,4400,4405,4407],{},[982,4398,4399],{},"MDR",[982,4401,4402],{},[1426,4403,4404],{},"MDR_May-2024_65634.92_Records.xlsx",[982,4406,4166],{},[982,4408,4409],{},"~500 KB",[961,4411,4412,4415,4420,4422],{},[982,4413,4414],{},"Merlin",[982,4416,4417],{},[1426,4418,4419],{},"Merlin_Nov24_eg.for.jack.xlsx",[982,4421,4166],{},[982,4423,4424],{},"~703 KB",[725,4426,4427,4428,4431],{},"That's ",[787,4429,4430],{},"18 adapters across 500+ files per year"," -each with its own naming convention, file format, and internal structure. From a 6 KB Bandcamp CSV to a single Orchard report that can reach 700 MB.",[732,4433,4435],{"id":4434},"spot-the-pattern","Spot the pattern",[725,4437,4438],{},"Go ahead, try. You won't find one.",[740,4440,4442,4446,4450,4454],{"className":4441},[743,744,745,746,747],[749,4443],{"description":4444,"title":4445},"`.xlsx` · `.xlsb` · `.xls` · `.XLS` · `.csv` · `.CSV`","5 file formats",[749,4447],{"description":4448,"title":4449},"`2024-07` · `202408` · `Aug_24` · `August_2024` · `20240801-20240831` · `2024021`","6 date conventions in filenames",[749,4451],{"description":4452,"title":4453},"Some distributors send both `.xls` and `.xlsx` versions of the exact same data.","Same report, multiple formats",[749,4455],{"description":4456,"title":4457},"camelCase, ALLCAPS, underscores, hyphens, internal reference numbers, random hash suffixes.","No naming standard",[725,4459,4460],{},"And that's just the filenames. Open these files and you'll find different column names for the same data, different date formats inside the cells, different encodings, and multi-sheet workbooks where each sheet follows its own rules.",[732,4462,4464],{"id":4463},"why-this-matters","Why this matters",[725,4466,4467],{},"Someone has to make sense of all this. Every month.",[725,4469,4470],{},"For most independent labels, that means hours of manual work -copying data between spreadsheets, reformatting dates, matching column names, fixing encoding issues that turn artist names into garbled text.",[725,4472,4473,4474,4477,4478,4481],{},"The cost isn't just time. It's ",[787,4475,4476],{},"delayed royalty payments"," to artists. It's ",[787,4479,4480],{},"reporting errors"," that erode trust. It's the finance team spending their week on data cleanup instead of analysis.",[1217,4483,4484],{},[725,4485,4486],{},"One distributor changed their report format mid-year without notice. The same filename pattern, but completely different column structure inside. Manual processes break silently when this happens.",[725,4488,4489],{},[4490,4491],"img",{"alt":4492,"src":4493},"Someone working on a laptop with spreadsheet data","/images/blog/musictechlab_blog_royalty-data-spreadsheet-laptop.webp",[732,4495,4497],{"id":4496},"how-teams-try-to-solve-this","How teams try to solve this",[725,4499,4500],{},"There's more than one way to tackle this problem. Here's how the most common approaches compare:",[955,4502,4503,4522],{},[958,4504,4505],{},[961,4506,4507,4510,4513,4516,4519],{},[964,4508,4509],{},"Approach",[964,4511,4512],{},"Setup effort",[964,4514,4515],{},"Maintenance",[964,4517,4518],{},"Handles format changes",[964,4520,4521],{},"Scales with new sources",[977,4523,4524,4543,4561,4580],{},[961,4525,4526,4531,4534,4537,4540],{},[982,4527,4528],{},[787,4529,4530],{},"Manual spreadsheets",[982,4532,4533],{},"None",[982,4535,4536],{},"Hours every month",[982,4538,4539],{},"Breaks silently",[982,4541,4542],{},"Every new source = more hours",[961,4544,4545,4551,4553,4555,4558],{},[982,4546,4547,4550],{},[787,4548,4549],{},"Generic ETL tools"," (Fivetran, Airbyte)",[982,4552,1120],{},[982,4554,1117],{},[982,4556,4557],{},"Limited - connectors are generic",[982,4559,4560],{},"Only if a connector exists",[961,4562,4563,4568,4571,4574,4577],{},[982,4564,4565],{},[787,4566,4567],{},"Custom Python scripts",[982,4569,4570],{},"High",[982,4572,4573],{},"High - fragile, hard to maintain",[982,4575,4576],{},"Depends on the developer",[982,4578,4579],{},"Every new source = new script",[961,4581,4582,4587,4590,4593,4596],{},[982,4583,4584],{},[787,4585,4586],{},"Adapter-based pipeline",[982,4588,4589],{},"High upfront",[982,4591,4592],{},"Low - each adapter is isolated",[982,4594,4595],{},"Adapter update, no side effects",[982,4597,4598],{},"Add an adapter, done",[725,4600,4601],{},"Generic ETL tools work well for standardised APIs and databases. But music royalty data doesn't come from APIs - it comes from email attachments, FTP servers, and distributor portals. Each source is its own special case. That's why an adapter-based approach wins here: each distributor gets its own parser, isolated from the rest, easy to update when formats change.",[732,4603,4605],{"id":4604},"one-clean-dataset","One clean dataset",[725,4607,4608],{},"Here's what the pipeline looks like in practice:",[1418,4610,4612],{"className":2303,"code":4611,"language":2305,"meta":1424,"style":1424},"flowchart LR\n    subgraph sources[\"Raw Files\"]\n        A[\".xlsx\"]\n        B[\".xlsb\"]\n        C[\".xls\"]\n        D[\".csv\"]\n    end\n\n    subgraph adapters[\"Adapter Layer\"]\n        E[\"FUGA\"]\n        F[\"ADA\"]\n        G[\"Orchard\"]\n        H[\"Bandcamp\"]\n        I[\"+ 14 more\"]\n    end\n\n    subgraph output[\"Unified Output\"]\n        J[(\"Clean dataset\")]\n    end\n\n    A --> E\n    B --> F\n    C --> G\n    D --> H\n\n    E --> J\n    F --> J\n    G --> J\n    H --> J\n    I --> J\n",[1426,4613,4614,4619,4624,4629,4634,4639,4644,4649,4653,4658,4663,4668,4673,4678,4683,4687,4691,4696,4701,4705,4709,4714,4719,4724,4729,4733,4738,4743,4748,4754],{"__ignoreMap":1424},[1447,4615,4616],{"class":1449,"line":1450},[1447,4617,4618],{"class":1457},"flowchart LR\n",[1447,4620,4621],{"class":1449,"line":1474},[1447,4622,4623],{"class":1457},"    subgraph sources[\"Raw Files\"]\n",[1447,4625,4626],{"class":1449,"line":1483},[1447,4627,4628],{"class":1457},"        A[\".xlsx\"]\n",[1447,4630,4631],{"class":1449,"line":1498},[1447,4632,4633],{"class":1457},"        B[\".xlsb\"]\n",[1447,4635,4636],{"class":1449,"line":1511},[1447,4637,4638],{"class":1457},"        C[\".xls\"]\n",[1447,4640,4641],{"class":1449,"line":1542},[1447,4642,4643],{"class":1457},"        D[\".csv\"]\n",[1447,4645,4646],{"class":1449,"line":1551},[1447,4647,4648],{"class":1457},"    end\n",[1447,4650,4651],{"class":1449,"line":1692},[1447,4652,2347],{"emptyLinePlaceholder":2194},[1447,4654,4655],{"class":1449,"line":1700},[1447,4656,4657],{"class":1457},"    subgraph adapters[\"Adapter Layer\"]\n",[1447,4659,4660],{"class":1449,"line":1715},[1447,4661,4662],{"class":1457},"        E[\"FUGA\"]\n",[1447,4664,4665],{"class":1449,"line":2360},[1447,4666,4667],{"class":1457},"        F[\"ADA\"]\n",[1447,4669,4670],{"class":1449,"line":2366},[1447,4671,4672],{"class":1457},"        G[\"Orchard\"]\n",[1447,4674,4675],{"class":1449,"line":2372},[1447,4676,4677],{"class":1457},"        H[\"Bandcamp\"]\n",[1447,4679,4680],{"class":1449,"line":2378},[1447,4681,4682],{"class":1457},"        I[\"+ 14 more\"]\n",[1447,4684,4685],{"class":1449,"line":2384},[1447,4686,4648],{"class":1457},[1447,4688,4689],{"class":1449,"line":2390},[1447,4690,2347],{"emptyLinePlaceholder":2194},[1447,4692,4693],{"class":1449,"line":2396},[1447,4694,4695],{"class":1457},"    subgraph output[\"Unified Output\"]\n",[1447,4697,4698],{"class":1449,"line":2402},[1447,4699,4700],{"class":1457},"        J[(\"Clean dataset\")]\n",[1447,4702,4703],{"class":1449,"line":2756},[1447,4704,4648],{"class":1457},[1447,4706,4707],{"class":1449,"line":2768},[1447,4708,2347],{"emptyLinePlaceholder":2194},[1447,4710,4711],{"class":1449,"line":2791},[1447,4712,4713],{"class":1457},"    A --> E\n",[1447,4715,4716],{"class":1449,"line":3572},[1447,4717,4718],{"class":1457},"    B --> F\n",[1447,4720,4721],{"class":1449,"line":3578},[1447,4722,4723],{"class":1457},"    C --> G\n",[1447,4725,4726],{"class":1449,"line":3630},[1447,4727,4728],{"class":1457},"    D --> H\n",[1447,4730,4731],{"class":1449,"line":3653},[1447,4732,2347],{"emptyLinePlaceholder":2194},[1447,4734,4735],{"class":1449,"line":3678},[1447,4736,4737],{"class":1457},"    E --> J\n",[1447,4739,4740],{"class":1449,"line":3683},[1447,4741,4742],{"class":1457},"    F --> J\n",[1447,4744,4745],{"class":1449,"line":3689},[1447,4746,4747],{"class":1457},"    G --> J\n",[1447,4749,4751],{"class":1449,"line":4750},29,[1447,4752,4753],{"class":1457},"    H --> J\n",[1447,4755,4757],{"class":1449,"line":4756},30,[1447,4758,4759],{"class":1457},"    I --> J\n",[725,4761,4762],{},"Every file goes through its format-specific adapter - handling encoding, column mapping, date parsing, and multi-sheet logic. What comes out the other side is one consistent dataset: same columns, same date format, same encoding. Ready for analysis, reporting, and artist payouts.",[725,4764,4765,4766,4769,4770,4773],{},"But consistent columns are only the beginning. The data inside those files is ",[2431,4767,4768],{"href":81},"just as messy"," - 830 raw values that need mapping to 19 canonical names before you can run a single meaningful query. And once the data is truly clean, it opens the door to ",[2431,4771,4772],{"href":85},"AI-powered analytics"," where business users ask questions in plain English and get charts back in seconds.",[725,4775,4776,4777,4780],{},"That's what we build at MusicTech Lab. Not another dashboard on top of messy data - but the ",[787,4778,4779],{},"data layer underneath"," that turns chaos into clarity.",[732,4782,4784],{"id":4783},"looks-familiar","Looks familiar?",[725,4786,4787],{},"If your monthly royalty workflow involves more spreadsheet wrangling than actual analysis, we should talk. We've built data pipelines for independent labels handling exactly this kind of complexity - and we can do the same for you.",[2144,4789,4790],{},"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":1424,"searchDepth":1474,"depth":1474,"links":4792},[4793,4794,4795,4796,4797,4798],{"id":4129,"depth":1474,"text":4130},{"id":4434,"depth":1474,"text":4435},{"id":4463,"depth":1474,"text":4464},{"id":4496,"depth":1474,"text":4497},{"id":4604,"depth":1474,"text":4605},{"id":4783,"depth":1474,"text":4784},"2026-02-27T00:00:00.000Z","Every month, independent labels receive royalty reports from over a dozen distributors. No two look the same. Here's what that actually looks like.",[4802,4805,4808],{"question":4803,"answer":4804},"Why do music distributors use different file formats?","There is no industry-wide standard for royalty report delivery. Each distributor built their own reporting system independently, resulting in different file formats, column names, date conventions, and encodings.",{"question":4806,"answer":4807},"What file formats are used in music royalty reporting?","Common formats include XLSX (modern Excel), XLSB (binary Excel), XLS (legacy Excel), and CSV with various encodings (UTF-8, UTF-16, ISO-8859-1). Some distributors send the same report in multiple formats.",{"question":4809,"answer":4810},"How can labels automate royalty data processing?","By building format-aware adapters that recognise each distributor's file structure and automatically normalise everything into a single, clean dataset -eliminating manual cleanup and reducing errors.",{"src":4812},"/images/blog/musictechlab_blog_13-distributors-5-file-formats-zero-standards.webp",{"enabled":2194,"items":4814},[4815,4818,4821,4823],{"text":4816,"icon":4817},"18 adapters parse 500+ files per year from 13 distributors in 5 different formats.","i-lucide-blocks",{"text":4819,"icon":4820},"File sizes range from 6 KB (Bandcamp) to 700 MB (The Orchard) with zero naming standards.","i-lucide-file-text",{"text":4822,"icon":752},"Adapter-based pipelines isolate each source, making format changes safe and side-effect-free.",{"text":4824,"icon":4825},"Manual spreadsheet processing delays royalty payments and introduces silent reporting errors.","i-lucide-alert-triangle",{},{"title":4828,"description":4829},"Music Royalty Data Chaos: 13 Distributors, 5 Formats | MusicTech Lab","See the real complexity of music royalty reporting -different file formats, naming conventions, and schemas from every distributor. Learn how automation solves it.",[2173,4109,2215,4831,4832],"streaming","metadata","RALDAG90DAM9IgwQPuVrQkFWVhTDlAAddo9XQWvnld4",{"id":4835,"title":112,"authors":4836,"badge":4839,"body":4840,"category":2173,"client":2174,"date":7094,"description":7095,"extension":2177,"faq":2174,"featured":69,"featuredOrder":2174,"hidden":69,"image":7096,"keyTakeaways":7098,"meta":7110,"navigation":2194,"path":113,"seo":7111,"status":2174,"stem":114,"tags":7112,"teaser":2174,"__hash__":7115,"score":1498},"posts/blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api.md",[4837],{"name":2227,"to":715,"avatar":4838},{"src":717},{"label":719,"color":720},{"type":722,"value":4841,"toc":7062},[4842,4849,4852,4856,4859,4913,4916,4918,4922,4925,4933,4941,4948,4950,4954,4957,4960,4964,4981,4984,5013,5017,5028,5031,5042,5046,5057,5066,5068,5072,5075,5192,5196,5260,5264,5316,5320,5323,6049,6062,6064,6068,6071,6074,6078,6081,6309,6313,6316,6494,6498,6505,6701,6725,6729,6735,6752,6759,6761,6765,6768,6776,6780,6783,6787,6790,6846,6849,6853,6856,6860,6863,6865,6869,6872,6892,6895,6949,6958,6960,6964,6967,6993,7002,7004,7008,7011,7014,7016,7020,7059],[725,4843,4844,4845,4848],{},"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 ",[2431,4846,4847],{"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.",[4850,4851],"hr",{},[732,4853,4855],{"id":4854},"why-distributors-outgrow-off-the-shelf-platforms","Why Distributors Outgrow Off-the-Shelf Platforms",[725,4857,4858],{},"The friction points are predictable:",[955,4860,4861,4871],{},[958,4862,4863],{},[961,4864,4865,4868],{},[964,4866,4867],{},"Pain Point",[964,4869,4870],{},"What Happens",[977,4872,4873,4883,4893,4903],{},[961,4874,4875,4880],{},[982,4876,4877],{},[787,4878,4879],{},"Territory restrictions",[982,4881,4882],{},"Many platforms only support album-level territory settings, forcing workarounds when individual tracks have different rights across markets",[961,4884,4885,4890],{},[982,4886,4887],{},[787,4888,4889],{},"Metadata flexibility",[982,4891,4892],{},"Contributor roles, localized titles, and DSP-specific artist IDs often require manual overrides the UI doesn't expose",[961,4894,4895,4900],{},[982,4896,4897],{},[787,4898,4899],{},"Reporting",[982,4901,4902],{},"Off-the-shelf dashboards rarely match the operational needs of a multi-label distributor",[961,4904,4905,4910],{},[982,4906,4907],{},[787,4908,4909],{},"Delivery control",[982,4911,4912],{},"When a platform handles delivery as a black box, debugging ingestion failures becomes a support ticket game",[725,4914,4915],{},"This is where the build-vs-buy decision becomes real.",[4850,4917],{},[732,4919,4921],{"id":4920},"the-two-paths-frontend-only-vs-full-stack","The Two Paths: Frontend-Only vs. Full Stack",[725,4923,4924],{},"Distributors who've outgrown their current setup face a fundamental architectural choice:",[1217,4926,4927],{},[725,4928,4929,4932],{},[787,4930,4931],{},"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.",[889,4934,4935],{},[725,4936,4937,4940],{},[787,4938,4939],{},"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.",[725,4942,4943,4944,4947],{},"This article focuses on ",[787,4945,4946],{},"Option A",": what it looks like in practice, where it excels, and where it hits structural limits.",[4850,4949],{},[732,4951,4953],{"id":4952},"what-the-revelator-api-actually-offers","What the Revelator API Actually Offers",[725,4955,4956],{},"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.",[725,4958,4959],{},"The API is RESTful, JSON-based, and covers five core modules:",[777,4961,4963],{"id":4962},"catalog-management","Catalog Management",[797,4965,4966,4972,4975,4978],{},[800,4967,4968,4969],{},"Create and edit releases via ",[1426,4970,4971],{},"POST /content/release/save",[800,4973,4974],{},"Upload audio (WAV/FLAC, minimum 16-bit, 44.1 kHz stereo) and cover art (minimum 1400x1400px JPG/RGB)",[800,4976,4977],{},"Manage ISRCs (International Standard Recording Codes), UPCs (Universal Product Codes), and external DSP artist IDs",[800,4979,4980],{},"Support for multi-disc releases and localized metadata",[777,4982,719],{"id":4983},"distribution",[797,4985,4986,4992,4995,5001,5004,5010],{},[800,4987,4988,4989],{},"Pre-delivery validation: ",[1426,4990,4991],{},"POST /distribution/release/{releaseId}/validate",[800,4993,4994],{},"Set DSP targets, release dates, and territories per release",[800,4996,4997,4998],{},"Queue releases for delivery: ",[1426,4999,5000],{},"POST /distribution/release/addtoqueue",[800,5002,5003],{},"Track delivery status (statuses range from -20 to 100; 50+ means delivered)",[800,5005,5006,5007],{},"Takedown support: ",[1426,5008,5009],{},"POST /distribution/release/takedown",[800,5011,5012],{},"Webhook callbacks for delivery status updates",[777,5014,5016],{"id":5015},"rights-and-royalties","Rights and Royalties",[797,5018,5019,5022,5025],{},[800,5020,5021],{},"Contract definition with configurable splits and recoupables",[800,5023,5024],{},"DSP statement import and reconciliation",[800,5026,5027],{},"Multi-currency payout automation via Tipalti and PayPal",[777,5029,5030],{"id":2214},"Analytics",[797,5032,5033,5036,5039],{},[800,5034,5035],{},"Streaming and revenue data queryable by track, release, region, or DSP",[800,5037,5038],{},"Playlist performance tracking",[800,5040,5041],{},"CSV export and raw data sync",[777,5043,5045],{"id":5044},"account-management","Account Management",[797,5047,5048,5051,5054],{},[800,5049,5050],{},"Parent-child account hierarchy (your enterprise is the parent; each label or artist is a child)",[800,5052,5053],{},"Full visibility into child account assets",[800,5055,5056],{},"Mandatory approval step before content reaches DSPs",[832,5058,5059],{},[725,5060,5061,5062,5065],{},"Revelator claims ",[787,5063,5064],{},"100+ DSP integrations",", including Spotify, Apple Music, Amazon, YouTube Music, YouTube Content ID, TikTok, and Deezer. DSP access is configurable per child account.",[4850,5067],{},[732,5069,5071],{"id":5070},"architecture-of-the-hybrid-solution","Architecture of the Hybrid Solution",[725,5073,5074],{},"The hybrid approach layers your custom platform on top of Revelator's delivery infrastructure:",[1418,5076,5078],{"className":2303,"code":5077,"language":2305,"meta":1424,"style":1424},"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",[1426,5079,5080,5085,5090,5095,5100,5105,5110,5114,5119,5124,5129,5134,5138,5143,5148,5153,5158,5163,5168,5172,5177,5182,5187],{"__ignoreMap":1424},[1447,5081,5082],{"class":1449,"line":1450},[1447,5083,5084],{"class":1457},"graph TD\n",[1447,5086,5087],{"class":1449,"line":1474},[1447,5088,5089],{"class":1457},"    subgraph Frontend[Your Custom Frontend]\n",[1447,5091,5092],{"class":1449,"line":1483},[1447,5093,5094],{"class":1457},"        AP[Artist Portal]\n",[1447,5096,5097],{"class":1449,"line":1498},[1447,5098,5099],{"class":1457},"        LD[Label Dashboard]\n",[1447,5101,5102],{"class":1449,"line":1511},[1447,5103,5104],{"class":1457},"        AT[Admin Tools]\n",[1447,5106,5107],{"class":1449,"line":1542},[1447,5108,5109],{"class":1457},"        AP & LD & AT --> BE[Your API / Backend]\n",[1447,5111,5112],{"class":1449,"line":1551},[1447,5113,4648],{"class":1457},[1447,5115,5116],{"class":1449,"line":1692},[1447,5117,5118],{"class":1457},"    subgraph Revelator[Revelator API Layer]\n",[1447,5120,5121],{"class":1449,"line":1700},[1447,5122,5123],{"class":1457},"        CAT[Catalog Mgmt API]\n",[1447,5125,5126],{"class":1449,"line":1715},[1447,5127,5128],{"class":1457},"        DEL[Delivery API]\n",[1447,5130,5131],{"class":1449,"line":2360},[1447,5132,5133],{"class":1457},"        ROY[Royalty Ingestion]\n",[1447,5135,5136],{"class":1449,"line":2366},[1447,5137,4648],{"class":1457},[1447,5139,5140],{"class":1449,"line":2372},[1447,5141,5142],{"class":1457},"    BE --> CAT & DEL & ROY\n",[1447,5144,5145],{"class":1449,"line":2378},[1447,5146,5147],{"class":1457},"    subgraph DSPs[Digital Service Providers]\n",[1447,5149,5150],{"class":1449,"line":2384},[1447,5151,5152],{"class":1457},"        SP[Spotify]\n",[1447,5154,5155],{"class":1449,"line":2390},[1447,5156,5157],{"class":1457},"        AM[Apple Music]\n",[1447,5159,5160],{"class":1449,"line":2396},[1447,5161,5162],{"class":1457},"        YT[YouTube]\n",[1447,5164,5165],{"class":1449,"line":2402},[1447,5166,5167],{"class":1457},"        MORE[100+ more]\n",[1447,5169,5170],{"class":1449,"line":2756},[1447,5171,4648],{"class":1457},[1447,5173,5174],{"class":1449,"line":2768},[1447,5175,5176],{"class":1457},"    DEL --> SP & AM & YT & MORE\n",[1447,5178,5179],{"class":1449,"line":2791},[1447,5180,5181],{"class":1457},"    style Frontend fill:#0f172a,stroke:#38bdf8,color:#f8fafc\n",[1447,5183,5184],{"class":1449,"line":3572},[1447,5185,5186],{"class":1457},"    style Revelator fill:#1e293b,stroke:#0ea5e9,color:#f8fafc\n",[1447,5188,5189],{"class":1449,"line":3578},[1447,5190,5191],{"class":1457},"    style DSPs fill:#0f172a,stroke:#334155,color:#f8fafc\n",[777,5193,5195],{"id":5194},"what-you-build","What You Build",[955,5197,5198,5208],{},[958,5199,5200],{},[961,5201,5202,5205],{},[964,5203,5204],{},"Layer",[964,5206,5207],{},"Responsibility",[977,5209,5210,5220,5230,5240,5250],{},[961,5211,5212,5217],{},[982,5213,5214],{},[787,5215,5216],{},"Artist & label portals",[982,5218,5219],{},"Branded onboarding, release submission, approval workflows",[961,5221,5222,5227],{},[982,5223,5224],{},[787,5225,5226],{},"Metadata layer",[982,5228,5229],{},"Your own database of releases, tracks, contributors, and rights - synced to Revelator via API",[961,5231,5232,5237],{},[982,5233,5234],{},[787,5235,5236],{},"Territory & rights engine",[982,5238,5239],{},"Business logic for track-level territory restrictions, split sheets, and embargo rules",[961,5241,5242,5247],{},[982,5243,5244],{},[787,5245,5246],{},"Reporting dashboards",[982,5248,5249],{},"Custom views pulling from Revelator's analytics API plus your own data",[961,5251,5252,5257],{},[982,5253,5254],{},[787,5255,5256],{},"Internal tools",[982,5258,5259],{},"Approval queues, QC checklists, bulk operations, CRM integration",[777,5261,5263],{"id":5262},"what-revelator-handles","What Revelator Handles",[955,5265,5266,5274],{},[958,5267,5268],{},[961,5269,5270,5272],{},[964,5271,5204],{},[964,5273,5207],{},[977,5275,5276,5286,5296,5306],{},[961,5277,5278,5283],{},[982,5279,5280],{},[787,5281,5282],{},"DDEX generation",[982,5284,5285],{},"Generates ERN (Electronic Release Notification) XML packages from catalog data you push via API",[961,5287,5288,5293],{},[982,5289,5290],{},[787,5291,5292],{},"DSP delivery",[982,5294,5295],{},"SFTP/API delivery to all connected DSPs, including retry logic and status tracking",[961,5297,5298,5303],{},[982,5299,5300],{},[787,5301,5302],{},"Royalty ingestion",[982,5304,5305],{},"Parsing DSP statements and normalizing revenue data",[961,5307,5308,5313],{},[982,5309,5310],{},[787,5311,5312],{},"Payout infrastructure",[982,5314,5315],{},"Payment rail integrations for artist payouts",[777,5317,5319],{"id":5318},"quick-example-creating-a-release-via-revelator-api","Quick Example: Creating a Release via Revelator API",[725,5321,5322],{},"Here's a minimal example of how your backend would create and distribute a release through Revelator's API:",[1418,5324,5326],{"className":2444,"code":5325,"language":2447,"meta":1424,"style":1424},"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",[1426,5327,5328,5335,5339,5353,5357,5362,5398,5418,5436,5441,5468,5503,5507,5512,5556,5576,5596,5609,5650,5687,5692,5712,5732,5746,5766,5843,5848,5852,5877,5881,5886,5902,5930,5941,5946,5951,5957,5982,6009,6021,6044],{"__ignoreMap":1424},[1447,5329,5330,5332],{"class":1449,"line":1450},[1447,5331,3081],{"class":2486},[1447,5333,5334],{"class":1457}," httpx\n",[1447,5336,5337],{"class":1449,"line":1474},[1447,5338,2347],{"emptyLinePlaceholder":2194},[1447,5340,5341,5344,5346,5348,5351],{"class":1449,"line":1483},[1447,5342,5343],{"class":1457},"REVELATOR_API ",[1447,5345,2478],{"class":1520},[1447,5347,2582],{"class":1520},[1447,5349,5350],{"class":2529},"https://api.revelator.com",[1447,5352,2625],{"class":1520},[1447,5354,5355],{"class":1449,"line":1498},[1447,5356,2347],{"emptyLinePlaceholder":2194},[1447,5358,5359],{"class":1449,"line":1511},[1447,5360,5361],{"class":2807},"# 1. Authenticate\n",[1447,5363,5364,5367,5369,5371,5373,5375,5377,5379,5381,5383,5386,5388,5391,5393,5396],{"class":1449,"line":1542},[1447,5365,5366],{"class":1457},"auth ",[1447,5368,2478],{"class":1520},[1447,5370,3759],{"class":1457},[1447,5372,2499],{"class":1520},[1447,5374,3764],{"class":1461},[1447,5376,1788],{"class":1520},[1447,5378,2526],{"class":2454},[1447,5380,2588],{"class":2529},[1447,5382,2533],{"class":1453},[1447,5384,5385],{"class":1461},"REVELATOR_API",[1447,5387,2544],{"class":1453},[1447,5389,5390],{"class":2529},"/partner/account/login\"",[1447,5392,3025],{"class":1520},[1447,5394,5395],{"class":3334}," json",[1447,5397,3803],{"class":1520},[1447,5399,5400,5402,5405,5407,5409,5411,5414,5416],{"class":1449,"line":1551},[1447,5401,3165],{"class":1520},[1447,5403,5404],{"class":2529},"email",[1447,5406,2588],{"class":1520},[1447,5408,3337],{"class":1520},[1447,5410,2582],{"class":1520},[1447,5412,5413],{"class":2529},"your@email.com",[1447,5415,2588],{"class":1520},[1447,5417,3209],{"class":1520},[1447,5419,5420,5422,5425,5427,5429,5431,5434],{"class":1449,"line":1692},[1447,5421,3165],{"class":1520},[1447,5423,5424],{"class":2529},"password",[1447,5426,2588],{"class":1520},[1447,5428,3337],{"class":1520},[1447,5430,2582],{"class":1520},[1447,5432,5433],{"class":2529},"your-password",[1447,5435,2625],{"class":1520},[1447,5437,5438],{"class":1449,"line":1700},[1447,5439,5440],{"class":1520},"})\n",[1447,5442,5443,5446,5448,5451,5453,5456,5459,5461,5464,5466],{"class":1449,"line":1715},[1447,5444,5445],{"class":1457},"token ",[1447,5447,2478],{"class":1520},[1447,5449,5450],{"class":1457}," auth",[1447,5452,2499],{"class":1520},[1447,5454,5455],{"class":1461},"json",[1447,5457,5458],{"class":1520},"()[",[1447,5460,2588],{"class":1520},[1447,5462,5463],{"class":2529},"token",[1447,5465,2588],{"class":1520},[1447,5467,3299],{"class":1520},[1447,5469,5470,5473,5475,5478,5480,5483,5485,5487,5489,5492,5494,5496,5498,5500],{"class":1449,"line":2360},[1447,5471,5472],{"class":1457},"headers ",[1447,5474,2478],{"class":1520},[1447,5476,5477],{"class":1520}," {",[1447,5479,2588],{"class":1520},[1447,5481,5482],{"class":2529},"Authorization",[1447,5484,2588],{"class":1520},[1447,5486,3337],{"class":1520},[1447,5488,3817],{"class":2454},[1447,5490,5491],{"class":2529},"\"Bearer ",[1447,5493,2533],{"class":1453},[1447,5495,5463],{"class":1457},[1447,5497,2544],{"class":1453},[1447,5499,2588],{"class":2529},[1447,5501,5502],{"class":1520},"}\n",[1447,5504,5505],{"class":1449,"line":2366},[1447,5506,2347],{"emptyLinePlaceholder":2194},[1447,5508,5509],{"class":1449,"line":2372},[1447,5510,5511],{"class":2807},"# 2. Create a release\n",[1447,5513,5514,5517,5519,5521,5523,5525,5527,5529,5531,5533,5535,5537,5540,5542,5545,5547,5550,5552,5554],{"class":1449,"line":2378},[1447,5515,5516],{"class":1457},"release ",[1447,5518,2478],{"class":1520},[1447,5520,3759],{"class":1457},[1447,5522,2499],{"class":1520},[1447,5524,3764],{"class":1461},[1447,5526,1788],{"class":1520},[1447,5528,2526],{"class":2454},[1447,5530,2588],{"class":2529},[1447,5532,2533],{"class":1453},[1447,5534,5385],{"class":1461},[1447,5536,2544],{"class":1453},[1447,5538,5539],{"class":2529},"/content/release/save\"",[1447,5541,3025],{"class":1520},[1447,5543,5544],{"class":3334}," headers",[1447,5546,2478],{"class":1520},[1447,5548,5549],{"class":1461},"headers",[1447,5551,3025],{"class":1520},[1447,5553,5395],{"class":3334},[1447,5555,3803],{"class":1520},[1447,5557,5558,5560,5563,5565,5567,5569,5572,5574],{"class":1449,"line":2384},[1447,5559,3165],{"class":1520},[1447,5561,5562],{"class":2529},"title",[1447,5564,2588],{"class":1520},[1447,5566,3337],{"class":1520},[1447,5568,2582],{"class":1520},[1447,5570,5571],{"class":2529},"Delilah - Summer Version",[1447,5573,2588],{"class":1520},[1447,5575,3209],{"class":1520},[1447,5577,5578,5580,5583,5585,5587,5589,5592,5594],{"class":1449,"line":2390},[1447,5579,3165],{"class":1520},[1447,5581,5582],{"class":2529},"releaseType",[1447,5584,2588],{"class":1520},[1447,5586,3337],{"class":1520},[1447,5588,2582],{"class":1520},[1447,5590,5591],{"class":2529},"Single",[1447,5593,2588],{"class":1520},[1447,5595,3209],{"class":1520},[1447,5597,5598,5600,5603,5605,5607],{"class":1449,"line":2396},[1447,5599,3165],{"class":1520},[1447,5601,5602],{"class":2529},"artists",[1447,5604,2588],{"class":1520},[1447,5606,3337],{"class":1520},[1447,5608,3160],{"class":1520},[1447,5610,5611,5614,5616,5618,5620,5622,5624,5627,5629,5631,5633,5636,5638,5640,5642,5645,5647],{"class":1449,"line":2402},[1447,5612,5613],{"class":1520},"        {",[1447,5615,2588],{"class":1520},[1447,5617,2541],{"class":2529},[1447,5619,2588],{"class":1520},[1447,5621,3337],{"class":1520},[1447,5623,2582],{"class":1520},[1447,5625,5626],{"class":2529},"MIKOLAS",[1447,5628,2588],{"class":1520},[1447,5630,3025],{"class":1520},[1447,5632,2582],{"class":1520},[1447,5634,5635],{"class":2529},"role",[1447,5637,2588],{"class":1520},[1447,5639,3337],{"class":1520},[1447,5641,2582],{"class":1520},[1447,5643,5644],{"class":2529},"MainArtist",[1447,5646,2588],{"class":1520},[1447,5648,5649],{"class":1520},"},\n",[1447,5651,5652,5654,5656,5658,5660,5662,5664,5667,5669,5671,5673,5675,5677,5679,5681,5683,5685],{"class":1449,"line":2756},[1447,5653,5613],{"class":1520},[1447,5655,2588],{"class":1520},[1447,5657,2541],{"class":2529},[1447,5659,2588],{"class":1520},[1447,5661,3337],{"class":1520},[1447,5663,2582],{"class":1520},[1447,5665,5666],{"class":2529},"Mark Neve",[1447,5668,2588],{"class":1520},[1447,5670,3025],{"class":1520},[1447,5672,2582],{"class":1520},[1447,5674,5635],{"class":2529},[1447,5676,2588],{"class":1520},[1447,5678,3337],{"class":1520},[1447,5680,2582],{"class":1520},[1447,5682,5644],{"class":2529},[1447,5684,2588],{"class":1520},[1447,5686,5502],{"class":1520},[1447,5688,5689],{"class":1449,"line":2768},[1447,5690,5691],{"class":1520},"    ],\n",[1447,5693,5694,5696,5699,5701,5703,5705,5708,5710],{"class":1449,"line":2791},[1447,5695,3165],{"class":1520},[1447,5697,5698],{"class":2529},"genre",[1447,5700,2588],{"class":1520},[1447,5702,3337],{"class":1520},[1447,5704,2582],{"class":1520},[1447,5706,5707],{"class":2529},"Pop",[1447,5709,2588],{"class":1520},[1447,5711,3209],{"class":1520},[1447,5713,5714,5716,5719,5721,5723,5725,5728,5730],{"class":1449,"line":3572},[1447,5715,3165],{"class":1520},[1447,5717,5718],{"class":2529},"releaseDate",[1447,5720,2588],{"class":1520},[1447,5722,3337],{"class":1520},[1447,5724,2582],{"class":1520},[1447,5726,5727],{"class":2529},"2026-04-01",[1447,5729,2588],{"class":1520},[1447,5731,3209],{"class":1520},[1447,5733,5734,5736,5739,5741,5743],{"class":1449,"line":3578},[1447,5735,3165],{"class":1520},[1447,5737,5738],{"class":2529},"territories",[1447,5740,2588],{"class":1520},[1447,5742,3337],{"class":1520},[1447,5744,5745],{"class":1520}," {\n",[1447,5747,5748,5750,5753,5755,5757,5759,5762,5764],{"class":1449,"line":3630},[1447,5749,2617],{"class":1520},[1447,5751,5752],{"class":2529},"include",[1447,5754,2588],{"class":1520},[1447,5756,3337],{"class":1520},[1447,5758,2582],{"class":1520},[1447,5760,5761],{"class":2529},"Worldwide",[1447,5763,2588],{"class":1520},[1447,5765,3209],{"class":1520},[1447,5767,5768,5770,5773,5775,5777,5780,5782,5785,5787,5789,5791,5794,5796,5798,5800,5803,5805,5807,5809,5812,5814,5816,5818,5821,5823,5825,5827,5830,5832,5834,5836,5839,5841],{"class":1449,"line":3653},[1447,5769,2617],{"class":1520},[1447,5771,5772],{"class":2529},"exclude",[1447,5774,2588],{"class":1520},[1447,5776,3337],{"class":1520},[1447,5778,5779],{"class":1520}," [",[1447,5781,2588],{"class":1520},[1447,5783,5784],{"class":2529},"JP",[1447,5786,2588],{"class":1520},[1447,5788,3025],{"class":1520},[1447,5790,2582],{"class":1520},[1447,5792,5793],{"class":2529},"DE",[1447,5795,2588],{"class":1520},[1447,5797,3025],{"class":1520},[1447,5799,2582],{"class":1520},[1447,5801,5802],{"class":2529},"AT",[1447,5804,2588],{"class":1520},[1447,5806,3025],{"class":1520},[1447,5808,2582],{"class":1520},[1447,5810,5811],{"class":2529},"CH",[1447,5813,2588],{"class":1520},[1447,5815,3025],{"class":1520},[1447,5817,2582],{"class":1520},[1447,5819,5820],{"class":2529},"NL",[1447,5822,2588],{"class":1520},[1447,5824,3025],{"class":1520},[1447,5826,2582],{"class":1520},[1447,5828,5829],{"class":2529},"AU",[1447,5831,2588],{"class":1520},[1447,5833,3025],{"class":1520},[1447,5835,2582],{"class":1520},[1447,5837,5838],{"class":2529},"NZ",[1447,5840,2588],{"class":1520},[1447,5842,3299],{"class":1520},[1447,5844,5845],{"class":1449,"line":3678},[1447,5846,5847],{"class":1520},"    }\n",[1447,5849,5850],{"class":1449,"line":3683},[1447,5851,5440],{"class":1520},[1447,5853,5854,5857,5859,5862,5864,5866,5868,5870,5873,5875],{"class":1449,"line":3689},[1447,5855,5856],{"class":1457},"release_id ",[1447,5858,2478],{"class":1520},[1447,5860,5861],{"class":1457}," release",[1447,5863,2499],{"class":1520},[1447,5865,5455],{"class":1461},[1447,5867,5458],{"class":1520},[1447,5869,2588],{"class":1520},[1447,5871,5872],{"class":2529},"id",[1447,5874,2588],{"class":1520},[1447,5876,3299],{"class":1520},[1447,5878,5879],{"class":1449,"line":4750},[1447,5880,2347],{"emptyLinePlaceholder":2194},[1447,5882,5883],{"class":1449,"line":4756},[1447,5884,5885],{"class":2807},"# 3. Validate before delivery\n",[1447,5887,5889,5892,5894,5896,5898,5900],{"class":1449,"line":5888},31,[1447,5890,5891],{"class":1457},"validation ",[1447,5893,2478],{"class":1520},[1447,5895,3759],{"class":1457},[1447,5897,2499],{"class":1520},[1447,5899,3764],{"class":1461},[1447,5901,3767],{"class":1520},[1447,5903,5905,5907,5909,5911,5913,5915,5918,5920,5923,5925,5928],{"class":1449,"line":5904},32,[1447,5906,3772],{"class":2454},[1447,5908,2588],{"class":2529},[1447,5910,2533],{"class":1453},[1447,5912,5385],{"class":1461},[1447,5914,2544],{"class":1453},[1447,5916,5917],{"class":2529},"/distribution/release/",[1447,5919,2533],{"class":1453},[1447,5921,5922],{"class":1461},"release_id",[1447,5924,2544],{"class":1453},[1447,5926,5927],{"class":2529},"/validate\"",[1447,5929,3209],{"class":1520},[1447,5931,5933,5936,5938],{"class":1449,"line":5932},33,[1447,5934,5935],{"class":3334},"    headers",[1447,5937,2478],{"class":1520},[1447,5939,5940],{"class":1461},"headers\n",[1447,5942,5944],{"class":1449,"line":5943},34,[1447,5945,1539],{"class":1520},[1447,5947,5949],{"class":1449,"line":5948},35,[1447,5950,2347],{"emptyLinePlaceholder":2194},[1447,5952,5954],{"class":1449,"line":5953},36,[1447,5955,5956],{"class":2807},"# 4. Queue for delivery to DSPs\n",[1447,5958,5960,5963,5966,5968,5970,5972,5974,5977,5979],{"class":1449,"line":5959},37,[1447,5961,5962],{"class":2486},"if",[1447,5964,5965],{"class":1457}," validation",[1447,5967,2499],{"class":1520},[1447,5969,5455],{"class":1461},[1447,5971,5458],{"class":1520},[1447,5973,2588],{"class":1520},[1447,5975,5976],{"class":2529},"valid",[1447,5978,2588],{"class":1520},[1447,5980,5981],{"class":1520},"]:\n",[1447,5983,5985,5988,5990,5992,5994,5996,5998,6000,6002,6004,6007],{"class":1449,"line":5984},38,[1447,5986,5987],{"class":1457},"    httpx",[1447,5989,2499],{"class":1520},[1447,5991,3764],{"class":1461},[1447,5993,1788],{"class":1520},[1447,5995,2526],{"class":2454},[1447,5997,2588],{"class":2529},[1447,5999,2533],{"class":1453},[1447,6001,5385],{"class":1461},[1447,6003,2544],{"class":1453},[1447,6005,6006],{"class":2529},"/distribution/release/addtoqueue\"",[1447,6008,3209],{"class":1520},[1447,6010,6012,6015,6017,6019],{"class":1449,"line":6011},39,[1447,6013,6014],{"class":3334},"        headers",[1447,6016,2478],{"class":1520},[1447,6018,5549],{"class":1461},[1447,6020,3209],{"class":1520},[1447,6022,6024,6027,6030,6032,6035,6037,6039,6042],{"class":1449,"line":6023},40,[1447,6025,6026],{"class":3334},"        json",[1447,6028,6029],{"class":1520},"={",[1447,6031,2588],{"class":1520},[1447,6033,6034],{"class":2529},"releaseId",[1447,6036,2588],{"class":1520},[1447,6038,3337],{"class":1520},[1447,6040,6041],{"class":1461}," release_id",[1447,6043,5502],{"class":1520},[1447,6045,6047],{"class":1449,"line":6046},41,[1447,6048,2794],{"class":1520},[1217,6050,6051],{},[725,6052,6053,6054,6057,6058,6061],{},"This is a simplified example. In production, you'd also upload audio files via ",[1426,6055,6056],{},"/media/audio/upload",", cover art via ",[1426,6059,6060],{},"/media/image/upload",", and handle webhook callbacks for delivery status updates.",[4850,6063],{},[732,6065,6067],{"id":6066},"ddex-as-the-backbone","DDEX as the Backbone",[725,6069,6070],{},"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.",[725,6072,6073],{},"An ERN message has three critical sections:",[777,6075,6077],{"id":6076},"resourcelist","ResourceList",[725,6079,6080],{},"Defines the audio files, cover art, and their metadata. Each sound recording includes an ISRC, title, artist credits, and territory-specific details:",[1418,6082,6086],{"className":6083,"code":6084,"language":6085,"meta":1424,"style":1424},"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",[1426,6087,6088,6099,6109,6129,6138,6156,6165,6182,6203,6222,6231,6240,6268,6285,6293,6301],{"__ignoreMap":1424},[1447,6089,6090,6093,6096],{"class":1449,"line":1450},[1447,6091,6092],{"class":1520},"\u003C",[1447,6094,6095],{"class":2502},"SoundRecording",[1447,6097,6098],{"class":1520},">\n",[1447,6100,6101,6104,6107],{"class":1449,"line":1474},[1447,6102,6103],{"class":1520},"  \u003C",[1447,6105,6106],{"class":2502},"SoundRecordingId",[1447,6108,6098],{"class":1520},[1447,6110,6111,6114,6117,6119,6122,6125,6127],{"class":1449,"line":1483},[1447,6112,6113],{"class":1520},"    \u003C",[1447,6115,6116],{"class":2502},"ISRC",[1447,6118,1709],{"class":1520},[1447,6120,6121],{"class":1457},"SKXXX2500001",[1447,6123,6124],{"class":1520},"\u003C/",[1447,6126,6116],{"class":2502},[1447,6128,6098],{"class":1520},[1447,6130,6131,6134,6136],{"class":1449,"line":1498},[1447,6132,6133],{"class":1520},"  \u003C/",[1447,6135,6106],{"class":2502},[1447,6137,6098],{"class":1520},[1447,6139,6140,6142,6145,6147,6150,6152,6154],{"class":1449,"line":1511},[1447,6141,6103],{"class":1520},[1447,6143,6144],{"class":2502},"ResourceReference",[1447,6146,1709],{"class":1520},[1447,6148,6149],{"class":1457},"A1",[1447,6151,6124],{"class":1520},[1447,6153,6144],{"class":2502},[1447,6155,6098],{"class":1520},[1447,6157,6158,6160,6163],{"class":1449,"line":1542},[1447,6159,6103],{"class":1520},[1447,6161,6162],{"class":2502},"SoundRecordingDetailsByTerritory",[1447,6164,6098],{"class":1520},[1447,6166,6167,6169,6172,6174,6176,6178,6180],{"class":1449,"line":1551},[1447,6168,6113],{"class":1520},[1447,6170,6171],{"class":2502},"TerritoryCode",[1447,6173,1709],{"class":1520},[1447,6175,5761],{"class":1457},[1447,6177,6124],{"class":1520},[1447,6179,6171],{"class":2502},[1447,6181,6098],{"class":1520},[1447,6183,6184,6186,6189,6192,6194,6196,6199,6201],{"class":1449,"line":1692},[1447,6185,6113],{"class":1520},[1447,6187,6188],{"class":2502},"Title",[1447,6190,6191],{"class":2454}," TitleType",[1447,6193,2478],{"class":1520},[1447,6195,2588],{"class":1520},[1447,6197,6198],{"class":2529},"FormalTitle",[1447,6200,2588],{"class":1520},[1447,6202,6098],{"class":1520},[1447,6204,6205,6208,6211,6213,6216,6218,6220],{"class":1449,"line":1700},[1447,6206,6207],{"class":1520},"      \u003C",[1447,6209,6210],{"class":2502},"TitleText",[1447,6212,1709],{"class":1520},[1447,6214,6215],{"class":1457},"Delilah",[1447,6217,6124],{"class":1520},[1447,6219,6210],{"class":2502},[1447,6221,6098],{"class":1520},[1447,6223,6224,6227,6229],{"class":1449,"line":1715},[1447,6225,6226],{"class":1520},"    \u003C/",[1447,6228,6188],{"class":2502},[1447,6230,6098],{"class":1520},[1447,6232,6233,6235,6238],{"class":1449,"line":2360},[1447,6234,6113],{"class":1520},[1447,6236,6237],{"class":2502},"DisplayArtist",[1447,6239,6098],{"class":1520},[1447,6241,6242,6244,6247,6250,6253,6255,6257,6259,6261,6264,6266],{"class":1449,"line":2366},[1447,6243,6207],{"class":1520},[1447,6245,6246],{"class":2502},"PartyName",[1447,6248,6249],{"class":1520},">\u003C",[1447,6251,6252],{"class":2502},"FullName",[1447,6254,1709],{"class":1520},[1447,6256,5626],{"class":1457},[1447,6258,6124],{"class":1520},[1447,6260,6252],{"class":2502},[1447,6262,6263],{"class":1520},">\u003C/",[1447,6265,6246],{"class":2502},[1447,6267,6098],{"class":1520},[1447,6269,6270,6272,6275,6277,6279,6281,6283],{"class":1449,"line":2372},[1447,6271,6207],{"class":1520},[1447,6273,6274],{"class":2502},"ArtistRole",[1447,6276,1709],{"class":1520},[1447,6278,5644],{"class":1457},[1447,6280,6124],{"class":1520},[1447,6282,6274],{"class":2502},[1447,6284,6098],{"class":1520},[1447,6286,6287,6289,6291],{"class":1449,"line":2378},[1447,6288,6226],{"class":1520},[1447,6290,6237],{"class":2502},[1447,6292,6098],{"class":1520},[1447,6294,6295,6297,6299],{"class":1449,"line":2384},[1447,6296,6133],{"class":1520},[1447,6298,6162],{"class":2502},[1447,6300,6098],{"class":1520},[1447,6302,6303,6305,6307],{"class":1449,"line":2390},[1447,6304,6124],{"class":1520},[1447,6306,6095],{"class":2502},[1447,6308,6098],{"class":1520},[777,6310,6312],{"id":6311},"releaselist","ReleaseList",[725,6314,6315],{},"Defines the product - the album or single - and links it to its component resources:",[1418,6317,6319],{"className":6083,"code":6318,"language":6085,"meta":1424,"style":1424},"\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",[1426,6320,6321,6330,6357,6375,6393,6402,6418,6435,6453,6470,6478,6486],{"__ignoreMap":1424},[1447,6322,6323,6325,6328],{"class":1449,"line":1450},[1447,6324,6092],{"class":1520},[1447,6326,6327],{"class":2502},"Release",[1447,6329,6098],{"class":1520},[1447,6331,6332,6334,6337,6339,6342,6344,6347,6349,6351,6353,6355],{"class":1449,"line":1474},[1447,6333,6103],{"class":1520},[1447,6335,6336],{"class":2502},"ReleaseId",[1447,6338,6249],{"class":1520},[1447,6340,6341],{"class":2502},"ICPN",[1447,6343,1709],{"class":1520},[1447,6345,6346],{"class":1457},"0123456789012",[1447,6348,6124],{"class":1520},[1447,6350,6341],{"class":2502},[1447,6352,6263],{"class":1520},[1447,6354,6336],{"class":2502},[1447,6356,6098],{"class":1520},[1447,6358,6359,6361,6364,6366,6369,6371,6373],{"class":1449,"line":1483},[1447,6360,6103],{"class":1520},[1447,6362,6363],{"class":2502},"ReleaseReference",[1447,6365,1709],{"class":1520},[1447,6367,6368],{"class":1457},"R0",[1447,6370,6124],{"class":1520},[1447,6372,6363],{"class":2502},[1447,6374,6098],{"class":1520},[1447,6376,6377,6379,6382,6384,6387,6389,6391],{"class":1449,"line":1498},[1447,6378,6103],{"class":1520},[1447,6380,6381],{"class":2502},"ReleaseType",[1447,6383,1709],{"class":1520},[1447,6385,6386],{"class":1457},"Album",[1447,6388,6124],{"class":1520},[1447,6390,6381],{"class":2502},[1447,6392,6098],{"class":1520},[1447,6394,6395,6397,6400],{"class":1449,"line":1511},[1447,6396,6103],{"class":1520},[1447,6398,6399],{"class":2502},"ReleaseDetailsByTerritory",[1447,6401,6098],{"class":1520},[1447,6403,6404,6406,6408,6410,6412,6414,6416],{"class":1449,"line":1542},[1447,6405,6113],{"class":1520},[1447,6407,6171],{"class":2502},[1447,6409,1709],{"class":1520},[1447,6411,5761],{"class":1457},[1447,6413,6124],{"class":1520},[1447,6415,6171],{"class":2502},[1447,6417,6098],{"class":1520},[1447,6419,6420,6422,6425,6427,6429,6431,6433],{"class":1449,"line":1551},[1447,6421,6113],{"class":1520},[1447,6423,6424],{"class":2502},"DisplayArtistName",[1447,6426,1709],{"class":1520},[1447,6428,5626],{"class":1457},[1447,6430,6124],{"class":1520},[1447,6432,6424],{"class":2502},[1447,6434,6098],{"class":1520},[1447,6436,6437,6439,6441,6443,6445,6447,6449,6451],{"class":1449,"line":1692},[1447,6438,6113],{"class":1520},[1447,6440,6188],{"class":2502},[1447,6442,6191],{"class":2454},[1447,6444,2478],{"class":1520},[1447,6446,2588],{"class":1520},[1447,6448,6198],{"class":2529},[1447,6450,2588],{"class":1520},[1447,6452,6098],{"class":1520},[1447,6454,6455,6457,6459,6461,6464,6466,6468],{"class":1449,"line":1700},[1447,6456,6207],{"class":1520},[1447,6458,6210],{"class":2502},[1447,6460,1709],{"class":1520},[1447,6462,6463],{"class":1457},"ONE",[1447,6465,6124],{"class":1520},[1447,6467,6210],{"class":2502},[1447,6469,6098],{"class":1520},[1447,6471,6472,6474,6476],{"class":1449,"line":1715},[1447,6473,6226],{"class":1520},[1447,6475,6188],{"class":2502},[1447,6477,6098],{"class":1520},[1447,6479,6480,6482,6484],{"class":1449,"line":2360},[1447,6481,6133],{"class":1520},[1447,6483,6399],{"class":2502},[1447,6485,6098],{"class":1520},[1447,6487,6488,6490,6492],{"class":1449,"line":2366},[1447,6489,6124],{"class":1520},[1447,6491,6327],{"class":2502},[1447,6493,6098],{"class":1520},[777,6495,6497],{"id":6496},"deallist","DealList",[725,6499,6500,6501,6504],{},"The ",[787,6502,6503],{},"only"," section that grants commercial rights. This is where territory restrictions live:",[1418,6506,6508],{"className":6083,"code":6507,"language":6085,"meta":1424,"style":1424},"\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",[1426,6509,6510,6519,6536,6545,6554,6570,6587,6605,6614,6633,6642,6651,6669,6677,6685,6693],{"__ignoreMap":1424},[1447,6511,6512,6514,6517],{"class":1449,"line":1450},[1447,6513,6092],{"class":1520},[1447,6515,6516],{"class":2502},"ReleaseDeal",[1447,6518,6098],{"class":1520},[1447,6520,6521,6523,6526,6528,6530,6532,6534],{"class":1449,"line":1474},[1447,6522,6103],{"class":1520},[1447,6524,6525],{"class":2502},"DealReleaseReference",[1447,6527,1709],{"class":1520},[1447,6529,6368],{"class":1457},[1447,6531,6124],{"class":1520},[1447,6533,6525],{"class":2502},[1447,6535,6098],{"class":1520},[1447,6537,6538,6540,6543],{"class":1449,"line":1483},[1447,6539,6103],{"class":1520},[1447,6541,6542],{"class":2502},"Deal",[1447,6544,6098],{"class":1520},[1447,6546,6547,6549,6552],{"class":1449,"line":1498},[1447,6548,6113],{"class":1520},[1447,6550,6551],{"class":2502},"DealTerms",[1447,6553,6098],{"class":1520},[1447,6555,6556,6558,6560,6562,6564,6566,6568],{"class":1449,"line":1511},[1447,6557,6207],{"class":1520},[1447,6559,6171],{"class":2502},[1447,6561,1709],{"class":1520},[1447,6563,5761],{"class":1457},[1447,6565,6124],{"class":1520},[1447,6567,6171],{"class":2502},[1447,6569,6098],{"class":1520},[1447,6571,6572,6574,6577,6579,6581,6583,6585],{"class":1449,"line":1542},[1447,6573,6207],{"class":1520},[1447,6575,6576],{"class":2502},"ExcludedTerritoryCode",[1447,6578,1709],{"class":1520},[1447,6580,5784],{"class":1457},[1447,6582,6124],{"class":1520},[1447,6584,6576],{"class":2502},[1447,6586,6098],{"class":1520},[1447,6588,6589,6591,6594,6596,6599,6601,6603],{"class":1449,"line":1551},[1447,6590,6207],{"class":1520},[1447,6592,6593],{"class":2502},"CommercialModelType",[1447,6595,1709],{"class":1520},[1447,6597,6598],{"class":1457},"SubscriptionModel",[1447,6600,6124],{"class":1520},[1447,6602,6593],{"class":2502},[1447,6604,6098],{"class":1520},[1447,6606,6607,6609,6612],{"class":1449,"line":1692},[1447,6608,6207],{"class":1520},[1447,6610,6611],{"class":2502},"Usage",[1447,6613,6098],{"class":1520},[1447,6615,6616,6619,6622,6624,6627,6629,6631],{"class":1449,"line":1700},[1447,6617,6618],{"class":1520},"        \u003C",[1447,6620,6621],{"class":2502},"UseType",[1447,6623,1709],{"class":1520},[1447,6625,6626],{"class":1457},"OnDemandStream",[1447,6628,6124],{"class":1520},[1447,6630,6621],{"class":2502},[1447,6632,6098],{"class":1520},[1447,6634,6635,6638,6640],{"class":1449,"line":1715},[1447,6636,6637],{"class":1520},"      \u003C/",[1447,6639,6611],{"class":2502},[1447,6641,6098],{"class":1520},[1447,6643,6644,6646,6649],{"class":1449,"line":2360},[1447,6645,6207],{"class":1520},[1447,6647,6648],{"class":2502},"ValidityPeriod",[1447,6650,6098],{"class":1520},[1447,6652,6653,6655,6658,6660,6663,6665,6667],{"class":1449,"line":2366},[1447,6654,6618],{"class":1520},[1447,6656,6657],{"class":2502},"StartDate",[1447,6659,1709],{"class":1520},[1447,6661,6662],{"class":1457},"2026-03-15",[1447,6664,6124],{"class":1520},[1447,6666,6657],{"class":2502},[1447,6668,6098],{"class":1520},[1447,6670,6671,6673,6675],{"class":1449,"line":2372},[1447,6672,6637],{"class":1520},[1447,6674,6648],{"class":2502},[1447,6676,6098],{"class":1520},[1447,6678,6679,6681,6683],{"class":1449,"line":2378},[1447,6680,6226],{"class":1520},[1447,6682,6551],{"class":2502},[1447,6684,6098],{"class":1520},[1447,6686,6687,6689,6691],{"class":1449,"line":2384},[1447,6688,6133],{"class":1520},[1447,6690,6542],{"class":2502},[1447,6692,6098],{"class":1520},[1447,6694,6695,6697,6699],{"class":1449,"line":2390},[1447,6696,6124],{"class":1520},[1447,6698,6516],{"class":2502},[1447,6700,6098],{"class":1520},[889,6702,6703],{},[725,6704,6705,6708,6709,6712,6713,6717,6718,6720,6721,6724],{},[787,6706,6707],{},"Common DDEX mistake:"," ",[1426,6710,6711],{},"DetailsByTerritory"," in ResourceList and ReleaseList describes ",[6714,6715,6716],"em",{},"how content is presented"," in different markets. The ",[1426,6719,6497],{}," defines ",[6714,6722,6723],{},"where content is available",". Confusing these two - applying territory restrictions in metadata instead of deals - is one of the most frequent implementation errors.",[777,6726,6728],{"id":6727},"territory-restrictions-in-practice","Territory Restrictions in Practice",[725,6730,6731,6732,3337],{},"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 ",[787,6733,6734],{},"three XML sections",[2106,6736,6737,6742,6747],{},[800,6738,6739,6741],{},[1426,6740,6162],{}," in ResourceList",[800,6743,6744,6746],{},[1426,6745,6399],{}," in ReleaseList",[800,6748,6749,6751],{},[1426,6750,6551],{}," in DealList",[725,6753,6754,6755,6758],{},"Platforms like Revelator handle this at the release level through their UI, but ",[787,6756,6757],{},"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.",[4850,6760],{},[732,6762,6764],{"id":6763},"real-world-gotchas","Real-World Gotchas",[725,6766,6767],{},"Having worked with distributors who use Revelator as their delivery backbone, here are the practical challenges we've encountered:",[889,6769,6770],{},[725,6771,6772,6775],{},[787,6773,6774],{},"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.",[777,6777,6779],{"id":6778},"_2-full-object-re-submission","2. Full Object Re-submission",[725,6781,6782],{},"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.",[777,6784,6786],{"id":6785},"_3-breaking-api-changes","3. Breaking API Changes",[725,6788,6789],{},"Revelator's API has had several breaking changes in 2025-2026:",[955,6791,6792,6802],{},[958,6793,6794],{},[961,6795,6796,6799],{},[964,6797,6798],{},"Change",[964,6800,6801],{},"When",[977,6803,6804,6819,6830,6838],{},[961,6805,6806,6816],{},[982,6807,6808,6809,6812,6813],{},"UPC type changed from ",[1426,6810,6811],{},"number"," to ",[1426,6814,6815],{},"string",[982,6817,6818],{},"Feb 2026",[961,6820,6821,6827],{},[982,6822,6823,6824],{},"API base URL migrated to ",[1426,6825,6826],{},"api.revelator.com",[982,6828,6829],{},"Mar 2026",[961,6831,6832,6835],{},[982,6833,6834],{},"Production/engineering credits became mandatory",[982,6836,6837],{},"Jun 2025",[961,6839,6840,6843],{},[982,6841,6842],{},"Zero-sentinel IDs deprecated",[982,6844,6845],{},"Nov 2025",[725,6847,6848],{},"Your integration layer needs resilience against schema drift.",[777,6850,6852],{"id":6851},"_4-dsp-specific-ddex-interpretation","4. DSP-Specific DDEX Interpretation",[725,6854,6855],{},"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.",[777,6857,6859],{"id":6858},"_5-locked-distribution-states","5. Locked Distribution States",[725,6861,6862],{},"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.",[4850,6864],{},[732,6866,6868],{"id":6867},"when-does-option-a-become-a-dead-end","When Does Option A Become a Dead End?",[725,6870,6871],{},"The hybrid approach works well when:",[797,6873,6874,6880,6886,6889],{},[800,6875,6876,6877],{},"Your catalog is under ",[787,6878,6879],{},"50,000 releases",[800,6881,6882,6883],{},"You have fewer than ",[787,6884,6885],{},"30 DSP targets",[800,6887,6888],{},"Territory complexity is moderate (album-level restrictions, not per-track-per-DSP)",[800,6890,6891],{},"Royalty reporting needs are standard (DSP-level aggregates, not sub-publishing splits)",[725,6893,6894],{},"It starts breaking down when:",[955,6896,6897,6907],{},[958,6898,6899],{},[961,6900,6901,6904],{},[964,6902,6903],{},"Signal",[964,6905,6906],{},"Why It Matters",[977,6908,6909,6919,6929,6939],{},[961,6910,6911,6916],{},[982,6912,6913],{},[787,6914,6915],{},"Royalty logic becomes the product",[982,6917,6918],{},"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",[961,6920,6921,6926],{},[982,6922,6923],{},[787,6924,6925],{},"You need real-time delivery control",[982,6927,6928],{},"The mandatory approval step and lack of granular delivery status callbacks limit automation at scale",[961,6930,6931,6936],{},[982,6932,6933],{},[787,6934,6935],{},"DSP-specific customization matters",[982,6937,6938],{},"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",[961,6940,6941,6946],{},[982,6942,6943],{},[787,6944,6945],{},"Vendor risk becomes unacceptable",[982,6947,6948],{},"Revelator is VC-backed. API changes, pricing shifts, or an acquisition could force migration under pressure",[1217,6950,6951],{},[725,6952,6953,6954,6957],{},"The practical tipping point is usually ",[787,6955,6956],{},"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.",[4850,6959],{},[732,6961,6963],{"id":6962},"the-hybrid-to-independent-migration-path","The Hybrid-to-Independent Migration Path",[725,6965,6966],{},"The smartest architecture for Option A anticipates Option B:",[2106,6968,6969,6975,6981,6987],{},[800,6970,6971,6974],{},[787,6972,6973],{},"Own your metadata."," Never treat Revelator as the source of truth. Your database is canonical; Revelator is a sync target.",[800,6976,6977,6980],{},[787,6978,6979],{},"Abstract the delivery layer."," Build an internal delivery interface that Revelator implements today but could be swapped for direct DSP integrations tomorrow.",[800,6982,6983,6986],{},[787,6984,6985],{},"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.",[800,6988,6989,6992],{},[787,6990,6991],{},"Invest in DDEX competency."," Understanding ERN generation and validation - even if Revelator handles it today - is essential knowledge for the eventual transition.",[832,6994,6995],{},[725,6996,6997,6998,7001],{},"This way, when Option A hits its ceiling, you migrate ",[787,6999,7000],{},"component by component"," rather than executing a risky big-bang rewrite.",[4850,7003],{},[732,7005,7007],{"id":7006},"conclusion","Conclusion",[725,7009,7010],{},"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.",[725,7012,7013],{},"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.",[4850,7015],{},[732,7017,7019],{"id":7018},"resources","Resources",[797,7021,7022,7030,7037,7044,7048,7053],{},[800,7023,7024],{},[2431,7025,7029],{"href":7026,"rel":7027},"https://api-docs.revelator.com",[7028],"nofollow","Revelator API Documentation",[800,7031,7032],{},[2431,7033,7036],{"href":7034,"rel":7035},"https://kb.ddex.net/implementing-each-standard/electronic-release-notification-message-suite-(ern)/",[7028],"DDEX ERN Knowledge Base",[800,7038,7039],{},[2431,7040,7043],{"href":7041,"rel":7042},"https://ddexvalidator.musictechlab.io/",[7028],"MTL DDEX Validator",[800,7045,7046],{},[2431,7047,132],{"href":133},[800,7049,7050],{},[2431,7051,7052],{"href":157},"Introduction to Generating DDEX Files Using Python",[800,7054,7055,7058],{},[2431,7056,7057],{"href":85},"AI-Powered Analytics Dashboard"," - once distribution data flows into your analytics pipeline, make it queryable with natural language",[2144,7060,7061],{},"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":1424,"searchDepth":1474,"depth":1474,"links":7063},[7064,7065,7066,7073,7078,7084,7090,7091,7092,7093],{"id":4854,"depth":1474,"text":4855},{"id":4920,"depth":1474,"text":4921},{"id":4952,"depth":1474,"text":4953,"children":7067},[7068,7069,7070,7071,7072],{"id":4962,"depth":1483,"text":4963},{"id":4983,"depth":1483,"text":719},{"id":5015,"depth":1483,"text":5016},{"id":2214,"depth":1483,"text":5030},{"id":5044,"depth":1483,"text":5045},{"id":5070,"depth":1474,"text":5071,"children":7074},[7075,7076,7077],{"id":5194,"depth":1483,"text":5195},{"id":5262,"depth":1483,"text":5263},{"id":5318,"depth":1483,"text":5319},{"id":6066,"depth":1474,"text":6067,"children":7079},[7080,7081,7082,7083],{"id":6076,"depth":1483,"text":6077},{"id":6311,"depth":1483,"text":6312},{"id":6496,"depth":1483,"text":6497},{"id":6727,"depth":1483,"text":6728},{"id":6763,"depth":1474,"text":6764,"children":7085},[7086,7087,7088,7089],{"id":6778,"depth":1483,"text":6779},{"id":6785,"depth":1483,"text":6786},{"id":6851,"depth":1483,"text":6852},{"id":6858,"depth":1483,"text":6859},{"id":6867,"depth":1474,"text":6868},{"id":6962,"depth":1474,"text":6963},{"id":7006,"depth":1474,"text":7007},{"id":7018,"depth":1474,"text":7019},"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":7097},"/images/blog/musictechlab_blog_building-a-custom-music-delivery-platform-on-the-revelator-api.webp",{"enabled":2194,"items":7099},[7100,7103,7105,7107],{"text":7101,"icon":7102},"Revelator claims 100+ DSP integrations but requires a manual approval step before delivery.","i-lucide-code",{"text":7104,"icon":4825},"Territory restrictions must be consistent across three XML sections or deliveries fail silently.",{"text":7106,"icon":1912},"Own your metadata from day one and treat Revelator as a sync target, not the source of truth.",{"text":7108,"icon":7109},"The hybrid approach typically hits its ceiling after 2-3 years of scaling.","i-lucide-trending-up",{},{"title":112,"description":7095},[2173,7113,7114,2215],"development","API","j-0HOY7SF2pZKgaTUKG4xI6pYGGOIVWAd9EiQc5LjGU",{"id":7117,"title":184,"authors":7118,"badge":2174,"body":7123,"category":2173,"client":2174,"date":7294,"description":7295,"extension":2177,"faq":2174,"featured":69,"featuredOrder":2174,"hidden":69,"image":7296,"keyTakeaways":7298,"meta":7310,"navigation":2194,"path":185,"seo":7311,"status":2174,"stem":186,"tags":7312,"teaser":2174,"__hash__":7313,"score":1498},"posts/blog/music-data/self-publishing-in-the-music-industry-a-tale-of-emuze-me.md",[7119],{"name":7120,"avatar":7121},"Adam Golański",{"src":7122},"/images/people/adam-golanski.webp",{"type":722,"value":7124,"toc":7287},[7125,7128,7131,7133,7137,7141,7143,7147,7153,7156,7159,7179,7184,7186,7190,7193,7211,7216,7218,7222,7225,7248,7253,7255,7259,7262,7279,7284],[725,7126,7127],{},"Self-publishing in music has shifted power from traditional record labels to individual artists. For decades, a few major labels controlled distribution and determined which artists succeeded. That model left creators dependent on labels for both exposure and income.",[725,7129,7130],{},"The advent of the internet and digital technologies in the late 20th century began to challenge this status quo. In the early 2000s, the proliferation of digital recording software and platforms like MySpace gave rise to a new era where artists could record and share their music online with minimal cost.",[4850,7132],{},[732,7134,7136],{"id":7135},"the-evolution-of-music-distribution","The Evolution of Music Distribution",[7138,7139],"project-timeline",{":items":7140},"[{\"title\":\"Early 2000s — MySpace Era\",\"description\":\"Digital recording software and platforms like MySpace let artists record and share music online with minimal cost. The first major step towards democratizing music production.\",\"icon\":\"i-lucide-music\"},{\"title\":\"Mid-2000s — iTunes Revolution\",\"description\":\"Artists could distribute music directly to a global audience and retain a greater portion of their earnings. The traditional distribution model was disrupted.\",\"icon\":\"i-lucide-store\"},{\"title\":\"2010s — Streaming Takes Over\",\"description\":\"Spotify, SoundCloud, and YouTube offered even more avenues for independent publishing, reaching listeners worldwide and transcending geographical barriers.\",\"icon\":\"i-lucide-radio\"},{\"title\":\"Today — Self-Publishing Is Mainstream\",\"description\":\"Artists control their creative output and reach audiences worldwide. Self-publishing is no longer an edgy alternative but a core part of the music industry.\",\"icon\":\"i-lucide-globe\"}]",[4850,7142],{},[732,7144,7146],{"id":7145},"digital-distribution-benefits-and-challenges","Digital Distribution: Benefits and Challenges",[725,7148,7149],{},[4490,7150],{"alt":7151,"src":7152},"Digital Distribution","/images/cdn-migrated/techivation-unsplash.webp",[725,7154,7155],{},"Technological advancements have played a crucial role in these changes. The advent of digital recording has democratized music production, making it more accessible and allowing for greater creative freedom. The shift from physical mediums like cassettes and CDs to digital formats has been drastic — physical formats have now become fetishized relics, replaced by digital recording and streaming services.",[725,7157,7158],{},"The digital era has also redefined artist-fan interactions. Social media and streaming platforms have bridged the gap between artists and their audiences, facilitating direct and more personal connections.",[740,7160,7162,7166,7171,7175],{"className":7161},[743,744,745,746,747],[749,7163],{"description":7164,"icon":7109,"title":7165},"Artists can reach global audiences instantly without label backing or physical distribution networks.","Wider Exposure",[749,7167],{"description":7168,"icon":7169,"title":7170},"Social media and streaming platforms enable personal, direct engagement with audiences.","i-lucide-users","Direct Fan Connections",[749,7172],{"description":7173,"icon":4825,"title":7174},"The ease of digital distribution has made it increasingly difficult for artists to stand out.","Market Oversaturation",[749,7176],{"description":7177,"icon":757,"title":7178},"Streaming offers wider exposure but often provides lower per-stream payouts compared to traditional sales.","Lower Per-Stream Payouts",[889,7180,7181],{},[725,7182,7183],{},"Artists have had to diversify their income sources, turning to live performances, online concerts, and placements in TV, films, and commercials to compensate for lower streaming revenue.",[4850,7185],{},[732,7187,7189],{"id":7188},"monetization-and-accessibility","Monetization and Accessibility",[725,7191,7192],{},"Digital distribution platforms like DistroKid, CD Baby, and TuneCore have become the primary avenue for music streaming and downloading. They act as intermediaries between artists and streaming services like Spotify, Apple Music, and Tidal.",[740,7194,7196,7201,7206],{"className":7195},[743,744,1908,746,747],[749,7197],{"description":7198,"icon":7199,"title":7200},"Artists provide sound files and metadata. The platform replicates data across all services, ensuring consistency.","i-lucide-upload","Streamlined Upload",[749,7202],{"description":7203,"icon":7204,"title":7205},"Platforms track streams and downloads across services, collecting and paying out royalties automatically.","i-lucide-wallet","Royalty Management",[749,7207],{"description":7208,"icon":7209,"title":7210},"Music becomes available on dozens of streaming services simultaneously with a single upload.","i-lucide-globe","Global Reach",[1217,7212,7213],{},[725,7214,7215],{},"The sheer volume of music on streaming platforms is both an advantage and a challenge. Standing out in an overcrowded digital space requires strategy, not just talent. Playlists play a crucial role, but securing a spot often requires industry connections or curator favour.",[4850,7217],{},[732,7219,7221],{"id":7220},"emuzeme-self-publishing-done-right","Emuze.me: Self-Publishing Done Right",[725,7223,7224],{},"Amongst these challenges, a local Polish service emerges as a quintessential example of a platform embracing the global trends of self-publishing and digital distribution. Created by e-muzyka, a part of the renowned Empik Group, emuze.me stands on a foundation of deep industry experience and technological expertise.",[740,7226,7228,7233,7238,7243],{"className":7227},[743,744,745,746,747],[749,7229],{"description":7230,"icon":7231,"title":7232},"Upload and distribute music to over 50 digital services worldwide, bypassing traditional barriers like distributor negotiations.","i-lucide-rocket","Direct Distribution",[749,7234],{"description":7235,"icon":7236,"title":7237},"Artists retain up to 85% of their sales revenue without restrictive long-term contracts — rare flexibility in the Polish market.","i-lucide-percent","85% Revenue Share",[749,7239],{"description":7240,"icon":7241,"title":7242},"Promotion across digital services, social media campaigns, and inclusion in newsletters to Polish media outlets.","i-lucide-megaphone","Marketing Support",[749,7244],{"description":7245,"icon":7246,"title":7247},"Designed for clarity and simplicity, accommodating artists at various stages of their career.","i-lucide-layout-dashboard","Simple Interface",[832,7249,7250],{},[725,7251,7252],{},"Emuze.me's impact on the music industry is significant. By integrating self-publishing and digital distribution, the platform expands artists' global reach while helping them creatively and financially — a testament to building fair digital platforms in music.",[4850,7254],{},[732,7256,7258],{"id":7257},"future-perspectives","Future Perspectives",[725,7260,7261],{},"As we look toward the future of music distribution, advances in technology are expected to play significant roles.",[740,7263,7265,7270,7274],{"className":7264},[743,744,1908,746,747],[749,7266],{"description":7267,"icon":7268,"title":7269},"Artificial intelligence could personalize music discovery further, offering sophisticated data analytics and marketing strategies.","i-lucide-brain","AI-Powered Discovery",[749,7271],{"description":7272,"icon":4817,"title":7273},"More transparent and efficient royalty distribution systems, giving artists greater control over intellectual property.","Blockchain Royalties",[749,7275],{"description":7276,"icon":7277,"title":7278},"Virtual and augmented reality may offer new ways for artists to present music and engage with fans.","i-lucide-glasses","Immersive Experiences",[889,7280,7281],{},[725,7282,7283],{},"A significant risk is the proliferation of AI-generated music, which can flood the market with vast amounts of content, making it harder for human artists to stand out and potentially exerting downward pressure on the value of music.",[725,7285,7286],{},"In this rapidly evolving domain, adaptability and innovation will be key. Platforms that can leverage new technologies while providing real support to artists and their creative freedom are likely to lead the way in the future of music distribution.",{"title":1424,"searchDepth":1474,"depth":1474,"links":7288},[7289,7290,7291,7292,7293],{"id":7135,"depth":1474,"text":7136},{"id":7145,"depth":1474,"text":7146},{"id":7188,"depth":1474,"text":7189},{"id":7220,"depth":1474,"text":7221},{"id":7257,"depth":1474,"text":7258},"2023-11-13T00:00:00.000Z","How self-publishing evolved in the music industry, shifting control from record labels to independent artists, and how Emuze.me empowers creators today.",{"src":7297},"/images/blog/musictechlab_blog_self-publishing-in-the-music-industry-a-tale-of-emuze-me.webp",{"enabled":2194,"items":7299},[7300,7303,7306,7308],{"text":7301,"icon":7302},"Self-publishing shifted power from major labels to independent artists over two decades.","i-lucide-music",{"text":7304,"icon":7305},"Emuze.me offers 85% revenue share and distribution to 50+ services without long-term contracts.","i-lucide-dollar-sign",{"text":7307,"icon":7109},"Streaming provides wider exposure but lower per-stream payouts than traditional sales.",{"text":7309,"icon":7268},"AI-generated music risks flooding platforms and making it harder for human artists to stand out.",{},{"title":184,"description":7295},[4109,2173,2215],"i-mVky4av__wlZUtY49WTl9RnNMDNC0LQ5Dakdw51cI",1776952558700]