Catalogue Ingest API

Send works

The request body for POST /api/v1/catalogue/works, field by field.

POST/api/v1/catalogue/works

Upsert one to 500 works. We create a catalogue when you omit catalog_id, or append to an existing one when you send it.

Headers#

HeaderRequiredDescription
AuthorizationYesBearer trk_live_...
Content-TypeYesapplication/json
Idempotency-KeyRecommended on commitUnique per request. Makes a commit safe to retry. Ignored in dry-run.
x-client-idNoIf sent, it must equal the key's client, or we return 403.

Query parameters#

ParameterTypeDescription
dry_runboolean?dry_run=true validates and reports without a write. Same as body "mode": "validate".

Request body#

The body is a strict JSON object. An unknown or misspelled field returns 400. We do not ignore it.

All shares use a 0 to 100 scale, so 50 means 50% and 1 means 1%. A share outside that range returns 400.

Envelope#

FieldTypeRequiredDescription
worksarrayYes1 to 500 works.
client_artist_refstringNoYour stable artist id. Routes works to that artist's catalogue. See Catalogue routing.
catalog_namestringNoNames a new catalogue. Default: API Import YYYY-MM-DD.
catalog_idstringNoAppends to an existing catalogue. 404 if it is not one of yours.
client_idstringNoIf present, it must match the client bound to your key.
modeenumNo"validate" for a dry-run, or "commit". Default "commit".
enforcementenumNo"warn" (default) stores the works and reports the problems. "strict" refuses the request when a blocking issue is present, and stores nothing.

works[]#

FieldTypeRequiredDescription
primary_titlestringYesThe title of the work.
recordingobjectYesSee below.
client_work_refstringRecommendedYour own stable id for this work. See below.
writersarrayNoSee below.
publishersarrayNoSee below.
iswcstringNoWe canonicalise it on store.
mlc_song_codestringNoThe MLC identifier for the work.
durationstringNoWe canonicalise it to HH:MM:SS on store.

Covers, remixes and edits#

A new recording of an existing song does not make you a writer of that song. If your release is a remix, an edit, a cover, a sample or an arrangement, say so:

json
{
  "primary_title": "Lady (Hear Me Tonight) - Slowed",
  "recording": { "artist_name": "Your Artist", "isrc": "..." },
  "derivative_of": { "kind": "cover", "iswc": "T-070.123.456-1" }
}

kind is one of remix, cover, edit, sample or arrangement. Send iswc or mlc_song_code for the original where you know it, or neither.

A declared remix, cover or edit claims nothing on the composition. We do not ask you for a songwriter split, and we register none. A sample or an arrangement can carry a real share. We still ask for the roster, and a person reviews the work.

We never guess this from a title. A word like "slowed" in a title is not a declaration, so a work without derivative_of is a claim that you wrote it.

About client_work_ref#

client_work_ref is the upsert key, and it is the single most useful field you can send.

  • Re-sending the same ref updates that work. It does not create a duplicate.
  • The response echoes your ref beside our work_id, so you can reconcile the two systems.
  • On an update your payload is authoritative for the roster. The writers and publishers you send replace the stored ones.
  • An update that carries no writers keeps the stored roster. We also report roster_wipe, so the omission stays visible. An absent field is not an instruction to erase a roster. A partner who corrects a title must not lose every writer by leaving the field out.
  • To remove one writer, send the roster that remains. There is no way to empty a roster in one call, and that is deliberate.
  • Identifiers you omit are kept, not cleared. So are values we found by enrichment.
  • An update stays in the work's existing catalogue. A catalog_id in the request only governs where new works are created.

works[].recording#

FieldTypeRequiredDescription
artist_namestringYesThe primary recording artist.
isrcstringNoThe recommended match anchor. We canonicalise it on store.
album_titlestringNoThe album name.
release_datestringNoThe release date. ISO format is best.
labelstringNoThe record label.
upcstringNoThe Universal Product Code of the release.
master_sharenumber (0-100)NoOwnership of the sound recording, not of the work. Leave it out if you do not know it.
spotify_urlstringNoA link to your release on Spotify, not to the original.
apple_urlstringNoA link to your release on Apple Music. We keep the track id from it.
original_release_datestringNoYYYY-MM-DD. When the recording FIRST came out, when that differs from release_date.
is_rereleasebooleanNoTrue when this release is the same recording issued again, rather than a new one.
migrated_onstringNoYYYY-MM-DD. When the release moved to you from another distributor.

Send a link where you have one. It lets us confirm that the ISRC belongs to the recording you distributed. It says nothing about who wrote the song, so it never replaces the writer splits.

Send the ISRC where you have it. It is the strongest anchor we have for matching your recording to industry data.

works[].writers[]#

FieldTypeRequiredDescription
writer_first_namestringYesThe writer's first name.
writer_last_namestringYesThe writer's last name.
writer_middle_namestringNoThe middle name or initial, if the writer registered with one.
writer_ipistringNoThe IPI name number, 9 to 11 digits. Not needed for a writer with no society: see below.
writer_role_codeenumNoOne of CA (default), C, A, AR, AD, SR, SA, TR.
writer_pro_affiliationstringNoThe writer's society, such as ASCAP or BMI. We accept 279 codes: see below.
writer_ownership_pctnumber (0-100)NoThe writer's share of the work.
writer_controlledbooleanEffectively yesWhether you control this writer. Default false. See below.
authorized_onstring (YYYY-MM-DD)When controlledThe date this writer agreed to let you administer their share.
authorization_refstringWhen controlledYour own reference for that agreement, so we can ask you for it later.
writer_emailstringNoA contact address for the writer.

Send the authorization fields for every writer you mark as controlled. The flag on its own says we administer the share. It does not say who agreed, and a society that questions the claim asks us exactly that. You do not need to send us the signed document.

Collect names however suits your interface, but send the split fields above. Use the name the writer registered with their PRO. Do not send a single writer_full_name. We reject unknown fields.

A writer with no society#

A society registers a writer who is not a member as a non-member. Such a writer holds no IPI (Interested Party Information) number, and none is needed.

Send writer_pro_affiliation as one of these values, and leave writer_ipi out:

text
NA   N/A   AN   NONE   NON-AFFILIATED   NON AFFILIATED   UNAFFILIATED

Our export checks apply the same exemption, so such a writer does not become a gap.

A writer who does hold an IPI number must still send it. We check its format and its check digits.

Society codes#

We accept the 279 CISAC (International Confederation of Societies of Authors and Composers) society codes. The full list, with the non-member values above, is at pro-codes.json.

Check a value against that list at your own intake. The person who typed it is still on screen, and only they can correct it. A value we refuse later reaches you long after they have gone.

works[].publishers[]#

FieldTypeRequiredDescription
publisher_namestringYesThe publisher name.
publisher_ipistringNoThe publisher IPI name number.
publisher_collection_sharenumber (0-100)NoThe mechanical collection share. This is the MLC value.
publisher_pr_sharenumber (0-100)NoThe performance collection share.
publisher_pro_affiliationstringNoThe publisher's PRO.
publisher_controlledbooleanNoWhether this is a controlled publisher. Default false.
publisher_roleenumNoE original publisher, AM administrator, SE sub-publisher.
publisher_mlc_numberstringNoThe MLC publisher number.
territorystringNoThe territory. Default "World".
linked_writer_indexintegerNoA 0-based index into this work's writers array.
linked_writer_ipistringNoThe IPI of the linked writer. An alternative to the index.

Response#

A commit returns 201. A dry-run returns 200. The body has the same shape either way. See Validation and gaps for every response field.