Built for secure music delivery

Multi-tenant isolation, API key auth, dual APIs, DDEX parsing, and secure file storage — everything a DSP needs to receive and serve catalog data.

Tenant Isolation

Every organization operates in a fully isolated environment. Attribute-based multi-tenancy ensures every query is automatically scoped to the current tenant — no data leaks between stores, ever.

  • Automatic query scoping per organization
  • Pre-release content isolation between stores
  • API keys scoped to their owning organization
API request scoped to organization
# API key is scoped to an organization
# The X-Organization-Id header selects the tenant

curl -H "Authorization: Bearer dds_abc123..." \
     -H "X-Organization-Id: org_spotify" \
     https://your-instance.fly.dev/api/json/releases

API Key Authentication

Create scoped API keys for each organization. Keys support granular permissions so you can grant exactly the access level needed.

  • Bearer token auth: Authorization: Bearer dds_...
  • Granular scopes: read, write, admin
  • Expiration support and last-used tracking
Authenticating with an API key
# Create a key via the dashboard, then use it:

curl -X GET \
     -H "Authorization: Bearer dds_live_a1b2c3d4e5f6..." \
     -H "Content-Type: application/vnd.api+json" \
     https://your-instance.fly.dev/api/json/releases

Dual API Layer

Access your catalog through whichever protocol fits your stack. Both APIs are generated from the same Ash resource definitions, so they stay perfectly in sync.

  • JSON:API (REST) with Swagger/OpenAPI docs at /api/json/swaggerui
  • GraphQL with playground at /gql/playground
  • Filter, sort, and paginate on any field
JSON:API and GraphQL examples
# JSON:API — list releases with artist included
curl -H "Authorization: Bearer dds_..." \
     "/api/json/releases?include=artists&filter[title]=Abbey+Road"

# GraphQL — query releases with tracks
curl -X POST /gql \
     -H "Authorization: Bearer dds_..." \
     -H "Content-Type: application/json" \
     -d '{"query": "{ releases { title tracks { title isrc } } }"}'

DDEX ERN Parsing

Upload any ERN XML and we handle the rest. Automatic version detection means you don't need to know whether a label sends 3.8.2 or 4.3 — both work seamlessly.

  • ERN 3.8.2 and 4.3 support
  • Extracts releases, tracks, artists, deals, territories, labels
  • ISRC and UPC validation
  • Automatic version detection from XML namespace
Supported ERN structure
<!-- ERN 4.3 — auto-detected from namespace -->
<ern:NewReleaseMessage
  xmlns:ern="http://ddex.net/xml/ern/43">
  <ReleaseList>
    <Release>
      <ReleaseId>
        <ICPN>00602445073658</ICPN>
      </ReleaseId>
      <ReferenceTitle>Abbey Road</ReferenceTitle>
      ...
    </Release>
  </ReleaseList>
</ern:NewReleaseMessage>

Secure File Storage

Audio files, artwork, and XML are stored on S3-compatible storage (Tigris) with presigned URLs. Every file path is scoped to the owning tenant.

  • Presigned URLs for secure download
  • Tenant-isolated paths: tenants/org/deliveries/id/...
  • Checksums and integrity verification
  • Lifecycle: upload → stored → purged
S3 key structure
# Files are stored under tenant-isolated paths:

tenants/org_spotify/deliveries/abc123/
  ├── message.xml
  ├── audio/
  │   ├── track_01.flac
  │   └── track_02.flac
  └── artwork/
      └── cover.jpg

# Presigned URLs provide time-limited access
# without exposing storage credentials

Real-time Processing

Deliveries are processed asynchronously with Oban workers. PubSub broadcasts status updates in real time, and every step is recorded for a full audit trail.

  • Async Oban workers with retries and backoff
  • PubSub delivery status updates
  • Full audit trail from receipt to completion
Delivery processing pipeline
# Delivery lifecycle:
#
# 1. Upload    → Files received, delivery created
# 2. Parsing   → XML validated, version detected
# 3. Ingesting → Releases, tracks, artists extracted
# 4. Complete  → Data available via API
#
# Each step broadcasts via PubSub:
# "delivery:<id>" → %{status: :parsing}
# "delivery:<id>" → %{status: :complete}

Ready to try it?

Upload a DDEX XML file and see how it becomes a queryable API in seconds.