Endpoints

The ChapterTwo API exposes procedures organized by namespace. Each procedure is either a query (read, GET) or mutation (write, POST). The endpoints below are available to all authenticated users via the CLI and SDK.

Roles

Each endpoint requires a minimum role. Organization members are assigned one of these roles:

RoleAccess Level
memberAny organization member (read access)
writerCan create and modify data (writer, admin, or owner)
adminAdministrative operations (admin or owner)

Explore Endpoints

Use the CLI or SDK to explore the full endpoint list:

bash
c2 endpoints # List all endpoints c2 endpoints --groups # Grouped by namespace c2 describe rightsholder.list # Detailed endpoint info

Namespaces

Endpoints are grouped by namespace. Use the CLI (c2 describe endpoint.name) for input/output schemas.

rightsholder

Manage rightsholders (artists, labels, publishers)

ProcedureTypeDescriptionRole
rightsholder.listqueryList rightsholders for the organizationmember
rightsholder.getByIdqueryGet a rightsholder by IDmember
rightsholder.createmutationCreate a new rightsholderwriter
rightsholder.updateByIdmutationUpdate a rightsholder by IDwriter
rightsholder.batchUpdatemutationBatch update multiple rightsholdersmember
rightsholder.deleteByIdmutationDelete a rightsholder by IDadmin

upload

Upload and process royalty statements

ProcedureTypeDescriptionRole
upload.listqueryList uploads for the organizationmember
upload.uploadFilemutationGenerate a presigned URL and initiate a file uploadwriter
upload.statementsmutationList statements for an organization or uploadmember
upload.uploadActionmutationExecute an action on an upload or statementwriter

product

Track and album product catalog

ProcedureTypeDescriptionRole
product.getByIdqueryGet a product by IDmember
product.getProductsByIdsqueryGet multiple products by their IDsmember
product.updateByIdmutationUpdate a product by IDwriter
product.getAllMusicalDetailsForProductqueryGet all musical details for a product in a blockmember
product.getMusicalDetailsBatchqueryGet musical details for multiple products in a blockmember

export

Data export and reporting

ProcedureTypeDescriptionRole
export.triggermutationTrigger a new royalty data exportmember
export.listqueryList all exports for the current organizationmember
export.statusqueryGet the status of a single exportmember
export.listFilesqueryList output files for a completed exportmember
export.downloadFilemutationGenerate a presigned download URL for a specific export filemember

file

File management

ProcedureTypeDescriptionRole
file.listqueryList files for the current organization with optional filtersmember
file.deleteFilesByIdsmutationDelete specific files from an upload by their IDswriter

fileStorage

Statement file storage and preview

ProcedureTypeDescriptionRole
fileStorage.downloadStatementFilemutationGenerate a presigned URL to download a statement file from S3member
fileStorage.getSamplequeryGet a paginated sample of rows from a statement CSV filemember

cube

Analytics and reporting via the Cube semantic layer

ProcedureTypeDescriptionRole
cube.metaqueryFetch Cube metadata (views, measures, dimensions)member
cube.querymutationExecute a Cube REST API query scoped to the organizationmember
cube.queryAsyncmutationSubmit a Cube query and return immediately with statusmember
cube.queryPollmutationPoll for a previously submitted Cube query resultmember
cube.sqlQuerymutationExecute a SQL query against the Cube SQL APImember
cube.sqlQueryAsyncmutationSubmit a Cube SQL query and return immediately with statusmember
cube.sqlQueryPollmutationPoll for a previously submitted Cube SQL query resultmember
cube.getSessionqueryGet or create an org-scoped Cube Cloud embed sessionmember

clustering

Product clustering and grouping

ProcedureTypeDescriptionRole
clustering.getClusterqueryGet a single cluster with its members and productsmember
clustering.getClustersListqueryGet a summary list of clusters for a blockmember
clustering.getTitleClusteringqueryGet title clustering data for a blockmember
clustering.getTopClustersqueryGet top clusters for a blockmember
clustering.getTopMdsFromBlockqueryGet top musical details with earnings from a blockmember
clustering.updateClusteringmutationApply clustering changeswriter

chat

AI chat conversations

ProcedureTypeDescriptionRole
chat.listqueryList chats for the current user in the organizationmember

exchangeRates

Currency exchange rates

ProcedureTypeDescriptionRole
exchangeRates.getqueryGet exchange rates for a given dateany

fetching

Processing queue status

ProcedureTypeDescriptionRole
fetching.inQueueCountqueryGet the number of musical details currently in the processing queuemember

user

User profile

ProcedureTypeDescriptionRole
user.currentUserqueryGet the currently authenticated userany

Calling Endpoints

Via SDK

typescript
import { createClient } from '@chapter-two-music/sdk'; const client = createClient({ apiUrl: 'https://api.prod.chaptertwo.com', token: process.env.C2_TOKEN!, orgId: 'your-org-uuid', }); // Query (GET) const data = await client.query('rightsholder.list', { pagination: { page: 1, limit: 10 }, }); // Mutation (POST) const result = await client.mutate('rightsholder.create', { name: 'New Artist', });

Via curl (tRPC HTTP)

The API uses tRPC's HTTP batch protocol. Every request requires the ?batch=1 query parameter, and input is wrapped with a "0" key (the batch index). The organizationId must be included in the input body:

bash
# Query (GET) — input is URL-encoded in the query string curl -H "Authorization: Bearer $TOKEN" \ "https://api.prod.chaptertwo.com/trpc/rightsholder.list?batch=1&input=%7B%220%22%3A%7B%22organizationId%22%3A%22YOUR_ORG_ID%22%7D%7D" # Mutation (POST) — input is in the JSON body curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"0":{"organizationId":"YOUR_ORG_ID","name":"New Artist"}}' \ "https://api.prod.chaptertwo.com/trpc/rightsholder.create?batch=1" # Response format (array, indexed to match batch) # [{"result":{"data":{"id":"...","name":"New Artist",...}}}]

Cube Query Schema

The cube.query, cube.queryAsync, and cube.queryPoll mutations accept a query object with the following shape:

typescript
{ query: { measures?: string[]; // e.g. ["EarningsView.totalEarnings"] dimensions?: string[]; // e.g. ["EarningsView.source"] filters?: Array<{ member: string; // e.g. "EarningsView.source" operator: string; // e.g. "equals", "notEquals", "contains", "gt", "lt" values?: string[]; // e.g. ["Spotify"] }>; timeDimensions?: Array<{ dimension: string; // e.g. "EarningsView.statementPeriod" granularity?: string; // "day" | "week" | "month" | "quarter" | "year" dateRange?: string | [string, string]; // e.g. "Last 12 months" or ["2024-01-01", "2024-12-31"] }>; order?: Record<string, "asc" | "desc">; // e.g. {"EarningsView.totalEarnings": "desc"} limit?: number; // 1-5000 total?: boolean; // include total count (default: false) } }

Example — top 10 tracks by earnings:

bash
curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"0":{"organizationId":"YOUR_ORG_ID","query":{"measures":["EarningsView.totalEarnings"],"dimensions":["EarningsView.trackTitle"],"order":{"EarningsView.totalEarnings":"desc"},"limit":10}}}' \ "https://api.prod.chaptertwo.com/trpc/cube.query?batch=1"

The cube.sqlQuery mutations accept a sql string instead of the query object. Use cube.meta to discover available measures and dimensions.