# DDEX delivery > Send releases as DDEX ERN: test a batch with POST /ddex/validate, deliver with POST /ddex or direct uploads, use SFTP, and read the delivery report. Releases come into HILLS Music as DDEX ERN batches. The API doesn't create or edit single releases field by field. You send the same batch you would send a store, over the API or SFTP. Each release in it becomes a draft on your catalog, and goes through review and delivery to the stores as usual. ## ERN versions | Version | Status | | --- | --- | | ERN 4.3 (`http://ddex.net/xml/ern/43`) | Recommended | | 4.2, 4.1 | Accepted | | 3.8.2, 3.8.1, 3.8, 3.7 | Accepted. The report warns that the version is legacy. | | Anything else | Rejected | Every accepted version goes through the same checks and import. A batch is a zip with one or more `NewReleaseMessage` XML files and the audio, cover art and videos they point at. ## Scopes | Scope | Allows | | --- | --- | | `ddex:validate` | Tests only. Good for CI. | | `ddex:write` | Tests, deliveries, direct uploads and delivery status. | ## 1. Test first `POST /ddex/validate` runs every check a delivery gets: the XML, the HILLS rules, the audio, artwork and video files, UPCs and ISRCs against your catalog, genres, territories and deals. **Nothing is created**: no release, recording, artist, code or delivery. Send a `.zip` or a single `.xml`. ```bash curl -X POST "https://api.hillsmusic.com/v1/ddex/validate?member_id=41" \ -H "Authorization: Bearer $HILLS_API_KEY" \ -H "Content-Type: application/zip" \ --data-binary @batch-0001.zip ``` ```js import { readFile } from "node:fs/promises"; const res = await fetch("https://api.hillsmusic.com/v1/ddex/validate?member_id=41", { method: "POST", headers: { Authorization: `Bearer ${process.env.HILLS_API_KEY}`, "Content-Type": "application/zip" }, body: await readFile("batch-0001.zip"), }); const { data } = await res.json(); console.log(data.verdict, data.summary); for (const c of data.checks.filter((c) => c.status === "error")) console.log(c.field, c.message); ``` `member_id` is the artist or label the batch is for. Leave it out for your own catalog. For an XML file, send `Content-Type: application/xml`. You can also send `multipart/form-data` with the field `file` (up to 512 MB). The answer has the ERN version, a summary, every check by stage (`pass`, `warning`, `error`, `skipped`) and a verdict: | Verdict | Means | | --- | --- | | `would_import` | Clean. It would become a draft. | | `would_import_with_warnings` | It would import. Read the warnings. | | `would_be_flagged` | It would wait for HILLS staff: a takedown, or an update of a release in review or live. | | `would_be_rejected` | It has errors. Nothing would be created for that release. | A batch with errors still answers `200`. The verdict says what went wrong. A batch too large to test answers `413 too_large`, and many tests at once can answer `429 busy`. Every test is also listed on the DDEX page in the web app, under **Test reports**, for 90 days. ## 2. Deliver `POST /ddex` takes the zip as the raw body (`Content-Type: application/zip`, up to 20 GB) or as the multipart field `file` (up to 512 MB). It answers `202` at once: ```bash curl -X POST "https://api.hillsmusic.com/v1/ddex?member_id=41&name=batch-0001.zip" \ -H "Authorization: Bearer $HILLS_API_KEY" \ -H "Content-Type: application/zip" \ --data-binary @batch-0001.zip # {"data":{"id":311,"status":"queued","environment":"production","status_url":"/v1/ddex/311"}} ``` For big files, prefer a direct upload. ## Direct uploads for large batches A direct upload sends the file straight to storage instead of through the API. 1. **Start.** `POST /ddex/uploads` with `name` and `size` in bytes (up to 20 GB), and `member_id` if needed. 2. **Upload.** Up to 64 MB, you get one `upload.url`: `PUT` the whole file to it. Above 64 MB, you get `upload.part_size` and one URL per part. `PUT` each slice of `part_size` bytes to its URL (the last slice is shorter). Parts can go in any order, and in parallel. These URLs are signed: send no `Authorization` header to them. 3. **Commit.** `POST /ddex/uploads/{id}/commit`. It checks every part arrived with the right size, then queues the batch like `POST /ddex` (`202`). ```js import { open, stat } from "node:fs/promises"; const H = { Authorization: `Bearer ${process.env.HILLS_API_KEY}`, "Content-Type": "application/json" }; const API = "https://api.hillsmusic.com/v1"; const file = "batch-0001.zip"; const { size } = await stat(file); const start = await fetch(`${API}/ddex/uploads`, { method: "POST", headers: H, body: JSON.stringify({ name: file, size, member_id: 41 }), }).then((r) => r.json()); const { id, upload } = start.data; const fh = await open(file); if (upload.url) { await fetch(upload.url, { method: "PUT", body: await fh.readFile() }); } else { for (const p of upload.parts) { const offset = (p.part_number - 1) * upload.part_size; const buf = Buffer.alloc(Math.min(upload.part_size, size - offset)); await fh.read(buf, 0, buf.length, offset); const r = await fetch(p.url, { method: "PUT", body: buf }); if (!r.ok) throw new Error(`part ${p.part_number}: ${r.status}`); } } await fh.close(); const commit = await fetch(`${API}/ddex/uploads/${id}/commit`, { method: "POST", headers: H }); console.log(commit.status, await commit.json()); // 202, then poll status_url ``` Part URLs work for 6 hours. `GET /ddex/uploads/{id}` gives fresh URLs for the parts still missing, with `received_parts` and `parts_total`. A commit with parts missing answers `409 upload_incomplete` and lists `missing` and `wrong_size`: send those, then commit again. `DELETE /ddex/uploads/{id}` drops an upload you won't finish. Uncommitted uploads are removed after 7 days. ## Over SFTP Each DDEX inbox is one SFTP login with two folders. You get the login with your inbox, on the DDEX page in the web app. | Folder | What happens | | --- | --- | | `/production//` | Delivered. Once the marker arrives, the batch is imported. | | `/test//` | Tested, like `POST /ddex/validate`. Nothing is created. The report is written next to it: `/test/.report.txt` and `.report.json`. | Upload the XML and its files into the batch folder, then the marker last: `BatchComplete_*.xml`, or any `*.complete` file. A single `/test/.xml` or `.zip` is tested too. Any other file in `/test`, such as `ping.txt`, is a connection test. A production batch folder that never gets its marker is removed after 7 days. Whatever sits in `/test` goes after the same time. See [Test folder](https://developer.hillsmusic.com/reference/test-folder/). ## 3. Read the delivery report Poll `GET /ddex/{id}` every 10–30 seconds, or subscribe to the `ddex.import.imported` [webhook](https://developer.hillsmusic.com/guides/webhooks/). The status moves `queued` → `parsing` → `validating` → `importing`, then ends as: - `done`: every release landed; - `partial`: some releases were left out; - `failed`: nothing was imported. ```bash curl "https://api.hillsmusic.com/v1/ddex/311" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` The answer has counts and one item per release, each with its `outcome`, `release_id` and `errors` (each error names its DDEX field). `GET /ddex` lists your last 100 deliveries. ## What gets created or flagged | Outcome | When | | --- | --- | | `created` | A new release. It becomes a draft on `member_id`'s catalog. | | `updated` | The UPC is already a draft (or a release sent back for changes) there. That draft is updated. | | `flagged` | A takedown, or an update of a release in review or live. It isn't applied. HILLS staff look at it. | | `left_out` | The release had errors. Nothing was created for it. Fix it and deliver again. | | `test` | A message marked as a `TestMessage`: checked, nothing created. | An ISRC you already have reuses that recording. An ISRC that belongs to another account leaves the release out. Drafts then go through review as usual: submit them in the web app, or ask HILLS Music to turn on auto-submit for your inbox. Follow them with [GET /releases](https://developer.hillsmusic.com/reference/releases/) and webhooks such as `release.approved` and `release.live`. Full parameters are in the [DDEX deliveries reference](https://developer.hillsmusic.com/reference/ddex-deliveries/). Source: https://developer.hillsmusic.com/guides/ddex/