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:
| Role | Access Level |
|---|---|
| member | Any organization member (read access) |
| writer | Can create and modify data (writer, admin, or owner) |
| admin | Administrative operations (admin or owner) |
Explore Endpoints
Use the CLI or SDK to explore the full endpoint list:
bashc2 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)
| Procedure | Type | Description | Role |
|---|---|---|---|
| rightsholder.list | query | List rightsholders for the organization | member |
| rightsholder.getById | query | Get a rightsholder by ID | member |
| rightsholder.create | mutation | Create a new rightsholder | writer |
| rightsholder.updateById | mutation | Update a rightsholder by ID | writer |
| rightsholder.batchUpdate | mutation | Batch update multiple rightsholders | member |
| rightsholder.deleteById | mutation | Delete a rightsholder by ID | admin |
upload
Upload and process royalty statements
| Procedure | Type | Description | Role |
|---|---|---|---|
| upload.list | query | List uploads for the organization | member |
| upload.uploadFile | mutation | Generate a presigned URL and initiate a file upload | writer |
| upload.statements | mutation | List statements for an organization or upload | member |
| upload.uploadAction | mutation | Execute an action on an upload or statement | writer |
product
Track and album product catalog
| Procedure | Type | Description | Role |
|---|---|---|---|
| product.getById | query | Get a product by ID | member |
| product.getProductsByIds | query | Get multiple products by their IDs | member |
| product.updateById | mutation | Update a product by ID | writer |
| product.getAllMusicalDetailsForProduct | query | Get all musical details for a product in a block | member |
| product.getMusicalDetailsBatch | query | Get musical details for multiple products in a block | member |
export
Data export and reporting
| Procedure | Type | Description | Role |
|---|---|---|---|
| export.trigger | mutation | Trigger a new royalty data export | member |
| export.list | query | List all exports for the current organization | member |
| export.status | query | Get the status of a single export | member |
| export.listFiles | query | List output files for a completed export | member |
| export.downloadFile | mutation | Generate a presigned download URL for a specific export file | member |
file
File management
| Procedure | Type | Description | Role |
|---|---|---|---|
| file.list | query | List files for the current organization with optional filters | member |
| file.deleteFilesByIds | mutation | Delete specific files from an upload by their IDs | writer |
fileStorage
Statement file storage and preview
| Procedure | Type | Description | Role |
|---|---|---|---|
| fileStorage.downloadStatementFile | mutation | Generate a presigned URL to download a statement file from S3 | member |
| fileStorage.getSample | query | Get a paginated sample of rows from a statement CSV file | member |
cube
Analytics and reporting via the Cube semantic layer
| Procedure | Type | Description | Role |
|---|---|---|---|
| cube.meta | query | Fetch Cube metadata (views, measures, dimensions) | member |
| cube.query | mutation | Execute a Cube REST API query scoped to the organization | member |
| cube.queryAsync | mutation | Submit a Cube query and return immediately with status | member |
| cube.queryPoll | mutation | Poll for a previously submitted Cube query result | member |
| cube.sqlQuery | mutation | Execute a SQL query against the Cube SQL API | member |
| cube.sqlQueryAsync | mutation | Submit a Cube SQL query and return immediately with status | member |
| cube.sqlQueryPoll | mutation | Poll for a previously submitted Cube SQL query result | member |
| cube.getSession | query | Get or create an org-scoped Cube Cloud embed session | member |
clustering
Product clustering and grouping
| Procedure | Type | Description | Role |
|---|---|---|---|
| clustering.getCluster | query | Get a single cluster with its members and products | member |
| clustering.getClustersList | query | Get a summary list of clusters for a block | member |
| clustering.getTitleClustering | query | Get title clustering data for a block | member |
| clustering.getTopClusters | query | Get top clusters for a block | member |
| clustering.getTopMdsFromBlock | query | Get top musical details with earnings from a block | member |
| clustering.updateClustering | mutation | Apply clustering changes | writer |
chat
AI chat conversations
| Procedure | Type | Description | Role |
|---|---|---|---|
| chat.list | query | List chats for the current user in the organization | member |
exchangeRates
Currency exchange rates
| Procedure | Type | Description | Role |
|---|---|---|---|
| exchangeRates.get | query | Get exchange rates for a given date | any |
fetching
Processing queue status
| Procedure | Type | Description | Role |
|---|---|---|---|
| fetching.inQueueCount | query | Get the number of musical details currently in the processing queue | member |
user
User profile
| Procedure | Type | Description | Role |
|---|---|---|---|
| user.currentUser | query | Get the currently authenticated user | any |
Calling Endpoints
Via SDK
typescriptimport { 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:
bashcurl -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.