API Reference

The Spondulix Search API is a JSON REST API for loading a catalog, managing its metadata and running searches from your own systems. It covers nearly everything the web interface does: making and configuring a library, loading and maintaining its catalog, writing its prompts, and searching it. Three things are left to the site. AI assistants can also talk to Spondulix directly through the MCP server, and the command-line client wraps the whole API for the terminal.

Base URL

Every endpoint lives under https://app.spondulix.ai/api/v1/. Requests with a body send JSON with a Content-Type: application/json header, and every response is JSON.

The API also describes itself: GET https://app.spondulix.ai/api/v1/openapi.yaml returns an OpenAPI 3.1 document of every operation, and openapi.json the same document as JSON; neither needs a token. The application’s test suite holds every response to it, so where these pages and that document disagree, the document is right.

Versions

The API has a version: info.version in the OpenAPI document, 1.1.1 at the time of writing, which moves with every change to the document. Every response carries it, even a 401 or a 404:

X-API-Version: 1.1.1

A client that sends the version it was built against learns where it stands:

X-Client-API-Version: 1.1.1
X-API-Version-Status Meaning
current The client was built against this server’s description
outdated The client is behind; it should suggest an upgrade
ahead The client is newer than this server, which has not yet been deployed with the description the client knows; expect endpoints it lacks
unknown What the client sent is not a version

Send the header on every request, or ask once: GET /api/v1/ping is the cheapest request to ask on.

Endpoints

The query operators reference lists the fields and comparison operators available when building a filter or boost, and the MCP server page explains how to connect an AI assistant.

Authentication

Every endpoint, including ping, requires a bearer token: your API token, or an OAuth token issued to an application you approved. GET /api/v1/me tells a client whose token it holds.

API token

Your token is shown on your account page, where it can also be reset. Send it in the Authorization header of each request:

Authorization: Bearer your-api-token-here

A missing, wrong or reset token gets a 401 Unauthorized response:

{ "message": "Bad credentials" }

OAuth

An application can sign a user in through their browser instead of asking for their API token. Spondulix is an OAuth 2.1 authorization server:

   
Discovery /.well-known/oauth-authorization-server, and /.well-known/oauth-protected-resource/api/v1 for the API as a resource
Registration POST /oauth/register (RFC 7591), or a Client ID Metadata Document URL as the client_id
Grant Authorization code with PKCE (S256). Loopback redirect URIs may use plain http.
Resource resource=https://app.spondulix.ai/api/v1 in the authorization request. A token is issued for one resource: a token for the API is refused by the MCP server, and one for the MCP server by the API.
Tokens Access tokens last an hour and come with a refresh token, which changes each time it is used

A token carries scopes, which the user approves:

Scope Allows
catalog Every GET
search POST .../searches
catalog_admin Making a library and changing its settings, its AI providers and its collaborators; withdrawing invitations; and changing your own account under /me
catalog_write Every other POST, PUT, PATCH and DELETE

The scopes do not contain one another: catalog_admin does not allow what catalog_write allows, nor the other way round. Ask for catalog_admin only if the application administers libraries; it reaches keys and passwords.

A token without the scope a request needs gets 403 Forbidden, whatever its user’s role:

{ "message": "The token does not carry the catalog_write scope" }

with a WWW-Authenticate: Bearer error="insufficient_scope", scope="catalog_write" header. A token with the scope is still held to its user’s role in the library.

An API token carries no scopes and may do whatever its user may.

Libraries and permissions

The API sees the libraries you own or collaborate on. Each collaborator has a role: owner, admin, editor or viewer. Every collaborator can read a library. Creating, updating, deleting and reordering records needs the editor role or above. Changing the library’s settings, its AI providers, its collaborators and its invitations needs admin or above. A role too low for the action gets 403 Forbidden:

{ "message": "You are not authorized to perform this action" }

A library you do not collaborate on is indistinguishable from one that does not exist: both are 404 Not Found.

Each library in the libraries response carries your role, so a client can tell whether a write will be allowed before trying it.

Identifiers

Tracks, composers, publishers and metadata tags are addressed by your identifiers. You supply an integer external ID when you create one of these records, and the id in every response is that external ID. Libraries, metadata types, searches and PROs use Spondulix’s own IDs. An unknown ID of either kind is 404 Not Found:

{ "message": "Record not found" }

Request format

Wrap the record in a key named after the resource, matching the web forms:

{ "composer": { "external_id": 125, "name": "Johann Sebastian Bach" } }

The wrapper keys are library, composer, publisher, metadata_type, metadata_tag, track and search. Unknown keys inside the wrapper are ignored, except for tracks, where an unknown key is treated as a misspelled metadata type and rejected.

What the API leaves to the site

Everything the API can do, a leaked token can do, at machine speed, until the token is reset. Three actions would be worse than losing the data a token already reaches, so they happen on the site only.

Action On the site In the API
Making an account Sign up and verify the address Nothing. There is no signup endpoint.
Deleting a library Library settings, under the danger zone; type the library’s name Nothing. DELETE /api/v1/libraries/{id} is 404.
Inviting someone to a library, or sending the invitation again The library’s collaborators page Nothing. The API lists, shows and rescinds pending invitations.

Deleting a library is the only irreversible thing an account can do, and an invitation is mail from the Spondulix domain to any address the caller names. Making a library stays in the API: a library made by a script is inert until a subscription is attached on the site, and scripted creation is what lets a new client be set up end to end from the command line.

Responses and errors

Status When Body
200 OK Successful read or update The record, or a list with a pagination object
201 Created Successful create The new record; a Location header points at it
204 No Content Successful delete Empty
401 Unauthorized Missing or bad token { "message": "Bad credentials" }
403 Forbidden Your role in the library does not allow the action, or the token does not carry the scope for it { "message": "..." }
404 Not Found Unknown library or record { "message": "Record not found" }
422 Unprocessable Content The record failed validation Field name to list of messages

A 422 body names each invalid field. Problems that are not about one field appear under base:

{
    "title": ["can't be blank"],
    "external_id": ["has already been taken"],
    "base": ["Unknown metadata type: genrre_id"]
}

Blank values

Sending null, an empty string or an empty list for a field removes its value. Optional text and numeric fields are stored as null when blank, never as an empty string, and a bpm of 0 means “unknown” and is stored as null. A track therefore either has a value for a field or it does not, and searches treat “does not” as null; see null handling in the operators reference.

Pagination

List endpoints return 25 records per page unless you ask for more with per_page, up to 200 (PROs are all on one page). Pass page to move through the pages:

GET /api/v1/libraries/2/composers?page=3&per_page=200

Every list response includes a pagination object. next_page and next_page_url are present only when there is a next page, and previous_page only when there is a previous one. next_page_url keeps your per_page and anything else in the query.

"pagination": {
  "total_entries": 10523,
  "per_page": 25,
  "current_page": 2,
  "total_pages": 421,
  "next_page": 3,
  "previous_page": 1,
  "next_page_url": "https://app.spondulix.ai/api/v1/libraries/2/composers?page=3&per_page=200"
}