Quickstart
api API play_arrow Quickstart terminal Explorer verified Webhook tester history Changelog report Errors monitor_heart Status menu_book Glossary help Help

Quickstart

From a brand-new sandbox account to a live release delivered to every DSP — in about 6 minutes of copy-paste. Every step runs against the real sandbox; lifecycle webhooks fire on the real channels. When you're ready for production, swap api-sandbox.tonegrid.pro/apiapi.tonegrid.pro and the same flow ships music for real.

timer ~6 minutes science sandbox-safe (no real money / no real DSP push) code copy-paste curl

Set these env vars first

export API="https://api-sandbox.tonegrid.pro/api"
export TG="<your tgk_ API key — minted in step 2>"
export REL=""             # release UUID — populated after step 4
export TRACK=""           # track UUID — populated after step 5
1

Create a sandbox account

1 min

Sign up on the sandbox to get a tenant. No card required. Sandbox is identical to production but isolated — no real money, no real DSP pushes, but full lifecycle simulation.

bash
curl -X POST "https://api-sandbox.tonegrid.pro/api/auth/signup" \
  -H "Content-Type: application/json" \
  -d '{
    "email":    "you@example.com",
    "password": "<a strong password>",
    "tenant_name": "Demo Records"
  }'
expected response
{
  "success": true,
  "message": "Account created.",
  "data": {
    "user":   { "uuid": "u-9f3a...", "email": "you@example.com" },
    "tenant": { "uuid": "t-d4c5...", "name": "Demo Records" },
    "access_token": "eyJhbGc...",
    "refresh_token": "rft_..."
  }
}
lightbulb
Save the access_token — you'll use it as a Bearer token in every subsequent call. It's a JWT with a 1-hour TTL; the refresh_token is set as an httpOnly cookie scoped to /api/auth.
3

Create an artist

An artist is the named entity all releases roll up under. Slugs are unique per tenant.

bash
curl -X POST "https://api-sandbox.tonegrid.pro/api/artists" \
  -H "Authorization: Bearer $TG" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ada Okafor",
    "slug": "ada-okafor"
  }'
expected response
{
  "success": true,
  "data": {
    "uuid": "a-3c2b...",
    "name": "Ada Okafor",
    "slug": "ada-okafor"
  }
}
lightbulb
Capture the artist uuid — you'll attach releases to it in the next step.
4

Create a release (draft)

1 min

New releases start in draft. You can edit freely until you call /submit. Set a release date at least 7 days in the future to give DSPs ingest lead time.

bash
curl -X POST "https://api-sandbox.tonegrid.pro/api/releases" \
  -H "Authorization: Bearer $TG" \
  -H "Content-Type: application/json" \
  -d '{
    "title":         "Midnight in Lagos",
    "type":          "ep",
    "artist_id":     "a-3c2b...",
    "release_date":  "2026-06-17",
    "primary_genre": "Afrobeats",
    "language":      "en",
    "upc":           "195497123456"
  }'
expected response
{
  "success": true,
  "data": {
    "uuid":   "r-12a3...",
    "status": "draft",
    "title":  "Midnight in Lagos",
    "type":   "ep",
    "release_date": "2026-06-17"
  }
}
lightbulb
Pass localized_titles as a BCP-47 keyed object (eg {"fr": "...", "yo": "..."}) and ToneGrid will emit per-locale DisplayTitle blocks in the DDEX automatically.
5

Add a track + upload audio

1 min

Track metadata first, then upload the audio file. Files over 100 MB trigger AWS S3 Multipart Upload automatically (peak memory pinned at 16 MB regardless of file size).

bash
# 1. Create the track row
curl -X POST "https://api-sandbox.tonegrid.pro/api/releases/$REL/tracks" \
  -H "Authorization: Bearer $TG" \
  -d '{
    "title":            "Lagos Skyline",
    "artist_id":        "a-3c2b...",
    "isrc":             "NGTON2600001",
    "duration_seconds": 218,
    "track_number":     1
  }'

# 2. Upload the audio (multipart form-data, field name `audio`)
curl -X POST "https://api-sandbox.tonegrid.pro/api/tracks/$TRACK/audio" \
  -H "Authorization: Bearer $TG" \
  -F "audio=@./lagos-skyline.wav"
expected response
{
  "success": true,
  "data": {
    "uuid":            "t-5d4c...",
    "title":           "Lagos Skyline",
    "isrc":            "NGTON2600001",
    "audio_format":    "WAV",
    "audio_bit_depth": 24,
    "audio_sample_rate_hz": 96000,
    "audio_channels":  2,
    "audio_md5":       "d41d8cd98f00b204e9800998ecf8427e"
  }
}
lightbulb
ffprobe runs on the uploaded file and back-fills audio_format / sample-rate / bit-depth / channels / MD5 in ~5 seconds. Then the audio-validator gates: tracks under 44.1 kHz / 16-bit / 2 channels are auto-rejected.
6

Attach DSPs

Tell ToneGrid which stores to ship to. List all active DSPs via GET /dsps first.

bash
curl -X POST "https://api-sandbox.tonegrid.pro/api/releases/$REL/dsps" \
  -H "Authorization: Bearer $TG" \
  -d '{
    "dsps": ["spotify", "apple-music", "amazon-music", "youtube-music", "tidal", "deezer", "tiktok", "audiomack", "boomplay"]
  }'
expected response
{
  "success": true,
  "message": "9 DSP(s) attached.",
  "data": {
    "attached": ["spotify","apple-music","amazon-music","youtube-music","tidal","deezer","tiktok","audiomack","boomplay"]
  }
}
lightbulb
Pass per-DSP overrides (territory, monetization, pricing, release-date) via PUT /releases/:uuid/dsps/:dsp_slug AFTER attaching — useful for Japan-only physical exclusives, US-only pre-orders, etc.
7

Validate the DDEX before approving (optional but recommended)

Run the generated NewReleaseMessage through the official DDEX ERN 4.3 XSD validator. Catches schema-breaking metadata before any DSP sees it.

bash
curl "https://api-sandbox.tonegrid.pro/api/releases/$REL/ddex/validate/spotify" \
  -H "Authorization: Bearer $TG" | jq .data.validation
expected response
{
  "valid":       true,
  "errors":      [],
  "xsd_path":    "/opt/ddex-schemas/ern43/release-notification.xsd",
  "ern_version": "4.3"
}
lightbulb
Hook this into your CI: if validation.valid !== true, surface the per-error line/column/message in your release-approval UI and block the user from submitting. Most aggregators ship blind — ToneGrid lets you XSD-check before push.
8

Approve + watch the lifecycle flow

2 min

Approving a release queues per-DSP delivery and fires release.dsp.<slug>.submitted for each one. The DDEX worker advances rows through submitted → accepted → live over ~2 minutes (sim mode); on real DSPs it's identical but driven by real ingestion ACKs.

bash
# 1. Approve
curl -X POST "https://api-sandbox.tonegrid.pro/api/releases/$REL/approve" \
  -H "Authorization: Bearer $TG" \
  -H "Idempotency-Key: approve-$REL-$(date +%s)"

# 2. Watch the per-DSP delivery state
watch -n 10 "curl -s -H \"Authorization: Bearer $TG\" \
  https://api-sandbox.tonegrid.pro/api/releases/$REL/ddex/deliveries | jq .data.deliveries"
expected response
// Right after approve:
{ "dsp": "spotify",      "status": "submitted" }
{ "dsp": "apple-music",  "status": "submitted" }
{ "dsp": "tidal",        "status": "submitted" }
// ... 9 rows

// T+1 min:  status flips to "accepted" + release.dsp.<slug>.accepted webhook fires
// T+2 min:  status flips to "live" + dsp_release_id populated + release.dsp.<slug>.live fires
{ "dsp": "spotify",      "status": "live", "dsp_release_id": "SIM-spot-9f3a2c", "live_at": "2026-06-17T00:02:11Z" }
lightbulb
Subscribe to webhooks via POST /webhooks with events: ["release.dsp.*.*"] to be pushed every transition instead of polling. Use the webhook tester to verify your HMAC signature handling before going live.
9

Preview the literal XML being shipped

See the exact NewReleaseMessage XML each DSP receives. This is the supply-chain proof — what you see is byte-identical to what lands at the DSP's ingestion endpoint.

bash
curl -O -J "https://api-sandbox.tonegrid.pro/api/releases/$REL/ddex/preview/spotify.xml" \
  -H "Authorization: Bearer $TG"

# saves: $REL-spotify.xml

# Or get JSON envelope with sha256:
curl "https://api-sandbox.tonegrid.pro/api/releases/$REL/ddex/preview/spotify" \
  -H "Authorization: Bearer $TG" | jq '.data | {sha256, size_bytes, message_type}'
expected response
{
  "sha256":       "9f4a1c2e88d3b7c6e2a4f1d5c8b9e0f7a6c4d2b1e3a8f5c7d9b1e6a2f4c8d3a5",
  "size_bytes":   12648,
  "message_type": "NewReleaseMessage"
}
lightbulb
For takedowns use POST /releases/:uuid/ddex/purge (PurgeReleaseMessage); for metadata corrections use POST /releases/:uuid/ddex/redeliver (re-pushes NewReleaseMessage on the same MessageThreadId). Both are in the DDEX section.

That's it. You shipped a release.

The same flow runs unchanged on production — swap the host, plug in your prod API key, and you're shipping real music to real stores. Subscribe to webhooks (or verify your signature handling first) and you're done.