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
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.”
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.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
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.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
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).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.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