Skip to main content

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.
Both surfaces are admin-only — every route requires the tenant admin role or higher.
Catalog Import never writes to your catalog automatically. Fetching and staging metadata is a read/stage-only operation; the only endpoint that creates or updates Asset/Product rows is POST /catalog-import/apply, and that call is always an explicit, reviewer-initiated action. See The Staged-Review Safety Model below.

Prerequisites

Authentication

All endpoints require a Bearer token for a tenant user with the admin 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
ISRC and UPC lists are capped at 500 items per import. Omitting all three inputs returns a 400.
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 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
Each diff entry carries 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).
conflict rows — where an incoming field disagrees with a non-empty existing value — do not auto-apply. They stay pending until a reviewer resolves them with an override in the next step.
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 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
To decline a batch instead, call 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
Both apply and reject require batchId or a non-empty itemIds[] — omitting both returns a 400.

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/fetch writes exclusively to CatalogImportItem rows. It never touches Asset or Product.
  • Apply is the sole write path. POST /catalog-import/apply is 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 failed with importError set) 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 stays pending until a reviewer sets an override via PUT /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-Disposition attachment header — 200 with the file body.
  • Row estimate > 5000: a Download record is created and an async render job is queued instead — 202 with a downloadId to poll.
Node.js
Node.js
cURL
If your export is queued asynchronously, poll the same download endpoint used for royalty and accounting reports:
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 (csv or xlsx): must be one of the resolved template’s supported formats; defaults to the template’s defaultFormat.
  • basis (works or assets, CWR-workstation template only): overrides the auto-detected basis — works if the tenant has any CWRWork rows, otherwise assets.
  • publisherId (uuid, CWR-workstation template only): publisher used for the publisher block; defaults to the tenant’s publisherType=original publisher.
  • catalogueName (string, CWR-workstation template only): fallback catalogue name when a work has neither libraryCatalogueName nor metadata.origin.library set.
Requesting an unregistered template id returns a 404. Requesting a format the resolved template doesn’t support, or a template-specific option validation failure, returns a 400.

Best Practices

  • Search before you fetch. Use GET /catalog-import/search to resolve an artist to the correct artistExternalId rather 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}/diff again immediately before apply on any batch you’re not applying right away.
  • Resolve every conflict explicitly. Don’t skip kind: conflict diff entries — they represent a real disagreement between an incoming value and your existing data, and they will not be included until you set a finalData override.
  • Treat apply as safe to retry. Because apply is idempotent against pending rows, you can safely re-call it after a partial failure or a network timeout without risking duplicate catalog rows.
  • Check formats before building an export UI. Not every template supports both csv and xlsx — read each template’s formats/defaultFormat from GET /catalog/export/templates rather than hardcoding.
  • Handle both export response shapes. A client calling POST /catalog/export must handle both the synchronous 200 file stream and the asynchronous 202 + poll path — which one you get depends on the tenant’s catalog size at request time, not on anything you control in the request.

Catalog Import

Catalog Export