Overview
The Catalog Import & Export API covers two related but independent tenant-scoped tools:- Catalog Import — search an artist on a configured metadata provider, fetch and merge cross-provider metadata into a staged review queue, inspect a diff against your existing catalog, optionally correct individual rows, then explicitly apply or reject the batch.
- Catalog Export — render your catalog (assets, products, artists, labels, splits, releases, or specialized formats like a CWR-workstation import file) from a registered set of export templates, either as a synchronous file download or an async job for large exports.
admin role or higher.
Prerequisites
Authentication
All endpoints require a Bearer token for a tenant user with theadmin role or higher.
Node.js
cURL
Active metadata providers
Catalog Import’s provider set is config-driven per deployment (METADATA_IMPORT_PROVIDERS) — a provider is active only if it’s configured and has credentials. You don’t need to name a provider up front; GET /catalog-import/providers tells you what’s currently available and what each one can do.
Catalog Import
Import Workflow
1
List active providers (optional)
See which metadata providers are configured for this deployment and what each one supports — free-text artist search, a full artist-catalog walk, ISRC lookup, and UPC lookup.
Node.js
cURL
A provider missing credentials is silently omitted rather than erroring — the returned list is always “what you can actually use right now.”
2
Search for an artist
Resolve a human-readable artist name to the provider-native
artistExternalId that POST /fetch needs. Search runs against whichever active provider supports searchArtist (the primary catalog provider is preferred).Node.js
cURL
If
q looks like an ISRC, it’s resolved to the recording’s credited artists first, then those names are searched — an identifier-shaped free-text query would otherwise return unrelated results. If q looks like a UPC/barcode, an empty result is returned since barcodes have no artist identity.3
Queue a fetch + staging job
Queue a background job that fetches metadata from every active provider for the given artist and/or explicit identifiers, merges the results across providers with provenance tracking, and stages the outcome as
CatalogImportItem rows. This call never writes to Asset/Product — it only returns a jobId and batchId to poll and review.At least one of artistExternalId, isrcs, or upcs is required. Supplying artistExternalId triggers a full artist-catalog walk on the primary catalog provider; every ISRC/UPC discovered during that walk is automatically queued for cross-provider enrichment alongside anything you pass explicitly.Node.js
Node.js
cURL
4
Poll the job status
Poll until the job’s BullMQ
state reaches completed. The result field carries the staging outcome (staged asset/product counts, skipped count, diff, merge stats) once settled.Node.js
cURL
Jobs are scoped to the caller’s tenant — polling a job id from another tenant returns
404.5
Review the staged batch
List every
CatalogImportItem staged under the batch — every status (pending, approved, rejected, imported, failed), ordered by type then creation time. This is a pure read.Node.js
cURL
6
Review the diff against your current catalog
Recompute the diff between the batch’s Each diff entry carries
pending staged rows and your tenant’s CURRENT Asset/Product catalog. This is useful right before applying, since the catalog may have changed since the original fetch. It performs SELECTs only — never writes.Node.js
cURL
kind (create, update, conflict, or skip), the matched entity (recording or release), a matchKey (isrc/upc/fuzzy), and a changes[] list with per-field current vs. incoming values and provenance (which provider’s value won, and any disagreeing sources).7
Set reviewer overrides (optional)
Correct a single
pending staged item before applying it — for example to resolve a conflict diff entry, fix a title, or set the artist-role map. finalData is merged into the item’s existing finalData (not replaced); only a fixed field allowlist is accepted (title, displayArtist, mainGenre, explicit, recordingDate, label, format, albumArt, iswc, releaseDate, version, catalogNumber) — any other key is silently dropped. artists replaces the item’s artist-role map outright.Node.js
cURL
At least one of
finalData or artists is required. The item must still be pending and owned by your tenant, or you’ll get a 404.8
Apply or reject
Apply is the only write path in the importer. Resolve the set of To decline a batch instead, call
pending staged items by batchId and/or itemIds (scoped to your tenant), and each one is created/updated against Asset/Product in its own transaction. Every processed item is marked imported (or failed, with importError set) and stamped with reviewedBy/reviewedAt.Node.js
Node.js
cURL
reject with the same request shape. Rejected rows are never applied, and rejecting is not permanent for the underlying identifier — a later import proposing the same ISRC/UPC re-stages it as a fresh row rather than being deduplicated away.Node.js
cURL
The Staged-Review Safety Model
Catalog Import is built around a strict read-then-write separation so a fetch can never silently mutate your catalog:- Fetch only ever stages.
POST /catalog-import/fetchwrites exclusively toCatalogImportItemrows. It never touchesAssetorProduct. - Apply is the sole write path.
POST /catalog-import/applyis the only endpoint in the importer that creates or updates catalog rows, and it only runs when a reviewer explicitly calls it — there is no auto-apply. - Per-item transactions. Apply processes each staged item in its own transaction. A failure on one item (marked
failedwithimportErrorset) does not roll back or block the others. - Idempotent re-application. Unique indexes on your tenant’s ISRC/UPC roster, combined with apply only ever touching rows with
status: pending, mean re-calling apply on an already-applied batch is a safe no-op (imported: 0, failed: 0) rather than creating duplicates. - Deduplicated staging. New fetches are deduplicated against already-pending or already-imported rows by ISRC/UPC before insert, so re-running the same import doesn’t pile up redundant staged rows.
- Conflicts require a human decision. A diff entry of
kind: conflict— an incoming value disagreeing with a non-empty existing value — never auto-applies. It stayspendinguntil a reviewer sets an override viaPUT /items/{id}.
Catalog Export
List registered export templates
Export templates are a code-defined registry, not a database table — the deployment ships with a fixed set of templates (a CWR-workstation import format, distributor formats, and CSV/XLSX exports for assets, products, artists, labels, splits, and releases).GET /catalog/export/templates lists what’s currently registered, including each template’s accepted options and supported output formats, so you can populate a template picker before running an export.
Node.js
cURL
This registry is distinct from the member-accessible generic
?export=csv query parameter available on existing list endpoints, which remains untouched. Use the template registry when you need a specialized or multi-entity export format.Run an export
POST /catalog/export resolves template from the registry, validates options against that specific template’s own options schema, then does one of two things depending on size:
- Row estimate ≤ 5000 (the common case): the rendered file streams back synchronously with a
Content-Dispositionattachment header —200with the file body. - Row estimate > 5000: a
Downloadrecord is created and an async render job is queued instead —202with adownloadIdto poll.
Node.js
Node.js
cURL
Node.js
cURL
Catalog exports share the
Download model with royalty/accounting reports (type: report, tagged metadata.exportKind: catalog), so they also show up alongside those in GET /download/list.Template options
Options are per-template — unknown or irrelevant keys are ignored by the resolved template’s own options schema:ids(string array): restrict the export to specific entity ids; omit to export the full tenant roster.format(csvorxlsx): must be one of the resolved template’s supportedformats; defaults to the template’sdefaultFormat.basis(worksorassets, CWR-workstation template only): overrides the auto-detected basis —worksif the tenant has anyCWRWorkrows, otherwiseassets.publisherId(uuid, CWR-workstation template only): publisher used for the publisher block; defaults to the tenant’spublisherType=originalpublisher.catalogueName(string, CWR-workstation template only): fallback catalogue name when a work has neitherlibraryCatalogueNamenormetadata.origin.libraryset.
Best Practices
- Search before you fetch. Use
GET /catalog-import/searchto resolve an artist to the correctartistExternalIdrather than guessing — a mismatched id triggers a catalog walk against the wrong artist. - Re-check the diff right before applying. The diff at fetch time can go stale if your catalog changes in the interval — call
GET /batch/{batchId}/diffagain immediately beforeapplyon any batch you’re not applying right away. - Resolve every conflict explicitly. Don’t skip
kind: conflictdiff entries — they represent a real disagreement between an incoming value and your existing data, and they will not be included until you set afinalDataoverride. - Treat apply as safe to retry. Because apply is idempotent against
pendingrows, you can safely re-call it after a partial failure or a network timeout without risking duplicate catalog rows. - Check
formatsbefore building an export UI. Not every template supports bothcsvandxlsx— read each template’sformats/defaultFormatfromGET /catalog/export/templatesrather than hardcoding. - Handle both export response shapes. A client calling
POST /catalog/exportmust handle both the synchronous200file stream and the asynchronous202+ poll path — which one you get depends on the tenant’s catalog size at request time, not on anything you control in the request.
Related Endpoints
Catalog Import
- List Active Providers
- Search for an Artist
- Queue a Fetch Job
- Poll Job Status
- List Batch Items
- Get Batch Diff
- Set Item Overrides
- Apply Staged Items
- Reject Staged Items