@withpica/mcp-sdk 3.23.0 → 3.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/dist/index.d.ts +317 -66
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +371 -149
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,6 +54,14 @@ function getTimeoutForPath(path) {
|
|
|
54
54
|
? LONG_TIMEOUT_MS
|
|
55
55
|
: DEFAULT_TIMEOUT_MS;
|
|
56
56
|
}
|
|
57
|
+
/** The `offset` query parameter of a request path (0 when absent). */
|
|
58
|
+
function offsetOf(path) {
|
|
59
|
+
const q = path.indexOf("?");
|
|
60
|
+
if (q === -1)
|
|
61
|
+
return 0;
|
|
62
|
+
const n = Number(new URLSearchParams(path.slice(q + 1)).get("offset"));
|
|
63
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
64
|
+
}
|
|
57
65
|
function sleep(ms) {
|
|
58
66
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
67
|
}
|
|
@@ -245,13 +253,20 @@ class BaseResource {
|
|
|
245
253
|
* for routes that stream a generated file (e.g. `text/csv`) rather than
|
|
246
254
|
* `{success, data}`. `request()` unconditionally calls `response.json()`,
|
|
247
255
|
* which throws a SyntaxError parsing CSV text; every such route needs this
|
|
248
|
-
* method instead (
|
|
249
|
-
* precedent this generalises).
|
|
256
|
+
* method instead (`ImportResource.getTemplate()`, `ExportResource.catalogCsv()`).
|
|
250
257
|
*
|
|
251
258
|
* `fetchWithRetry` already throws `ApiError` for non-2xx responses (reading
|
|
252
259
|
* the body as text), so reaching here means `response.ok`. Still guard
|
|
253
260
|
* against a 2xx JSON error envelope (`{success:false}`) via content-type,
|
|
254
261
|
* since a route can commit to status 200 before a later validation step.
|
|
262
|
+
*
|
|
263
|
+
* An HTML page is refused. Neither route that uses this
|
|
264
|
+
* (`/admin/import/template`, `/admin/works/export` CSV formats) answers
|
|
265
|
+
* `text/html`, so one means something in front of PICA answered (on a
|
|
266
|
+
* protected Vercel preview, a missing bypass header ends on Vercel's
|
|
267
|
+
* login page with status 200). Returning that page as the file read as a
|
|
268
|
+
* success (2026-10-06, pica_import_template). Locked by
|
|
269
|
+
* `mcp-server/src/__tests__/sdk-request-text-and-bare-fetch.test.ts`.
|
|
255
270
|
*/
|
|
256
271
|
async requestText(method, path) {
|
|
257
272
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -275,6 +290,9 @@ class BaseResource {
|
|
|
275
290
|
}
|
|
276
291
|
return typeof data === "string" ? data : JSON.stringify(data);
|
|
277
292
|
}
|
|
293
|
+
if (contentType.includes("text/html")) {
|
|
294
|
+
throw new ApiError(`Expected a file from ${path} but got an HTML page; the request did not reach PICA`, response.status, false);
|
|
295
|
+
}
|
|
278
296
|
return response.text();
|
|
279
297
|
}
|
|
280
298
|
/**
|
|
@@ -302,8 +320,18 @@ class BaseResource {
|
|
|
302
320
|
// `total`/`hasMore` (e.g. GET /admin/recordings/search, ADR unified
|
|
303
321
|
// catalog search). Check nested first, then flat, then fall back to
|
|
304
322
|
// `count` / items.length so untouched callers keep their prior behaviour.
|
|
305
|
-
|
|
306
|
-
|
|
323
|
+
// An explicit `pagination.total: null` means the server does not know
|
|
324
|
+
// the total — keep it null rather than falling through to the page
|
|
325
|
+
// length, which would read as "that's all of them".
|
|
326
|
+
const total = json.pagination && json.pagination.total === null
|
|
327
|
+
? null
|
|
328
|
+
: (json.pagination?.total ?? json.total ?? json.count ?? items.length);
|
|
329
|
+
// With no server-side hasMore, the answer is offset + this page < total.
|
|
330
|
+
// `total > items.length` alone ignored the offset, so every LAST page of
|
|
331
|
+
// a multi-page result said "more" (row-cap T3).
|
|
332
|
+
const hasMore = json.pagination?.hasMore ??
|
|
333
|
+
json.hasMore ??
|
|
334
|
+
(total !== null && offsetOf(path) + items.length < total);
|
|
307
335
|
// Carry the server's zero-result guidance through — silently dropping it
|
|
308
336
|
// here left agents staring at a bare empty list (ops_issue 689a8e9a).
|
|
309
337
|
const recovery_hint = typeof json.recovery_hint === "string" ? json.recovery_hint : undefined;
|
|
@@ -396,6 +424,15 @@ class WorksResource extends BaseResource {
|
|
|
396
424
|
const qs = qp.toString();
|
|
397
425
|
return this.requestPaginated("GET", `/admin/works${qs ? `?${qs}` : ""}`);
|
|
398
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* The agreements linked to ONE work (GET /admin/works/:id/agreements).
|
|
429
|
+
* Inspect used `agreements.list()`, which returned the org's first page of
|
|
430
|
+
* agreements under a work's heading (row-cap T3).
|
|
431
|
+
*/
|
|
432
|
+
async getAgreements(workId) {
|
|
433
|
+
const res = await this.request("GET", `/admin/works/${workId}/agreements`);
|
|
434
|
+
return Array.isArray(res) ? res : [];
|
|
435
|
+
}
|
|
399
436
|
async get(id) {
|
|
400
437
|
return this.request("GET", `/admin/works/${id}`);
|
|
401
438
|
}
|
|
@@ -507,6 +544,22 @@ class WorksResource extends BaseResource {
|
|
|
507
544
|
async addWriters(workId, names) {
|
|
508
545
|
return this.request("POST", `/admin/works/${workId}/writers`, { names });
|
|
509
546
|
}
|
|
547
|
+
/** A song's directory topics: approved, proposed, moods awaiting review, settings. */
|
|
548
|
+
async getThemes(workId) {
|
|
549
|
+
return this.request("GET", `/admin/works/${encodeURIComponent(workId)}/themes`);
|
|
550
|
+
}
|
|
551
|
+
/** Set the song's approved topics directly, and/or its switch and lyric visibility. */
|
|
552
|
+
async setThemes(workId, body) {
|
|
553
|
+
return this.request("PUT", `/admin/works/${encodeURIComponent(workId)}/themes`, body);
|
|
554
|
+
}
|
|
555
|
+
/** Propose topics (and recording moods) for the owner to review. Nothing goes public. */
|
|
556
|
+
async proposeThemes(workId, body) {
|
|
557
|
+
return this.request("POST", `/admin/works/${encodeURIComponent(workId)}/themes/proposals`, body);
|
|
558
|
+
}
|
|
559
|
+
/** The owner's review of proposed topics and moods. */
|
|
560
|
+
async reviewThemes(workId, body) {
|
|
561
|
+
return this.request("PATCH", `/admin/works/${encodeURIComponent(workId)}/themes/proposals`, body);
|
|
562
|
+
}
|
|
510
563
|
}
|
|
511
564
|
class PeopleResource extends BaseResource {
|
|
512
565
|
async list(params) {
|
|
@@ -618,6 +671,23 @@ class PeopleResource extends BaseResource {
|
|
|
618
671
|
return this.request("GET", `/admin/people/triangulate?${qp.toString()}`);
|
|
619
672
|
}
|
|
620
673
|
}
|
|
674
|
+
function licenseEnquiryListPath(params) {
|
|
675
|
+
const queryParams = new URLSearchParams();
|
|
676
|
+
if (params?.work_id)
|
|
677
|
+
queryParams.set("work_id", params.work_id);
|
|
678
|
+
if (params?.status)
|
|
679
|
+
queryParams.set("status", params.status);
|
|
680
|
+
if (params?.project_type)
|
|
681
|
+
queryParams.set("project_type", params.project_type);
|
|
682
|
+
if (params?.territory)
|
|
683
|
+
queryParams.set("territory", params.territory);
|
|
684
|
+
if (params?.limit !== undefined)
|
|
685
|
+
queryParams.set("limit", String(params.limit));
|
|
686
|
+
if (params?.offset !== undefined)
|
|
687
|
+
queryParams.set("offset", String(params.offset));
|
|
688
|
+
const query = queryParams.toString();
|
|
689
|
+
return `/admin/license-enquiries${query ? `?${query}` : ""}`;
|
|
690
|
+
}
|
|
621
691
|
class LicensingResource extends BaseResource {
|
|
622
692
|
/**
|
|
623
693
|
* Search for sync-licensable tracks by audio characteristics
|
|
@@ -663,33 +733,26 @@ class LicensingResource extends BaseResource {
|
|
|
663
733
|
return this.request("POST", "/admin/license-enquiries", enquiry);
|
|
664
734
|
}
|
|
665
735
|
/**
|
|
666
|
-
* List license enquiries with optional filters
|
|
736
|
+
* List license enquiries with optional filters (one page, the bare array;
|
|
737
|
+
* see listEnquiriesPage for the real total).
|
|
667
738
|
*/
|
|
668
739
|
async listEnquiries(params) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
queryParams.set("project_type", params.project_type);
|
|
676
|
-
if (params?.territory)
|
|
677
|
-
queryParams.set("territory", params.territory);
|
|
678
|
-
if (params?.limit !== undefined)
|
|
679
|
-
queryParams.set("limit", String(params.limit));
|
|
680
|
-
if (params?.offset !== undefined)
|
|
681
|
-
queryParams.set("offset", String(params.offset));
|
|
682
|
-
const query = queryParams.toString();
|
|
683
|
-
const path = `/admin/license-enquiries${query ? `?${query}` : ""}`;
|
|
684
|
-
// The admin route returns successResponse({ data: [...], count }); after
|
|
685
|
-
// request() unwraps the outer { success, data } envelope the payload is the
|
|
686
|
-
// { data, count } object, not the array. Tolerate both shapes (mirrors
|
|
687
|
-
// listDiscoveriesRecent + the other list resources) so the caller never
|
|
688
|
-
// calls `.map` on a non-array — the "t.map is not a function" crash that
|
|
689
|
-
// made pica_list_license_enquiries fail on every call shape.
|
|
690
|
-
const res = (await this.request("GET", path));
|
|
740
|
+
// Older servers nested the list as `{ data: { data: [...], count } }`, so
|
|
741
|
+
// after request() unwraps the outer envelope the payload was an object,
|
|
742
|
+
// not the array. Tolerate both shapes so the caller never calls `.map` on
|
|
743
|
+
// a non-array — the "t.map is not a function" crash that made
|
|
744
|
+
// pica_list_license_enquiries fail on every call shape.
|
|
745
|
+
const res = (await this.request("GET", licenseEnquiryListPath(params)));
|
|
691
746
|
return Array.isArray(res) ? res : (res?.data ?? []);
|
|
692
747
|
}
|
|
748
|
+
/**
|
|
749
|
+
* One page of license enquiries WITH the real total and has_more
|
|
750
|
+
* (row-cap T3): `listEnquiries()` returns the bare array, so callers
|
|
751
|
+
* reported the page length as the total.
|
|
752
|
+
*/
|
|
753
|
+
async listEnquiriesPage(params) {
|
|
754
|
+
return this.requestPaginated("GET", licenseEnquiryListPath(params));
|
|
755
|
+
}
|
|
693
756
|
/**
|
|
694
757
|
* Get a specific license enquiry by ID
|
|
695
758
|
*/
|
|
@@ -712,34 +775,40 @@ class LicensingResource extends BaseResource {
|
|
|
712
775
|
return this.request("PATCH", `/admin/license-enquiries/${id}`, { counterOffer, notes });
|
|
713
776
|
}
|
|
714
777
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
778
|
+
function bookingListPath(params) {
|
|
779
|
+
const qp = new URLSearchParams();
|
|
780
|
+
if (params?.status)
|
|
781
|
+
qp.set("status", params.status);
|
|
782
|
+
if (params?.priority)
|
|
783
|
+
qp.set("priority", params.priority);
|
|
784
|
+
if (params?.query)
|
|
785
|
+
qp.set("query", params.query);
|
|
786
|
+
if (params?.enquiry_type)
|
|
787
|
+
qp.set("enquiry_type", params.enquiry_type);
|
|
788
|
+
if (params?.follow_up_required !== undefined)
|
|
789
|
+
qp.set("follow_up_required", String(params.follow_up_required));
|
|
790
|
+
if (params?.limit !== undefined)
|
|
791
|
+
qp.set("limit", String(params.limit));
|
|
792
|
+
if (params?.offset !== undefined)
|
|
793
|
+
qp.set("offset", String(params.offset));
|
|
794
|
+
const qs = qp.toString();
|
|
795
|
+
return `/admin/bookings${qs ? `?${qs}` : ""}`;
|
|
796
|
+
}
|
|
720
797
|
class BookingsResource extends BaseResource {
|
|
721
798
|
/**
|
|
722
|
-
*
|
|
723
|
-
*
|
|
799
|
+
* One page of booking enquiries (the bare array; see listPage for the real
|
|
800
|
+
* total). `request()` unwraps the route's `{ data, count }` envelope.
|
|
724
801
|
*/
|
|
725
802
|
async list(params) {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
if (params?.follow_up_required !== undefined)
|
|
736
|
-
qp.set("follow_up_required", String(params.follow_up_required));
|
|
737
|
-
if (params?.limit !== undefined)
|
|
738
|
-
qp.set("limit", String(params.limit));
|
|
739
|
-
if (params?.offset !== undefined)
|
|
740
|
-
qp.set("offset", String(params.offset));
|
|
741
|
-
const qs = qp.toString();
|
|
742
|
-
return this.request("GET", `/admin/bookings${qs ? `?${qs}` : ""}`);
|
|
803
|
+
return this.request("GET", bookingListPath(params));
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* One page of booking enquiries WITH the real total and has_more
|
|
807
|
+
* (row-cap T3): `list()` returns the bare array, so callers reported the
|
|
808
|
+
* page length as the total.
|
|
809
|
+
*/
|
|
810
|
+
async listPage(params) {
|
|
811
|
+
return this.requestPaginated("GET", bookingListPath(params));
|
|
743
812
|
}
|
|
744
813
|
async get(id) {
|
|
745
814
|
return this.request("GET", `/admin/bookings/${encodeURIComponent(id)}`);
|
|
@@ -1074,39 +1143,51 @@ class MultimediaResource extends BaseResource {
|
|
|
1074
1143
|
});
|
|
1075
1144
|
}
|
|
1076
1145
|
}
|
|
1146
|
+
function agreementListPath(params) {
|
|
1147
|
+
const queryParams = new URLSearchParams();
|
|
1148
|
+
if (params?.query)
|
|
1149
|
+
queryParams.set("query", params.query);
|
|
1150
|
+
if (params?.agreement_type)
|
|
1151
|
+
queryParams.set("agreement_type", params.agreement_type);
|
|
1152
|
+
if (params?.status)
|
|
1153
|
+
queryParams.set("status", params.status);
|
|
1154
|
+
if (params?.party_name)
|
|
1155
|
+
queryParams.set("party_name", params.party_name);
|
|
1156
|
+
if (params?.includeWorkCounts)
|
|
1157
|
+
queryParams.set("includeWorkCounts", "true");
|
|
1158
|
+
if (params?.awaiting_signature)
|
|
1159
|
+
queryParams.set("awaiting_signature", "true");
|
|
1160
|
+
if (params?.start_date_from)
|
|
1161
|
+
queryParams.set("start_date_from", params.start_date_from);
|
|
1162
|
+
if (params?.start_date_to)
|
|
1163
|
+
queryParams.set("start_date_to", params.start_date_to);
|
|
1164
|
+
if (params?.end_date_from)
|
|
1165
|
+
queryParams.set("end_date_from", params.end_date_from);
|
|
1166
|
+
if (params?.end_date_to)
|
|
1167
|
+
queryParams.set("end_date_to", params.end_date_to);
|
|
1168
|
+
if (params?.expiring_within_days !== undefined)
|
|
1169
|
+
queryParams.set("expiring_within_days", String(params.expiring_within_days));
|
|
1170
|
+
if (params?.has_end_date !== undefined)
|
|
1171
|
+
queryParams.set("has_end_date", String(params.has_end_date));
|
|
1172
|
+
if (params?.limit !== undefined)
|
|
1173
|
+
queryParams.set("limit", String(params.limit));
|
|
1174
|
+
if (params?.offset !== undefined)
|
|
1175
|
+
queryParams.set("offset", String(params.offset));
|
|
1176
|
+
const query = queryParams.toString();
|
|
1177
|
+
return `/admin/agreements${query ? `?${query}` : ""}`;
|
|
1178
|
+
}
|
|
1077
1179
|
class AgreementsResource extends BaseResource {
|
|
1180
|
+
/** One page of agreements (the bare array; see listPage for the real total). */
|
|
1078
1181
|
async list(params) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (params?.includeWorkCounts)
|
|
1089
|
-
queryParams.set("includeWorkCounts", "true");
|
|
1090
|
-
if (params?.awaiting_signature)
|
|
1091
|
-
queryParams.set("awaiting_signature", "true");
|
|
1092
|
-
if (params?.start_date_from)
|
|
1093
|
-
queryParams.set("start_date_from", params.start_date_from);
|
|
1094
|
-
if (params?.start_date_to)
|
|
1095
|
-
queryParams.set("start_date_to", params.start_date_to);
|
|
1096
|
-
if (params?.end_date_from)
|
|
1097
|
-
queryParams.set("end_date_from", params.end_date_from);
|
|
1098
|
-
if (params?.end_date_to)
|
|
1099
|
-
queryParams.set("end_date_to", params.end_date_to);
|
|
1100
|
-
if (params?.expiring_within_days !== undefined)
|
|
1101
|
-
queryParams.set("expiring_within_days", String(params.expiring_within_days));
|
|
1102
|
-
if (params?.has_end_date !== undefined)
|
|
1103
|
-
queryParams.set("has_end_date", String(params.has_end_date));
|
|
1104
|
-
if (params?.limit !== undefined)
|
|
1105
|
-
queryParams.set("limit", String(params.limit));
|
|
1106
|
-
if (params?.offset !== undefined)
|
|
1107
|
-
queryParams.set("offset", String(params.offset));
|
|
1108
|
-
const query = queryParams.toString();
|
|
1109
|
-
return this.request("GET", `/admin/agreements${query ? `?${query}` : ""}`);
|
|
1182
|
+
return this.request("GET", agreementListPath(params));
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* One page of agreements WITH the real total and has_more (row-cap T3):
|
|
1186
|
+
* `list()` returns the bare array, so callers reported the page length
|
|
1187
|
+
* as the total.
|
|
1188
|
+
*/
|
|
1189
|
+
async listPage(params) {
|
|
1190
|
+
return this.requestPaginated("GET", agreementListPath(params));
|
|
1110
1191
|
}
|
|
1111
1192
|
async get(id) {
|
|
1112
1193
|
return this.request("GET", `/admin/agreements/${id}`);
|
|
@@ -1179,10 +1260,28 @@ class AgreementsResource extends BaseResource {
|
|
|
1179
1260
|
return this.request("POST", `/admin/agreements/${id}/send-for-signature`, { expiresInDays: params?.expires_in_days });
|
|
1180
1261
|
}
|
|
1181
1262
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1263
|
+
/** Sync fit: GET /admin/sync-fit, the same read as /inspect/sync. */
|
|
1264
|
+
class SyncFitResource extends BaseResource {
|
|
1265
|
+
async query(params) {
|
|
1266
|
+
const q = new URLSearchParams();
|
|
1267
|
+
if (params?.work_id)
|
|
1268
|
+
q.set("work_id", params.work_id);
|
|
1269
|
+
if (params?.screen_project_id)
|
|
1270
|
+
q.set("screen_project_id", params.screen_project_id);
|
|
1271
|
+
if (params?.song_facts)
|
|
1272
|
+
q.set("song_facts", "true");
|
|
1273
|
+
if (params?.limit)
|
|
1274
|
+
q.set("limit", String(params.limit));
|
|
1275
|
+
if (params?.offset)
|
|
1276
|
+
q.set("offset", String(params.offset));
|
|
1277
|
+
const qs = q.toString();
|
|
1278
|
+
const res = await this.request("GET", `/admin/sync-fit${qs ? `?${qs}` : ""}`);
|
|
1279
|
+
if (res && typeof res === "object" && "data" in res && res.data) {
|
|
1280
|
+
return res.data;
|
|
1281
|
+
}
|
|
1282
|
+
return res;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1186
1285
|
class SyncPlacementsResource extends BaseResource {
|
|
1187
1286
|
async list(params) {
|
|
1188
1287
|
const queryParams = new URLSearchParams();
|
|
@@ -1725,6 +1824,16 @@ class EnrichmentResource extends BaseResource {
|
|
|
1725
1824
|
async bulkRejectProposals(input) {
|
|
1726
1825
|
return this.request("POST", "/admin/enrichment-proposals/bulk-reject", input);
|
|
1727
1826
|
}
|
|
1827
|
+
/**
|
|
1828
|
+
* What bulkRejectProposals would reject, without rejecting: the route
|
|
1829
|
+
* resolves the same pending set the real call would (row-cap T3).
|
|
1830
|
+
*/
|
|
1831
|
+
async previewBulkRejectProposals(input) {
|
|
1832
|
+
return this.request("POST", "/admin/enrichment-proposals/bulk-reject", {
|
|
1833
|
+
...input,
|
|
1834
|
+
dry_run: true,
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1728
1837
|
/**
|
|
1729
1838
|
* Apply many pending proposals in one call — the accept side of the
|
|
1730
1839
|
* /inspect/found queue.
|
|
@@ -2548,7 +2657,7 @@ class ExportResource extends BaseResource {
|
|
|
2548
2657
|
// request() unconditionally calls response.json(), which throws parsing
|
|
2549
2658
|
// a CSV body — every pro/distributor/nro call 500'd with INTERNAL_ERROR
|
|
2550
2659
|
// (pica_export_catalog_csv: 5/5 prod errors, ops audit 2026-07-04).
|
|
2551
|
-
// requestText()
|
|
2660
|
+
// requestText() serves ImportResource.getTemplate() too, for
|
|
2552
2661
|
// the same response shape.
|
|
2553
2662
|
if (params.format === "sync-pdf") {
|
|
2554
2663
|
return this.request("GET", path);
|
|
@@ -2847,14 +2956,10 @@ class ImportResource extends BaseResource {
|
|
|
2847
2956
|
});
|
|
2848
2957
|
}
|
|
2849
2958
|
async getTemplate(domain) {
|
|
2850
|
-
//
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
});
|
|
2855
|
-
if (!response.ok)
|
|
2856
|
-
throw new Error(`Template fetch failed: ${response.status}`);
|
|
2857
|
-
return response.text();
|
|
2959
|
+
// The route answers text/csv, so requestText(), not request(). It was a
|
|
2960
|
+
// bare fetch() with no preview-protection bypass header, so on a Vercel
|
|
2961
|
+
// preview it never reached the route (2026-10-06).
|
|
2962
|
+
return this.requestText("GET", `/admin/import/template?domain=${encodeURIComponent(domain)}`);
|
|
2858
2963
|
}
|
|
2859
2964
|
/**
|
|
2860
2965
|
* Emails that reached the organisation's forwarding address, newest first,
|
|
@@ -2937,6 +3042,40 @@ class CollaboratorsResource extends BaseResource {
|
|
|
2937
3042
|
const qs = qp.toString();
|
|
2938
3043
|
return this.request("GET", `/admin/collaborators/invites${qs ? `?${qs}` : ""}`);
|
|
2939
3044
|
}
|
|
3045
|
+
/**
|
|
3046
|
+
* One page of the invites this org sent WITH the real total and has_more
|
|
3047
|
+
* (row-cap T3). `listInvites()` is typed as an array but the route nests
|
|
3048
|
+
* its rows as `data: { data, count }`, so it never carried a usable total.
|
|
3049
|
+
*/
|
|
3050
|
+
async listInvitesPage(params) {
|
|
3051
|
+
const qp = new URLSearchParams();
|
|
3052
|
+
if (params?.status)
|
|
3053
|
+
qp.set("status", params.status);
|
|
3054
|
+
if (params?.workId)
|
|
3055
|
+
qp.set("workId", params.workId);
|
|
3056
|
+
if (params?.limit !== undefined)
|
|
3057
|
+
qp.set("limit", String(params.limit));
|
|
3058
|
+
if (params?.offset)
|
|
3059
|
+
qp.set("offset", String(params.offset));
|
|
3060
|
+
const qs = qp.toString();
|
|
3061
|
+
return this.requestNestedPage(`/admin/collaborators/invites${qs ? `?${qs}` : ""}`, params?.offset ?? 0);
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
3064
|
+
* Reads a collaborator list route, whose rows sit at `data.data` (kept
|
|
3065
|
+
* for published SDKs) with the total in the top-level `pagination`.
|
|
3066
|
+
*/
|
|
3067
|
+
async requestNestedPage(path, offset) {
|
|
3068
|
+
const json = await this.requestWithEnvelope("GET", path);
|
|
3069
|
+
const inner = json.data;
|
|
3070
|
+
const rows = Array.isArray(inner) ? inner : (inner?.data ?? []);
|
|
3071
|
+
const pagination = json.pagination;
|
|
3072
|
+
const total = pagination?.total ?? rows.length;
|
|
3073
|
+
return {
|
|
3074
|
+
data: rows,
|
|
3075
|
+
total,
|
|
3076
|
+
hasMore: pagination?.hasMore ?? offset + rows.length < total,
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
2940
3079
|
async resendInvite(inviteId) {
|
|
2941
3080
|
return this.request("POST", `/admin/collaborators/invites/${inviteId}/resend`);
|
|
2942
3081
|
}
|
|
@@ -2955,6 +3094,21 @@ class CollaboratorsResource extends BaseResource {
|
|
|
2955
3094
|
const res = await this.request("GET", `/admin/collaborators/incoming${qs ? `?${qs}` : ""}`);
|
|
2956
3095
|
return res.data;
|
|
2957
3096
|
}
|
|
3097
|
+
/**
|
|
3098
|
+
* One page of the invites addressed to the caller WITH the real total and
|
|
3099
|
+
* has_more (row-cap T3: `listIncoming()` returns only the rows).
|
|
3100
|
+
*/
|
|
3101
|
+
async listIncomingPage(params) {
|
|
3102
|
+
const qp = new URLSearchParams();
|
|
3103
|
+
if (params?.status)
|
|
3104
|
+
qp.set("status", params.status);
|
|
3105
|
+
if (params?.limit !== undefined)
|
|
3106
|
+
qp.set("limit", String(params.limit));
|
|
3107
|
+
if (params?.offset)
|
|
3108
|
+
qp.set("offset", String(params.offset));
|
|
3109
|
+
const qs = qp.toString();
|
|
3110
|
+
return this.requestNestedPage(`/admin/collaborators/incoming${qs ? `?${qs}` : ""}`, params?.offset ?? 0);
|
|
3111
|
+
}
|
|
2958
3112
|
/**
|
|
2959
3113
|
* ADR-157 warm path — accept an invite addressed to the authenticated
|
|
2960
3114
|
* user, and flip its status to confirmed. The credit write is
|
|
@@ -3186,6 +3340,10 @@ class DirectoryResource extends BaseResource {
|
|
|
3186
3340
|
async updateSettings(updates) {
|
|
3187
3341
|
return this.request("PATCH", "/admin/directory/settings", updates);
|
|
3188
3342
|
}
|
|
3343
|
+
/** Listed songs with lyrics and topics on, but no approved topic yet. */
|
|
3344
|
+
async untaggedThemes() {
|
|
3345
|
+
return this.request("GET", "/admin/directory/themes/untagged");
|
|
3346
|
+
}
|
|
3189
3347
|
}
|
|
3190
3348
|
class ProjectsResource extends BaseResource {
|
|
3191
3349
|
async list(params) {
|
|
@@ -3432,6 +3590,21 @@ class RecordingSplitsResource extends BaseResource {
|
|
|
3432
3590
|
}
|
|
3433
3591
|
}
|
|
3434
3592
|
class PublishersResource extends BaseResource {
|
|
3593
|
+
/**
|
|
3594
|
+
* One page of the publisher registry with an honest has_more. The total
|
|
3595
|
+
* is null: the route does not count the platform-wide registry.
|
|
3596
|
+
*/
|
|
3597
|
+
async listPage(params) {
|
|
3598
|
+
const queryParams = new URLSearchParams();
|
|
3599
|
+
if (params?.query)
|
|
3600
|
+
queryParams.set("query", params.query);
|
|
3601
|
+
if (params?.limit)
|
|
3602
|
+
queryParams.set("limit", String(params.limit));
|
|
3603
|
+
if (params?.offset)
|
|
3604
|
+
queryParams.set("offset", String(params.offset));
|
|
3605
|
+
const qs = queryParams.toString();
|
|
3606
|
+
return this.requestPaginated("GET", `/admin/publishers${qs ? `?${qs}` : ""}`);
|
|
3607
|
+
}
|
|
3435
3608
|
async list(params) {
|
|
3436
3609
|
const queryParams = new URLSearchParams();
|
|
3437
3610
|
if (params?.query)
|
|
@@ -3446,6 +3619,25 @@ class PublishersResource extends BaseResource {
|
|
|
3446
3619
|
}
|
|
3447
3620
|
}
|
|
3448
3621
|
class LabelsResource extends BaseResource {
|
|
3622
|
+
/**
|
|
3623
|
+
* One page of label/distributor organisations with an honest has_more.
|
|
3624
|
+
* The total is null: the route does not count platform-wide rows.
|
|
3625
|
+
*/
|
|
3626
|
+
async listPage(params) {
|
|
3627
|
+
const queryParams = new URLSearchParams();
|
|
3628
|
+
if (params?.query)
|
|
3629
|
+
queryParams.set("query", params.query);
|
|
3630
|
+
if (params?.limit)
|
|
3631
|
+
queryParams.set("limit", String(params.limit));
|
|
3632
|
+
if (params?.offset)
|
|
3633
|
+
queryParams.set("offset", String(params.offset));
|
|
3634
|
+
if (params?.org_type)
|
|
3635
|
+
queryParams.set("org_type", params.org_type);
|
|
3636
|
+
if (params?.ids?.length)
|
|
3637
|
+
queryParams.set("ids", params.ids.join(","));
|
|
3638
|
+
const qs = queryParams.toString();
|
|
3639
|
+
return this.requestPaginated("GET", `/admin/labels${qs ? `?${qs}` : ""}`);
|
|
3640
|
+
}
|
|
3449
3641
|
async list(params) {
|
|
3450
3642
|
const queryParams = new URLSearchParams();
|
|
3451
3643
|
if (params?.query)
|
|
@@ -3474,14 +3666,20 @@ class LabelsResource extends BaseResource {
|
|
|
3474
3666
|
return (res ?? null);
|
|
3475
3667
|
}
|
|
3476
3668
|
}
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3669
|
+
function partyClaimListPath(params) {
|
|
3670
|
+
const queryParams = new URLSearchParams();
|
|
3671
|
+
if (params?.target_organisation_id) {
|
|
3672
|
+
queryParams.set("target_organisation_id", params.target_organisation_id);
|
|
3673
|
+
}
|
|
3674
|
+
if (params?.status)
|
|
3675
|
+
queryParams.set("status", params.status);
|
|
3676
|
+
if (params?.limit)
|
|
3677
|
+
queryParams.set("limit", String(params.limit));
|
|
3678
|
+
if (params?.offset)
|
|
3679
|
+
queryParams.set("offset", String(params.offset));
|
|
3680
|
+
const qs = queryParams.toString();
|
|
3681
|
+
return `/admin/party-claims${qs ? `?${qs}` : ""}`;
|
|
3682
|
+
}
|
|
3485
3683
|
class PartyClaimsResource extends BaseResource {
|
|
3486
3684
|
async invite(params) {
|
|
3487
3685
|
const res = await this.request("POST", "/admin/party-claims", params);
|
|
@@ -3493,20 +3691,19 @@ class PartyClaimsResource extends BaseResource {
|
|
|
3493
3691
|
return (res ?? null);
|
|
3494
3692
|
}
|
|
3495
3693
|
async list(params) {
|
|
3496
|
-
const
|
|
3497
|
-
if (params?.target_organisation_id) {
|
|
3498
|
-
queryParams.set("target_organisation_id", params.target_organisation_id);
|
|
3499
|
-
}
|
|
3500
|
-
if (params?.status)
|
|
3501
|
-
queryParams.set("status", params.status);
|
|
3502
|
-
if (params?.limit)
|
|
3503
|
-
queryParams.set("limit", String(params.limit));
|
|
3504
|
-
const qs = queryParams.toString();
|
|
3505
|
-
const res = await this.request("GET", `/admin/party-claims${qs ? `?${qs}` : ""}`);
|
|
3694
|
+
const res = await this.request("GET", partyClaimListPath(params));
|
|
3506
3695
|
if (Array.isArray(res))
|
|
3507
3696
|
return res;
|
|
3508
3697
|
return (res?.data ?? []);
|
|
3509
3698
|
}
|
|
3699
|
+
/**
|
|
3700
|
+
* One page of sent claim invites WITH the real total and has_more
|
|
3701
|
+
* (row-cap T3): `list()` returns the bare array, so callers reported the
|
|
3702
|
+
* page length as the total.
|
|
3703
|
+
*/
|
|
3704
|
+
async listPage(params) {
|
|
3705
|
+
return this.requestPaginated("GET", partyClaimListPath(params));
|
|
3706
|
+
}
|
|
3510
3707
|
}
|
|
3511
3708
|
/**
|
|
3512
3709
|
* Claim your profile: invite people to claim their profile (a preview
|
|
@@ -3538,25 +3735,38 @@ class ProfileClaimsResource extends BaseResource {
|
|
|
3538
3735
|
};
|
|
3539
3736
|
}
|
|
3540
3737
|
}
|
|
3738
|
+
function releaseListPath(params) {
|
|
3739
|
+
const parts = [];
|
|
3740
|
+
if (params?.limit != null)
|
|
3741
|
+
parts.push(`limit=${params.limit}`);
|
|
3742
|
+
if (params?.offset != null)
|
|
3743
|
+
parts.push(`offset=${params.offset}`);
|
|
3744
|
+
if (params?.query)
|
|
3745
|
+
parts.push(`query=${encodeURIComponent(params.query)}`);
|
|
3746
|
+
if (params?.upc)
|
|
3747
|
+
parts.push(`upc=${encodeURIComponent(params.upc)}`);
|
|
3748
|
+
if (params?.spotify_album_id)
|
|
3749
|
+
parts.push(`spotify_album_id=${encodeURIComponent(params.spotify_album_id)}`);
|
|
3750
|
+
if (params?.release_date_from)
|
|
3751
|
+
parts.push(`release_date_from=${encodeURIComponent(params.release_date_from)}`);
|
|
3752
|
+
if (params?.release_date_to)
|
|
3753
|
+
parts.push(`release_date_to=${encodeURIComponent(params.release_date_to)}`);
|
|
3754
|
+
const qs = parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
3755
|
+
return `/admin/releases${qs}`;
|
|
3756
|
+
}
|
|
3541
3757
|
class ReleasesResource extends BaseResource {
|
|
3758
|
+
/** One page of releases (the bare array; see listPage for the real total). */
|
|
3542
3759
|
async list(params) {
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
parts.push(`spotify_album_id=${encodeURIComponent(params.spotify_album_id)}`);
|
|
3554
|
-
if (params?.release_date_from)
|
|
3555
|
-
parts.push(`release_date_from=${encodeURIComponent(params.release_date_from)}`);
|
|
3556
|
-
if (params?.release_date_to)
|
|
3557
|
-
parts.push(`release_date_to=${encodeURIComponent(params.release_date_to)}`);
|
|
3558
|
-
const qs = parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
3559
|
-
return this.request("GET", `/admin/releases${qs}`);
|
|
3760
|
+
return this.request("GET", releaseListPath(params));
|
|
3761
|
+
}
|
|
3762
|
+
/**
|
|
3763
|
+
* One page of releases WITH the real total and has_more (row-cap T3):
|
|
3764
|
+
* `list()` returns the bare array, so callers guessed has_more from "the
|
|
3765
|
+
* page came back full". `total` is null when the server cannot count (a
|
|
3766
|
+
* title search past its match cap); has_more is the server's own answer.
|
|
3767
|
+
*/
|
|
3768
|
+
async listPage(params) {
|
|
3769
|
+
return this.requestPaginated("GET", releaseListPath(params));
|
|
3560
3770
|
}
|
|
3561
3771
|
async get(id) {
|
|
3562
3772
|
return this.request("GET", `/admin/releases/${id}`);
|
|
@@ -3675,15 +3885,27 @@ class ReleasesResource extends BaseResource {
|
|
|
3675
3885
|
return this.request("POST", "/admin/releases/create-with-tracks", data);
|
|
3676
3886
|
}
|
|
3677
3887
|
}
|
|
3888
|
+
function sessionListPath(params) {
|
|
3889
|
+
const query = new URLSearchParams();
|
|
3890
|
+
if (params?.limit)
|
|
3891
|
+
query.set("limit", String(params.limit));
|
|
3892
|
+
if (params?.offset)
|
|
3893
|
+
query.set("offset", String(params.offset));
|
|
3894
|
+
const qs = query.toString();
|
|
3895
|
+
return `/admin/sessions${qs ? `?${qs}` : ""}`;
|
|
3896
|
+
}
|
|
3678
3897
|
class SessionsResource extends BaseResource {
|
|
3898
|
+
/** One page of upcoming sessions (see listPage for the real total). */
|
|
3679
3899
|
async list(params) {
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3900
|
+
return this.request("GET", sessionListPath(params));
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* One page of the next 7 days' sessions WITH the real total and has_more
|
|
3904
|
+
* (row-cap T3): `list()` returns the bare array, so callers could only
|
|
3905
|
+
* guess has_more from "the page came back full".
|
|
3906
|
+
*/
|
|
3907
|
+
async listPage(params) {
|
|
3908
|
+
return this.requestPaginated("GET", sessionListPath(params));
|
|
3687
3909
|
}
|
|
3688
3910
|
async get(id) {
|
|
3689
3911
|
return this.request("GET", `/admin/sessions/${id}`);
|
|
@@ -4038,6 +4260,11 @@ class CustodyResource extends BaseResource {
|
|
|
4038
4260
|
async respond(body) {
|
|
4039
4261
|
return this.request("POST", "/admin/custody/respond", body);
|
|
4040
4262
|
}
|
|
4263
|
+
/**
|
|
4264
|
+
* Custody claims, one page per direction. Since row-cap T3 the result also
|
|
4265
|
+
* carries `totals: { incoming, outgoing }` (null for a direction not asked
|
|
4266
|
+
* for), `has_more: { incoming, outgoing }`, `limit` and `offset`.
|
|
4267
|
+
*/
|
|
4041
4268
|
async list(params) {
|
|
4042
4269
|
const qp = new URLSearchParams();
|
|
4043
4270
|
if (params?.direction)
|
|
@@ -4046,6 +4273,8 @@ class CustodyResource extends BaseResource {
|
|
|
4046
4273
|
qp.set("status", params.status);
|
|
4047
4274
|
if (params?.limit !== undefined)
|
|
4048
4275
|
qp.set("limit", String(params.limit));
|
|
4276
|
+
if (params?.offset)
|
|
4277
|
+
qp.set("offset", String(params.offset));
|
|
4049
4278
|
const qs = qp.toString();
|
|
4050
4279
|
return this.request("GET", `/admin/custody/list${qs ? `?${qs}` : ""}`);
|
|
4051
4280
|
}
|
|
@@ -4264,6 +4493,7 @@ export class PicaClient {
|
|
|
4264
4493
|
agreements;
|
|
4265
4494
|
// ADR-222 — sync placements (W3, paired with W4 REST routes).
|
|
4266
4495
|
syncPlacements;
|
|
4496
|
+
syncFit;
|
|
4267
4497
|
workspace;
|
|
4268
4498
|
memory;
|
|
4269
4499
|
audit;
|
|
@@ -4346,24 +4576,15 @@ export class PicaClient {
|
|
|
4346
4576
|
// Registration Integrity Floor (spec 2026-07-09, Task 8).
|
|
4347
4577
|
integrity;
|
|
4348
4578
|
async catalogStats(params) {
|
|
4349
|
-
const baseUrl = this.works["baseUrl"];
|
|
4350
|
-
const apiKey = this.works["apiKey"];
|
|
4351
4579
|
const qp = new URLSearchParams();
|
|
4352
4580
|
if (params?.entity)
|
|
4353
4581
|
qp.set("entity", params.entity);
|
|
4354
4582
|
if (params?.group_by)
|
|
4355
4583
|
qp.set("group_by", params.group_by);
|
|
4356
4584
|
const suffix = qp.toString() ? `?${qp.toString()}` : "";
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
"Content-Type": "application/json",
|
|
4361
|
-
},
|
|
4362
|
-
});
|
|
4363
|
-
if (!res.ok)
|
|
4364
|
-
throw new Error(`Catalog stats failed: ${res.status}`);
|
|
4365
|
-
const json = (await res.json());
|
|
4366
|
-
return (json.data || json);
|
|
4585
|
+
// Through the shared request path (bypass header, timeout, retry, typed
|
|
4586
|
+
// errors) rather than a bare fetch() that skipped all four (2026-10-06).
|
|
4587
|
+
return this.works["request"]("GET", `/admin/catalog/stats${suffix}`);
|
|
4367
4588
|
}
|
|
4368
4589
|
constructor(config) {
|
|
4369
4590
|
if (!config.apiKey) {
|
|
@@ -4385,6 +4606,7 @@ export class PicaClient {
|
|
|
4385
4606
|
this.multimedia = new MultimediaResource(baseUrl, config.apiKey, debug);
|
|
4386
4607
|
this.agreements = new AgreementsResource(baseUrl, config.apiKey, debug);
|
|
4387
4608
|
this.syncPlacements = new SyncPlacementsResource(baseUrl, config.apiKey, debug);
|
|
4609
|
+
this.syncFit = new SyncFitResource(baseUrl, config.apiKey, debug);
|
|
4388
4610
|
this.workspace = new WorkspaceResource(baseUrl, config.apiKey, debug);
|
|
4389
4611
|
this.memory = new MemoryResource(baseUrl, config.apiKey, debug);
|
|
4390
4612
|
this.audit = new AuditResource(baseUrl, config.apiKey, debug);
|