ChapterTwo API

ChapterTwo is a music royalty management platform. This page is the canonical reference for the public tRPC API hosted at https://api.prod.chaptertwo.com. Every endpoint, the Cube analytics schema, and an agent-integration guide live on this single page so AI assistants and humans can cross-reference the entire surface area without navigation.

SurfaceCount
Total procedures142
Queries (GET)67
Mutations (POST)75
Namespaces25
Cube analytics views1
If you are an AI agent, fetch one of these instead of scraping this page:
  • /llms.txt (alias /llm.txt) — the canonical reference. Agent-first ordering: a 5-line TL;DR, then setup, the three Cube rules, a sandbox Python helper, the agent guide with worked examples and explicit anti-patterns, then the analytics_full_view schema, and finally the per-procedure dump.
  • /schemas.json — full Zod-derived JSON Schema for every procedure (regexes, enums, defaults). Use when you need strict input validation.
  • /cube-schema.json — full Cube view schema (measures, dimensions, time dimensions). Same data as cube.meta but unauthenticated.
  • https://mcp.chaptertwo.com/api/mcp — for MCP-aware clients (Claude Desktop, Cursor).

Quick start

The fastest path is the CLI. It handles OAuth, organization scoping, token refresh, and output formatting so you can call any procedure with one command.

bash
# 1. Install npm install -g @chapter-two-music/cli # 2. Authenticate (browser) c2 auth login # 3. Verify and call your first endpoint c2 whoami c2 rightsholder.list # 4. Use the access token from scripts TOKEN=$(c2 auth token) ORG=$(c2 whoami --format json | jq -r '.organizationId') curl -H "Authorization: Bearer $TOKEN" \ "https://api.prod.chaptertwo.com/trpc/health"

Or use the typed SDK from any Node/TypeScript project:

typescript
import { createClient } from '@chapter-two-music/sdk'; const client = createClient({ apiUrl: 'https://api.prod.chaptertwo.com', token: process.env.C2_TOKEN!, // `c2 auth token` or OAuth access token orgId: process.env.C2_ORG_ID!, // your organization UUID }); const { data, pagination } = await client.query('rightsholder.list', { pagination: { page: 1, limit: 25 }, });

Authentication

All procedures except health require authentication. Two credential types are accepted: OAuth 2.0 access tokens (for users) and API keys (for service-to-service integrations).

OAuth 2.0 with PKCE

User sessions use OAuth 2.0 authorization-code flow with PKCE. The CLI and MCP clients run the flow automatically. For custom integrations, use the discovery endpoint to bind a client:

EndpointURL
Discoveryhttps://mcp.chaptertwo.com/.well-known/oauth-authorization-server
Authorizehttps://mcp.chaptertwo.com/api/oauth/authorize
Tokenhttps://mcp.chaptertwo.com/api/oauth/token

Send the access token as Authorization: Bearer <token> on every request.

API keys

For long-running automated workflows, request an API key by emailing support@chaptertwo.com. API keys carry an explicit scope of organization IDs and route patterns.

bash
curl -H "x-api-key: $C2_API_KEY" \ -H "x-organization-id: $ORG_ID" \ "https://api.prod.chaptertwo.com/trpc/health"
With API keys you must send x-organization-id as a header AND repeat organizationId in the request body. With OAuth you only need the body field.

Token lifecycle

TokenLifetimeNotes
OAuth access token1 hourBearer token. Refresh before expiry using the refresh token.
OAuth refresh token30 daysSingle-use — every refresh rotates it. After 30 days re-authenticate.
API keyUntil rotatedLong-lived. Rotate via apiKey.update / apiKey.delete.
bash
# Refresh the access token curl -X POST https://mcp.chaptertwo.com/api/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token&refresh_token=$REFRESH_TOKEN"

Transport

Base URLs

ServiceURL
API (production)https://api.prod.chaptertwo.com
MCP serverhttps://mcp.chaptertwo.com/api/mcp
OAuth discoveryhttps://mcp.chaptertwo.com/.well-known/oauth-authorization-server

tRPC HTTP batch protocol

The API speaks tRPC over HTTP. Procedures are addressed at /trpc/<namespace>.<procedure>. There are two modes — always prefer batch mode ?batch=1 (the SDK uses this internally):

bash
# Query (GET) — note the URL-encoded JSON curl -H "Authorization: Bearer $TOKEN" \ "https://api.prod.chaptertwo.com/trpc/rightsholder.list?batch=1&input=%7B%220%22%3A%7B%22organizationId%22%3A%22$ORG%22%2C%22pagination%22%3A%7B%22page%22%3A1%2C%22limit%22%3A10%7D%7D%7D" # Mutation (POST) curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"0":{"organizationId":"'$ORG'","name":"New Artist"}}' \ "https://api.prod.chaptertwo.com/trpc/rightsholder.create?batch=1"

Organization scoping

Almost every procedure is scoped to a single organization. The server enforces that the authenticated principal belongs to the requested org via the organization-context-middleware. Always include organizationId (UUID) in the procedure input. The two exceptions — user.currentUser and exchangeRates.get — do not require it.

Pagination

List queries take a pagination object and return a uniform envelope. The default limit is 300 when no pagination params are provided, or 200 when only page is provided. Maximum is 2000.

typescript
// Input { pagination: { page: 1, // 1-indexed limit: 25, // 1..2000 sortBy?: string; sortOrder?: 'asc' | 'desc'; } } // Output { data: T[], pagination: { page: number, limit: number, total: number, totalPages: number, hasNext: boolean, hasPrev: boolean, } }
A few endpoints have non-standard envelopes: upload.statements uses { items, totalFiles, pagination }; user.currentUser and exchangeRates.get return flat shapes.

Errors

Every error follows the tRPC error envelope:

json
[{ "error": { "message": "Rightsholder not found", "code": -32004, "data": { "code": "NOT_FOUND", "errorCode": "RIGHTSHOLDER_NOT_FOUND", "httpStatus": 404, "path": "rightsholder.getById" } } }]
data.codeHTTPMeaning
UNAUTHORIZED401Missing or invalid token / API key
FORBIDDEN403Authenticated but lacks the required role
NOT_FOUND404Resource does not exist or not visible to this org
BAD_REQUEST400Input failed Zod validation
CONFLICT409Unique-constraint violation (e.g. rightsholder name)
TIMEOUT408Cube query exceeded the long-poll window — retry via async
INTERNAL_SERVER_ERROR500Unexpected error — safe to retry once

API reference

All 142 procedures, grouped by namespace. Each procedure shows its HTTP method, scope, summary, full path, and input schema. POST = mutation, GET = query. cli means the procedure is exposed by the public CLI; admin means it's gated to ChapterTwo admin tooling.

activity (3)

activity.byEntityGET

List activity events for a single subject

GET /trpc/activity.byEntity?batch=1&input=…

Input
organizationId*string (uuid)
subjectType*"rightsholder" | "file" | "upload" | "export" | "product" | "mapping" | "lens" | "chat" | "organization" | "user"
subjectId*string
cursorstring
limit*integer(default 50)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

activity.feedGET

List activity events for the organisation

GET /trpc/activity.feed?batch=1&input=…

activity.trackVisitPOST

Record that the current user opened a subject

POST /trpc/activity.trackVisit?batch=1

Input
organizationId*string (uuid)
subjectType*"rightsholder" | "file" | "upload" | "export" | "product" | "mapping" | "lens" | "chat" | "organization" | "user"
subjectId*string
subjectLabelstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

clustering (6)

Track/product clustering and grouping inside a processing block.

clustering.getClusterGETcli

Get a single cluster with its members and products

GET /trpc/clustering.getCluster?batch=1&input=…

Input
organizationId*string (uuid)
blockId*string (uuid)
clusterId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

clustering.getClustersListGETcli

Get a summary list of clusters for a block

GET /trpc/clustering.getClustersList?batch=1&input=…

Input
organizationId*string (uuid)
blockId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

clustering.getTitleClusteringGETcli

Get title clustering data for a block

GET /trpc/clustering.getTitleClustering?batch=1&input=…

Input
organizationId*string (uuid)
blockId*string (uuid)
paginationobject(default {"page":1,"limit":50})
pagination.pageinteger(default 1)
pagination.limitinteger(default 20)
sortField*"entries" | "titleDisplay" | "similarity" | "earnings"(default "entries")
sortDirection*"asc" | "desc"(default "desc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

clustering.getTopClustersGETcli

Get top clusters for a block

GET /trpc/clustering.getTopClusters?batch=1&input=…

Input
organizationId*string (uuid)
blockId*string (uuid)
wantedMusicalDetailsstring[]
wantedClusterIdsstring (uuid)[]
limitinteger(default 300)
thresholdnumber(default 0.001)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

clustering.getTopMdsFromBlockGETcli

Get top musical details with earnings from a block

GET /trpc/clustering.getTopMdsFromBlock?batch=1&input=…

Input
organizationId*string (uuid)
blockId*string (uuid)
limitinteger(default 300)
minEarningsPercentagenumber

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

clustering.updateClusteringPOSTcli

Apply clustering changes to reassign musical details between clusters

POST /trpc/clustering.updateClustering?batch=1

Input
organizationId*string (uuid)
blockId*string (uuid)
changes*object[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

cube (8)

Analytics queries and SQL passthrough against the Cube semantic layer.

cube.getSessionGETcli

Get or create an org-scoped Cube Cloud embed session

GET /trpc/cube.getSession?batch=1&input=…

cube.metaGETcli

Fetch Cube metadata (views, measures, dimensions)

GET /trpc/cube.meta?batch=1&input=…

cube.queryPOSTcli

Execute a Cube REST API query scoped to the organization

POST /trpc/cube.query?batch=1

Input
organizationId*string (uuid)
query*object
query.measuresstring[]
query.dimensionsstring[]
query.filtersobject[]
query.timeDimensionsobject[]
query.orderobject
query.limitinteger
query.total*boolean(default false)
usePreaggboolean

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

cube.queryAsyncPOSTcli

Submit a Cube query and return immediately with status

POST /trpc/cube.queryAsync?batch=1

Input
organizationId*string (uuid)
query*object
query.measuresstring[]
query.dimensionsstring[]
query.filtersobject[]
query.timeDimensionsobject[]
query.orderobject
query.limitinteger
query.total*boolean(default false)
usePreaggboolean
noCacheboolean

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

cube.queryPollPOSTcli

Poll for a previously submitted Cube query result

POST /trpc/cube.queryPoll?batch=1

Input
organizationId*string (uuid)
query*object
query.measuresstring[]
query.dimensionsstring[]
query.filtersobject[]
query.timeDimensionsobject[]
query.orderobject
query.limitinteger
query.total*boolean(default false)
usePreaggboolean

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

cube.sqlQueryPOST

Execute a SQL query against the Cube SQL API

POST /trpc/cube.sqlQuery?batch=1

Input
organizationId*string (uuid)
sql*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

cube.sqlQueryAsyncPOST

Submit a Cube SQL query and return immediately with status

POST /trpc/cube.sqlQueryAsync?batch=1

Input
organizationId*string (uuid)
sql*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

cube.sqlQueryPollPOST

Poll for a previously submitted Cube SQL query result

POST /trpc/cube.sqlQueryPoll?batch=1

Input
organizationId*string (uuid)
sql*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

dataFreshness (4)

Inspect ETL freshness and ingestion progress.

dataFreshness.checkEarningsGET

Check earnings data freshness for selected rightsholders

GET /trpc/dataFreshness.checkEarnings?batch=1&input=…

Input
organizationId*string (uuid)
rightsholderIds*string (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

dataFreshness.checkProductsGET

Check products data freshness for selected rightsholders

GET /trpc/dataFreshness.checkProducts?batch=1&input=…

Input
organizationId*string (uuid)
rightsholderIds*string (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

dataFreshness.ingestionStatsGET

Full aggregated preview stats from DynamoDB for selected rightsholders

GET /trpc/dataFreshness.ingestionStats?batch=1&input=…

Input
organizationId*string (uuid)
rightsholderIds*string (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

dataFreshness.isReadyGET

Readiness check using DynamoDB status categories + RDS warehoused status

GET /trpc/dataFreshness.isReady?batch=1&input=…

Input
organizationId*string (uuid)
rightsholderIds*string (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

distributor (5)

Statement distributor formats (e.g. Kobalt, MLC, Spotify-direct).

distributor.createPOSTadmin

Create a new distributor

POST /trpc/distributor.create?batch=1

Input
organizationId*string (uuid)
name*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

distributor.deleteByIdPOST

Delete a distributor by ID

POST /trpc/distributor.deleteById?batch=1

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

distributor.getByIdGETadmin

Get a distributor by ID

GET /trpc/distributor.getById?batch=1&input=…

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

distributor.listGETadmin

List distributors with their mappings

GET /trpc/distributor.list?batch=1&input=…

Input
organizationId*string (uuid)
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

distributor.updateByIdPOSTadmin

Update a distributor by ID

POST /trpc/distributor.updateById?batch=1

Input
organizationId*string (uuid)
id*string (uuid)
namestring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

exchangeRates (1)

Foreign exchange rates used to convert statement currencies to USD.

exchangeRates.getGETcli

Get exchange rates for a given date

GET /trpc/exchangeRates.get?batch=1&input=…

fetching (6)

Music enrichment / fetching queue status.

fetching.getFetchingCoverageByRightsholdersGET

GET /trpc/fetching.getFetchingCoverageByRightsholders?batch=1&input=…

Input
organizationId*string (uuid)
rightsholderIds*string (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

fetching.inQueueCountGETcli

Get the number of musical details currently in the processing queue

GET /trpc/fetching.inQueueCount?batch=1&input=…

fetching.listGETadmin

List blocks with musical detail fetching status

GET /trpc/fetching.list?batch=1&input=…

Input
organizationIdstring (uuid)
searchstring
pagination*object(default {"page":1,"limit":10})
pagination.page*integer(default 1)
pagination.limit*integer(default 10)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

fetching.refetchByBlockPOST

Re-fetch terminal musical details for a block

POST /trpc/fetching.refetchByBlock?batch=1

Input
organizationId*string (uuid)
blockId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

fetching.reprocessSkippedByBlockPOST

Reprocess skipped musical details for a block

POST /trpc/fetching.reprocessSkippedByBlock?batch=1

Input
organizationId*string (uuid)
blockId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

fetching.stopFetchingByBlockPOST

Stop fetching for a block by skipping pending items

POST /trpc/fetching.stopFetchingByBlock?batch=1

Input
organizationId*string (uuid)
blockId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

file (3)

Per-organization file management.

file.deleteFilesByIdsPOSTcli

Delete specific files from an upload by their IDs

POST /trpc/file.deleteFilesByIds?batch=1

Input
organizationId*string (uuid)
fileIds*string[]
uploadId*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

file.getFilesDeleteInfoGET

Get deletion impact info for a set of files (blocks and catalogs affected)

GET /trpc/file.getFilesDeleteInfo?batch=1&input=…

Input
organizationId*string (uuid)
fileIds*string[]
uploadId*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

file.listGETcli

List files for the current organization with optional filters

GET /trpc/file.list?batch=1&input=…

Input
organizationId*string (uuid)
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")
filtersobject
filters.fileIdstring[]
filters.dateStartstring
filters.dateEndstring
filters.rightsholderIdstring (uuid)
filters.distributorIdstring (uuid)
filters.customNamestring
filters.namestring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

fileStorage (2)

Statement file storage — presigned downloads and CSV samples.

fileStorage.downloadStatementFilePOSTcli

Generate a presigned URL to download a statement file from S3

POST /trpc/fileStorage.downloadStatementFile?batch=1

Input
organizationId*string (uuid)
type*"normalized" | "trimmed" | "original" | "single" | "removed" | "standardized" | "original-excel"
file*string
fileNamestring
originalExcelFilestring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

fileStorage.getSampleGETcli

Get a paginated sample of rows from a statement CSV file

GET /trpc/fileStorage.getSample?batch=1&input=…

Input
organizationId*string (uuid)
type*"normalized" | "trimmed" | "original" | "single" | "removed" | "standardized"
file*string
startRow*integer(default 0)
endRow*integer(default 200)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

health (1)

Service health check.

healthGET

GET /trpc/health?batch=1&input=…

insights (2)

Default FX-rate dates and rates for analytics.

insights.getDefaultFxDateGET

GET /trpc/insights.getDefaultFxDate?batch=1&input=…

Input
organizationId*string (uuid)
rightsholderIds*string (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

insights.getDefaultFxRatesGET

GET /trpc/insights.getDefaultFxRates?batch=1&input=…

Input
organizationId*string (uuid)
rightsholderIds*string (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lens (9)

Saved analytics lenses (charts/dashboards).

lens.createPOST

POST /trpc/lens.create?batch=1

Input
organizationId*string (uuid)
name*string
descriptionstring
scope*"personal" | "organization"(default "organization")
tags*string[](default [])
kind*"query" | "pivot" | "funnel" | "flow" | "retention" | "drill_down" | "valuation"
viewNameunknown
payload*unknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lens.deletePOST

POST /trpc/lens.delete?batch=1

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lens.duplicatePOST

POST /trpc/lens.duplicate?batch=1

Input
organizationId*string (uuid)
id*string (uuid)
namestring
scope"personal" | "organization"

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lens.getGET

GET /trpc/lens.get?batch=1&input=…

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lens.listGET

GET /trpc/lens.list?batch=1&input=…

Input
organizationId*string (uuid)
kind"query" | "pivot" | "funnel" | "flow" | "retention" | "drill_down" | "valuation"
viewNamestring
scope"personal" | "organization"
tagstring
ownerUserIdstring (uuid)
searchstring
pinnedOnlyboolean
recentOnlyboolean
sort"updated_desc" | "created_desc" | "name_asc" | "last_opened_desc"
cursorunknown
limit*integer(default 50)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lens.listTagsGET

GET /trpc/lens.listTags?batch=1&input=…

lens.recordOpenedPOST

POST /trpc/lens.recordOpened?batch=1

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lens.setPinnedPOST

POST /trpc/lens.setPinned?batch=1

Input
organizationId*string (uuid)
id*string (uuid)
pinned*boolean

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lens.updatePOST

POST /trpc/lens.update?batch=1

Input
organizationId*string (uuid)
id*string (uuid)
namestring
descriptionunknown
scope"personal" | "organization"
tagsstring[]
payloadunknown
viewNameunknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSalesType (8)

Sales-type lookup tables and AI resolvers (admin).

lookupSalesType.batchResolveWithAIPOSTadmin

Batch resolve multiple sales type strings using AI (max 10)

POST /trpc/lookupSalesType.batchResolveWithAI?batch=1

Input
salesTypes*string[]
saveToDb*boolean(default false)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSalesType.createPOSTadmin

Create or upsert a sales type lookup entry

POST /trpc/lookupSalesType.create?batch=1

Input
original*string
cleansed*string
statusstring
reasonunknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSalesType.deletePOSTadmin

Delete a sales type lookup entry by id

POST /trpc/lookupSalesType.delete?batch=1

lookupSalesType.getSalesTypeLookupGETadmin

Get a single sales type lookup entry by id or original value

GET /trpc/lookupSalesType.getSalesTypeLookup?batch=1&input=…

Input
idstring (uuid)
originalstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSalesType.listGETadmin

List sales type lookup entries with optional search and status filter

GET /trpc/lookupSalesType.list?batch=1&input=…

Input
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")
searchstring
statusstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSalesType.missingSalesTypesGETadmin

List sales types that are present in data but have no lookup mapping

GET /trpc/lookupSalesType.missingSalesTypes?batch=1&input=…

Input
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSalesType.resolveWithAIPOSTadmin

Resolve a sales type string using AI classification

POST /trpc/lookupSalesType.resolveWithAI?batch=1

Input
original*string
saveToDb*boolean(default false)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSalesType.updatePOSTadmin

Update a sales type lookup entry by id

POST /trpc/lookupSalesType.update?batch=1

Input
id*string (uuid)
cleansedstring
primarySalesTypeunknown
secondarySalesTypeunknown
statusstring
reasonunknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSource (12)

Source lookup tables and AI resolvers (admin).

lookupSource.batchResolveWithAIPOSTadmin

Batch resolve multiple source strings using AI (max 10)

POST /trpc/lookupSource.batchResolveWithAI?batch=1

Input
sources*string[]
saveToDb*boolean(default false)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSource.createPOSTadmin

Create or upsert a source lookup entry

POST /trpc/lookupSource.create?batch=1

Input
original*string
collectionSourceunknown
earningsSourceunknown
earningsCategoryunknown
earningsParentCompanyunknown
publisherSourceunknown
status*string
reasonunknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSource.deletePOSTadmin

Delete a source lookup entry by id

POST /trpc/lookupSource.delete?batch=1

lookupSource.getSourceLookupGETadmin

Get a single source lookup entry by id or original value

GET /trpc/lookupSource.getSourceLookup?batch=1&input=…

Input
idstring (uuid)
originalstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSource.listGETadmin

List source lookup entries with optional search and status filter

GET /trpc/lookupSource.list?batch=1&input=…

Input
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")
searchstring
statusunknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSource.missingSourcesGETadmin

List sources that are present in data but have no lookup mapping

GET /trpc/lookupSource.missingSources?batch=1&input=…

Input
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSource.ragCreatePOSTadmin

Add a new RAG source vector with auto-generated embedding

POST /trpc/lookupSource.ragCreate?batch=1

lookupSource.ragDeletePOSTadmin

Delete a RAG source vector by id

POST /trpc/lookupSource.ragDelete?batch=1

lookupSource.ragListGETadmin

List all RAG source vectors used for AI resolution

GET /trpc/lookupSource.ragList?batch=1&input=…

lookupSource.ragUpdatePOSTadmin

Update a RAG source vector and regenerate its embedding

POST /trpc/lookupSource.ragUpdate?batch=1

Input
id*string (uuid)
name*string
category*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

lookupSource.resolveWithAIPOSTadmin

Resolve a source string using AI classification

POST /trpc/lookupSource.resolveWithAI?batch=1

lookupSource.updatePOSTadmin

Update a source lookup entry by id

POST /trpc/lookupSource.update?batch=1

Input
id*string (uuid)
collectionSourceunknown
earningsSourceunknown
earningsCategoryunknown
earningsParentCompanyunknown
publisherSourceunknown
statusstring
reasonunknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping (11)

Distributor → standard column mappings (admin).

mapping.batchUpdatePOSTadmin

Batch update multiple mappings in a single transaction

POST /trpc/mapping.batchUpdate?batch=1

Input
organizationId*string (uuid)
updates*unknown[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.createPOSTadmin

Create a new column mapping

POST /trpc/mapping.create?batch=1

Input
organizationId*string (uuid)
namestring
headers*string[]
fieldsobject
fields.stmtYearobject
fields.stmtPeriodobject
fields.stmtCadenceobject
fields.currencyobject
fields.royaltyEarningsobject
fields.grossRoyaltyEarningsobject
fields.salesPeriodStartobject
fields.salesPeriodEndobject
fields.salesTypeobject
fields.subSalesTypeobject
fields.sourceobject
fields.subSourceobject
fields.unitsobject
fields.countryobject
fields.typeOfRightobject
fields.accountNumberobject
fields.accountNameobject
fields.legalVendorobject
fields.territoryDeductionobject
fields.taxDeductionobject
fields.royaltyRateobject
fields.otherAdjustmentsobject
fields.rawTitleobject
fields.rawArtistobject
fields.rawReleaseTitleobject
fields.rawReleaseArtistobject
fields.composersobject
fields.isrcobject
fields.upcobject
fields.iswcobject
fields.otherIdentifierobject
fields.payorobject
headerHash*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.deleteByIdPOST

Delete a mapping by id (admin/owner only)

POST /trpc/mapping.deleteById?batch=1

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.getByDistributorGETadmin

Get mappings associated with a specific distributor

GET /trpc/mapping.getByDistributor?batch=1&input=…

Input
organizationId*string (uuid)
distributorId*string (uuid)
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.getByIdGETadmin

Get a mapping by id (deprecated: use getMapping instead)

GET /trpc/mapping.getById?batch=1&input=…

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.getMappingGETadmin

Get a mapping by id or header hash

GET /trpc/mapping.getMapping?batch=1&input=…

Input
organizationId*string (uuid)
idstring (uuid)
headerHashstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.listGETadmin

List mappings for the current organization

GET /trpc/mapping.list?batch=1&input=…

Input
organizationId*string (uuid)
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")
idsstring (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.processSamplePOST

Process a sample of rows using a field mapping to preview transformations

POST /trpc/mapping.processSample?batch=1

Input
organizationId*string (uuid)
statementId*string
startRow*integer(default 0)
endRow*integer(default 5)
mappingId*string (uuid)
mapping*object
mapping.stmtYear*object
mapping.stmtPeriod*object
mapping.stmtCadence*object
mapping.currency*object
mapping.royaltyEarnings*object
mapping.grossRoyaltyEarningsobject
mapping.salesPeriodStartobject
mapping.salesPeriodEndobject
mapping.salesTypeobject
mapping.subSalesTypeobject
mapping.sourceobject
mapping.subSourceobject
mapping.unitsobject
mapping.countryobject
mapping.typeOfRightobject
mapping.accountNumberobject
mapping.accountNameobject
mapping.legalVendorobject
mapping.territoryDeductionobject
mapping.taxDeductionobject
mapping.royaltyRateobject
mapping.otherAdjustmentsobject
mapping.rawTitleobject
mapping.rawArtistobject
mapping.rawReleaseTitleobject
mapping.rawReleaseArtistobject
mapping.composersobject
mapping.isrcobject
mapping.upcobject
mapping.iswcobject
mapping.otherIdentifierobject
mapping.payorobject
variablesobject
fileName*string(default "")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.redrivePreviewGETadmin

Preview which files and organizations would be affected by a mapping redrive

GET /trpc/mapping.redrivePreview?batch=1&input=…

Input
organizationId*string (uuid)
mappingId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.revision.listByMappingGETadmin

GET /trpc/mapping.revision.listByMapping?batch=1&input=…

Input
organizationId*string (uuid)
mappingId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

mapping.updateByIdPOSTadmin

Update a mapping by id, optionally reassigning distributors

POST /trpc/mapping.updateById?batch=1

Input
organizationId*string (uuid)
id*string (uuid)
distributorIdstring (uuid)[]
verifyboolean
redriveFileIdsstring[]
namestring
headersstring[]
fieldsobject
fields.stmtYearobject
fields.stmtPeriodobject
fields.stmtCadenceobject
fields.currencyobject
fields.royaltyEarningsobject
fields.grossRoyaltyEarningsobject
fields.salesPeriodStartobject
fields.salesPeriodEndobject
fields.salesTypeobject
fields.subSalesTypeobject
fields.sourceobject
fields.subSourceobject
fields.unitsobject
fields.countryobject
fields.typeOfRightobject
fields.accountNumberobject
fields.accountNameobject
fields.legalVendorobject
fields.territoryDeductionobject
fields.taxDeductionobject
fields.royaltyRateobject
fields.otherAdjustmentsobject
fields.rawTitleobject
fields.rawArtistobject
fields.rawReleaseTitleobject
fields.rawReleaseArtistobject
fields.composersobject
fields.isrcobject
fields.upcobject
fields.iswcobject
fields.otherIdentifierobject
fields.payorobject
fileCategoriesunknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

market (3)

Market data — quotes, treasury yields, history.

market.getHistoryGET

GET /trpc/market.getHistory?batch=1&input=…

Input
organizationId*string (uuid)
ticker*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

market.getQuotesGET

GET /trpc/market.getQuotes?batch=1&input=…

Input
organizationId*string (uuid)
symbols*string[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

market.getTreasuryYieldsGET

GET /trpc/market.getTreasuryYields?batch=1&input=…

musicalDetails (7)

Musical-details lookup, override, and execution (admin).

musicalDetails.bulkOverridePOSTadmin

Bulk override musical detail results

POST /trpc/musicalDetails.bulkOverride?batch=1

Input
organizationId*string (uuid)
blockIdstring (uuid)
overrides*object[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

musicalDetails.executeQueryGETadmin

Execute a filter query on musical details

GET /trpc/musicalDetails.executeQuery?batch=1&input=…

Input
organizationId*string (uuid)
query*unknown
hasBlockRule*boolean(default false)
hasRightsholderRule*boolean(default false)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

musicalDetails.getDistinctTargetProvidersGETadmin

Get distinct target providers for musical details

GET /trpc/musicalDetails.getDistinctTargetProviders?batch=1&input=…

musicalDetails.getDistinctTargetTypesGETadmin

Get distinct target types for musical details

GET /trpc/musicalDetails.getDistinctTargetTypes?batch=1&input=…

musicalDetails.getMusicalDetailsGETadmin

Get paginated musical details with filter

GET /trpc/musicalDetails.getMusicalDetails?batch=1&input=…

Input
organizationId*string (uuid)
query*unknown
hasBlockRule*boolean(default false)
hasRightsholderRule*boolean(default false)
includeTrail*boolean(default false)
pagination*object(default {"page":1,"pageSize":100})
pagination.page*integer(default 1)
pagination.pageSize*integer(default 100)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

musicalDetails.ignoreTitlesPOSTadmin

Mark musical detail titles as ignored

POST /trpc/musicalDetails.ignoreTitles?batch=1

Input
organizationId*string (uuid)
titles*string[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

musicalDetails.updateResultsPOSTadmin

Update musical detail results with override values

POST /trpc/musicalDetails.updateResults?batch=1

Input
organizationId*string (uuid)
resultIds*string (uuid)[]
overrideValues*object
overrideValues.title*string
overrideValues.artist*string
overrideValues.releaseDate*string
overrideValues.imageUrlstring
overrideValues.genresobject[]
overrideValues.albumsobject[]
overrideValues.songWritersobject[]
overrideValues.fetchedComposersobject[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

musicEnrichment (1)

Track and rightsholder music-enrichment metadata (admin).

musicEnrichment.queryGETadmin

Execute a Chartmetric data query for music enrichment

GET /trpc/musicEnrichment.query?batch=1&input=…

organization (16)

Organization administration — users, invitations, settings.

organization.addUserPOST

Invite a user to the organization

POST /trpc/organization.addUser?batch=1

organization.createPOST

Create a new organization

POST /trpc/organization.create?batch=1

organization.deletePOST

Soft delete the organization

POST /trpc/organization.delete?batch=1

organization.getByIdGET

Get the current organization by ID

GET /trpc/organization.getById?batch=1&input=…

organization.getInvitationLinkPOST

Get the acceptance link for a pending invitation

POST /trpc/organization.getInvitationLink?batch=1

Input
organizationId*string (uuid)
invitationId*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.listGETadmin

List organizations for the current user

GET /trpc/organization.list?batch=1&input=…

Input
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.listInvitationsGET

List pending invitations in the organization

GET /trpc/organization.listInvitations?batch=1&input=…

Input
organizationId*string (uuid)
status*"pending" | "accepted" | "revoked" | "expired"[](default ["pending"])

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.listRightsholderMdPrioritiesGETadmin

List rightsholder MD fetch priorities for the organization

GET /trpc/organization.listRightsholderMdPriorities?batch=1&input=…

Input
organizationId*string (uuid)
searchstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.listUsersGETadmin

List users in the organization

GET /trpc/organization.listUsers?batch=1&input=…

Input
organizationId*string (uuid)
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.removeUserPOST

Remove a user from the organization

POST /trpc/organization.removeUser?batch=1

Input
organizationId*string (uuid)
userId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.resendInvitationPOST

Resend a pending invitation by revoking and re-creating it

POST /trpc/organization.resendInvitation?batch=1

Input
organizationId*string (uuid)
invitationId*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.revokeInvitationPOST

Revoke a pending invitation

POST /trpc/organization.revokeInvitation?batch=1

Input
organizationId*string (uuid)
invitationId*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.setRightsholderMdPrioritiesPOST

Replace the full rightsholder MD fetch priority ordering

POST /trpc/organization.setRightsholderMdPriorities?batch=1

Input
organizationId*string (uuid)
rightsholderIds*string (uuid)[](default [])

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.updatePOST

Update organization details

POST /trpc/organization.update?batch=1

Input
organizationId*string (uuid)
name*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.updateMdFetchSettingsPOST

Update musical detail fetch settings for the organization

POST /trpc/organization.updateMdFetchSettings?batch=1

Input
organizationId*string (uuid)
mdFetchMultiplierinteger
mdFetchFocusboolean

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

organization.updateRolePOST

Update a user

POST /trpc/organization.updateRole?batch=1

Input
organizationId*string (uuid)
userId*string (uuid)
role*"member" | "reader" | "writer" | "admin" | "c2_customer_support" | "owner" | "c2_superadmin"

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

payor (1)

payor.listGlobalGET

List globally-known payors (for autocomplete)

GET /trpc/payor.listGlobal?batch=1&input=…

product (8)

Track and album product catalog.

product.getAllMusicalDetailsForProductGETcli

Get all musical details for a product in a block

GET /trpc/product.getAllMusicalDetailsForProduct?batch=1&input=…

Input
organizationId*string (uuid)
productId*string (uuid)
blockId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

product.getByBlockGET

Get products for a specific block

GET /trpc/product.getByBlock?batch=1&input=…

Input
organizationId*string (uuid)
blockId*string (uuid)
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 20)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")
searchQuerystring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

product.getByIdGETcli

Get a product by ID

GET /trpc/product.getById?batch=1&input=…

Input
organizationId*string (uuid)
productId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

product.getMusicalDetailsBatchGETcli

Get musical details for multiple products in a block

GET /trpc/product.getMusicalDetailsBatch?batch=1&input=…

Input
organizationId*string (uuid)
productIds*string (uuid)[]
blockId*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

product.getMusicalDetailsByBlockGET

Get musical details for a product within a block

GET /trpc/product.getMusicalDetailsByBlock?batch=1&input=…

Input
organizationId*string (uuid)
blockId*string (uuid)
productId*string (uuid)
paginationobject
pagination.pageinteger(default 1)
pagination.limitinteger(default 5000)
pagination.sortBystring
pagination.sortOrder"asc" | "desc"(default "asc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

product.getProductsByIdsGETcli

Get multiple products by their IDs

GET /trpc/product.getProductsByIds?batch=1&input=…

Input
organizationId*string (uuid)
productIds*string (uuid)[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

product.updateByIdPOSTcli

Update a product by ID

POST /trpc/product.updateById?batch=1

product.updateProductMusicalDetailsRelationshipsPOST

Create, update, and delete product to musical detail relationships

POST /trpc/product.updateProductMusicalDetailsRelationships?batch=1

rightsholder (7)

Manage rightsholders (artists, labels, publishers).

rightsholder.batchUpdatePOSTcli

Batch update multiple rightsholders

POST /trpc/rightsholder.batchUpdate?batch=1

Input
organizationId*string (uuid)
updates*unknown[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

rightsholder.createPOSTcli

Create a new rightsholder

POST /trpc/rightsholder.create?batch=1

Input
organizationId*string (uuid)
name*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

rightsholder.deleteByIdPOSTcli

Delete a rightsholder by ID

POST /trpc/rightsholder.deleteById?batch=1

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

rightsholder.getByIdGETcli

Get a rightsholder by ID

GET /trpc/rightsholder.getById?batch=1&input=…

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

rightsholder.listGETcli

List rightsholders for the organization

GET /trpc/rightsholder.list?batch=1&input=…

Input
organizationId*string (uuid)
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

rightsholder.trackOpenPOST

DEPRECATED: use activity.trackVisit

POST /trpc/rightsholder.trackOpen?batch=1

Input
organizationId*string (uuid)
id*string (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

rightsholder.updateByIdPOSTcli

Update a rightsholder by ID

POST /trpc/rightsholder.updateById?batch=1

Input
organizationId*string (uuid)
id*string (uuid)
name*string
legalNamestring
aliasesunknown
musoIdsunknown
spotifyIdsunknown
mlcPartyIdsunknown
ipiNameNumbersunknown
isnisunknown
isUmpgunknown
isDeletedboolean
lastUpdatedByIdunknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

sourceGoldData (3)

Gold-data overrides for sources (admin).

sourceGoldData.deletePOST

Delete a source_gold_data row

POST /trpc/sourceGoldData.delete?batch=1

sourceGoldData.listGET

List source_gold_data rows with optional search

GET /trpc/sourceGoldData.list?batch=1&input=…

Input
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")
searchstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

sourceGoldData.upsertPOST

Create or update a source_gold_data row

POST /trpc/sourceGoldData.upsert?batch=1

Input
rawValue*string
earningsSourceunknown
earningsCategoryunknown
parentCompanyunknown
collectionSourceunknown
publisherSourceunknown
notesunknown
idstring (uuid)

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload (10)

Upload royalty statements and execute upload actions.

upload.abortedPOST

Signal that a direct upload was aborted client-side

POST /trpc/upload.aborted?batch=1

Input
organizationId*string (uuid)
statementId*string
reasonstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.callbackPOST

Submit a mapping callback for statement processing

POST /trpc/upload.callback?batch=1

Input
organizationId*string (uuid)
type*string
mapping_id*string
file_idstring
upload_idstring
action"skip" | "bad"
distributor_idstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.heartbeatPOST

Extend the TTL on in-progress direct uploads

POST /trpc/upload.heartbeat?batch=1

Input
organizationId*string (uuid)
statementIds*string[]

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.listGETcli

List uploads for the organization

GET /trpc/upload.list?batch=1&input=…

Input
organizationId*string (uuid)
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")
filterobject
filter.searchstring
filter.date_startstring
filter.date_endstring

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.resolveAllFlagsPOST

Resolve all unresolved flags on a statement

POST /trpc/upload.resolveAllFlags?batch=1

Input
organizationId*string (uuid)
uploadId*string
statementId*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.resolveFlagPOST

Resolve a single flag on a statement

POST /trpc/upload.resolveFlag?batch=1

Input
organizationId*string (uuid)
uploadId*string
statementId*string
flagId*string

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.setFlagShownPOST

Set the shown/seen state of a flag on a statement

POST /trpc/upload.setFlagShown?batch=1

Input
organizationId*string (uuid)
uploadId*string
statementId*string
flagId*string
shown*boolean

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.statementsPOSTcli

List statements for an organization or upload

POST /trpc/upload.statements?batch=1

Input
organizationId*string (uuid)
rightsholderIdstring (uuid)
customNamestring
pagination*object(default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"})
pagination.page*integer(default 1)
pagination.limit*integer(default 200)
pagination.sortBystring
pagination.sortOrder*"asc" | "desc"(default "asc")
filterobject
filter.statementStatusstring[]
filter.statementIdsstring[]
filter.includeDeletedboolean(default false)
filter.fileNamestringCase-insensitive substring match on the uploaded file name

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.uploadActionPOSTcli

Execute an action on an upload or statement

POST /trpc/upload.uploadAction?batch=1

Input
organizationId*string (uuid)
action*unknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

upload.uploadFilePOSTcli

Generate a presigned URL and initiate a file upload

POST /trpc/upload.uploadFile?batch=1

user (5)

Authenticated user profile and data-access roles.

user.currentUserGETcli

Get the currently authenticated user

GET /trpc/user.currentUser?batch=1&input=…

user.preferencesGET

Get the current user\u2019s per-organisation preferences

GET /trpc/user.preferences?batch=1&input=…

user.setDataAccessRolePOST

Set the data access role for a user

POST /trpc/user.setDataAccessRole?batch=1

Input
organizationId*string (uuid)
userId*string (uuid)
role*unknown

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

user.setRightsholderSortPOST

Persist the home-page rightsholder sort selection

POST /trpc/user.setRightsholderSort?batch=1

Input
organizationId*string (uuid)
sort*"last_updated" | "alphabetical" | "recently_created"

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

user.updateOrganizationUserStatusPOST

Update a user

POST /trpc/user.updateOrganizationUserStatus?batch=1

Input
organizationId*string (uuid)
userId*string (uuid)
status*"active" | "disabled"

* required. Full Zod-derived JSON Schema for every endpoint is in /schemas.json.

Cube analytics

All royalty analytics flow through the Cube semantic layer. The cube.* procedures wrap two Cube APIs: the JSON /v1/load REST API and the SQL /v1/cubesql API. Both are org-scoped server-side via a self-signed JWT — clients cannot escape their tenant.

analytics_full_view is the single canonical view for earnings questions. Every measure, dimension, and time dimension you need — totals, top tracks, by-source and by-territory breakdowns, currency exposure, and trailing-twelve-month windows — lives on this one view. The schema is published below.

Three rules every Cube query must follow:
  • Always include a filter on analytics_full_view.rightsholder_id. The view is multi-tenant within an org and unfiltered queries are slow.
  • Always prefer analytics_full_view.royalty_earnings_usd for monetary measures. Only use royalty_earnings when the question is explicitly about original currency.
  • Never include organization_id in filters. Cube's queryRewrite injects it from the authenticated JWT and filters that target it are stripped server-side.

cube.query input shape

cube.query, cube.queryAsync, and cube.queryPoll all accept the same payload:

typescript
{ organizationId: string; // UUID, required query: { measures?: string[]; // e.g. ["analytics_full_view.royalty_earnings_usd"] dimensions?: string[]; // e.g. ["analytics_full_view.product_title"] timeDimensions?: Array<{ dimension: string; // e.g. "analytics_full_view.sales_period_start" granularity?: 'day' | 'week' | 'month' | 'quarter' | 'year'; dateRange?: string | [string, string]; // "Last 12 months" or ["2024-01-01", "2024-12-31"] }>; filters?: Array<{ member: string; operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'gt' | 'gte' | 'lt' | 'lte' | 'set' | 'notSet' | 'inDateRange' | 'notInDateRange' | 'beforeDate' | 'afterDate'; values?: string[]; }>; order?: Record<string, 'asc' | 'desc'>; limit?: number; // 1..50000 total?: boolean; // include total row count (default false) } }

The response contains a row per result tuple, keyed by fully-qualified member path:

json
{ "query": { /* echoed */ }, "lastRefreshTime": "2026-05-04T17:00:00Z", "annotation": { "measures": {...}, "dimensions": {...}, "timeDimensions": {...} }, "data": [ { "analytics_full_view.royalty_earnings_usd": "12345.67", "analytics_full_view.product_title": "Song Name" } ] }

SQL passthrough — cube.sqlQuery

For ad-hoc analysis, send raw SQL against the Cube SQL API. Org-scoping is still enforced via the JWT. Response is positional rows + a column schema:

json
{ "schema": [ { "name": "royalty_earnings_usd", "column_type": "Double" }, { "name": "product_title", "column_type": "String" } ], "rows": [ ["12345.67", "Song Name"] ] }

Async polling pattern

Cube queries can take longer than a Lambda invocation can wait. Use the queryAsync / queryPoll pair to avoid timeouts:

Discovering schema dynamically

Use cube.meta at runtime to fetch the live cube definitions, or consume the typed views from @chapter-two-music/cube. The authoritative schema for analytics_full_view is below.

analytics_full_view

The canonical view for royalty analytics. Every measure and dimension you need to answer earnings questions — totals, breakdowns by source / sales type / territory / track / artist / distributor, currency exposure, and trailing-twelve-month windows — lives here. Always filter by `analytics_full_view.rightsholder_id` and prefer `analytics_full_view.royalty_earnings_usd` for monetary measures.

Measures

FieldTypeDescription
analytics_full_view.statement_countnumberNumber of distinct statements
analytics_full_view.statement_row_countnumberNumber of statement records
analytics_full_view.royalty_earningsnumberNet royalty earnings in the statement
analytics_full_view.gross_royalty_earningsnumberGross royalty earnings in the statement
analytics_full_view.unitsnumberNumber of sales, streams or other quantity units reported on the royalty row level. Extracted from statement file.
analytics_full_view.royalty_earnings_usdnumberNet royalty earnings converted to USD at the FX rate as of the date the rightsholder
analytics_full_view.gross_royalty_earnings_usdnumberGross royalty earnings converted to USD at the FX rate as of the date the rightsholder
analytics_full_view.product_dollar_agenumberEarnings-weighted average product age. Each product\
analytics_full_view.fetching_percentagenumberPercentage of royalty earnings (in USD) attributed to tracks for which resolution is complete (but not necessarily successful).
analytics_full_view.product_age_avgnumberAverage product age in years, excluding missing release dates. Returns 0 when no products have known ages.
analytics_full_view.product_age_percentagenumberPercentage of distinct products for which the product age is defined.
analytics_full_view.product_dollar_age_percentagenumberPercentage of LTM earnings that are part of the dollar age calculation, i.e. earnings attributed to a product with a known age. This calculation only includes products with positive earnings.

Dimensions

FieldTypeDescription
analytics_full_view.stmt_yearnumberCalendar year the statement is associated with. Extracted from statement file, derived by Baltazar or provided by user
analytics_full_view.stmt_periodnumberPeriod number within the year (1-12 for monthly, 1-4 for quarterly, 1-2 for half-yearly, 1 for yearly). Extracted from statement file, derived by Baltazar or user-provided.
analytics_full_view.stmt_cadencestringReporting frequency (M = monthly, Q = quarterly, H = half-yearly, Y = yearly). Extracted from statement file, derived by Baltazar or user-provided.
analytics_full_view.currencystringEarnings Currency. ISO 3-letter code. Extracted from statement file, derived by Baltazar or user-provided.
analytics_full_view.territory_rawstringTerritory string. Extracted from statement file.
analytics_full_view.primary_rights_typestringHigh-level rights category. Publishing, Master or Neighboring. Derived by Baltazar
analytics_full_view.secondary_rights_typestringAdditional detail on the rights type for the royalties, mapped to a set of standard values
analytics_full_view.rightsholder_idstringGenerated identifier for the rightsholder receiving the royalties (e.g. artist, label, publisher)
analytics_full_view.custom_namestringFile level label set during ingestion. User-provided.
analytics_full_view.file_namestringName of the uploaded file. Extracted from statement file.
analytics_full_view.account_numberstringAccount identifier. Extracted from statement file.
analytics_full_view.account_namestringAccount identifier. Extracted from statement file.
analytics_full_view.legal_vendorstringName of the legal entity or internal business unit that collected the earnings and issued the statement
analytics_full_view.payorstringThe paying entity associated with the royalty row
analytics_full_view.track_title_rawstringTrack/Work string. Extracted from statement file.
analytics_full_view.track_artist_rawstringArtist string. Extracted from statement file.
analytics_full_view.track_titlestringTrack title resolved via third-party enrichment, prior to product clustering. Derived by Baltazar.
analytics_full_view.track_artiststringArtist name resolved via third-party enrichment, prior to product clustering. Derived by Baltazar.
analytics_full_view.release_title_rawstringRelease-level title string. Extracted from statement file.
analytics_full_view.release_artist_rawstringRelease-level artist string. Extracted from statement file.
analytics_full_view.isrcstringStandard ISRC - unique identifier for a sound recording. Extracted from statement file.
analytics_full_view.iswcstringStandard ISWC - unique identifier for a musical composition. Extracted from statement file.
analytics_full_view.upcstringStandard UPC - unique identifier for a release (album or single). Extracted from statement file.
analytics_full_view.other_identifierstringAdditional product or track identifier supplied on the statement (outside ISRC, ISWC or UPC). Extracted from statement file.
analytics_full_view.composers_rawstringComposer name list from the statement
analytics_full_view.other_adjustmentsstringOther earnings adjustments reported in the statement
analytics_full_view.royalty_ratenumberPer-unit or percentage rate paid on the royalty line. Extracted from statement file.
analytics_full_view.tax_deductionnumberPercentage deducted from earnings for taxes
analytics_full_view.territory_deductionnumberTerritory-specific percentage deducted from earnings
analytics_full_view.stmt_period_fullstringCombined period identifier (e.g. 2023M12, 2024Q03). Derived by Baltazar.
analytics_full_view.stmt_period_monthstringStatement period harmonized into its latest calendar month (e.g. M2 → M2, Q3 → M9, H1 → M6, Y1 → M12). Extracted from statement file, derived by Baltazar or user-provided.
analytics_full_view.sales_type_rawstringSales type as it appears in the statement
analytics_full_view.sales_subtype_rawstringAdditional detail on sales type as it appears in the statement
analytics_full_view.source_rawstringRoyalty source as it appears in the statement
analytics_full_view.subsource_rawstringAdditional detail on royalty source as it appears in the statement
analytics_full_view.full_sales_type_rawstringSales type string. Extracted from statement file.
analytics_full_view.full_source_rawstringSource string. Extracted from statement file.
analytics_full_view.release_titlestringRelease-level title resolved via third-party enrichment, prior to product clustering. Derived by Baltazar.
analytics_full_view.release_artiststringRelease-level artist resolved via third-party enrichment, prior to product clustering. Derived by Baltazar.
analytics_full_view.product_agenumberAge - enriched and clustered (measured from product release date to the end of the most recent available statement period). Derived by Baltazar.
analytics_full_view.product_age_bucketstringProduct age rounded down to an integer. Products whose age is 8 years or greater are assigned the value
analytics_full_view.product_titlestringTitle - enriched and clustered. Derived by Baltazar.
analytics_full_view.product_artiststringArtist - enriched and clustered. Derived by Baltazar.
analytics_full_view.primary_sales_typestringTop-level sales type (e.g. Streaming) - normalized and categorized. Derived by Baltazar.
analytics_full_view.secondary_sales_typestringSub-level sales type (e.g. Interactive Streaming) - normalized and categorized. Derived by Baltazar.
analytics_full_view.sourcestringNormalized source name derived from (in order of priority) the collection source, publisher source, or earnings source.
analytics_full_view.earnings_collection_sourcestringCollection source (e.g. MLC) - normalized and categorized. Derived by Baltazar.
analytics_full_view.earnings_sourcestringEarnings source (e.g. Spotify) - normalized and categorized. Derived by Baltazar.
analytics_full_view.earnings_categorystringCategory classification of the earnings source (e.g., streaming, download, broadcast). Null if earnings source is unknown.
analytics_full_view.earnings_parent_companystringCorporate owner of the earnings source. Only populated when ownership is explicitly verified. For example, YouTube → Google, Instagram/Facebook → Meta, TikTok → ByteDance, etc.
analytics_full_view.publisher_sourcestringCanonical English name of the music publisher or publishing administrator when not acting as a collection source. Includes major publishers (w.g. Warner Chappell, Sony Music Publishing, Universal Music Publishing, BMG), independents (e.g. Kobalt, Downtown, Concord, peermusic), and administrators (e.g. Songtrust, CD Baby Pro, TuneCore Publishing).
analytics_full_view.territorystringEarnings territory (e.g. US) - normalized and categorized. Derived by Baltazar.
analytics_full_view.rightsholder_namestringRightsholder or project name. User-provided.
analytics_full_view.distributor_namestringThe overarching statement format owner. Derived by Baltazar.
analytics_full_view.tmstringTrailing 12-month earnings window per distributor, then combined. 00.TM = current, 01.LTM = last twelve months, 02.PTM = previous twelve months, etc. Derived by Baltazar.
analytics_full_view.fx_ratenumberForeign exchange rate used to convert the statement's original currency to USD. Derived by Baltazar.

Time dimensions

Agent integration guide

How to wire ChapterTwo into an autonomous agent (Claude, Cursor, custom GPT, etc.) so it can answer royalty questions, run analytics, and trigger workflows safely. The same patterns power the in-product Data Agent that ships with ChapterTwo (Claude Haiku 4.5 with Gemini fallback) — see packages/agents/DATA-AGENT.md for the full internal architecture.

Earnings recipe

Almost every analytics question reduces to the same five-step recipe. Follow it in order; do not skip steps.

typescript
import { createClient } from '@chapter-two-music/sdk'; const client = createClient({ apiUrl: 'https://api.prod.chaptertwo.com', token: process.env.C2_TOKEN!, orgId: process.env.C2_ORG_ID!, }); // 1. Boot — confirm principal + org const me = await client.query('user.currentUser'); // 2. Resolve rightsholder const { data: rightsholders } = await client.query('rightsholder.list', { pagination: { page: 1, limit: 50 }, }); const rh = rightsholders[0]; // or prompt the user to pick // 3 + 4 + 5. Top 10 tracks by USD earnings, last 12 months const earnings = await client.mutate('cube.query', { query: { measures: ['analytics_full_view.royalty_earnings_usd'], dimensions: ['analytics_full_view.product_title', 'analytics_full_view.product_artist'], filters: [ { member: 'analytics_full_view.rightsholder_id', operator: 'equals', values: [rh.id] }, ], timeDimensions: [{ dimension: 'analytics_full_view.sales_period_start', dateRange: 'Last 12 months', }], order: { 'analytics_full_view.royalty_earnings_usd': 'desc' }, limit: 10, }, });

Endpoint discovery

The SDK ships with a static catalogue of every procedure plus its Zod-derived JSON schema. Use it to build dynamic tool manifests for your agent without burning tokens on this page:

typescript
import { ENDPOINTS, // string[] of all procedure names listEndpoints, // (namespace) => string[] isValidEndpoint, getEndpointMeta, // returns { type, inputSchema, description? } } from '@chapter-two-music/sdk'; // Build agent tool definitions from the catalogue const tools = ENDPOINTS.map((endpoint) => { const meta = getEndpointMeta(endpoint); return { name: endpoint.replace('.', '_'), description: meta?.description ?? endpoint, parameters: meta?.inputSchema, }; });

Cube polling loop

typescript
async function runCube(query: CubeQuery, maxAttempts = 30) { let res = await client.mutate('cube.queryAsync', { query }); for (let i = 0; res.status === 'processing' && i < maxAttempts; i++) { await new Promise((r) => setTimeout(r, 2_000)); res = await client.mutate('cube.queryPoll', { query }); } if (res.status !== 'completed') throw new Error('Cube query timed out'); return res.data; }

Common query patterns

Substitute the actual rightsholder UUID(s). Strip the analytics_full_view. prefix from response keys before presenting data to the user.

Questionmeasures + dimensions
Total earnings (USD)m: royalty_earnings_usd
By calendar yearm: royalty_earnings_usd, d: stmt_year
Top platformsm: royalty_earnings_usd, d: earnings_source
By territorym: royalty_earnings_usd, d: territory
Top tracksm: royalty_earnings_usd, d: product_title
Streaming vs downloadm: royalty_earnings_usd, d: primary_sales_type
Master vs publishingm: royalty_earnings_usd, d: primary_rights_type
TM windows (LTM, PTM)m: royalty_earnings_usd, d: tm
Catalogue maturitym: product_dollar_age

Best practices

MCP server

For Claude Desktop, Cursor, and other MCP-aware clients, point the client at https://mcp.chaptertwo.com/api/mcp. Auth is auto-negotiated via OAuth + PKCE. The MCP server exposes a curated subset of tools and a generic c2_api_call escape hatch.

json
{ "mcpServers": { "chaptertwo": { "url": "https://mcp.chaptertwo.com/api/mcp" } } }
ToolDescription
c2_healthCheck API health
c2_whoamiCurrent user + organization
c2_get_permissionsPermissions and capabilities for the principal
c2_list_endpointsCatalogue of all procedures, optionally filtered by namespace
c2_search_endpointsKeyword search across procedures
c2_describe_endpointSchema + metadata for one procedure
c2_api_callCall any procedure (auto-detects query vs mutation)
c2_query_analyticsWrapper around cube.query with sane defaults
c2_cube_metaLive Cube schema (measures, dimensions, descriptions)
The MCP server has a 55 s timeout per request. Long-running Cube queries should always go through c2_query_analytics which uses the async polling loop.