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#
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer trk_live_... |
Content-Type | Yes | application/json |
Idempotency-Key | Recommended on commit | Unique per request. Makes a commit safe to retry. Ignored in dry-run. |
x-client-id | No | If sent, it must equal the key's client, or we return 403. |
Query parameters#
| Parameter | Type | Description |
|---|---|---|
dry_run | boolean | ?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#
| Field | Type | Required | Description |
|---|---|---|---|
works | array | Yes | 1 to 500 works. |
client_artist_ref | string | No | Your stable artist id. Routes works to that artist's catalogue. See Catalogue routing. |
catalog_name | string | No | Names a new catalogue. Default: API Import YYYY-MM-DD. |
catalog_id | string | No | Appends to an existing catalogue. 404 if it is not one of yours. |
client_id | string | No | If present, it must match the client bound to your key. |
mode | enum | No | "validate" for a dry-run, or "commit". Default "commit". |
enforcement | enum | No | "warn" (default) stores the works and reports the problems. "strict" refuses the request when a blocking issue is present, and stores nothing. |
works[]#
| Field | Type | Required | Description |
|---|---|---|---|
primary_title | string | Yes | The title of the work. |
recording | object | Yes | See below. |
client_work_ref | string | Recommended | Your own stable id for this work. See below. |
writers | array | No | See below. |
publishers | array | No | See below. |
iswc | string | No | We canonicalise it on store. |
mlc_song_code | string | No | The MLC identifier for the work. |
duration | string | No | We 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:
{
"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
writersandpublishersyou 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_idin the request only governs where new works are created.
works[].recording#
| Field | Type | Required | Description |
|---|---|---|---|
artist_name | string | Yes | The primary recording artist. |
isrc | string | No | The recommended match anchor. We canonicalise it on store. |
album_title | string | No | The album name. |
release_date | string | No | The release date. ISO format is best. |
label | string | No | The record label. |
upc | string | No | The Universal Product Code of the release. |
master_share | number (0-100) | No | Ownership of the sound recording, not of the work. Leave it out if you do not know it. |
spotify_url | string | No | A link to your release on Spotify, not to the original. |
apple_url | string | No | A link to your release on Apple Music. We keep the track id from it. |
original_release_date | string | No | YYYY-MM-DD. When the recording FIRST came out, when that differs from release_date. |
is_rerelease | boolean | No | True when this release is the same recording issued again, rather than a new one. |
migrated_on | string | No | YYYY-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[]#
| Field | Type | Required | Description |
|---|---|---|---|
writer_first_name | string | Yes | The writer's first name. |
writer_last_name | string | Yes | The writer's last name. |
writer_middle_name | string | No | The middle name or initial, if the writer registered with one. |
writer_ipi | string | No | The IPI name number, 9 to 11 digits. Not needed for a writer with no society: see below. |
writer_role_code | enum | No | One of CA (default), C, A, AR, AD, SR, SA, TR. |
writer_pro_affiliation | string | No | The writer's society, such as ASCAP or BMI. We accept 279 codes: see below. |
writer_ownership_pct | number (0-100) | No | The writer's share of the work. |
writer_controlled | boolean | Effectively yes | Whether you control this writer. Default false. See below. |
authorized_on | string (YYYY-MM-DD) | When controlled | The date this writer agreed to let you administer their share. |
authorization_ref | string | When controlled | Your own reference for that agreement, so we can ask you for it later. |
writer_email | string | No | A 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:
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[]#
| Field | Type | Required | Description |
|---|---|---|---|
publisher_name | string | Yes | The publisher name. |
publisher_ipi | string | No | The publisher IPI name number. |
publisher_collection_share | number (0-100) | No | The mechanical collection share. This is the MLC value. |
publisher_pr_share | number (0-100) | No | The performance collection share. |
publisher_pro_affiliation | string | No | The publisher's PRO. |
publisher_controlled | boolean | No | Whether this is a controlled publisher. Default false. |
publisher_role | enum | No | E original publisher, AM administrator, SE sub-publisher. |
publisher_mlc_number | string | No | The MLC publisher number. |
territory | string | No | The territory. Default "World". |
linked_writer_index | integer | No | A 0-based index into this work's writers array. |
linked_writer_ipi | string | No | The 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.