Guides
Import a catalogue
Move releases that are already in the stores, in batches, keeping their UPCs, ISRCs and original release dates.
On this page
Moving releases that are already in the stores, from another distributor or from your own system. Each release keeps its UPC, every track its ISRC, and the day it first came out, so the stores can link the new delivery to the pages they already have. Its old version comes down only after the new one is live.
1. Use your own ids#
Labels, artists, releases and tracks take an externalId: your own id, unique per kind in your organization. List endpoints filter by it, so you never need a table of our ids next to yours, and a second run of your import can look up what the first one made. metadata keeps up to 20 string keys of your own on each record.
Reusing an externalId for another record answers 409 EXTERNAL_ID_IN_USE.
2. Labels and artists first#
Create each imprint with POST /labels; its releaseDefaults fill the © and ℗ holders, genre and language of releases that leave them out. Then the artists, with their store profiles: a Spotify or Apple Music id puts the moved releases on the artist's existing page instead of a new one.
import { StillmoreApi } from "./stillmore-api";
const api = new StillmoreApi({ apiKey: process.env.STILLMORE_API_KEY! });
const label = await api.post<{ id: string }>("/labels", {
name: "North Records",
externalId: "label-7",
releaseDefaults: { cLineHolder: "North Records", pLineHolder: "North Records", primaryGenre: "Pop", language: "en" }
});
const artist = await api.post<{ id: string }>("/artists", {
name: "June Hale",
externalId: "artist-118",
profiles: { spotify: "<Spotify artist id>", appleMusic: "<Apple Music artist id>" }
});3. Releases, 25 at a time#
POST /releases/batch takes up to 25 releases. Each is created on its own, as POST /releases would; the answer lists every result in the order sent, and one failing doesn't stop the others. For a moved release, send:
| Field | For a moved release |
|---|---|
upc | The UPC or EAN it has in the stores now |
tracks[].isrc | Each recording's ISRC |
originalReleaseDate | The day it first came out |
releaseDate | The day it goes live from here: today or later |
externalId | Your id for the release, and for each track |
// catalogue: your own rows, read from your system.
const answer = await api.post<{ results: Array<{ index: number; ok: boolean; release: { id: string } | null; error: { code: string; message: string } | null }> }>("/releases/batch", {
releases: catalogue.slice(0, 25).map((row) => ({
labelId: label.id,
title: row.title,
type: row.type,
primaryArtistIds: [artist.id],
upc: row.upc,
originalReleaseDate: row.firstReleased,
releaseDate: "2026-12-04",
externalId: row.id,
tracks: row.tracks.map((track) => ({ title: track.title, isrc: track.isrc, externalId: track.id }))
}))
});
for (const result of answer.results) {
if (!result.ok) console.warn(`Row ${result.index}: ${result.error!.code} ${result.error!.message}`);
}4. Covers and masters from links#
Files you host elsewhere don't need downloading first: POST /releases/{id}/artwork/import and POST /tracks/{id}/audio/import take a public link, and Stillmore Group fetches the file in the background and checks it like an upload. Follow each import with GET /imports/{importId} until it says SUCCEEDED or FAILED; error says what was wrong with a failed one. The delivery guide shows the loop.
5. Check what the stores will see#
GET /releases/{id}/checks before sending. Two checks matter most for a moved catalogue:
| Check | What it means |
|---|---|
UPC_IN_USE | The UPC is already on another release here |
ISRC_IN_USE_ELSEWHERE | The ISRC is on a track of another account; a person checks who owns the recording |
They don't stop a submission, but a release with either one isn't clean, so a person looks at it before it is approved.
6. Send them, 50 at a time#
POST /releases/submit takes up to 50 releaseIds; each is submitted on its own, and the answer gives every release's status after the attempt, or its error. Releases sent a day are counted (100 a day by default, see Rate limits): over it, the answer is 429 SUBMISSION_LIMIT_REACHED until midnight UTC, so a large catalogue goes over a few days.
7. Switch over#
When release.live arrives for a moved release, give the stores five working days to link the new delivery to the old one (the workspace's own tool waits as long), then ask your old distributor to take its version down. List the UPC and every ISRC so they take down the right deliveries. Ask only once the new one is live: until then, the old version is the one in the stores.
Keeping in step afterwards#
Ask lists only for what changed: updatedSince returns the records changed at or after a time, and every record carries updatedAt. GET /tracks?isrc=… finds a track by its code.