Tracks API Documentation

Tracks are the catalog itself: the title, lyrics, flags, tempo and duration of each recording, its composers and publishers with their shares, the artists it sounds like, and a value for each of the library’s metadata types.

Tracks are addressed by your own external ID. Metadata tags, composers and publishers can be created on the fly from the object forms described below, so a single track upload can carry everything the track needs, and the bulk endpoint takes up to 200 tracks at a time when loading a catalog. What GET gives, PUT and POST take, so a track read from the API can be sent back as it is; library_id, created_at, updated_at and url are accepted and ignored. Reading is open to every collaborator; creating, updating and deleting need the editor role.

Endpoints

GET /api/v1/libraries/{library_id}/tracks

Retrieve the tracks in a library, ordered by title. With q, only the tracks that have every word of it somewhere in their text: this finds tracks by what is written in them, where the search endpoint finds music by what it is like.

Path Parameters

Name Type Description Required
library_id integer ID of the library Yes

Query Parameters

Name Type Description
page integer Page number for pagination (default: 1)
per_page integer Records per page, up to 200 (default: 25)
q string List only the tracks that have every word of this in their title, description, lyrics, tags or credits

Response

{
    "pagination": {
        "total_entries": 34,
        "per_page": 25,
        "current_page": 1,
        "total_pages": 2,
        "next_page": 2,
        "next_page_url": "https://app.spondulix.ai/api/v1/libraries/2/tracks?page=2"
    },
    "tracks": [
        {
            "id": 999,
            "library_id": 2,
            "title": "Drunken Daisy - You Gotta Have Banjo in the Band",
            "description": "An energetic tune that is part bluegrass and part Irish folk dance...",
            "lyrics": null,
            "lyrics_chorus_only": null,
            "active": true,
            "instrumental": true,
            "explicit": false,
            "clean_available": false,
            "public_domain": false,
            "bpm": null,
            "duration": 185,
            "isrc": null,
            "style_alikes": ["chieftains", "clancy brothers", "flatt and scruggs"],
            "catalog_id": 2,
            "genre_id": 3,
            "subgenre_ids": [17, 169],
            "mood_names": ["calm", "warm"],
            "instrument_ids": [1960, 1943, 1782, 1698],
            "release_date": "2019-04-12",
            "created_at": "2024-08-19T12:42:55.454-05:00",
            "updated_at": "2024-10-16T17:10:42.698-05:00",
            "tracks_composers": [
                { "composer_id": 186, "share": "100.0" }
            ],
            "tracks_publishers": [
                { "publisher_id": 2, "share": "50.0" },
                { "publisher_id": 37, "share": "50.0" }
            ],
            "url": "https://app.spondulix.ai/api/v1/libraries/2/tracks/999"
        }
    ]
}

Response Fields

Field Type Description
id integer External ID of the track
library_id integer ID of the library the track belongs to
title string Title of the track (max 255 chars)
description string | null Description of the track (max 1500 chars)
lyrics string | null Full lyrics of the track (max 5000 chars)
lyrics_chorus_only string | null Chorus lyrics of the track (max 5000 chars)
active boolean Whether the track is active
instrumental boolean Whether the track is instrumental
explicit boolean Whether the track contains explicit content
clean_available boolean Whether a clean version is available
public_domain boolean Whether the track is in the public domain
bpm integer | null Beats per minute; null when unknown
duration integer | null Duration in seconds
isrc string | null International Standard Recording Code
style_alikes array Names of artists the track sounds like
{type}_id integer | null External ID of the tag, for each single-valued tag type that uses external IDs
{type}_ids array External IDs of the tags, for each multi-valued tag type that uses external IDs
{type}_name string | null Name of the tag, for each single-valued tag type that does not
{type}_names array Names of the tags, for each multi-valued tag type that does not
{type} mixed The value, for each non-tag metadata type. Multi-valued types use the plural name and hold an array.
tracks_composers array Composers with shares; composer_id is the composer’s external ID and share is a string or null
tracks_publishers array Publishers with shares; publisher_id is the publisher’s external ID and share is a string or null
created_at string Creation timestamp
updated_at string Last update timestamp
url string URL of the track

Metadata keys are named after the library’s metadata types, so they differ from library to library. A type with no value on the track appears as null or an empty array.

HTTP Response Codes

Code Description
200 OK Successful request
404 Not Found Invalid library ID

POST /api/v1/libraries/{library_id}/tracks

Create a track.

Path Parameters

Name Type Description Required
library_id integer ID of the library Yes

Request Body

Wrap the fields in a track key.

Field Type Description Required
id integer Your external ID for the track, unique within the library Yes
title string Title of the track (max 255 chars) Yes
description string Description of the track (max 1500 chars) No
lyrics string Full lyrics (max 5000 chars). Indexed for full-text and semantic search. No
lyrics_chorus_only string Chorus lyrics (max 5000 chars) No
active boolean Whether the track is active No
instrumental boolean Whether the track is instrumental No
explicit boolean Whether the track contains explicit content No
clean_available boolean Whether a clean version is available No
public_domain boolean Whether the track is in the public domain No
bpm integer Beats per minute. 0 means unknown and is stored as null. No
duration integer Duration in seconds, greater than zero No
isrc string ISRC, two letters, three letters or digits, seven digits. Stored upper case; unique within the library. No
style_alikes array Names of artists the track sounds like No
{type}, {type}_id, {type}_ids, {type}_name, {type}_names mixed A value for a metadata type (see below) No
tracks_composers array Composers with shares (see below) No
tracks_publishers array Publishers with shares (see below) No

Text longer than a field’s maximum is cut to fit rather than rejected. A key that is not a built-in field or a metadata type is reported as an error.

Blank values. Sending null, an empty string or an empty list removes the value. Optional text fields are stored as null when blank, so “has no description” has exactly one representation and searches can find it with is_null.

Metadata values

Each of the library’s metadata types can be set under its name. The accepted forms depend on the type. Each type’s track_keys in the metadata types response names the exact keys for that type, so a client need not work them out.

Tag types, single-valued:

  • genre_id: 3 sets the tag with external ID 3. An ID the type does not have is ignored.
  • genre_name: "rock" sets the tag by name. On a type without external IDs a tag that does not exist is created. On a type with them the tag must exist already, since a name alone gives a new tag no ID; use the object form below to create one.
  • genre: { "id": 3, "name": "rock" } sets the tag by external ID, creating it with that name if it does not exist. An existing tag is not renamed.
  • genre: 3 or genre: "rock" are treated as an ID or a name respectively.

Tag types that allow multiple values take a list in the same forms: subgenre_ids: [17, 169], subgenre_names: ["blues", "folk"] or subgenres: [{ "id": 17, "name": "blues" }]. The list replaces the track’s previous tags of that type.

Other types take the value directly, under the type’s name: release_date: "2019-04-12", rating: 4.5, cover_song: true. A multi-valued type takes a list under the plural name, which replaces the previous values.

Composers and publishers

tracks_composers and tracks_publishers are lists that replace the track’s previous composers or publishers. Each entry carries a share and refers to the record either by external ID or as an object:

{
    "tracks_composers": [
        { "composer_id": 186, "share": 50.0 },
        {
            "composer": {
                "id": 187,
                "name": "New Composer",
                "pro_id": 21,
                "ipi_number": "00000000456"
            },
            "share": 50.0
        }
    ],
    "tracks_publishers": [
        { "publisher": { "id": 2, "name": "Mike's Awesome Music", "pro_id": 21 }, "share": 100.0 }
    ]
}

A composer_id or publisher_id must already exist in the library. An object form creates the record if its id is new and otherwise refers to the existing record without changing it. A credit sent without a share is stored with none and comes back as "share": null. The fields accepted are the same as the composers and publishers endpoints take.

Example

curl -X POST https://app.spondulix.ai/api/v1/libraries/2/tracks \
  -H "Authorization: Bearer your-api-token-here" \
  -H "Content-Type: application/json" \
  -d '{
    "track": {
      "id": 1001,
      "title": "Highway Windows Down",
      "lyrics": "Roll the windows down ...",
      "instrumental": false,
      "bpm": 118,
      "duration": 204,
      "genre_name": "indie pop",
      "moods": [{ "id": 12, "name": "upbeat" }, { "id": 40, "name": "summer" }],
      "style_alikes": ["haim", "best coast"],
      "tracks_composers": [{ "composer_id": 186, "share": 100 }]
    }
  }'

Response

Returns the created track as shown in the list response.

HTTP Response Codes

Code Description
201 Created Successful creation
403 Forbidden Your role does not allow editing
404 Not Found Invalid library ID
409 Conflict The track could not be saved because other requests were changing the same records; send it again
422 Unprocessable Content Invalid request (see response body for details)

A 409 is rare: the save is retried four times first. Its body is { "message": "The database was busy with a conflicting change; try again (Deadlocked)" }, nothing of the track was saved, and sending it again is safe.

A 422 body lists the problems by field, with metadata and composer problems under base:

{
    "title": ["can't be blank"],
    "base": ["Unknown metadata type: genrre_id", "Composer 187 is invalid: Name can't be blank"]
}

Naming a tag that does not exist on a type with external IDs fails with a message that says what to send instead:

{
    "base": ["The genre tag \"ska\" does not exist, and a new genre needs an id: use genres: [{\"id\": ..., \"name\": \"ska\"}]"]
}

POST /api/v1/libraries/{library_id}/tracks/bulk

Create or update up to 200 tracks in one request. Each track is what POST .../tracks takes. A track whose id is already in the library is updated, and one whose id is not is created.

Each track stands or falls alone. A track that fails is reported and leaves nothing behind, not even the tags, composers or publishers it would have created; the others are saved.

Path Parameters

Name Type Description Required
library_id integer ID of the library Yes

Request Body

Field Type Description Required
tracks array The tracks, up to 200, each with the fields POST .../tracks takes Yes
{
    "tracks": [
        { "id": 1001, "title": "Highway Windows Down", "bpm": 118 },
        { "id": 1002, "title": "" }
    ]
}

Response

{
    "created": 1,
    "updated": 0,
    "failures": [
        { "index": 1, "id": 1002, "errors": { "title": ["can't be blank"] } }
    ]
}

Response Fields

Field Type Description
created integer Number of tracks created
updated integer Number of tracks updated
failures array The tracks that were not saved; empty when all were
index integer Place of the track in the list, counting from 0
id integer | null The id the track was sent with
errors object Field name to list of messages, as in a 422 from POST .../tracks. A track that kept losing to other requests has a message under base ending (Deadlocked) or (LockWaitTimeout); nothing of it was saved and sending it again is safe.

HTTP Response Codes

Code Description
200 OK The request was understood. Some tracks may have failed; see failures.
403 Forbidden Your role does not allow editing
404 Not Found Invalid library ID
422 Unprocessable Content tracks is missing, is not a list, or holds more than 200

GET /api/v1/libraries/{library_id}/tracks/{id}

Retrieve a track.

Path Parameters

Name Type Description Required
library_id integer ID of the library Yes
id integer External ID of the track Yes

Response

Returns the track as shown in the list response.

HTTP Response Codes

Code Description
200 OK Successful request
404 Not Found Invalid library ID or track ID

PUT /api/v1/libraries/{library_id}/tracks/{id}

Update a track, or create it if there is no track with that ID, so a loader can send every track the same way without asking first. A new track needs a title, as it does on create, and the response is 201 Created. PATCH works the same way. The body takes the same fields as create; only the fields you send change, so a request with just { "track": { "bpm": 124 } } leaves everything else alone. A list field (style_alikes, tags of a multi-valued type, tracks_composers, tracks_publishers) replaces the whole list when it is sent.

Path Parameters

Name Type Description Required
library_id integer ID of the library Yes
id integer External ID of the track Yes

Response

Returns the updated or created track.

HTTP Response Codes

Code Description
200 OK Successful update
201 Created There was no track with that ID, and one was created
403 Forbidden Your role does not allow editing
404 Not Found Invalid library ID
409 Conflict The track could not be saved because other requests were changing the same records; send it again
422 Unprocessable Content Invalid request (see response body for details)

DELETE /api/v1/libraries/{library_id}/tracks/{id}

Delete a track, along with its metadata values, lyric index entries and any search results that referenced it.

Path Parameters

Name Type Description Required
library_id integer ID of the library Yes
id integer External ID of the track Yes

Response

No response body.

HTTP Response Codes

Code Description
204 No Content Successful deletion
403 Forbidden Your role does not allow editing
404 Not Found Invalid library ID or track ID

GET /api/v1/libraries/{library_id}/tracks/{id}/lyric_vectors

Retrieve the track’s lyric chunks and whether each has been embedded. Lyrics are cut into chunks, the whole and each section, and every chunk is embedded in the background through the library’s embedding batches. The vector itself is never returned. Not paged.

Path Parameters

Name Type Description Required
library_id integer ID of the library Yes
id integer External ID of the track Yes

Response

{
    "lyric_vectors": [
        {
            "id": 169840,
            "position": 0,
            "chunk_type": "full",
            "chunk_text": "1000 feet\n\ni ve got a thousand feet to climb ...",
            "chunk_occurrences": 1,
            "track_chunk_count": 1,
            "chunk_size_share": 1.0,
            "embedding_attempts": 0,
            "embedding_batch_id": null,
            "created_at": "2026-09-16T17:05:25.300-05:00",
            "updated_at": "2026-09-16T17:05:25.300-05:00",
            "embedded": false
        }
    ]
}

Response Fields

Field Type Description
id integer ID of the chunk
position integer Place of the chunk among those of its kind
chunk_type string full for the whole of the lyrics, section_0, section_1 and so on for its sections
chunk_text string The text, as it is embedded: lower case, without punctuation
chunk_occurrences integer How many times this text occurs in the lyrics, as a repeated chorus does
track_chunk_count integer Occurrences of every section of the track, added up
chunk_size_share number The chunk’s length as a share of the whole lyrics
embedding_attempts integer Times the chunk has been sent to be embedded
embedding_batch_id integer | null The batch it is in
embedded boolean Whether the chunk has been embedded
created_at, updated_at string Timestamps

HTTP Response Codes

Code Description
200 OK Successful request
404 Not Found Invalid library ID or track ID