# ChapterTwo API — Reference for AI Agents > ChapterTwo is a music royalty management platform. This file is the canonical, > single-page reference for the public tRPC API. **Designed agent-first**: a > 5-line TL;DR comes first, then the operational guidance, then the schema, then > the per-procedure dump. An agent that reads only the first ~2K tokens has > enough context to make correct calls. - Procedures: 142 (67 GET / 75 POST) - Namespaces: 25 - API base: `https://api.prod.chaptertwo.com` - MCP server: `https://mcp.chaptertwo.com/api/mcp` (preferred for MCP-aware clients) **Linked machine-readable schemas** (no auth, fetch as JSON for "just-in-time" loading): - `https://docs.chaptertwo.com/schemas.json` — full Zod-derived JSON Schema for every procedure, keyed by name. Fetch when you need strict input validation for one procedure. - `https://docs.chaptertwo.com/cube-schema.json` — full `analytics_full_view` schema. Same data as `cube.meta` but unauthenticated, so an agent can discover the schema before its first authenticated call. ## Agent TL;DR — minimum to make a correct earnings call 1. Authenticate. Bearer token in `Authorization`. Org UUID in EVERY request body as `organizationId`. The header alone is not enough for OAuth. 2. Resolve a rightsholder: `GET /trpc/rightsholder.list?batch=1&input={"0":{"organizationId":"…"}}`. Cache the rightsholder UUID — every Cube query MUST filter by it. 3. Call `POST /trpc/cube.queryAsync` with body `{"organizationId":"…","query":{"measures":["analytics_full_view.royalty_earnings_usd"],"dimensions":["analytics_full_view.product_title"],"filters":[{"member":"analytics_full_view.rightsholder_id","operator":"equals","values":[""]}],"order":{"analytics_full_view.royalty_earnings_usd":"desc"},"limit":10}}`. 4. If the response is `{"status":"processing"}`, poll `POST /trpc/cube.queryPoll` with the SAME body every 2-3 seconds (Cube is idempotent). When status flips to `"completed"`, read rows from `data.data` (note the double nesting on async; sync `cube.query` returns rows at `data` directly). 5. Strip the `analytics_full_view.` prefix from response keys when presenting to humans. **Three rules that, if violated, will break your Cube query:** 1. ALWAYS filter by `analytics_full_view.rightsholder_id`. 2. ALWAYS use `analytics_full_view.royalty_earnings_usd` for monetary measures (default to USD). 3. NEVER include `organization_id` in filters — it is injected from the JWT. If you only need a few calls, jump to section 5 (Agent guide). If you're building a full integration, read in order. For dynamic tool definitions, fetch `/schemas.json` and don't inline procedure schemas in your agent's system prompt. ## Table of contents 1. Quick start (CLI / SDK / curl) 2. Authentication 3. Transport (tRPC over HTTP) 4. For sandbox / embedded agents (Python copy-paste) 5. Agent integration guide — recipe + worked examples + anti-patterns 6. Cube analytics rules + async polling 7. Cube schema — `analytics_full_view` 8. Procedure reference (compact tables) ## 1. Quick start ```bash npm install -g @chapter-two-music/cli c2 auth login # OAuth in browser c2 whoami # confirm user + org c2 rightsholder.list # first call # 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" ``` ```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!, }); const result = await client.query('rightsholder.list', { pagination: { page: 1, limit: 25 }, }); ``` ## 2. Authentication Two credentials are accepted: 1. **OAuth 2.0 with PKCE** — for users. Tokens issued by mcp.chaptertwo.com. - Discovery: https://mcp.chaptertwo.com/.well-known/oauth-authorization-server - Authorize: https://mcp.chaptertwo.com/api/oauth/authorize - Token: https://mcp.chaptertwo.com/api/oauth/token - Access token lifetime: 1 hour - Refresh token lifetime: 30 days, single-use (rotates on every refresh) - Send as `Authorization: Bearer ` 2. **API keys** — for service-to-service. Request from support@chaptertwo.com. Send as `x-api-key: ` and ALSO include `x-organization-id`. ```bash # Refresh token grant 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" ``` ## 3. Transport The API speaks tRPC over HTTP. Procedures live at `/trpc/.`. - **Batch mode (recommended)**: `?batch=1`. Input keyed as `{"0": {...}}`, response is `[{"result":{"data": ...}}]`. - **Single mode**: no `batch` param. Input is sent raw, response is `{"result":{"data": ...}}`. Note: only `batch=1` enables batch mode — `batch=true` and `batch=0` are treated as single. - Queries (GET): URL-encoded JSON in the `input` query parameter. - Mutations (POST): JSON in the request body. ```bash # Query (GET) 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%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 Every procedure except `user.currentUser` and `exchangeRates.get` requires `organizationId` (UUID) inside the input body. Sending only the `x-organization-id` header is NOT sufficient for OAuth-authenticated requests. ### Pagination Standard envelope: ```typescript // Input { pagination: { page: number, limit: number, sortBy?: string, sortOrder?: 'asc' | 'desc' } } // Output { data: T[], pagination: { page: number, limit: number, total: number, totalPages: number, hasNext: boolean, hasPrev: boolean, } } ``` Default limit when omitted: 300. Default when only `page` is set: 200. Maximum: 2000. > **Agent tip**: ask for a small `limit` (10-50) and only paginate if you actually need more > rows. Pulling the full 300 default into your context wastes tokens — context is your scarcest > resource. ### Errors ```json [{ "error": { "message": "Rightsholder not found", "code": -32004, "data": { "code": "NOT_FOUND", "errorCode": "RIGHTSHOLDER_NOT_FOUND", "httpStatus": 404, "path": "rightsholder.getById" } } }] ``` Branch on `data.code` (programmatic) and `data.errorCode` (specific). Don't string-match the human-readable `message`. The "How to recover" column is the action your agent should take before surfacing the error to the user. | data.code | HTTP | What it means | How to recover | |-----------|------|---------------|----------------| | UNAUTHORIZED | 401 | Missing or invalid token | Refresh the OAuth access token, or re-issue. Then retry once. | | FORBIDDEN | 403 | Authenticated but lacks the role | Surface to the user; ask whoever owns the org to grant the role. Do not retry. | | NOT_FOUND | 404 | Resource doesn't exist or isn't visible to this org | Re-resolve the ID via the relevant `*.list`. Don't retry the same ID. | | BAD_REQUEST | 400 | Input failed Zod validation | Fetch `/schemas.json` for this procedure, fix the field, retry once. Do not retry blindly. | | CONFLICT | 409 | Unique-constraint violation | Surface to the user (the name / id is taken). Don't auto-rename. | | TIMEOUT | 408 | Cube long-poll exceeded the 25 s window | Switch to `cube.queryAsync` + `cube.queryPoll`. Narrow the date range or add filters. | | INTERNAL_SERVER_ERROR | 500 | Unexpected | Safe to retry once with backoff. If it fails twice, surface to the user. | ## 4. For sandbox / embedded agents If you're running inside a sandboxed environment with pre-provisioned credentials (e.g. ChapterTwo Studio, an MCP host, or a CI agent), the token is usually written to a file path and the org ID lives in an environment variable. Drop in this Python helper and you have working analytics in 30 lines. ```python import os, requests, json, urllib.parse, time # Adjust the credential lookups to match your sandbox conventions: TOKEN = open(".sandbox/token").read().strip() ORG_ID = os.environ["C2_ORGANIZATION_ID"] BASE = "https://api.prod.chaptertwo.com" HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"} def trpc_query(procedure: str, input_data: dict) -> dict: """Call a tRPC query (GET) — auto-injects organizationId.""" input_data["organizationId"] = ORG_ID encoded = urllib.parse.quote(json.dumps(input_data)) r = requests.get(f"{BASE}/trpc/{procedure}?input={encoded}", headers=HEADERS) r.raise_for_status() return r.json()["result"]["data"] def trpc_mutate(procedure: str, input_data: dict) -> dict: """Call a tRPC mutation (POST) — auto-injects organizationId.""" input_data["organizationId"] = ORG_ID r = requests.post(f"{BASE}/trpc/{procedure}", headers=HEADERS, json=input_data) r.raise_for_status() return r.json()["result"]["data"] def cube_query(query_params: dict, max_polls: int = 15) -> list[dict] | None: """Run a Cube analytics query with async polling. Returns the row array (already unwrapped from the {status, data: {data: [...]}} envelope).""" payload = {"organizationId": ORG_ID, "query": query_params} trpc_mutate("cube.queryAsync", payload) for _ in range(max_polls): time.sleep(3) r = requests.post(f"{BASE}/trpc/cube.queryPoll", headers=HEADERS, json=payload) r.raise_for_status() data = r.json().get("result", {}).get("data", {}) if data.get("status") == "completed": return data["data"]["data"] return None # --- Example: top 5 tracks by USD earnings, last 12 months --- rightsholders = trpc_query("rightsholder.list", {})["data"] rh_id = rightsholders[0]["id"] top_tracks = cube_query({ "measures": ["analytics_full_view.royalty_earnings_usd"], "dimensions": ["analytics_full_view.product_title"], "filters": [ {"member": "analytics_full_view.rightsholder_id", "operator": "equals", "values": [rh_id]}, ], "order": {"analytics_full_view.royalty_earnings_usd": "desc"}, "limit": 5, }) ``` ## 5. Agent integration guide The same patterns power the in-product Data Agent (Claude Haiku 4.5 with Gemini fallback) — see `packages/agents/DATA-AGENT.md` in the source repo for the full internal architecture. ### MCP-first integration If your agent supports MCP (Claude Desktop, Cursor, Anthropic SDK with the `mcp` connector), **prefer the MCP server over hand-rolled tool definitions**. The server's tools are tuned for agent ergonomics — `c2_query_analytics` already implements the async polling loop, so your agent calls it once and gets rows back. No Cube envelope to unwrap. ```json { "mcpServers": { "chaptertwo": { "url": "https://mcp.chaptertwo.com/api/mcp" } } } ``` | Tool | Description | |------|-------------| | c2_health | API health | | c2_whoami | Current user + org | | c2_get_permissions | Permissions for the principal | | c2_list_endpoints | Catalogue of procedures | | c2_search_endpoints | Keyword search | | c2_describe_endpoint | Schema for one procedure | | c2_api_call | Call any procedure (auto query vs mutation) | | c2_query_analytics | **Cube wrapper with async polling — preferred for analytics** | | c2_cube_meta | Live Cube schema | The MCP server has a 55-second per-request timeout. Long Cube queries always go through `c2_query_analytics` so they don't hit it. ### Earnings recipe — five steps every analytics question follows 1. **Verify auth.** `user.currentUser`. If multi-org, ask which org and cache the UUID. 2. **Resolve rightsholder(s).** `rightsholder.list`. Every Cube query MUST filter by `analytics_full_view.rightsholder_id` — never run an unscoped earnings query. 3. **Discover the schema.** When unsure of a field, call `cube.meta` (or fetch `/cube-schema.json` unauthenticated) first. Do not guess field names. 4. **Build the query.** Default measure is `analytics_full_view.royalty_earnings_usd`. Always set `limit` (10 for top-N, 20 for breakdowns) and `order`. 5. **Execute.** Use `cube.query` for sub-30s queries; the `queryAsync` / `queryPoll` pair for anything larger or unknown. ```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!, }); const me = await client.query('user.currentUser'); // 1 const { data: rightsholders } = await client.query('rightsholder.list', { // 2 pagination: { page: 1, limit: 50 }, }); const rh = rightsholders[0]; const earnings = await client.mutate('cube.query', { // 3 + 4 + 5 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, }, }); ``` ### Worked examples — question to tool-call sequence These show the full chain so an agent can pattern-match against an incoming user question. #### Example 1 — "What did The Doe Family earn last year, broken down by source?" ``` 1. trpc_query("user.currentUser", {}) -> { id, organizations: [{ id: ORG, ... }] } (1 call) 2. trpc_query("rightsholder.list", { pagination: { limit: 50 } }) -> filter result on name === "The Doe Family"; cache rh_id (1 call) 3. cube_query({ (1-N calls) measures: ["analytics_full_view.royalty_earnings_usd"], dimensions: ["analytics_full_view.earnings_source"], filters: [ { member: "analytics_full_view.rightsholder_id", operator: "equals", values: [rh_id] }, { member: "analytics_full_view.stmt_year", operator: "equals", values: ["2024"] }, ], order: { "analytics_full_view.royalty_earnings_usd": "desc" }, limit: 20, }) -> [{ "earnings_source": "Spotify", "royalty_earnings_usd": "45000.00" }, ...] 4. Format response. Strip the analytics_full_view. prefix. Mention top 3 sources by USD, include the unaccounted "Other" tail if rows = 20 with hasNext. ``` #### Example 2 — "Show me the monthly trend for streaming revenue this year" ``` 1. (rightsholder already cached from previous turn) 2. cube_query({ (1-N calls) measures: ["analytics_full_view.royalty_earnings_usd"], dimensions: ["analytics_full_view.stmt_period_full"], filters: [ { member: "analytics_full_view.rightsholder_id", operator: "equals", values: [rh_id] }, { member: "analytics_full_view.stmt_year", operator: "equals", values: ["2024"] }, { member: "analytics_full_view.primary_sales_type", operator: "equals", values: ["Streaming"] }, ], order: { "analytics_full_view.stmt_period_full": "asc" }, // ASC for time series limit: 24, }) -> [{ "stmt_period_full": "2024M01", "royalty_earnings_usd": "...", ... }] 3. Render as a line chart. Note that stmt_period_full is sortable as a string (e.g. "2024M01" < "2024M02" < "2024Q01" < "2024Q02"). ``` ### Common query patterns | Question | measures + dimensions | |----------|----------------------| | Total earnings (USD) | m: royalty_earnings_usd | | By calendar year | m: royalty_earnings_usd, d: stmt_year | | Top platforms | m: royalty_earnings_usd, d: earnings_source | | By territory | m: royalty_earnings_usd, d: territory | | Top tracks | m: royalty_earnings_usd, d: product_title, product_artist | | Top artists | m: royalty_earnings_usd, d: product_artist | | Streaming vs download | m: royalty_earnings_usd, d: primary_sales_type | | Sales-type detail | m: royalty_earnings_usd, d: primary_sales_type, secondary_sales_type | | Master vs publishing | m: royalty_earnings_usd, d: primary_rights_type | | Monthly time series | m: royalty_earnings_usd, d: stmt_period_full (asc) | | TM windows (LTM, PTM) | m: royalty_earnings_usd, d: tm | | By distributor | m: royalty_earnings_usd, d: distributor_name | | By currency | m: royalty_earnings_usd, d: currency | | Catalogue maturity | m: product_dollar_age | All field names must be prefixed with `analytics_full_view.`. Strip the prefix from response keys before presenting data. Use `product_title` / `product_artist` (clustered + enriched), not the pre-clustering `track_title` / `track_artist`. ### Anti-patterns — DO NOT These are the failure modes most likely to cause silent wrong answers or wasted context. Listed in roughly decreasing order of damage. - **DO NOT** run a Cube query without an `analytics_full_view.rightsholder_id` filter. The query will be slow or rejected, and an unfiltered total spans every catalog in the org. - **DO NOT** include `organization_id` in Cube filters. The server strips it, but you'll think you scoped a query when you didn't. - **DO NOT** use `royalty_earnings` (original currency) when the user asked for "earnings", "revenue", or "how much". They mean USD. - **DO NOT** use `*_raw` dimensions (`source_raw`, `territory_raw`, `full_sales_type_raw`) unless the user explicitly says "raw" or "as it appears in the statement". Raw fields are unmapped, dirty strings. - **DO NOT** treat a top-N breakdown sum as a global total. If `limit: 20` returned 20 rows, the sum is "top 20" — run a second query without dimensions for the true total. - **DO NOT** loop `rightsholder.getById` in a list. Use `rightsholder.list` once. - **DO NOT** ask for `limit: 2000` "to be safe". You will pay for it in tokens. Ask for what you need (10-50) and follow `pagination.hasNext` if needed. - **DO NOT** inline `/schemas.json` into your agent's system prompt. Fetch it on demand for the procedures you actually call, then discard. - **DO NOT** call `rightsholder.deleteById`, `organization.delete`, or other destructive mutations without the user's explicit go-ahead. There is no soft-delete. - **DO NOT** string-match the `error.message`. Branch on `error.data.code` and `error.data.errorCode` instead. ### Best practices (positive form) - Always pass `organizationId` in the body. Header alone is not enough for OAuth. Cache it once. - Default to USD: use `analytics_full_view.royalty_earnings_usd` unless the user explicitly asks for original currency. - Prefer normalized dimensions over raw. Bound every query (`dateRange` on `sales_period_start` or filter by `stmt_year`). - Always set `limit` + `order`. 10 for top-N, 20 for breakdowns, 24 for monthly series. - Refresh OAuth access tokens proactively (~5 min before expiry), not after a 401. - Cache this file (or the parts you need) for the session — re-fetching every turn wastes tokens. Only refresh on a new session or when the agent encounters an unexpected schema. ### Endpoint discovery (just-in-time) Don't inline all 134 procedure schemas into your agent's system prompt. Instead, fetch on demand: discover the procedure name from this file's section 8, then `fetch /schemas.json` and read `schemas[procedureName]` for the full Zod-derived JSON Schema. ```typescript import { ENDPOINTS, getEndpointMeta } from '@chapter-two-music/sdk'; // SDK route: ships catalogue + JSON Schema const tools = ENDPOINTS.map((endpoint) => { const meta = getEndpointMeta(endpoint); return { name: endpoint.replace('.', '_'), description: meta?.description ?? endpoint, parameters: meta?.inputSchema, }; }); // HTTP route: same data, no SDK const schemas = await fetch('https://docs.chaptertwo.com/schemas.json').then((r) => r.json()); const meta = schemas['rightsholder.list']; // { type, method, path, summary?, scope?, inputSchema } ``` ## 6. Cube analytics rules + async polling All royalty analytics flow through the Cube semantic layer. `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 lives there. Inline schema is in section 7; the full machine-readable JSON is at `https://docs.chaptertwo.com/cube-schema.json`. **Three rules every Cube query MUST follow:** 1. Always include a filter on `analytics_full_view.rightsholder_id`. 2. Default monetary measure is `analytics_full_view.royalty_earnings_usd`. 3. NEVER include `organization_id` in filters — it is injected from the JWT. ### cube.query input ```typescript { organizationId: string; 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]; }>; filters?: Array<{ member: string; operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'gt' | 'gte' | 'lt' | 'lte' | 'set' | 'notSet' | 'inDateRange' | 'notInDateRange' | 'beforeDate' | 'afterDate'; values?: string[]; }>; order?: Record; limit?: number; // 1..50000 total?: boolean; } } ``` ### cube.query response ```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" } ] } ``` ### Async polling pattern Long Cube queries can outlive a Lambda invocation. Use the async pair: 1. `cube.queryAsync` (or `cube.sqlQueryAsync`) submits the query. 2. The response is either `{ status: "completed", data }` or `{ status: "processing" }`. 3. While processing, re-send the SAME query body to `cube.queryPoll` (Cube is idempotent). ```typescript async function runCube(query, maxAttempts = 30) { // queryAsync / queryPoll return { status, data? } — the inner `data` is // the same shape as cube.query (raw Cube payload with .data, .annotation, etc.). 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; // CubeQueryResult — rows live at res.data.data } ``` ### cube.sqlQuery response ```json { "schema": [{ "name": "...", "column_type": "Double" }], "rows": [["..."]] } ``` ## 7. Cube schema — analytics_full_view The same data as `cube.meta` for the documented view. For programmatic consumption fetch `https://docs.chaptertwo.com/cube-schema.json` (no auth required). ### 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** | Field | Type | Description | |-------|------|-------------| | analytics_full_view.statement_count | number | Number of distinct statements | | analytics_full_view.statement_row_count | number | Number of statement records | | analytics_full_view.royalty_earnings | number | Net royalty earnings in the statement | | analytics_full_view.gross_royalty_earnings | number | Gross royalty earnings in the statement | | analytics_full_view.units | number | Number of sales, streams or other quantity units reported on the royalty row level. Extracted from statement file. | | analytics_full_view.royalty_earnings_usd | number | Net royalty earnings converted to USD at the FX rate as of the date the rightsholder | | analytics_full_view.gross_royalty_earnings_usd | number | Gross royalty earnings converted to USD at the FX rate as of the date the rightsholder | | analytics_full_view.product_dollar_age | number | Earnings-weighted average product age. Each product\ | | analytics_full_view.fetching_percentage | number | Percentage of royalty earnings (in USD) attributed to tracks for which resolution is complete (but not necessarily successful). | | analytics_full_view.product_age_avg | number | Average product age in years, excluding missing release dates. Returns 0 when no products have known ages. | | analytics_full_view.product_age_percentage | number | Percentage of distinct products for which the product age is defined. | | analytics_full_view.product_dollar_age_percentage | number | Percentage 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** | Field | Type | Description | |-------|------|-------------| | analytics_full_view.stmt_year | number | Calendar year the statement is associated with. Extracted from statement file, derived by Baltazar or provided by user | | analytics_full_view.stmt_period | number | Period 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_cadence | string | Reporting frequency (M = monthly, Q = quarterly, H = half-yearly, Y = yearly). Extracted from statement file, derived by Baltazar or user-provided. | | analytics_full_view.currency | string | Earnings Currency. ISO 3-letter code. Extracted from statement file, derived by Baltazar or user-provided. | | analytics_full_view.territory_raw | string | Territory string. Extracted from statement file. | | analytics_full_view.primary_rights_type | string | High-level rights category. Publishing, Master or Neighboring. Derived by Baltazar | | analytics_full_view.secondary_rights_type | string | Additional detail on the rights type for the royalties, mapped to a set of standard values | | analytics_full_view.rightsholder_id | string | Generated identifier for the rightsholder receiving the royalties (e.g. artist, label, publisher) | | analytics_full_view.custom_name | string | File level label set during ingestion. User-provided. | | analytics_full_view.file_name | string | Name of the uploaded file. Extracted from statement file. | | analytics_full_view.account_number | string | Account identifier. Extracted from statement file. | | analytics_full_view.account_name | string | Account identifier. Extracted from statement file. | | analytics_full_view.legal_vendor | string | Name of the legal entity or internal business unit that collected the earnings and issued the statement | | analytics_full_view.payor | string | The paying entity associated with the royalty row | | analytics_full_view.track_title_raw | string | Track/Work string. Extracted from statement file. | | analytics_full_view.track_artist_raw | string | Artist string. Extracted from statement file. | | analytics_full_view.track_title | string | Track title resolved via third-party enrichment, prior to product clustering. Derived by Baltazar. | | analytics_full_view.track_artist | string | Artist name resolved via third-party enrichment, prior to product clustering. Derived by Baltazar. | | analytics_full_view.release_title_raw | string | Release-level title string. Extracted from statement file. | | analytics_full_view.release_artist_raw | string | Release-level artist string. Extracted from statement file. | | analytics_full_view.isrc | string | Standard ISRC - unique identifier for a sound recording. Extracted from statement file. | | analytics_full_view.iswc | string | Standard ISWC - unique identifier for a musical composition. Extracted from statement file. | | analytics_full_view.upc | string | Standard UPC - unique identifier for a release (album or single). Extracted from statement file. | | analytics_full_view.other_identifier | string | Additional product or track identifier supplied on the statement (outside ISRC, ISWC or UPC). Extracted from statement file. | | analytics_full_view.composers_raw | string | Composer name list from the statement | | analytics_full_view.other_adjustments | string | Other earnings adjustments reported in the statement | | analytics_full_view.royalty_rate | number | Per-unit or percentage rate paid on the royalty line. Extracted from statement file. | | analytics_full_view.tax_deduction | number | Percentage deducted from earnings for taxes | | analytics_full_view.territory_deduction | number | Territory-specific percentage deducted from earnings | | analytics_full_view.stmt_period_full | string | Combined period identifier (e.g. 2023M12, 2024Q03). Derived by Baltazar. | | analytics_full_view.stmt_period_month | string | Statement 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_raw | string | Sales type as it appears in the statement | | analytics_full_view.sales_subtype_raw | string | Additional detail on sales type as it appears in the statement | | analytics_full_view.source_raw | string | Royalty source as it appears in the statement | | analytics_full_view.subsource_raw | string | Additional detail on royalty source as it appears in the statement | | analytics_full_view.full_sales_type_raw | string | Sales type string. Extracted from statement file. | | analytics_full_view.full_source_raw | string | Source string. Extracted from statement file. | | analytics_full_view.release_title | string | Release-level title resolved via third-party enrichment, prior to product clustering. Derived by Baltazar. | | analytics_full_view.release_artist | string | Release-level artist resolved via third-party enrichment, prior to product clustering. Derived by Baltazar. | | analytics_full_view.product_age | number | Age - 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_bucket | string | Product age rounded down to an integer. Products whose age is 8 years or greater are assigned the value | | analytics_full_view.product_title | string | Title - enriched and clustered. Derived by Baltazar. | | analytics_full_view.product_artist | string | Artist - enriched and clustered. Derived by Baltazar. | | analytics_full_view.primary_sales_type | string | Top-level sales type (e.g. Streaming) - normalized and categorized. Derived by Baltazar. | | analytics_full_view.secondary_sales_type | string | Sub-level sales type (e.g. Interactive Streaming) - normalized and categorized. Derived by Baltazar. | | analytics_full_view.source | string | Normalized source name derived from (in order of priority) the collection source, publisher source, or earnings source. | | analytics_full_view.earnings_collection_source | string | Collection source (e.g. MLC) - normalized and categorized. Derived by Baltazar. | | analytics_full_view.earnings_source | string | Earnings source (e.g. Spotify) - normalized and categorized. Derived by Baltazar. | | analytics_full_view.earnings_category | string | Category classification of the earnings source (e.g., streaming, download, broadcast). Null if earnings source is unknown. | | analytics_full_view.earnings_parent_company | string | Corporate 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_source | string | Canonical 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.territory | string | Earnings territory (e.g. US) - normalized and categorized. Derived by Baltazar. | | analytics_full_view.rightsholder_name | string | Rightsholder or project name. User-provided. | | analytics_full_view.distributor_name | string | The overarching statement format owner. Derived by Baltazar. | | analytics_full_view.tm | string | Trailing 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_rate | number | Foreign exchange rate used to convert the statement's original currency to USD. Derived by Baltazar. | **Time dimensions** - analytics_full_view.sales_period_start - analytics_full_view.sales_period_end - analytics_full_view.release_date - analytics_full_view.release_release_date - analytics_full_view.product_release_date ## 8. Procedure reference All 142 procedures, grouped by namespace. Each procedure shows its method, scope, HTTP path, and a compact input table (top-level fields and one level of nested object children). > **For full Zod-derived JSON Schema** (with regexes, enums, defaults, etc.), fetch > `https://docs.chaptertwo.com/schemas.json` — it returns a map of `{ procedureName: jsonSchema }`. Legend: `POST` = mutation, `GET` = query. Scope tags: `cli` = exposed via the public CLI, `admin` = ChapterTwo admin tooling only. ### Response examples — top procedures The two Cube transports return DIFFERENT shapes. `cube.query` is sync and returns the raw Cube payload directly. `cube.queryAsync` and `cube.queryPoll` wrap it in a status envelope because they may return before Cube finishes — callers must check `status` first. ```json // user.currentUser { "id": "user_abc...", "email": "you@example.com", "organizations": [ { "id": "", "name": "Acme Records", "role": "admin" } ] } // rightsholder.list (data array trimmed) { "data": [ { "id": "", "name": "Artist Name", "organizationId": "", "legalName": null, "aliases": [], "createdAt": "2024-...", "updatedAt": "2024-..." } ], "pagination": { "page": 1, "limit": 50, "total": 42, "totalPages": 1, "hasNext": false, "hasPrev": false } } // cube.query — sync, returns the raw Cube payload (no status wrapper) { "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", "analytics_full_view.product_artist": "Artist Name" } ] } // cube.queryAsync / cube.queryPoll — status envelope, two shapes: // 1) still running: { "status": "processing" } // 2) finished — the inner `data` field is the SAME shape as cube.query above: { "status": "completed", "data": { "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" } ] } } ``` ### activity (3) #### activity.byEntity [GET] List activity events for a single subject Path: `GET /trpc/activity.byEntity?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `subjectType` | "rightsholder" \| "file" \| "upload" \| "export" \| "product" \| "mapping" \| "lens" \| "chat" \| "organization" \| "user" | yes | | | `subjectId` | string | yes | | | `cursor` | string | no | | | `limit` | integer | yes | default 50 | #### activity.feed [GET] List activity events for the organisation Path: `GET /trpc/activity.feed?batch=1&input=…` #### activity.trackVisit [POST] Record that the current user opened a subject Path: `POST /trpc/activity.trackVisit?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `subjectType` | "rightsholder" \| "file" \| "upload" \| "export" \| "product" \| "mapping" \| "lens" \| "chat" \| "organization" \| "user" | yes | | | `subjectId` | string | yes | | | `subjectLabel` | string | no | | ### clustering (6) Track/product clustering and grouping inside a processing block. #### clustering.getCluster [GET · cli] Get a single cluster with its members and products Path: `GET /trpc/clustering.getCluster?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | | `clusterId` | string (uuid) | yes | | #### clustering.getClustersList [GET · cli] Get a summary list of clusters for a block Path: `GET /trpc/clustering.getClustersList?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | #### clustering.getTitleClustering [GET · cli] Get title clustering data for a block Path: `GET /trpc/clustering.getTitleClustering?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | | `pagination` | object | no | default {"page":1,"limit":50} | | `pagination.page` | integer | no | default 1 | | `pagination.limit` | integer | no | default 20 | | `sortField` | "entries" \| "titleDisplay" \| "similarity" \| "earnings" | yes | default "entries" | | `sortDirection` | "asc" \| "desc" | yes | default "desc" | #### clustering.getTopClusters [GET · cli] Get top clusters for a block Path: `GET /trpc/clustering.getTopClusters?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | | `wantedMusicalDetails` | string[] | no | | | `wantedClusterIds` | string (uuid)[] | no | | | `limit` | integer | no | default 300 | | `threshold` | number | no | default 0.001 | #### clustering.getTopMdsFromBlock [GET · cli] Get top musical details with earnings from a block Path: `GET /trpc/clustering.getTopMdsFromBlock?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | | `limit` | integer | no | default 300 | | `minEarningsPercentage` | number | no | | #### clustering.updateClustering [POST · cli] Apply clustering changes to reassign musical details between clusters Path: `POST /trpc/clustering.updateClustering?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | | `changes` | object[] | yes | | ### cube (8) Analytics queries and SQL passthrough against the Cube semantic layer. #### cube.getSession [GET · cli] Get or create an org-scoped Cube Cloud embed session Path: `GET /trpc/cube.getSession?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### cube.meta [GET · cli] Fetch Cube metadata (views, measures, dimensions) Path: `GET /trpc/cube.meta?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### cube.query [POST · cli] Execute a Cube REST API query scoped to the organization Path: `POST /trpc/cube.query?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `query` | object | yes | | | `query.measures` | string[] | no | | | `query.dimensions` | string[] | no | | | `query.filters` | object[] | no | | | `query.timeDimensions` | object[] | no | | | `query.order` | object | no | | | `query.limit` | integer | no | | | `query.total` | boolean | yes | default false | | `usePreagg` | boolean | no | | #### cube.queryAsync [POST · cli] Submit a Cube query and return immediately with status Path: `POST /trpc/cube.queryAsync?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `query` | object | yes | | | `query.measures` | string[] | no | | | `query.dimensions` | string[] | no | | | `query.filters` | object[] | no | | | `query.timeDimensions` | object[] | no | | | `query.order` | object | no | | | `query.limit` | integer | no | | | `query.total` | boolean | yes | default false | | `usePreagg` | boolean | no | | | `noCache` | boolean | no | | #### cube.queryPoll [POST · cli] Poll for a previously submitted Cube query result Path: `POST /trpc/cube.queryPoll?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `query` | object | yes | | | `query.measures` | string[] | no | | | `query.dimensions` | string[] | no | | | `query.filters` | object[] | no | | | `query.timeDimensions` | object[] | no | | | `query.order` | object | no | | | `query.limit` | integer | no | | | `query.total` | boolean | yes | default false | | `usePreagg` | boolean | no | | #### cube.sqlQuery [POST] Execute a SQL query against the Cube SQL API Path: `POST /trpc/cube.sqlQuery?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `sql` | string | yes | | #### cube.sqlQueryAsync [POST] Submit a Cube SQL query and return immediately with status Path: `POST /trpc/cube.sqlQueryAsync?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `sql` | string | yes | | #### cube.sqlQueryPoll [POST] Poll for a previously submitted Cube SQL query result Path: `POST /trpc/cube.sqlQueryPoll?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `sql` | string | yes | | ### dataFreshness (4) Inspect ETL freshness and ingestion progress. #### dataFreshness.checkEarnings [GET] Check earnings data freshness for selected rightsholders Path: `GET /trpc/dataFreshness.checkEarnings?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderIds` | string (uuid)[] | yes | | #### dataFreshness.checkProducts [GET] Check products data freshness for selected rightsholders Path: `GET /trpc/dataFreshness.checkProducts?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderIds` | string (uuid)[] | yes | | #### dataFreshness.ingestionStats [GET] Full aggregated preview stats from DynamoDB for selected rightsholders Path: `GET /trpc/dataFreshness.ingestionStats?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderIds` | string (uuid)[] | yes | | #### dataFreshness.isReady [GET] Readiness check using DynamoDB status categories + RDS warehoused status Path: `GET /trpc/dataFreshness.isReady?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderIds` | string (uuid)[] | yes | | ### distributor (5) Statement distributor formats (e.g. Kobalt, MLC, Spotify-direct). #### distributor.create [POST · admin] Create a new distributor Path: `POST /trpc/distributor.create?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `name` | string | yes | | #### distributor.deleteById [POST] Delete a distributor by ID Path: `POST /trpc/distributor.deleteById?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### distributor.getById [GET · admin] Get a distributor by ID Path: `GET /trpc/distributor.getById?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### distributor.list [GET · admin] List distributors with their mappings Path: `GET /trpc/distributor.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | #### distributor.updateById [POST · admin] Update a distributor by ID Path: `POST /trpc/distributor.updateById?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | | `name` | string | no | | ### exchangeRates (1) Foreign exchange rates used to convert statement currencies to USD. #### exchangeRates.get [GET · cli] Get exchange rates for a given date Path: `GET /trpc/exchangeRates.get?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `date` | string | no | | ### fetching (6) Music enrichment / fetching queue status. #### fetching.getFetchingCoverageByRightsholders [GET] Path: `GET /trpc/fetching.getFetchingCoverageByRightsholders?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderIds` | string (uuid)[] | yes | | #### fetching.inQueueCount [GET · cli] Get the number of musical details currently in the processing queue Path: `GET /trpc/fetching.inQueueCount?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### fetching.list [GET · admin] List blocks with musical detail fetching status Path: `GET /trpc/fetching.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | no | | | `search` | string | no | | | `pagination` | object | yes | default {"page":1,"limit":10} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 10 | #### fetching.refetchByBlock [POST] Re-fetch terminal musical details for a block Path: `POST /trpc/fetching.refetchByBlock?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | #### fetching.reprocessSkippedByBlock [POST] Reprocess skipped musical details for a block Path: `POST /trpc/fetching.reprocessSkippedByBlock?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | #### fetching.stopFetchingByBlock [POST] Stop fetching for a block by skipping pending items Path: `POST /trpc/fetching.stopFetchingByBlock?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | ### file (3) Per-organization file management. #### file.deleteFilesByIds [POST · cli] Delete specific files from an upload by their IDs Path: `POST /trpc/file.deleteFilesByIds?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `fileIds` | string[] | yes | | | `uploadId` | string | yes | | #### file.getFilesDeleteInfo [GET] Get deletion impact info for a set of files (blocks and catalogs affected) Path: `GET /trpc/file.getFilesDeleteInfo?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `fileIds` | string[] | yes | | | `uploadId` | string | yes | | #### file.list [GET · cli] List files for the current organization with optional filters Path: `GET /trpc/file.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | | `filters` | object | no | | | `filters.fileId` | string[] | no | | | `filters.dateStart` | string | no | | | `filters.dateEnd` | string | no | | | `filters.rightsholderId` | string (uuid) | no | | | `filters.distributorId` | string (uuid) | no | | | `filters.customName` | string | no | | | `filters.name` | string | no | | ### fileStorage (2) Statement file storage — presigned downloads and CSV samples. #### fileStorage.downloadStatementFile [POST · cli] Generate a presigned URL to download a statement file from S3 Path: `POST /trpc/fileStorage.downloadStatementFile?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `type` | "normalized" \| "trimmed" \| "original" \| "single" \| "removed" \| "standardized" \| "original-excel" | yes | | | `file` | string | yes | | | `fileName` | string | no | | | `originalExcelFile` | string | no | | #### fileStorage.getSample [GET · cli] Get a paginated sample of rows from a statement CSV file Path: `GET /trpc/fileStorage.getSample?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `type` | "normalized" \| "trimmed" \| "original" \| "single" \| "removed" \| "standardized" | yes | | | `file` | string | yes | | | `startRow` | integer | yes | default 0 | | `endRow` | integer | yes | default 200 | ### health (1) Service health check. #### health [GET] Path: `GET /trpc/health?batch=1&input=…` ### insights (2) Default FX-rate dates and rates for analytics. #### insights.getDefaultFxDate [GET] Path: `GET /trpc/insights.getDefaultFxDate?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderIds` | string (uuid)[] | yes | | #### insights.getDefaultFxRates [GET] Path: `GET /trpc/insights.getDefaultFxRates?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderIds` | string (uuid)[] | yes | | ### lens (9) Saved analytics lenses (charts/dashboards). #### lens.create [POST] Path: `POST /trpc/lens.create?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `name` | string | yes | | | `description` | string | no | | | `scope` | "personal" \| "organization" | yes | default "organization" | | `tags` | string[] | yes | default [] | | `kind` | "query" \| "pivot" \| "funnel" \| "flow" \| "retention" \| "drill_down" \| "valuation" | yes | | | `viewName` | unknown | no | | | `payload` | unknown | yes | | #### lens.delete [POST] Path: `POST /trpc/lens.delete?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### lens.duplicate [POST] Path: `POST /trpc/lens.duplicate?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | | `name` | string | no | | | `scope` | "personal" \| "organization" | no | | #### lens.get [GET] Path: `GET /trpc/lens.get?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### lens.list [GET] Path: `GET /trpc/lens.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `kind` | "query" \| "pivot" \| "funnel" \| "flow" \| "retention" \| "drill_down" \| "valuation" | no | | | `viewName` | string | no | | | `scope` | "personal" \| "organization" | no | | | `tag` | string | no | | | `ownerUserId` | string (uuid) | no | | | `search` | string | no | | | `pinnedOnly` | boolean | no | | | `recentOnly` | boolean | no | | | `sort` | "updated_desc" \| "created_desc" \| "name_asc" \| "last_opened_desc" | no | | | `cursor` | unknown | no | | | `limit` | integer | yes | default 50 | #### lens.listTags [GET] Path: `GET /trpc/lens.listTags?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### lens.recordOpened [POST] Path: `POST /trpc/lens.recordOpened?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### lens.setPinned [POST] Path: `POST /trpc/lens.setPinned?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | | `pinned` | boolean | yes | | #### lens.update [POST] Path: `POST /trpc/lens.update?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | | `name` | string | no | | | `description` | unknown | no | | | `scope` | "personal" \| "organization" | no | | | `tags` | string[] | no | | | `payload` | unknown | no | | | `viewName` | unknown | no | | ### lookupSalesType (8) Sales-type lookup tables and AI resolvers (admin). #### lookupSalesType.batchResolveWithAI [POST · admin] Batch resolve multiple sales type strings using AI (max 10) Path: `POST /trpc/lookupSalesType.batchResolveWithAI?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `salesTypes` | string[] | yes | | | `saveToDb` | boolean | yes | default false | #### lookupSalesType.create [POST · admin] Create or upsert a sales type lookup entry Path: `POST /trpc/lookupSalesType.create?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `original` | string | yes | | | `cleansed` | string | yes | | | `status` | string | no | | | `reason` | unknown | no | | #### lookupSalesType.delete [POST · admin] Delete a sales type lookup entry by id Path: `POST /trpc/lookupSalesType.delete?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | yes | | #### lookupSalesType.getSalesTypeLookup [GET · admin] Get a single sales type lookup entry by id or original value Path: `GET /trpc/lookupSalesType.getSalesTypeLookup?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | no | | | `original` | string | no | | #### lookupSalesType.list [GET · admin] List sales type lookup entries with optional search and status filter Path: `GET /trpc/lookupSalesType.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | | `search` | string | no | | | `status` | string | no | | #### lookupSalesType.missingSalesTypes [GET · admin] List sales types that are present in data but have no lookup mapping Path: `GET /trpc/lookupSalesType.missingSalesTypes?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | #### lookupSalesType.resolveWithAI [POST · admin] Resolve a sales type string using AI classification Path: `POST /trpc/lookupSalesType.resolveWithAI?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `original` | string | yes | | | `saveToDb` | boolean | yes | default false | #### lookupSalesType.update [POST · admin] Update a sales type lookup entry by id Path: `POST /trpc/lookupSalesType.update?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | yes | | | `cleansed` | string | no | | | `primarySalesType` | unknown | no | | | `secondarySalesType` | unknown | no | | | `status` | string | no | | | `reason` | unknown | no | | ### lookupSource (12) Source lookup tables and AI resolvers (admin). #### lookupSource.batchResolveWithAI [POST · admin] Batch resolve multiple source strings using AI (max 10) Path: `POST /trpc/lookupSource.batchResolveWithAI?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `sources` | string[] | yes | | | `saveToDb` | boolean | yes | default false | #### lookupSource.create [POST · admin] Create or upsert a source lookup entry Path: `POST /trpc/lookupSource.create?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `original` | string | yes | | | `collectionSource` | unknown | no | | | `earningsSource` | unknown | no | | | `earningsCategory` | unknown | no | | | `earningsParentCompany` | unknown | no | | | `publisherSource` | unknown | no | | | `status` | string | yes | | | `reason` | unknown | no | | #### lookupSource.delete [POST · admin] Delete a source lookup entry by id Path: `POST /trpc/lookupSource.delete?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | yes | | #### lookupSource.getSourceLookup [GET · admin] Get a single source lookup entry by id or original value Path: `GET /trpc/lookupSource.getSourceLookup?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | no | | | `original` | string | no | | #### lookupSource.list [GET · admin] List source lookup entries with optional search and status filter Path: `GET /trpc/lookupSource.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | | `search` | string | no | | | `status` | unknown | no | | #### lookupSource.missingSources [GET · admin] List sources that are present in data but have no lookup mapping Path: `GET /trpc/lookupSource.missingSources?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | #### lookupSource.ragCreate [POST · admin] Add a new RAG source vector with auto-generated embedding Path: `POST /trpc/lookupSource.ragCreate?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `name` | string | yes | | | `category` | string | yes | | #### lookupSource.ragDelete [POST · admin] Delete a RAG source vector by id Path: `POST /trpc/lookupSource.ragDelete?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | yes | | #### lookupSource.ragList [GET · admin] List all RAG source vectors used for AI resolution Path: `GET /trpc/lookupSource.ragList?batch=1&input=…` #### lookupSource.ragUpdate [POST · admin] Update a RAG source vector and regenerate its embedding Path: `POST /trpc/lookupSource.ragUpdate?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | yes | | | `name` | string | yes | | | `category` | string | yes | | #### lookupSource.resolveWithAI [POST · admin] Resolve a source string using AI classification Path: `POST /trpc/lookupSource.resolveWithAI?batch=1` #### lookupSource.update [POST · admin] Update a source lookup entry by id Path: `POST /trpc/lookupSource.update?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | yes | | | `collectionSource` | unknown | no | | | `earningsSource` | unknown | no | | | `earningsCategory` | unknown | no | | | `earningsParentCompany` | unknown | no | | | `publisherSource` | unknown | no | | | `status` | string | no | | | `reason` | unknown | no | | ### mapping (11) Distributor → standard column mappings (admin). #### mapping.batchUpdate [POST · admin] Batch update multiple mappings in a single transaction Path: `POST /trpc/mapping.batchUpdate?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `updates` | unknown[] | yes | | #### mapping.create [POST · admin] Create a new column mapping Path: `POST /trpc/mapping.create?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `name` | string | no | | | `headers` | string[] | yes | | | `fields` | object | no | | | `fields.stmtYear` | object | no | | | `fields.stmtPeriod` | object | no | | | `fields.stmtCadence` | object | no | | | `fields.currency` | object | no | | | `fields.royaltyEarnings` | object | no | | | `fields.grossRoyaltyEarnings` | object | no | | | `fields.salesPeriodStart` | object | no | | | `fields.salesPeriodEnd` | object | no | | | `fields.salesType` | object | no | | | `fields.subSalesType` | object | no | | | `fields.source` | object | no | | | `fields.subSource` | object | no | | | `fields.units` | object | no | | | `fields.country` | object | no | | | `fields.typeOfRight` | object | no | | | `fields.accountNumber` | object | no | | | `fields.accountName` | object | no | | | `fields.legalVendor` | object | no | | | `fields.territoryDeduction` | object | no | | | `fields.taxDeduction` | object | no | | | `fields.royaltyRate` | object | no | | | `fields.otherAdjustments` | object | no | | | `fields.rawTitle` | object | no | | | `fields.rawArtist` | object | no | | | `fields.rawReleaseTitle` | object | no | | | `fields.rawReleaseArtist` | object | no | | | `fields.composers` | object | no | | | `fields.isrc` | object | no | | | `fields.upc` | object | no | | | `fields.iswc` | object | no | | | `fields.otherIdentifier` | object | no | | | `fields.payor` | object | no | | | `headerHash` | string | yes | | #### mapping.deleteById [POST] Delete a mapping by id (admin/owner only) Path: `POST /trpc/mapping.deleteById?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### mapping.getByDistributor [GET · admin] Get mappings associated with a specific distributor Path: `GET /trpc/mapping.getByDistributor?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `distributorId` | string (uuid) | yes | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | #### mapping.getById [GET · admin] Get a mapping by id (deprecated: use getMapping instead) Path: `GET /trpc/mapping.getById?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### mapping.getMapping [GET · admin] Get a mapping by id or header hash Path: `GET /trpc/mapping.getMapping?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | no | | | `headerHash` | string | no | | #### mapping.list [GET · admin] List mappings for the current organization Path: `GET /trpc/mapping.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | | `ids` | string (uuid)[] | no | | #### mapping.processSample [POST] Process a sample of rows using a field mapping to preview transformations Path: `POST /trpc/mapping.processSample?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `statementId` | string | yes | | | `startRow` | integer | yes | default 0 | | `endRow` | integer | yes | default 5 | | `mappingId` | string (uuid) | yes | | | `mapping` | object | yes | | | `mapping.stmtYear` | object | yes | | | `mapping.stmtPeriod` | object | yes | | | `mapping.stmtCadence` | object | yes | | | `mapping.currency` | object | yes | | | `mapping.royaltyEarnings` | object | yes | | | `mapping.grossRoyaltyEarnings` | object | no | | | `mapping.salesPeriodStart` | object | no | | | `mapping.salesPeriodEnd` | object | no | | | `mapping.salesType` | object | no | | | `mapping.subSalesType` | object | no | | | `mapping.source` | object | no | | | `mapping.subSource` | object | no | | | `mapping.units` | object | no | | | `mapping.country` | object | no | | | `mapping.typeOfRight` | object | no | | | `mapping.accountNumber` | object | no | | | `mapping.accountName` | object | no | | | `mapping.legalVendor` | object | no | | | `mapping.territoryDeduction` | object | no | | | `mapping.taxDeduction` | object | no | | | `mapping.royaltyRate` | object | no | | | `mapping.otherAdjustments` | object | no | | | `mapping.rawTitle` | object | no | | | `mapping.rawArtist` | object | no | | | `mapping.rawReleaseTitle` | object | no | | | `mapping.rawReleaseArtist` | object | no | | | `mapping.composers` | object | no | | | `mapping.isrc` | object | no | | | `mapping.upc` | object | no | | | `mapping.iswc` | object | no | | | `mapping.otherIdentifier` | object | no | | | `mapping.payor` | object | no | | | `variables` | object | no | | | `fileName` | string | yes | default "" | #### mapping.redrivePreview [GET · admin] Preview which files and organizations would be affected by a mapping redrive Path: `GET /trpc/mapping.redrivePreview?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `mappingId` | string (uuid) | yes | | #### mapping.revision.listByMapping [GET · admin] Path: `GET /trpc/mapping.revision.listByMapping?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `mappingId` | string (uuid) | yes | | #### mapping.updateById [POST · admin] Update a mapping by id, optionally reassigning distributors Path: `POST /trpc/mapping.updateById?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | | `distributorId` | string (uuid)[] | no | | | `verify` | boolean | no | | | `redriveFileIds` | string[] | no | | | `name` | string | no | | | `headers` | string[] | no | | | `fields` | object | no | | | `fields.stmtYear` | object | no | | | `fields.stmtPeriod` | object | no | | | `fields.stmtCadence` | object | no | | | `fields.currency` | object | no | | | `fields.royaltyEarnings` | object | no | | | `fields.grossRoyaltyEarnings` | object | no | | | `fields.salesPeriodStart` | object | no | | | `fields.salesPeriodEnd` | object | no | | | `fields.salesType` | object | no | | | `fields.subSalesType` | object | no | | | `fields.source` | object | no | | | `fields.subSource` | object | no | | | `fields.units` | object | no | | | `fields.country` | object | no | | | `fields.typeOfRight` | object | no | | | `fields.accountNumber` | object | no | | | `fields.accountName` | object | no | | | `fields.legalVendor` | object | no | | | `fields.territoryDeduction` | object | no | | | `fields.taxDeduction` | object | no | | | `fields.royaltyRate` | object | no | | | `fields.otherAdjustments` | object | no | | | `fields.rawTitle` | object | no | | | `fields.rawArtist` | object | no | | | `fields.rawReleaseTitle` | object | no | | | `fields.rawReleaseArtist` | object | no | | | `fields.composers` | object | no | | | `fields.isrc` | object | no | | | `fields.upc` | object | no | | | `fields.iswc` | object | no | | | `fields.otherIdentifier` | object | no | | | `fields.payor` | object | no | | | `fileCategories` | unknown | no | | ### market (3) Market data — quotes, treasury yields, history. #### market.getHistory [GET] Path: `GET /trpc/market.getHistory?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `ticker` | string | yes | | #### market.getQuotes [GET] Path: `GET /trpc/market.getQuotes?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `symbols` | string[] | yes | | #### market.getTreasuryYields [GET] Path: `GET /trpc/market.getTreasuryYields?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | ### musicalDetails (7) Musical-details lookup, override, and execution (admin). #### musicalDetails.bulkOverride [POST · admin] Bulk override musical detail results Path: `POST /trpc/musicalDetails.bulkOverride?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | no | | | `overrides` | object[] | yes | | #### musicalDetails.executeQuery [GET · admin] Execute a filter query on musical details Path: `GET /trpc/musicalDetails.executeQuery?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `query` | unknown | yes | | | `hasBlockRule` | boolean | yes | default false | | `hasRightsholderRule` | boolean | yes | default false | #### musicalDetails.getDistinctTargetProviders [GET · admin] Get distinct target providers for musical details Path: `GET /trpc/musicalDetails.getDistinctTargetProviders?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### musicalDetails.getDistinctTargetTypes [GET · admin] Get distinct target types for musical details Path: `GET /trpc/musicalDetails.getDistinctTargetTypes?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### musicalDetails.getMusicalDetails [GET · admin] Get paginated musical details with filter Path: `GET /trpc/musicalDetails.getMusicalDetails?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `query` | unknown | yes | | | `hasBlockRule` | boolean | yes | default false | | `hasRightsholderRule` | boolean | yes | default false | | `includeTrail` | boolean | yes | default false | | `pagination` | object | yes | default {"page":1,"pageSize":100} | | `pagination.page` | integer | yes | default 1 | | `pagination.pageSize` | integer | yes | default 100 | #### musicalDetails.ignoreTitles [POST · admin] Mark musical detail titles as ignored Path: `POST /trpc/musicalDetails.ignoreTitles?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `titles` | string[] | yes | | #### musicalDetails.updateResults [POST · admin] Update musical detail results with override values Path: `POST /trpc/musicalDetails.updateResults?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `resultIds` | string (uuid)[] | yes | | | `overrideValues` | object | yes | | | `overrideValues.title` | string | yes | | | `overrideValues.artist` | string | yes | | | `overrideValues.releaseDate` | string | yes | | | `overrideValues.imageUrl` | string | no | | | `overrideValues.genres` | object[] | no | | | `overrideValues.albums` | object[] | no | | | `overrideValues.songWriters` | object[] | no | | | `overrideValues.fetchedComposers` | object[] | no | | ### musicEnrichment (1) Track and rightsholder music-enrichment metadata (admin). #### musicEnrichment.query [GET · admin] Execute a Chartmetric data query for music enrichment Path: `GET /trpc/musicEnrichment.query?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | ### organization (16) Organization administration — users, invitations, settings. #### organization.addUser [POST] Invite a user to the organization Path: `POST /trpc/organization.addUser?batch=1` #### organization.create [POST] Create a new organization Path: `POST /trpc/organization.create?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `name` | string | yes | | #### organization.delete [POST] Soft delete the organization Path: `POST /trpc/organization.delete?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### organization.getById [GET] Get the current organization by ID Path: `GET /trpc/organization.getById?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### organization.getInvitationLink [POST] Get the acceptance link for a pending invitation Path: `POST /trpc/organization.getInvitationLink?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `invitationId` | string | yes | | #### organization.list [GET · admin] List organizations for the current user Path: `GET /trpc/organization.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | #### organization.listInvitations [GET] List pending invitations in the organization Path: `GET /trpc/organization.listInvitations?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `status` | "pending" \| "accepted" \| "revoked" \| "expired"[] | yes | default ["pending"] | #### organization.listRightsholderMdPriorities [GET · admin] List rightsholder MD fetch priorities for the organization Path: `GET /trpc/organization.listRightsholderMdPriorities?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `search` | string | no | | #### organization.listUsers [GET · admin] List users in the organization Path: `GET /trpc/organization.listUsers?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | #### organization.removeUser [POST] Remove a user from the organization Path: `POST /trpc/organization.removeUser?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `userId` | string (uuid) | yes | | #### organization.resendInvitation [POST] Resend a pending invitation by revoking and re-creating it Path: `POST /trpc/organization.resendInvitation?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `invitationId` | string | yes | | #### organization.revokeInvitation [POST] Revoke a pending invitation Path: `POST /trpc/organization.revokeInvitation?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `invitationId` | string | yes | | #### organization.setRightsholderMdPriorities [POST] Replace the full rightsholder MD fetch priority ordering Path: `POST /trpc/organization.setRightsholderMdPriorities?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderIds` | string (uuid)[] | yes | default [] | #### organization.update [POST] Update organization details Path: `POST /trpc/organization.update?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `name` | string | yes | | #### organization.updateMdFetchSettings [POST] Update musical detail fetch settings for the organization Path: `POST /trpc/organization.updateMdFetchSettings?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `mdFetchMultiplier` | integer | no | | | `mdFetchFocus` | boolean | no | | #### organization.updateRole [POST] Update a user Path: `POST /trpc/organization.updateRole?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `userId` | string (uuid) | yes | | | `role` | "member" \| "reader" \| "writer" \| "admin" \| "c2_customer_support" \| "owner" \| "c2_superadmin" | yes | | ### payor (1) #### payor.listGlobal [GET] List globally-known payors (for autocomplete) Path: `GET /trpc/payor.listGlobal?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | ### product (8) Track and album product catalog. #### product.getAllMusicalDetailsForProduct [GET · cli] Get all musical details for a product in a block Path: `GET /trpc/product.getAllMusicalDetailsForProduct?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `productId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | #### product.getByBlock [GET] Get products for a specific block Path: `GET /trpc/product.getByBlock?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 20 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | | `searchQuery` | string | no | | #### product.getById [GET · cli] Get a product by ID Path: `GET /trpc/product.getById?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `productId` | string (uuid) | yes | | #### product.getMusicalDetailsBatch [GET · cli] Get musical details for multiple products in a block Path: `GET /trpc/product.getMusicalDetailsBatch?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `productIds` | string (uuid)[] | yes | | | `blockId` | string (uuid) | yes | | #### product.getMusicalDetailsByBlock [GET] Get musical details for a product within a block Path: `GET /trpc/product.getMusicalDetailsByBlock?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `blockId` | string (uuid) | yes | | | `productId` | string (uuid) | yes | | | `pagination` | object | no | | | `pagination.page` | integer | no | default 1 | | `pagination.limit` | integer | no | default 5000 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | no | default "asc" | #### product.getProductsByIds [GET · cli] Get multiple products by their IDs Path: `GET /trpc/product.getProductsByIds?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `productIds` | string (uuid)[] | yes | | #### product.updateById [POST · cli] Update a product by ID Path: `POST /trpc/product.updateById?batch=1` #### product.updateProductMusicalDetailsRelationships [POST] Create, update, and delete product to musical detail relationships Path: `POST /trpc/product.updateProductMusicalDetailsRelationships?batch=1` ### rightsholder (7) Manage rightsholders (artists, labels, publishers). #### rightsholder.batchUpdate [POST · cli] Batch update multiple rightsholders Path: `POST /trpc/rightsholder.batchUpdate?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `updates` | unknown[] | yes | | #### rightsholder.create [POST · cli] Create a new rightsholder Path: `POST /trpc/rightsholder.create?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `name` | string | yes | | #### rightsholder.deleteById [POST · cli] Delete a rightsholder by ID Path: `POST /trpc/rightsholder.deleteById?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### rightsholder.getById [GET · cli] Get a rightsholder by ID Path: `GET /trpc/rightsholder.getById?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### rightsholder.list [GET · cli] List rightsholders for the organization Path: `GET /trpc/rightsholder.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | #### rightsholder.trackOpen [POST] DEPRECATED: use activity.trackVisit Path: `POST /trpc/rightsholder.trackOpen?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | #### rightsholder.updateById [POST · cli] Update a rightsholder by ID Path: `POST /trpc/rightsholder.updateById?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `id` | string (uuid) | yes | | | `name` | string | yes | | | `legalName` | string | no | | | `aliases` | unknown | no | | | `musoIds` | unknown | no | | | `spotifyIds` | unknown | no | | | `mlcPartyIds` | unknown | no | | | `ipiNameNumbers` | unknown | no | | | `isnis` | unknown | no | | | `isUmpg` | unknown | no | | | `isDeleted` | boolean | no | | | `lastUpdatedById` | unknown | no | | ### sourceGoldData (3) Gold-data overrides for sources (admin). #### sourceGoldData.delete [POST] Delete a source_gold_data row Path: `POST /trpc/sourceGoldData.delete?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `id` | string (uuid) | yes | | #### sourceGoldData.list [GET] List source_gold_data rows with optional search Path: `GET /trpc/sourceGoldData.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | | `search` | string | no | | #### sourceGoldData.upsert [POST] Create or update a source_gold_data row Path: `POST /trpc/sourceGoldData.upsert?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `rawValue` | string | yes | | | `earningsSource` | unknown | no | | | `earningsCategory` | unknown | no | | | `parentCompany` | unknown | no | | | `collectionSource` | unknown | no | | | `publisherSource` | unknown | no | | | `notes` | unknown | no | | | `id` | string (uuid) | no | | ### upload (10) Upload royalty statements and execute upload actions. #### upload.aborted [POST] Signal that a direct upload was aborted client-side Path: `POST /trpc/upload.aborted?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `statementId` | string | yes | | | `reason` | string | no | | #### upload.callback [POST] Submit a mapping callback for statement processing Path: `POST /trpc/upload.callback?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `type` | string | yes | | | `mapping_id` | string | yes | | | `file_id` | string | no | | | `upload_id` | string | no | | | `action` | "skip" \| "bad" | no | | | `distributor_id` | string | no | | #### upload.heartbeat [POST] Extend the TTL on in-progress direct uploads Path: `POST /trpc/upload.heartbeat?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `statementIds` | string[] | yes | | #### upload.list [GET · cli] List uploads for the organization Path: `GET /trpc/upload.list?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | | `filter` | object | no | | | `filter.search` | string | no | | | `filter.date_start` | string | no | | | `filter.date_end` | string | no | | #### upload.resolveAllFlags [POST] Resolve all unresolved flags on a statement Path: `POST /trpc/upload.resolveAllFlags?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `uploadId` | string | yes | | | `statementId` | string | yes | | #### upload.resolveFlag [POST] Resolve a single flag on a statement Path: `POST /trpc/upload.resolveFlag?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `uploadId` | string | yes | | | `statementId` | string | yes | | | `flagId` | string | yes | | #### upload.setFlagShown [POST] Set the shown/seen state of a flag on a statement Path: `POST /trpc/upload.setFlagShown?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `uploadId` | string | yes | | | `statementId` | string | yes | | | `flagId` | string | yes | | | `shown` | boolean | yes | | #### upload.statements [POST · cli] List statements for an organization or upload Path: `POST /trpc/upload.statements?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `rightsholderId` | string (uuid) | no | | | `customName` | string | no | | | `pagination` | object | yes | default {"page":1,"limit":300,"sortBy":"name","sortOrder":"asc"} | | `pagination.page` | integer | yes | default 1 | | `pagination.limit` | integer | yes | default 200 | | `pagination.sortBy` | string | no | | | `pagination.sortOrder` | "asc" \| "desc" | yes | default "asc" | | `filter` | object | no | | | `filter.statementStatus` | string[] | no | | | `filter.statementIds` | string[] | no | | | `filter.includeDeleted` | boolean | no | default false | | `filter.fileName` | string | no | Case-insensitive substring match on the uploaded file name | #### upload.uploadAction [POST · cli] Execute an action on an upload or statement Path: `POST /trpc/upload.uploadAction?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `action` | unknown | yes | | #### upload.uploadFile [POST · cli] Generate a presigned URL and initiate a file upload Path: `POST /trpc/upload.uploadFile?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | ### user (5) Authenticated user profile and data-access roles. #### user.currentUser [GET · cli] Get the currently authenticated user Path: `GET /trpc/user.currentUser?batch=1&input=…` #### user.preferences [GET] Get the current user\u2019s per-organisation preferences Path: `GET /trpc/user.preferences?batch=1&input=…` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | #### user.setDataAccessRole [POST] Set the data access role for a user Path: `POST /trpc/user.setDataAccessRole?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `userId` | string (uuid) | yes | | | `role` | unknown | yes | | #### user.setRightsholderSort [POST] Persist the home-page rightsholder sort selection Path: `POST /trpc/user.setRightsholderSort?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `sort` | "last_updated" \| "alphabetical" \| "recently_created" | yes | | #### user.updateOrganizationUserStatus [POST] Update a user Path: `POST /trpc/user.updateOrganizationUserStatus?batch=1` | Param | Type | Required | Notes | |-------|------|----------|-------| | `organizationId` | string (uuid) | yes | | | `userId` | string (uuid) | yes | | | `status` | "active" \| "disabled" | yes | |