@minipim/sdk 0.4.1 → 0.6.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/dist/openapi.d.ts CHANGED
@@ -166,6 +166,7 @@ interface paths {
166
166
  default_locale?: string;
167
167
  default_channel?: string;
168
168
  default_currency?: string;
169
+ content_pages_enabled?: boolean;
169
170
  };
170
171
  initialAdmin?: {
171
172
  /** Format: email */
@@ -258,7 +259,10 @@ interface paths {
258
259
  path?: never;
259
260
  cookie?: never;
260
261
  };
261
- /** Get the current tenant organization */
262
+ /**
263
+ * Get the current tenant organization
264
+ * @description Includes `features`, the resolved state of optional features for this org. `entitled` is the plan-level answer; `enabled` is the org’s own preference. A feature is usable only when both are true.
265
+ */
262
266
  get: {
263
267
  parameters: {
264
268
  query?: never;
@@ -286,6 +290,12 @@ interface paths {
286
290
  createdAt: string;
287
291
  /** Format: date-time */
288
292
  updatedAt: string;
293
+ features: {
294
+ content_pages: {
295
+ entitled: boolean;
296
+ enabled: boolean;
297
+ };
298
+ };
289
299
  };
290
300
  };
291
301
  };
@@ -328,6 +338,7 @@ interface paths {
328
338
  default_locale?: string;
329
339
  default_channel?: string;
330
340
  default_currency?: string;
341
+ content_pages_enabled?: boolean;
331
342
  };
332
343
  };
333
344
  };
@@ -602,10 +613,15 @@ interface paths {
602
613
  path?: never;
603
614
  cookie?: never;
604
615
  };
605
- /** List attributes */
616
+ /**
617
+ * List attributes
618
+ * @description Defaults to product attributes. Pass `entityKind=content` for content-page fields.
619
+ */
606
620
  get: {
607
621
  parameters: {
608
- query?: never;
622
+ query?: {
623
+ entityKind?: "product" | "content";
624
+ };
609
625
  header?: never;
610
626
  path?: never;
611
627
  cookie?: never;
@@ -628,6 +644,8 @@ interface paths {
628
644
  /** Format: date-time */
629
645
  updatedAt: string;
630
646
  code: string;
647
+ /** @enum {string} */
648
+ entityKind: "product" | "content";
631
649
  label: (string | number | boolean | ("null" | null)) | unknown[] | {
632
650
  [key: string]: unknown;
633
651
  };
@@ -669,6 +687,11 @@ interface paths {
669
687
  content: {
670
688
  "application/json": {
671
689
  code: string;
690
+ /**
691
+ * @default product
692
+ * @enum {string}
693
+ */
694
+ entityKind?: "product" | "content";
672
695
  label: {
673
696
  [key: string]: string;
674
697
  };
@@ -710,6 +733,8 @@ interface paths {
710
733
  /** Format: date-time */
711
734
  updatedAt: string;
712
735
  code: string;
736
+ /** @enum {string} */
737
+ entityKind: "product" | "content";
713
738
  label: (string | number | boolean | ("null" | null)) | unknown[] | {
714
739
  [key: string]: unknown;
715
740
  };
@@ -791,6 +816,8 @@ interface paths {
791
816
  /** Format: date-time */
792
817
  updatedAt: string;
793
818
  code: string;
819
+ /** @enum {string} */
820
+ entityKind: "product" | "content";
794
821
  label: (string | number | boolean | ("null" | null)) | unknown[] | {
795
822
  [key: string]: unknown;
796
823
  };
@@ -887,6 +914,11 @@ interface paths {
887
914
  content: {
888
915
  "application/json": {
889
916
  code?: string;
917
+ /**
918
+ * @default product
919
+ * @enum {string}
920
+ */
921
+ entityKind?: "product" | "content";
890
922
  label?: {
891
923
  [key: string]: string;
892
924
  };
@@ -928,6 +960,8 @@ interface paths {
928
960
  /** Format: date-time */
929
961
  updatedAt: string;
930
962
  code: string;
963
+ /** @enum {string} */
964
+ entityKind: "product" | "content";
931
965
  label: (string | number | boolean | ("null" | null)) | unknown[] | {
932
966
  [key: string]: unknown;
933
967
  };
@@ -992,10 +1026,15 @@ interface paths {
992
1026
  path?: never;
993
1027
  cookie?: never;
994
1028
  };
995
- /** List attribute families */
1029
+ /**
1030
+ * List attribute families
1031
+ * @description Defaults to product families. Pass `entityKind=content` for content-page types.
1032
+ */
996
1033
  get: {
997
1034
  parameters: {
998
- query?: never;
1035
+ query?: {
1036
+ entityKind?: "product" | "content";
1037
+ };
999
1038
  header?: never;
1000
1039
  path?: never;
1001
1040
  cookie?: never;
@@ -1018,6 +1057,8 @@ interface paths {
1018
1057
  /** Format: date-time */
1019
1058
  updatedAt: string;
1020
1059
  code: string;
1060
+ /** @enum {string} */
1061
+ entityKind: "product" | "content";
1021
1062
  label: (string | number | boolean | ("null" | null)) | unknown[] | {
1022
1063
  [key: string]: unknown;
1023
1064
  };
@@ -1040,6 +1081,11 @@ interface paths {
1040
1081
  content: {
1041
1082
  "application/json": {
1042
1083
  code: string;
1084
+ /**
1085
+ * @default product
1086
+ * @enum {string}
1087
+ */
1088
+ entityKind?: "product" | "content";
1043
1089
  label: {
1044
1090
  [key: string]: string;
1045
1091
  };
@@ -1064,6 +1110,8 @@ interface paths {
1064
1110
  /** Format: date-time */
1065
1111
  updatedAt: string;
1066
1112
  code: string;
1113
+ /** @enum {string} */
1114
+ entityKind: "product" | "content";
1067
1115
  label: (string | number | boolean | ("null" | null)) | unknown[] | {
1068
1116
  [key: string]: unknown;
1069
1117
  };
@@ -1129,6 +1177,8 @@ interface paths {
1129
1177
  /** Format: date-time */
1130
1178
  updatedAt: string;
1131
1179
  code: string;
1180
+ /** @enum {string} */
1181
+ entityKind: "product" | "content";
1132
1182
  label: (string | number | boolean | ("null" | null)) | unknown[] | {
1133
1183
  [key: string]: unknown;
1134
1184
  };
@@ -1258,6 +1308,8 @@ interface paths {
1258
1308
  /** Format: date-time */
1259
1309
  updatedAt: string;
1260
1310
  code: string;
1311
+ /** @enum {string} */
1312
+ entityKind: "product" | "content";
1261
1313
  label: (string | number | boolean | ("null" | null)) | unknown[] | {
1262
1314
  [key: string]: unknown;
1263
1315
  };
@@ -1468,6 +1520,51 @@ interface paths {
1468
1520
  * Iterate by reading `hasMore`. Pass `?withTotal=true` to add a `total`
1469
1521
  * field (costs one COUNT query). Default `limit` is 50, **max 200**.
1470
1522
  * Reconcile after downtime with `?updatedSince=<iso8601>`.
1523
+ *
1524
+ * ### Filtering by category
1525
+ *
1526
+ * `?categoryId=` is **self-only**: it matches products assigned directly to
1527
+ * that category and nothing filed beneath it. Many catalogs assign products
1528
+ * only to leaf categories, so a self-only filter on a parent legitimately
1529
+ * returns zero rows.
1530
+ *
1531
+ * Add `?includeDescendants=true` to match the category **and its entire
1532
+ * subtree**, resolved server-side in a single query:
1533
+ *
1534
+ * ```bash
1535
+ * # Every product anywhere under "Backdrops", paginated normally
1536
+ * curl 'https://api.minipim.com/v1/products?categoryId=8d2a1f3c-...&includeDescendants=true&limit=50'
1537
+ * ```
1538
+ *
1539
+ * Prefer this over fetching the tree and issuing one request per descendant —
1540
+ * it is one round trip, and it paginates as a single result set.
1541
+ *
1542
+ * ### Sorting and filtering by price (or any numeric attribute)
1543
+ *
1544
+ * Price is not a column — it is an attribute, and its **code is per-organization
1545
+ * data**, so there is no `sortBy=price`. Name the attribute instead:
1546
+ *
1547
+ * ```bash
1548
+ * # Cheapest first
1549
+ * curl '.../v1/products?sortBy=attribute&sortAttribute=price&sortDir=asc'
1550
+ *
1551
+ * # Between $10.00 and $50.00 — bounds are INTEGER CENTS for money attributes
1552
+ * curl '.../v1/products?filterAttribute=price&filterMin=1000&filterMax=5000'
1553
+ * ```
1554
+ *
1555
+ * - Works on `money`, `number` and `decimal` attributes. `measurement` is
1556
+ * refused: its values carry a unit, so ordering raw amounts would rank 5 g
1557
+ * above 2 kg.
1558
+ * - For `money`, bounds and ordering use `amount_cents`. **`filterMin=1000`
1559
+ * is $10.00.** No currency conversion happens — mixed-currency catalogs are
1560
+ * compared by number, so scope the query to one currency if that matters.
1561
+ * - The value read is the one at the **default scope** (no locale, no channel).
1562
+ * A product with no default-scope value sorts **last** in both directions and
1563
+ * is excluded by either bound — it has no price, rather than a price of zero.
1564
+ * - Unknown code, or a code of the wrong type, is a **422** naming the problem.
1565
+ * It never silently degrades to `name` ordering.
1566
+ * - `sortAttribute` and `filterAttribute` are independent; set both to the
1567
+ * same code for the usual "cheapest first, within a budget" query.
1471
1568
  */
1472
1569
  get: {
1473
1570
  parameters: {
@@ -1476,15 +1573,20 @@ interface paths {
1476
1573
  q?: string;
1477
1574
  familyId?: string;
1478
1575
  categoryId?: string;
1576
+ includeDescendants?: boolean | ("true" | "false" | "1" | "0");
1479
1577
  brand?: string;
1480
1578
  tag?: string | string[];
1481
1579
  connectorId?: string;
1482
1580
  updatedSince?: string;
1483
- sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance";
1581
+ sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance" | "attribute";
1582
+ sortAttribute?: string;
1583
+ filterAttribute?: string;
1584
+ filterMin?: number;
1585
+ filterMax?: number;
1484
1586
  sortDir?: "asc" | "desc";
1485
1587
  limit?: number;
1486
1588
  offset?: number;
1487
- withTotal?: boolean;
1589
+ withTotal?: boolean | ("true" | "false" | "1" | "0");
1488
1590
  locale?: string;
1489
1591
  channel?: string;
1490
1592
  };
@@ -1662,11 +1764,16 @@ interface paths {
1662
1764
  q?: string;
1663
1765
  familyId?: string;
1664
1766
  categoryId?: string;
1767
+ includeDescendants?: boolean | ("true" | "false" | "1" | "0");
1665
1768
  brand?: string;
1666
1769
  tag?: string | string[];
1667
1770
  connectorId?: string;
1668
1771
  updatedSince?: string;
1669
- sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance";
1772
+ sortBy?: "name" | "slug" | "status" | "updatedAt" | "createdAt" | "relevance" | "attribute";
1773
+ sortAttribute?: string;
1774
+ filterAttribute?: string;
1775
+ filterMin?: number;
1776
+ filterMax?: number;
1670
1777
  sortDir?: "asc" | "desc";
1671
1778
  locale?: string;
1672
1779
  channel?: string;
@@ -3397,7 +3504,7 @@ interface paths {
3397
3504
  patch?: never;
3398
3505
  trace?: never;
3399
3506
  };
3400
- "/v1/channels": {
3507
+ "/v1/content": {
3401
3508
  parameters: {
3402
3509
  query?: never;
3403
3510
  header?: never;
@@ -3405,48 +3512,22 @@ interface paths {
3405
3512
  cookie?: never;
3406
3513
  };
3407
3514
  /**
3408
- * List channels
3409
- * @description Channels are the surfaces your catalog publishes to
3410
- * storefronts, marketplaces, ad platforms. Used in attribute-value resolution
3411
- * together with locale: `(locale, channel)` keys each value. `settings` is
3412
- * free-form per-channel config; `settings.required_attributes` drives the
3413
- * per-channel completeness score on products.
3414
- *
3415
- * **Example response**
3416
- * ```json
3417
- * [
3418
- * {
3419
- * "id": "3ac5ab02-93fd-49b7-91a3-681fc36eabdd",
3420
- * "code": "headless-main",
3421
- * "name": "Headless Storefront",
3422
- * "locales": ["en_US"],
3423
- * "settings": null,
3424
- * "createdAt": "2026-06-02T22:28:01.926Z",
3425
- * "updatedAt": "2026-06-02T22:28:01.926Z"
3426
- * },
3427
- * {
3428
- * "id": "f1c2b3a4-...",
3429
- * "code": "bc_pdo6b28kp8_default",
3430
- * "name": "BigCommerce (default)",
3431
- * "locales": ["en_US"],
3432
- * "settings": {
3433
- * "bc_channel_id": 1,
3434
- * "bc_store_hash": "pdo6b28kp8",
3435
- * "required_attributes": ["description", "price", "brand", "weight"]
3436
- * },
3437
- * "createdAt": "...",
3438
- * "updatedAt": "..."
3439
- * }
3440
- * ]
3441
- * ```
3442
- *
3443
- * Channels seeded by connectors carry `bc_*` / `shopify_*` keys in
3444
- * `settings` so they can be identified and scoped-wiped on connector
3445
- * uninstall — leave those alone unless you know what you're doing.
3515
+ * List content pages
3516
+ * @description Filters and pagination mirror `GET /v1/products`. `tag` is repeatable and matches pages carrying ALL listed tags.
3446
3517
  */
3447
3518
  get: {
3448
3519
  parameters: {
3449
- query?: never;
3520
+ query?: {
3521
+ status?: "draft" | "active" | "archived";
3522
+ q?: string;
3523
+ familyId?: string;
3524
+ categoryId?: string;
3525
+ channelId?: string;
3526
+ tag?: string | string[];
3527
+ updatedSince?: string;
3528
+ limit?: number;
3529
+ offset?: number;
3530
+ };
3450
3531
  header?: never;
3451
3532
  path?: never;
3452
3533
  cookie?: never;
@@ -3460,27 +3541,63 @@ interface paths {
3460
3541
  };
3461
3542
  content: {
3462
3543
  "application/json": {
3463
- /** Format: uuid */
3464
- id: string;
3465
- /** Format: uuid */
3466
- organizationId: string;
3467
- /** Format: date-time */
3468
- createdAt: string;
3469
- /** Format: date-time */
3470
- updatedAt: string;
3471
- code: string;
3472
- name: string;
3473
- locales: string[];
3474
- settings: ((string | number | boolean | ("null" | null)) | unknown[] | {
3475
- [key: string]: unknown;
3476
- }) | null;
3477
- }[];
3544
+ data: {
3545
+ /** Format: uuid */
3546
+ id: string;
3547
+ /** Format: uuid */
3548
+ organizationId: string;
3549
+ /** Format: date-time */
3550
+ createdAt: string;
3551
+ /** Format: date-time */
3552
+ updatedAt: string;
3553
+ /** Format: uuid */
3554
+ familyId: string;
3555
+ title: string;
3556
+ slug: string;
3557
+ /** @enum {string} */
3558
+ status: "draft" | "active" | "archived";
3559
+ /** Format: date-time */
3560
+ publishedAt: string | null;
3561
+ /** Format: uuid */
3562
+ channelId: string | null;
3563
+ attributes: (string | number | boolean | ("null" | null)) | unknown[] | {
3564
+ [key: string]: unknown;
3565
+ };
3566
+ tags: string[];
3567
+ /** Format: uuid */
3568
+ createdBy: string | null;
3569
+ /** Format: uuid */
3570
+ updatedBy: string | null;
3571
+ }[];
3572
+ limit: number;
3573
+ offset: number;
3574
+ hasMore?: boolean;
3575
+ total?: number;
3576
+ };
3577
+ };
3578
+ };
3579
+ /** @description Default Response */
3580
+ 403: {
3581
+ headers: {
3582
+ [name: string]: unknown;
3583
+ };
3584
+ content: {
3585
+ "application/json": {
3586
+ error: {
3587
+ code: string;
3588
+ message: string;
3589
+ details?: unknown;
3590
+ };
3591
+ };
3478
3592
  };
3479
3593
  };
3480
3594
  };
3481
3595
  };
3482
3596
  put?: never;
3483
- /** Create a channel */
3597
+ /**
3598
+ * Create a content page
3599
+ * @description Validates `attributes` against the family at write time, exactly as products do — an undeclared attribute code is a 422. Required attributes are enforced only for `active` pages, so a draft may be saved incomplete. Emits `content_page.created`.
3600
+ */
3484
3601
  post: {
3485
3602
  parameters: {
3486
3603
  query?: never;
@@ -3491,13 +3608,29 @@ interface paths {
3491
3608
  requestBody: {
3492
3609
  content: {
3493
3610
  "application/json": {
3494
- code: string;
3495
- name: string;
3496
- /** @default [] */
3497
- locales?: string[];
3498
- settings?: {
3499
- [key: string]: unknown;
3611
+ /** Format: uuid */
3612
+ familyId: string;
3613
+ title: string;
3614
+ slug?: string;
3615
+ /**
3616
+ * @default draft
3617
+ * @enum {string}
3618
+ */
3619
+ status?: "draft" | "active" | "archived";
3620
+ /** Format: date-time */
3621
+ publishedAt?: string;
3622
+ /** Format: uuid */
3623
+ channelId?: string | null;
3624
+ /** @default {} */
3625
+ attributes?: {
3626
+ [key: string]: {
3627
+ locale: string | null;
3628
+ channel: string | null;
3629
+ value?: unknown;
3630
+ }[];
3500
3631
  };
3632
+ /** @default [] */
3633
+ tags?: string[];
3501
3634
  };
3502
3635
  };
3503
3636
  };
@@ -3517,17 +3650,29 @@ interface paths {
3517
3650
  createdAt: string;
3518
3651
  /** Format: date-time */
3519
3652
  updatedAt: string;
3520
- code: string;
3521
- name: string;
3522
- locales: string[];
3523
- settings: ((string | number | boolean | ("null" | null)) | unknown[] | {
3653
+ /** Format: uuid */
3654
+ familyId: string;
3655
+ title: string;
3656
+ slug: string;
3657
+ /** @enum {string} */
3658
+ status: "draft" | "active" | "archived";
3659
+ /** Format: date-time */
3660
+ publishedAt: string | null;
3661
+ /** Format: uuid */
3662
+ channelId: string | null;
3663
+ attributes: (string | number | boolean | ("null" | null)) | unknown[] | {
3524
3664
  [key: string]: unknown;
3525
- }) | null;
3665
+ };
3666
+ tags: string[];
3667
+ /** Format: uuid */
3668
+ createdBy: string | null;
3669
+ /** Format: uuid */
3670
+ updatedBy: string | null;
3526
3671
  };
3527
3672
  };
3528
3673
  };
3529
3674
  /** @description Default Response */
3530
- 422: {
3675
+ 403: {
3531
3676
  headers: {
3532
3677
  [name: string]: unknown;
3533
3678
  };
@@ -3541,50 +3686,23 @@ interface paths {
3541
3686
  };
3542
3687
  };
3543
3688
  };
3544
- };
3545
- };
3546
- delete?: never;
3547
- options?: never;
3548
- head?: never;
3549
- patch?: never;
3550
- trace?: never;
3551
- };
3552
- "/v1/channels/{id}": {
3553
- parameters: {
3554
- query?: never;
3555
- header?: never;
3556
- path?: never;
3557
- cookie?: never;
3558
- };
3559
- get?: never;
3560
- put?: never;
3561
- post?: never;
3562
- /**
3563
- * Delete a channel (surface)
3564
- * @description Removes a channel and its per-product publish records (ON DELETE CASCADE). The channel stops appearing as a completeness/readiness surface. Channel-scoped attribute values are stored in the product JSONB (not FKs) and are left untouched — they simply no longer resolve to a live channel. Use this to clear a surface left behind after uninstalling the connector that seeded it (channels are decoupled from connectors by design).
3565
- */
3566
- delete: {
3567
- parameters: {
3568
- query?: never;
3569
- header?: never;
3570
- path: {
3571
- id: string;
3572
- };
3573
- cookie?: never;
3574
- };
3575
- requestBody?: never;
3576
- responses: {
3577
3689
  /** @description Default Response */
3578
- 204: {
3690
+ 404: {
3579
3691
  headers: {
3580
3692
  [name: string]: unknown;
3581
3693
  };
3582
3694
  content: {
3583
- "application/json": "null" | null;
3695
+ "application/json": {
3696
+ error: {
3697
+ code: string;
3698
+ message: string;
3699
+ details?: unknown;
3700
+ };
3701
+ };
3584
3702
  };
3585
3703
  };
3586
3704
  /** @description Default Response */
3587
- 404: {
3705
+ 409: {
3588
3706
  headers: {
3589
3707
  [name: string]: unknown;
3590
3708
  };
@@ -3598,9 +3716,602 @@ interface paths {
3598
3716
  };
3599
3717
  };
3600
3718
  };
3601
- };
3602
- };
3603
- options?: never;
3719
+ /** @description Default Response */
3720
+ 422: {
3721
+ headers: {
3722
+ [name: string]: unknown;
3723
+ };
3724
+ content: {
3725
+ "application/json": {
3726
+ error: {
3727
+ code: string;
3728
+ message: string;
3729
+ details?: unknown;
3730
+ };
3731
+ };
3732
+ };
3733
+ };
3734
+ };
3735
+ };
3736
+ delete?: never;
3737
+ options?: never;
3738
+ head?: never;
3739
+ patch?: never;
3740
+ trace?: never;
3741
+ };
3742
+ "/v1/content/{id}": {
3743
+ parameters: {
3744
+ query?: never;
3745
+ header?: never;
3746
+ path?: never;
3747
+ cookie?: never;
3748
+ };
3749
+ /** Get a content page */
3750
+ get: {
3751
+ parameters: {
3752
+ query?: never;
3753
+ header?: never;
3754
+ path: {
3755
+ id: string;
3756
+ };
3757
+ cookie?: never;
3758
+ };
3759
+ requestBody?: never;
3760
+ responses: {
3761
+ /** @description Default Response */
3762
+ 200: {
3763
+ headers: {
3764
+ [name: string]: unknown;
3765
+ };
3766
+ content: {
3767
+ "application/json": {
3768
+ /** Format: uuid */
3769
+ id: string;
3770
+ /** Format: uuid */
3771
+ organizationId: string;
3772
+ /** Format: date-time */
3773
+ createdAt: string;
3774
+ /** Format: date-time */
3775
+ updatedAt: string;
3776
+ /** Format: uuid */
3777
+ familyId: string;
3778
+ title: string;
3779
+ slug: string;
3780
+ /** @enum {string} */
3781
+ status: "draft" | "active" | "archived";
3782
+ /** Format: date-time */
3783
+ publishedAt: string | null;
3784
+ /** Format: uuid */
3785
+ channelId: string | null;
3786
+ attributes: (string | number | boolean | ("null" | null)) | unknown[] | {
3787
+ [key: string]: unknown;
3788
+ };
3789
+ tags: string[];
3790
+ /** Format: uuid */
3791
+ createdBy: string | null;
3792
+ /** Format: uuid */
3793
+ updatedBy: string | null;
3794
+ categoryIds: string[];
3795
+ };
3796
+ };
3797
+ };
3798
+ /** @description Default Response */
3799
+ 403: {
3800
+ headers: {
3801
+ [name: string]: unknown;
3802
+ };
3803
+ content: {
3804
+ "application/json": {
3805
+ error: {
3806
+ code: string;
3807
+ message: string;
3808
+ details?: unknown;
3809
+ };
3810
+ };
3811
+ };
3812
+ };
3813
+ /** @description Default Response */
3814
+ 404: {
3815
+ headers: {
3816
+ [name: string]: unknown;
3817
+ };
3818
+ content: {
3819
+ "application/json": {
3820
+ error: {
3821
+ code: string;
3822
+ message: string;
3823
+ details?: unknown;
3824
+ };
3825
+ };
3826
+ };
3827
+ };
3828
+ };
3829
+ };
3830
+ put?: never;
3831
+ post?: never;
3832
+ /**
3833
+ * Delete a content page
3834
+ * @description Emits `content_page.deleted`.
3835
+ */
3836
+ delete: {
3837
+ parameters: {
3838
+ query?: never;
3839
+ header?: never;
3840
+ path: {
3841
+ id: string;
3842
+ };
3843
+ cookie?: never;
3844
+ };
3845
+ requestBody?: never;
3846
+ responses: {
3847
+ /** @description Default Response */
3848
+ 204: {
3849
+ headers: {
3850
+ [name: string]: unknown;
3851
+ };
3852
+ content: {
3853
+ "application/json": "null" | null;
3854
+ };
3855
+ };
3856
+ /** @description Default Response */
3857
+ 403: {
3858
+ headers: {
3859
+ [name: string]: unknown;
3860
+ };
3861
+ content: {
3862
+ "application/json": {
3863
+ error: {
3864
+ code: string;
3865
+ message: string;
3866
+ details?: unknown;
3867
+ };
3868
+ };
3869
+ };
3870
+ };
3871
+ /** @description Default Response */
3872
+ 404: {
3873
+ headers: {
3874
+ [name: string]: unknown;
3875
+ };
3876
+ content: {
3877
+ "application/json": {
3878
+ error: {
3879
+ code: string;
3880
+ message: string;
3881
+ details?: unknown;
3882
+ };
3883
+ };
3884
+ };
3885
+ };
3886
+ };
3887
+ };
3888
+ options?: never;
3889
+ head?: never;
3890
+ /**
3891
+ * Update a content page
3892
+ * @description `attributes` is merged per code, not replaced wholesale. Stamps `publishedAt` on the first transition to `active` unless the caller supplies one. Emits `content_page.updated` carrying `previous_status`.
3893
+ */
3894
+ patch: {
3895
+ parameters: {
3896
+ query?: never;
3897
+ header?: never;
3898
+ path: {
3899
+ id: string;
3900
+ };
3901
+ cookie?: never;
3902
+ };
3903
+ requestBody: {
3904
+ content: {
3905
+ "application/json": {
3906
+ title?: string;
3907
+ slug?: string;
3908
+ /** @enum {string} */
3909
+ status?: "draft" | "active" | "archived";
3910
+ /** Format: date-time */
3911
+ publishedAt?: string | null;
3912
+ /** Format: uuid */
3913
+ channelId?: string | null;
3914
+ attributes?: {
3915
+ [key: string]: {
3916
+ locale: string | null;
3917
+ channel: string | null;
3918
+ value?: unknown;
3919
+ }[];
3920
+ };
3921
+ tags?: string[];
3922
+ };
3923
+ };
3924
+ };
3925
+ responses: {
3926
+ /** @description Default Response */
3927
+ 200: {
3928
+ headers: {
3929
+ [name: string]: unknown;
3930
+ };
3931
+ content: {
3932
+ "application/json": {
3933
+ /** Format: uuid */
3934
+ id: string;
3935
+ /** Format: uuid */
3936
+ organizationId: string;
3937
+ /** Format: date-time */
3938
+ createdAt: string;
3939
+ /** Format: date-time */
3940
+ updatedAt: string;
3941
+ /** Format: uuid */
3942
+ familyId: string;
3943
+ title: string;
3944
+ slug: string;
3945
+ /** @enum {string} */
3946
+ status: "draft" | "active" | "archived";
3947
+ /** Format: date-time */
3948
+ publishedAt: string | null;
3949
+ /** Format: uuid */
3950
+ channelId: string | null;
3951
+ attributes: (string | number | boolean | ("null" | null)) | unknown[] | {
3952
+ [key: string]: unknown;
3953
+ };
3954
+ tags: string[];
3955
+ /** Format: uuid */
3956
+ createdBy: string | null;
3957
+ /** Format: uuid */
3958
+ updatedBy: string | null;
3959
+ };
3960
+ };
3961
+ };
3962
+ /** @description Default Response */
3963
+ 403: {
3964
+ headers: {
3965
+ [name: string]: unknown;
3966
+ };
3967
+ content: {
3968
+ "application/json": {
3969
+ error: {
3970
+ code: string;
3971
+ message: string;
3972
+ details?: unknown;
3973
+ };
3974
+ };
3975
+ };
3976
+ };
3977
+ /** @description Default Response */
3978
+ 404: {
3979
+ headers: {
3980
+ [name: string]: unknown;
3981
+ };
3982
+ content: {
3983
+ "application/json": {
3984
+ error: {
3985
+ code: string;
3986
+ message: string;
3987
+ details?: unknown;
3988
+ };
3989
+ };
3990
+ };
3991
+ };
3992
+ /** @description Default Response */
3993
+ 409: {
3994
+ headers: {
3995
+ [name: string]: unknown;
3996
+ };
3997
+ content: {
3998
+ "application/json": {
3999
+ error: {
4000
+ code: string;
4001
+ message: string;
4002
+ details?: unknown;
4003
+ };
4004
+ };
4005
+ };
4006
+ };
4007
+ /** @description Default Response */
4008
+ 422: {
4009
+ headers: {
4010
+ [name: string]: unknown;
4011
+ };
4012
+ content: {
4013
+ "application/json": {
4014
+ error: {
4015
+ code: string;
4016
+ message: string;
4017
+ details?: unknown;
4018
+ };
4019
+ };
4020
+ };
4021
+ };
4022
+ };
4023
+ };
4024
+ trace?: never;
4025
+ };
4026
+ "/v1/content/{id}/categories": {
4027
+ parameters: {
4028
+ query?: never;
4029
+ header?: never;
4030
+ path?: never;
4031
+ cookie?: never;
4032
+ };
4033
+ get?: never;
4034
+ /**
4035
+ * Replace a content page’s categories
4036
+ * @description Replaces the full set — pass an empty array to clear. Uses the same `categories` table products do, so a category means one thing across the whole org.
4037
+ */
4038
+ put: {
4039
+ parameters: {
4040
+ query?: never;
4041
+ header?: never;
4042
+ path: {
4043
+ id: string;
4044
+ };
4045
+ cookie?: never;
4046
+ };
4047
+ requestBody: {
4048
+ content: {
4049
+ "application/json": {
4050
+ categoryIds: string[];
4051
+ };
4052
+ };
4053
+ };
4054
+ responses: {
4055
+ /** @description Default Response */
4056
+ 200: {
4057
+ headers: {
4058
+ [name: string]: unknown;
4059
+ };
4060
+ content: {
4061
+ "application/json": {
4062
+ data: {
4063
+ /** Format: uuid */
4064
+ contentPageId: string;
4065
+ /** Format: uuid */
4066
+ categoryId: string;
4067
+ position: number;
4068
+ }[];
4069
+ };
4070
+ };
4071
+ };
4072
+ /** @description Default Response */
4073
+ 403: {
4074
+ headers: {
4075
+ [name: string]: unknown;
4076
+ };
4077
+ content: {
4078
+ "application/json": {
4079
+ error: {
4080
+ code: string;
4081
+ message: string;
4082
+ details?: unknown;
4083
+ };
4084
+ };
4085
+ };
4086
+ };
4087
+ /** @description Default Response */
4088
+ 404: {
4089
+ headers: {
4090
+ [name: string]: unknown;
4091
+ };
4092
+ content: {
4093
+ "application/json": {
4094
+ error: {
4095
+ code: string;
4096
+ message: string;
4097
+ details?: unknown;
4098
+ };
4099
+ };
4100
+ };
4101
+ };
4102
+ };
4103
+ };
4104
+ post?: never;
4105
+ delete?: never;
4106
+ options?: never;
4107
+ head?: never;
4108
+ patch?: never;
4109
+ trace?: never;
4110
+ };
4111
+ "/v1/channels": {
4112
+ parameters: {
4113
+ query?: never;
4114
+ header?: never;
4115
+ path?: never;
4116
+ cookie?: never;
4117
+ };
4118
+ /**
4119
+ * List channels
4120
+ * @description Channels are the surfaces your catalog publishes to —
4121
+ * storefronts, marketplaces, ad platforms. Used in attribute-value resolution
4122
+ * together with locale: `(locale, channel)` keys each value. `settings` is
4123
+ * free-form per-channel config; `settings.required_attributes` drives the
4124
+ * per-channel completeness score on products.
4125
+ *
4126
+ * **Example response**
4127
+ * ```json
4128
+ * [
4129
+ * {
4130
+ * "id": "3ac5ab02-93fd-49b7-91a3-681fc36eabdd",
4131
+ * "code": "headless-main",
4132
+ * "name": "Headless Storefront",
4133
+ * "locales": ["en_US"],
4134
+ * "settings": null,
4135
+ * "createdAt": "2026-06-02T22:28:01.926Z",
4136
+ * "updatedAt": "2026-06-02T22:28:01.926Z"
4137
+ * },
4138
+ * {
4139
+ * "id": "f1c2b3a4-...",
4140
+ * "code": "bc_pdo6b28kp8_default",
4141
+ * "name": "BigCommerce (default)",
4142
+ * "locales": ["en_US"],
4143
+ * "settings": {
4144
+ * "bc_channel_id": 1,
4145
+ * "bc_store_hash": "pdo6b28kp8",
4146
+ * "required_attributes": ["description", "price", "brand", "weight"]
4147
+ * },
4148
+ * "createdAt": "...",
4149
+ * "updatedAt": "..."
4150
+ * }
4151
+ * ]
4152
+ * ```
4153
+ *
4154
+ * Channels seeded by connectors carry `bc_*` / `shopify_*` keys in
4155
+ * `settings` so they can be identified and scoped-wiped on connector
4156
+ * uninstall — leave those alone unless you know what you're doing.
4157
+ */
4158
+ get: {
4159
+ parameters: {
4160
+ query?: never;
4161
+ header?: never;
4162
+ path?: never;
4163
+ cookie?: never;
4164
+ };
4165
+ requestBody?: never;
4166
+ responses: {
4167
+ /** @description Default Response */
4168
+ 200: {
4169
+ headers: {
4170
+ [name: string]: unknown;
4171
+ };
4172
+ content: {
4173
+ "application/json": {
4174
+ /** Format: uuid */
4175
+ id: string;
4176
+ /** Format: uuid */
4177
+ organizationId: string;
4178
+ /** Format: date-time */
4179
+ createdAt: string;
4180
+ /** Format: date-time */
4181
+ updatedAt: string;
4182
+ code: string;
4183
+ name: string;
4184
+ locales: string[];
4185
+ settings: ((string | number | boolean | ("null" | null)) | unknown[] | {
4186
+ [key: string]: unknown;
4187
+ }) | null;
4188
+ }[];
4189
+ };
4190
+ };
4191
+ };
4192
+ };
4193
+ put?: never;
4194
+ /** Create a channel */
4195
+ post: {
4196
+ parameters: {
4197
+ query?: never;
4198
+ header?: never;
4199
+ path?: never;
4200
+ cookie?: never;
4201
+ };
4202
+ requestBody: {
4203
+ content: {
4204
+ "application/json": {
4205
+ code: string;
4206
+ name: string;
4207
+ /** @default [] */
4208
+ locales?: string[];
4209
+ settings?: {
4210
+ [key: string]: unknown;
4211
+ };
4212
+ };
4213
+ };
4214
+ };
4215
+ responses: {
4216
+ /** @description Default Response */
4217
+ 201: {
4218
+ headers: {
4219
+ [name: string]: unknown;
4220
+ };
4221
+ content: {
4222
+ "application/json": {
4223
+ /** Format: uuid */
4224
+ id: string;
4225
+ /** Format: uuid */
4226
+ organizationId: string;
4227
+ /** Format: date-time */
4228
+ createdAt: string;
4229
+ /** Format: date-time */
4230
+ updatedAt: string;
4231
+ code: string;
4232
+ name: string;
4233
+ locales: string[];
4234
+ settings: ((string | number | boolean | ("null" | null)) | unknown[] | {
4235
+ [key: string]: unknown;
4236
+ }) | null;
4237
+ };
4238
+ };
4239
+ };
4240
+ /** @description Default Response */
4241
+ 422: {
4242
+ headers: {
4243
+ [name: string]: unknown;
4244
+ };
4245
+ content: {
4246
+ "application/json": {
4247
+ error: {
4248
+ code: string;
4249
+ message: string;
4250
+ details?: unknown;
4251
+ };
4252
+ };
4253
+ };
4254
+ };
4255
+ };
4256
+ };
4257
+ delete?: never;
4258
+ options?: never;
4259
+ head?: never;
4260
+ patch?: never;
4261
+ trace?: never;
4262
+ };
4263
+ "/v1/channels/{id}": {
4264
+ parameters: {
4265
+ query?: never;
4266
+ header?: never;
4267
+ path?: never;
4268
+ cookie?: never;
4269
+ };
4270
+ get?: never;
4271
+ put?: never;
4272
+ post?: never;
4273
+ /**
4274
+ * Delete a channel (surface)
4275
+ * @description Removes a channel and its per-product publish records (ON DELETE CASCADE). The channel stops appearing as a completeness/readiness surface. Channel-scoped attribute values are stored in the product JSONB (not FKs) and are left untouched — they simply no longer resolve to a live channel. Use this to clear a surface left behind after uninstalling the connector that seeded it (channels are decoupled from connectors by design).
4276
+ */
4277
+ delete: {
4278
+ parameters: {
4279
+ query?: never;
4280
+ header?: never;
4281
+ path: {
4282
+ id: string;
4283
+ };
4284
+ cookie?: never;
4285
+ };
4286
+ requestBody?: never;
4287
+ responses: {
4288
+ /** @description Default Response */
4289
+ 204: {
4290
+ headers: {
4291
+ [name: string]: unknown;
4292
+ };
4293
+ content: {
4294
+ "application/json": "null" | null;
4295
+ };
4296
+ };
4297
+ /** @description Default Response */
4298
+ 404: {
4299
+ headers: {
4300
+ [name: string]: unknown;
4301
+ };
4302
+ content: {
4303
+ "application/json": {
4304
+ error: {
4305
+ code: string;
4306
+ message: string;
4307
+ details?: unknown;
4308
+ };
4309
+ };
4310
+ };
4311
+ };
4312
+ };
4313
+ };
4314
+ options?: never;
3604
4315
  head?: never;
3605
4316
  /**
3606
4317
  * Update a channel (settings, locales, name)
@@ -3979,8 +4690,13 @@ interface paths {
3979
4690
  * ```
3980
4691
  *
3981
4692
  * To get a single category's full ancestor path, walk `parentId` upwards
3982
- * client-side, or hit `GET /v1/categories/{id}` which returns the same row
3983
- * shape with the resolved path included in `path`.
4693
+ * client-side this endpoint returns the whole tree in one call, so no extra
4694
+ * requests are needed. (`GET /v1/categories/{id}` returns the same row shape
4695
+ * for one category; it does **not** resolve an ancestor path.)
4696
+ *
4697
+ * To list the products under a category *including* its subtree, use
4698
+ * `GET /v1/products?categoryId=<id>&includeDescendants=true` rather than
4699
+ * resolving descendants here and issuing one request per id.
3984
4700
  *
3985
4701
  * Returns the full tree by default. `?limit=` / `?offset=` are supported
3986
4702
  * for large taxonomies (ordered by `position`); omit them to get everything.
@@ -4932,7 +5648,110 @@ interface paths {
4932
5648
  /** Format: uuid */
4933
5649
  mediaId: string;
4934
5650
  /** @enum {string} */
4935
- entityType: "product" | "variant";
5651
+ entityType: "product" | "variant" | "content_page";
5652
+ /** Format: uuid */
5653
+ entityId: string;
5654
+ /** @enum {string} */
5655
+ role: "hero" | "gallery" | "thumbnail" | "technical" | "lifestyle" | "swatch";
5656
+ position: number;
5657
+ altText: ((string | number | boolean | ("null" | null)) | unknown[] | {
5658
+ [key: string]: unknown;
5659
+ }) | null;
5660
+ /** Format: uuid */
5661
+ channelId: string | null;
5662
+ };
5663
+ url: string | null;
5664
+ }[];
5665
+ };
5666
+ };
5667
+ };
5668
+ /** @description Default Response */
5669
+ 404: {
5670
+ headers: {
5671
+ [name: string]: unknown;
5672
+ };
5673
+ content: {
5674
+ "application/json": {
5675
+ error: {
5676
+ code: string;
5677
+ message: string;
5678
+ details?: unknown;
5679
+ };
5680
+ };
5681
+ };
5682
+ };
5683
+ };
5684
+ };
5685
+ put?: never;
5686
+ post?: never;
5687
+ delete?: never;
5688
+ options?: never;
5689
+ head?: never;
5690
+ patch?: never;
5691
+ trace?: never;
5692
+ };
5693
+ "/v1/content/{id}/media": {
5694
+ parameters: {
5695
+ query?: never;
5696
+ header?: never;
5697
+ path?: never;
5698
+ cookie?: never;
5699
+ };
5700
+ /**
5701
+ * List media attached to a content page
5702
+ * @description Same shape as the product/variant galleries. Lives here rather than under /v1/content so it sits with its siblings and reuses the same URL resolution; associations are tenant-scoped by RLS regardless.
5703
+ */
5704
+ get: {
5705
+ parameters: {
5706
+ query?: never;
5707
+ header?: never;
5708
+ path: {
5709
+ id: string;
5710
+ };
5711
+ cookie?: never;
5712
+ };
5713
+ requestBody?: never;
5714
+ responses: {
5715
+ /** @description Default Response */
5716
+ 200: {
5717
+ headers: {
5718
+ [name: string]: unknown;
5719
+ };
5720
+ content: {
5721
+ "application/json": {
5722
+ data: {
5723
+ /** Format: uuid */
5724
+ id: string;
5725
+ /** Format: uuid */
5726
+ organizationId: string;
5727
+ /** Format: date-time */
5728
+ createdAt: string;
5729
+ /** Format: date-time */
5730
+ updatedAt: string;
5731
+ /** @enum {string} */
5732
+ type: "image" | "video" | "document" | "model_3d";
5733
+ storageKey: string;
5734
+ filename: string;
5735
+ mimeType: string;
5736
+ sizeBytes: number;
5737
+ width: number | null;
5738
+ height: number | null;
5739
+ metadata: ((string | number | boolean | ("null" | null)) | unknown[] | {
5740
+ [key: string]: unknown;
5741
+ }) | null;
5742
+ association: {
5743
+ /** Format: uuid */
5744
+ id: string;
5745
+ /** Format: uuid */
5746
+ organizationId: string;
5747
+ /** Format: date-time */
5748
+ createdAt: string;
5749
+ /** Format: date-time */
5750
+ updatedAt: string;
5751
+ /** Format: uuid */
5752
+ mediaId: string;
5753
+ /** @enum {string} */
5754
+ entityType: "product" | "variant" | "content_page";
4936
5755
  /** Format: uuid */
4937
5756
  entityId: string;
4938
5757
  /** @enum {string} */
@@ -5032,7 +5851,7 @@ interface paths {
5032
5851
  /** Format: uuid */
5033
5852
  mediaId: string;
5034
5853
  /** @enum {string} */
5035
- entityType: "product" | "variant";
5854
+ entityType: "product" | "variant" | "content_page";
5036
5855
  /** Format: uuid */
5037
5856
  entityId: string;
5038
5857
  /** @enum {string} */
@@ -5134,7 +5953,7 @@ interface paths {
5134
5953
  /** Format: uuid */
5135
5954
  mediaId: string;
5136
5955
  /** @enum {string} */
5137
- entityType: "product" | "variant";
5956
+ entityType: "product" | "variant" | "content_page";
5138
5957
  /** Format: uuid */
5139
5958
  entityId: string;
5140
5959
  /** @enum {string} */
@@ -5211,7 +6030,7 @@ interface paths {
5211
6030
  /** Format: uuid */
5212
6031
  mediaId: string;
5213
6032
  /** @enum {string} */
5214
- entityType: "product" | "variant";
6033
+ entityType: "product" | "variant" | "content_page";
5215
6034
  /** Format: uuid */
5216
6035
  entityId: string;
5217
6036
  /**
@@ -5248,7 +6067,7 @@ interface paths {
5248
6067
  /** Format: uuid */
5249
6068
  mediaId: string;
5250
6069
  /** @enum {string} */
5251
- entityType: "product" | "variant";
6070
+ entityType: "product" | "variant" | "content_page";
5252
6071
  /** Format: uuid */
5253
6072
  entityId: string;
5254
6073
  /** @enum {string} */
@@ -5376,7 +6195,7 @@ interface paths {
5376
6195
  /** Format: uuid */
5377
6196
  mediaId: string;
5378
6197
  /** @enum {string} */
5379
- entityType: "product" | "variant";
6198
+ entityType: "product" | "variant" | "content_page";
5380
6199
  /** Format: uuid */
5381
6200
  entityId: string;
5382
6201
  /** @enum {string} */
@@ -5432,7 +6251,7 @@ interface paths {
5432
6251
  content: {
5433
6252
  "application/json": {
5434
6253
  /** @enum {string} */
5435
- entityType: "product" | "variant";
6254
+ entityType: "product" | "variant" | "content_page";
5436
6255
  /** Format: uuid */
5437
6256
  entityId: string;
5438
6257
  orderedIds: string[];
@@ -5459,7 +6278,7 @@ interface paths {
5459
6278
  /** Format: uuid */
5460
6279
  mediaId: string;
5461
6280
  /** @enum {string} */
5462
- entityType: "product" | "variant";
6281
+ entityType: "product" | "variant" | "content_page";
5463
6282
  /** Format: uuid */
5464
6283
  entityId: string;
5465
6284
  /** @enum {string} */
@@ -5538,7 +6357,7 @@ interface paths {
5538
6357
  /** Format: uuid */
5539
6358
  mediaId: string;
5540
6359
  /** @enum {string} */
5541
- entityType: "product" | "variant";
6360
+ entityType: "product" | "variant" | "content_page";
5542
6361
  /** Format: uuid */
5543
6362
  entityId: string;
5544
6363
  /** @enum {string} */