@lonca/trendyol 0.2.0 → 0.4.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/index.d.ts CHANGED
@@ -77,6 +77,21 @@ declare class BrandsResource {
77
77
  * ```
78
78
  */
79
79
  list(params?: CursorPaginationParams): Promise<CursorPage<Brand>>;
80
+ /**
81
+ * Search brands by name. Useful when you need a brand's numeric ID for
82
+ * `createProducts` and don't want to page through the full `list()`
83
+ * (1000 brands per page).
84
+ *
85
+ * **Discovery-first wire fact (verified STAGE 2026-05-25):** Trendyol's
86
+ * doc claims this is a case-sensitive *exact* match, but live behaviour
87
+ * is **substring + case-insensitive** — `search('Trendyol')` returns
88
+ * 17 hits including `TRENDYOLMILLA`, `trendyol vavist`, `Trendyol Üyelik`.
89
+ * Plan for ranking your results client-side if you need an exact match.
90
+ * The endpoint returns an empty array when nothing matches (no 404).
91
+ *
92
+ * @param name The brand name to search for.
93
+ */
94
+ search(name: string): Promise<Brand[]>;
80
95
  }
81
96
 
82
97
  /**
@@ -98,6 +113,22 @@ interface CategoryAttributeValue {
98
113
  id: string;
99
114
  name: string;
100
115
  }
116
+ /**
117
+ * Result of `categories.getByBarcodes` — a barcode → category mapping
118
+ * sourced from Trendyol's Export Center (AutoFT) lookup endpoint.
119
+ */
120
+ interface BarcodeCategoryLookup {
121
+ /** Successful matches. */
122
+ matches: Array<{
123
+ barcode: string;
124
+ category: {
125
+ id: string;
126
+ name: string;
127
+ };
128
+ }>;
129
+ /** Barcodes Trendyol could not resolve to a category. */
130
+ notFound: string[];
131
+ }
101
132
  /**
102
133
  * A required or optional attribute for products in a given category.
103
134
  * Use these when constructing a `createProduct V2` payload — the API rejects
@@ -126,27 +157,40 @@ interface CategoryAttribute {
126
157
  * NOTE: Trendyol's live API often omits this field on the `getCategoryAttributes`
127
158
  * response — the endpoint returns attribute metadata + flags, not the full value
128
159
  * catalog. In that case `values` is an empty array. If `allowCustom` is `true`,
129
- * any custom text is accepted; otherwise consult Trendyol's separate value
130
- * lookup mechanisms (a dedicated `getAttributeValues` endpoint may land in a
131
- * future release of this SDK).
160
+ * any custom text is accepted; otherwise use `client.categories.getAttributeValues(categoryId, attributeId)`
161
+ * to fetch the catalog from the dedicated V2 endpoint.
132
162
  */
133
163
  values: CategoryAttributeValue[];
134
164
  }
135
165
 
166
+ type ListCategoryAttributeValuesParams = CursorPaginationParams;
136
167
  /**
137
168
  * Trendyol category-tree and category-attribute endpoints.
138
169
  *
139
170
  * Rate limits (per Trendyol service limits):
140
171
  * - Category list: 50 req/min
141
172
  * - Category attributes: 50 req/min
173
+ * - Category attribute values: 50 req/min (same service tier)
142
174
  *
143
- * Both rate counters live on the same Trendyol service, so we share one
144
- * limiter across both endpoints.
175
+ * All three counters live on the same Trendyol service, so we share one
176
+ * limiter across the endpoints.
145
177
  */
146
178
  declare class CategoriesResource {
147
179
  private readonly transport;
180
+ /**
181
+ * Trendyol seller (supplier) ID — required only for the AutoFT-scoped
182
+ * `getByBarcodes` lookup. Other category endpoints don't need it.
183
+ * Provided automatically when constructed via `createTrendyolClient`.
184
+ */
185
+ private readonly sellerId?;
148
186
  private readonly limiter;
149
- constructor(transport: TrendyolTransport, limiter?: TokenBucketRateLimiter);
187
+ constructor(transport: TrendyolTransport, limiter?: TokenBucketRateLimiter,
188
+ /**
189
+ * Trendyol seller (supplier) ID — required only for the AutoFT-scoped
190
+ * `getByBarcodes` lookup. Other category endpoints don't need it.
191
+ * Provided automatically when constructed via `createTrendyolClient`.
192
+ */
193
+ sellerId?: number | undefined);
150
194
  /**
151
195
  * Fetch the full Trendyol category tree.
152
196
  *
@@ -163,6 +207,43 @@ declare class CategoriesResource {
163
207
  * @param categoryId Trendyol numeric category ID; accepts `string` or `number`.
164
208
  */
165
209
  getAttributes(categoryId: string | number): Promise<CategoryAttribute[]>;
210
+ /**
211
+ * Fetch the allowed values for a single category attribute (paginated).
212
+ *
213
+ * `getCategoryAttributes` returns attribute metadata + flags but typically
214
+ * omits the value catalog. Use this method to fetch the catalog for an
215
+ * attribute when `allowCustom` is `false` and you need to map your data
216
+ * onto Trendyol's accepted values.
217
+ *
218
+ * @param categoryId Trendyol numeric category ID; accepts `string` or `number`.
219
+ * @param attributeId Attribute ID returned by `getAttributes`.
220
+ * @param params Cursor pagination (max page size 1000; default 100).
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * import { paginate } from '@lonca/core';
225
+ * for await (const value of paginate((p) =>
226
+ * client.categories.getAttributeValues(catId, attrId, p),
227
+ * )) {
228
+ * console.log(value.id, value.name);
229
+ * }
230
+ * ```
231
+ */
232
+ getAttributeValues(categoryId: string | number, attributeId: string | number, params?: ListCategoryAttributeValuesParams): Promise<CursorPage<CategoryAttributeValue>>;
233
+ /**
234
+ * Look up category info for a list of barcodes (Trendyol Export Center
235
+ * / AutoFT endpoint).
236
+ *
237
+ * **Requires Export Center enrollment.** Sellers who have not joined
238
+ * Trendyol's "İhracat Merkezi" program will get an auth error on this
239
+ * endpoint even though their regular Marketplace credentials are valid.
240
+ *
241
+ * @param barcodes 1–N barcodes to look up.
242
+ * @throws {ValidationError} when `barcodes` is empty.
243
+ * @throws Error when the client was created without a `sellerId` and this
244
+ * method is called directly (use `createTrendyolClient` to wire it).
245
+ */
246
+ getByBarcodes(barcodes: string[]): Promise<BarcodeCategoryLookup>;
166
247
  }
167
248
 
168
249
  /**
@@ -310,6 +391,145 @@ interface PackageHistoryEntry {
310
391
  createdAt?: string;
311
392
  raw: Record<string, unknown>;
312
393
  }
394
+ /**
395
+ * A package line update tuple used by `updatePackageStatus` and
396
+ * `cancelPackageItem`. `lineId` is the per-line ID from `ShipmentPackage.lines[].lineId`.
397
+ */
398
+ interface PackageLineUpdate {
399
+ lineId: number;
400
+ quantity: number;
401
+ }
402
+ /**
403
+ * Input for `orders.updatePackageStatus`. Trendyol restricts the seller-side
404
+ * status push to `Picking` (mark as being prepared) and `Invoiced`
405
+ * (invoice issued); other transitions are driven by Trendyol / the cargo
406
+ * provider. `lines` is optional and only used when transitioning subset of
407
+ * line items.
408
+ */
409
+ interface UpdatePackageStatusInput {
410
+ status: 'Picking' | 'Invoiced';
411
+ lines?: PackageLineUpdate[];
412
+ }
413
+ /**
414
+ * Input for `orders.cancelPackageItem` — Trendyol's "supply failure" notification.
415
+ * Marks specific line items as un-suppliable. `reasonId` is a numeric code
416
+ * Trendyol publishes separately (e.g. `577` = "tedarik edemiyorum"); consult
417
+ * Trendyol's seller panel or the "Tedarik Edememe" docs for current values.
418
+ */
419
+ interface CancelPackageItemInput {
420
+ lines: PackageLineUpdate[];
421
+ reasonId: number;
422
+ }
423
+ /**
424
+ * One row from `orders.getCargoInvoiceItems` — a cargo invoice line item
425
+ * that ties a parcel ID to its cargo fee. Useful for reconciling Trendyol's
426
+ * cargo deductions against your shipped packages.
427
+ */
428
+ interface CargoInvoiceItem {
429
+ /** e.g. "Gönderi Kargo Bedeli" (outbound) or "İade Kargo Bedeli" (return). */
430
+ shipmentPackageType?: string;
431
+ /** Cargo parcel unique ID. */
432
+ parcelUniqueId?: number | string;
433
+ orderNumber?: string;
434
+ /** Fee charged in this row. */
435
+ amount?: number;
436
+ /** Desi value used to compute the fee. */
437
+ desi?: number;
438
+ /** Untouched raw row. */
439
+ raw: Record<string, unknown>;
440
+ }
441
+ /**
442
+ * Filter params for `orders.listStream` — the streaming alternative to
443
+ * `orders.list`. Uses Trendyol's opaque `nextCursor` (forwarded as the
444
+ * `@lonca/core` `CursorPaginationParams.cursor`) instead of page-index
445
+ * pagination.
446
+ */
447
+ interface ListOrdersStreamParams {
448
+ cursor?: string;
449
+ limit?: number;
450
+ /**
451
+ * CSV of package-item statuses to filter by (e.g.
452
+ * `'Created,Picking,Invoiced'`). Trendyol accepts the same status
453
+ * vocabulary as `ShipmentPackageStatus`.
454
+ */
455
+ packageItemStatuses?: string;
456
+ /** Lower bound for `lastModified` (Trendyol expects ms-epoch). */
457
+ lastModifiedStartDate?: Date;
458
+ /** Upper bound for `lastModified`. */
459
+ lastModifiedEndDate?: Date;
460
+ }
461
+ /**
462
+ * Box / packaging metadata for `orders.updateBoxInfo`. Both fields are
463
+ * optional but at least one should be set for the call to be meaningful.
464
+ */
465
+ interface UpdateBoxInfoInput {
466
+ /** Desi value (volumetric weight used by Trendyol for shipping cost). */
467
+ deci?: number;
468
+ /** Number of physical boxes in the shipment. */
469
+ boxQuantity?: number;
470
+ }
471
+ /**
472
+ * Per-line labor cost for `orders.updateLaborCosts`. The Trendyol API
473
+ * accepts a raw array of these (no envelope) — the SDK forwards as-is.
474
+ */
475
+ interface LaborCostInput {
476
+ orderLineId: number;
477
+ /** Labor cost charged per single unit of this line. */
478
+ laborCostPerItem: number;
479
+ }
480
+ /**
481
+ * Trendyol cargo provider codes accepted by `orders.changeCargoProvider`.
482
+ * Use the string union for autocomplete; `(string & {})` keeps unknown
483
+ * codes type-compatible so Trendyol can add providers without breaking
484
+ * callers.
485
+ */
486
+ type TrendyolCargoProvider = 'YKMP' | 'ARASMP' | 'SURATMP' | 'HOROZMP' | 'DHLECOMMP' | 'PTTMP' | 'CEVAMP' | 'TEXMP' | 'KOLAYGELSINMP' | 'CEVATEDARIK' | (string & {});
487
+ /**
488
+ * Per-line quantity split for `orders.splitPackageByQuantity`. Each item
489
+ * in `quantities` becomes its own new package containing that many units
490
+ * of `orderLineId`.
491
+ *
492
+ * @example
493
+ * // splits 5 units of line 100 into 3 packages: 2 + 2 + 1
494
+ * { orderLineId: 100, quantities: [2, 2, 1] }
495
+ */
496
+ interface QuantitySplit {
497
+ orderLineId: number;
498
+ quantities: number[];
499
+ }
500
+ /**
501
+ * A group of line IDs that should become a new package together,
502
+ * for `orders.multiSplitPackage`.
503
+ */
504
+ interface SplitGroup {
505
+ orderLineIds: number[];
506
+ }
507
+ /**
508
+ * One package's contents for `orders.splitMultiPackagesByQuantity`. Each
509
+ * element of the outer array becomes a new package; each `packageDetails`
510
+ * entry carries an `orderLineId` and the **single** quantity assigned to
511
+ * that package (note: singular `quantities`, despite the field name).
512
+ */
513
+ interface PackageDetail {
514
+ orderLineId: number;
515
+ /** Quantity of this line to include in this package (singular integer). */
516
+ quantities: number;
517
+ }
518
+ interface SplitPackagePlan {
519
+ packageDetails: PackageDetail[];
520
+ }
521
+ /**
522
+ * Input for `orders.processAlternativeDelivery`. Used when the seller is
523
+ * shipping via a non-Trendyol cargo provider — provide either a phone number
524
+ * (which Trendyol SMSes the tracking link to) or a direct tracking URL.
525
+ */
526
+ interface ProcessAlternativeDeliveryInput {
527
+ /** When true, `trackingInfo` is a phone number; when false, a tracking URL. */
528
+ isPhoneNumber: boolean;
529
+ trackingInfo: string;
530
+ /** Provider-specific extra parameters (Trendyol forwards verbatim). */
531
+ params: Record<string, string>;
532
+ }
313
533
  /**
314
534
  * A Trendyol order — Trendyol models orders as "shipment packages". A single
315
535
  * customer order may produce multiple shipment packages (one per warehouse,
@@ -407,6 +627,162 @@ declare class OrdersResource {
407
627
  * ```
408
628
  */
409
629
  list(params?: ListOrdersParams): Promise<CursorPage<ShipmentPackage>>;
630
+ /**
631
+ * Push a shipment-package status update.
632
+ *
633
+ * Trendyol restricts the seller-side push to two transitions:
634
+ * - `Picking` — order picked up from the shelf / being prepared
635
+ * - `Invoiced` — invoice issued, ready for cargo handoff
636
+ *
637
+ * Other transitions (`Shipped`, `Delivered`, etc.) are driven by Trendyol
638
+ * or the cargo provider — call `processAlternativeDelivery` or
639
+ * `manualDeliverByPackageId` if you ship outside Trendyol's cargo
640
+ * network.
641
+ *
642
+ * Returns void; Trendyol responds with 200 + empty body on success.
643
+ */
644
+ updatePackageStatus(packageId: string | number, input: UpdatePackageStatusInput): Promise<void>;
645
+ /**
646
+ * Notify Trendyol that one or more line items cannot be supplied
647
+ * ("Tedarik Edememe Bildirimi"). Marks the listed line IDs as
648
+ * `UnSupplied`. Trendyol cancels those quantities and notifies the
649
+ * customer.
650
+ *
651
+ * `reasonId` is a numeric code Trendyol publishes separately — consult
652
+ * the seller panel or the "Tedarik Edememe" docs for current values.
653
+ *
654
+ * Returns void; Trendyol responds with 200 + empty body on success.
655
+ */
656
+ cancelPackageItem(packageId: string | number, input: CancelPackageItemInput): Promise<void>;
657
+ /**
658
+ * Extend the agreed delivery date for a shipment package by 1, 2, or 3 days.
659
+ * Trendyol enforces the [1, 3] range server-side; the SDK validates client-side
660
+ * to fail fast.
661
+ *
662
+ * Returns void; Trendyol responds with 200 + empty body on success.
663
+ */
664
+ extendDeliveryDate(packageId: string | number, extendedDayCount: 1 | 2 | 3): Promise<void>;
665
+ /**
666
+ * Notify Trendyol of an alternative delivery channel — used when the
667
+ * seller is shipping via a non-Trendyol cargo provider. Trendyol then
668
+ * either SMSes the customer the tracking link (when `isPhoneNumber` is
669
+ * `true`) or stores the tracking URL on the package directly.
670
+ *
671
+ * Returns void; Trendyol responds with 200 + empty body on success.
672
+ */
673
+ processAlternativeDelivery(packageId: string | number, input: ProcessAlternativeDeliveryInput): Promise<void>;
674
+ /**
675
+ * Split a shipment package by moving a set of line IDs into a new
676
+ * package. The original package keeps the remaining lines.
677
+ *
678
+ * @param packageId The package to split.
679
+ * @param orderLineIds Line IDs to move into the new package (1+).
680
+ * @throws {ValidationError} when `orderLineIds` is empty.
681
+ */
682
+ splitPackage(packageId: string | number, orderLineIds: number[]): Promise<void>;
683
+ /**
684
+ * Split a shipment package by quantity. Each `QuantitySplit` entry
685
+ * carves a single line into multiple packages — e.g. `{ orderLineId: 100,
686
+ * quantities: [2, 2, 1] }` splits 5 units of line 100 into three packages
687
+ * of 2 + 2 + 1.
688
+ *
689
+ * @throws {ValidationError} when `quantitySplit` is empty.
690
+ */
691
+ splitPackageByQuantity(packageId: string | number, quantitySplit: QuantitySplit[]): Promise<void>;
692
+ /**
693
+ * Split a shipment package into multiple new packages by grouping line
694
+ * IDs. Each `SplitGroup` becomes one new package containing the listed
695
+ * line IDs.
696
+ *
697
+ * @throws {ValidationError} when `splitGroups` is empty.
698
+ */
699
+ multiSplitPackage(packageId: string | number, splitGroups: SplitGroup[]): Promise<void>;
700
+ /**
701
+ * Split a shipment package into multiple new packages, each containing a
702
+ * mix of line items at specific quantities. This is the most expressive
703
+ * split — use it when you need fine-grained control over which line IDs
704
+ * and how many of each end up in each new package.
705
+ *
706
+ * @throws {ValidationError} when `splitPackages` is empty.
707
+ */
708
+ splitMultiPackagesByQuantity(packageId: string | number, splitPackages: SplitPackagePlan[]): Promise<void>;
709
+ /**
710
+ * Change the cargo provider on an existing shipment package. Use one of
711
+ * Trendyol's documented marketplace cargo codes (`'YKMP'`, `'ARASMP'`,
712
+ * `'SURATMP'`, etc.) — see `TrendyolCargoProvider` for the full list.
713
+ */
714
+ changeCargoProvider(packageId: string | number, cargoProvider: TrendyolCargoProvider): Promise<void>;
715
+ /**
716
+ * Mark a shipment package as manually delivered via its package ID.
717
+ * Used when the seller delivered the order outside Trendyol's cargo
718
+ * network and needs to flip the package to `Delivered` after handover.
719
+ *
720
+ * No request body; Trendyol responds with 200 + empty body on success.
721
+ */
722
+ manualDeliverByPackageId(packageId: string | number): Promise<void>;
723
+ /**
724
+ * Manual-deliver variant that takes the cargo tracking number instead
725
+ * of the package ID. Useful when you only have the tracking number on
726
+ * hand (e.g., from a cargo provider webhook).
727
+ *
728
+ * Note the path structure: tracking number sits at a sibling location,
729
+ * not under `/shipment-packages/{id}/...`.
730
+ */
731
+ manualDeliverByTrackingNumber(cargoTrackingNumber: string | number): Promise<void>;
732
+ /**
733
+ * Mark a package as delivered through an authorized service ("yetkili
734
+ * servis"). For appliance / installation-required products that are
735
+ * delivered + installed by a third-party service partner.
736
+ */
737
+ markDeliveredByService(packageId: string | number): Promise<void>;
738
+ /**
739
+ * Update box / packaging metadata on a shipment package (desi value
740
+ * and/or number of boxes). Either field can be sent alone.
741
+ */
742
+ updateBoxInfo(packageId: string | number, input: UpdateBoxInfoInput): Promise<void>;
743
+ /**
744
+ * Update labor costs for one or more order lines.
745
+ *
746
+ * **Wire note:** Trendyol's request body is a raw array (no envelope),
747
+ * not `{ items: [...] }`. The SDK forwards `items` verbatim.
748
+ *
749
+ * @throws {ValidationError} when `items` is empty.
750
+ */
751
+ updateLaborCosts(packageId: string | number, items: LaborCostInput[]): Promise<void>;
752
+ /**
753
+ * Reassign a shipment package to a different warehouse. `warehouseId`
754
+ * comes from `client.suppliers.getAddresses()` (filter by `isShipmentAddress`).
755
+ */
756
+ updateWarehouse(packageId: string | number, warehouseId: number): Promise<void>;
757
+ /**
758
+ * Stream variant of `list()`. Trendyol's `getShipmentPackagesStream`
759
+ * returns the same `ShipmentPackage` shape but paginates with an opaque
760
+ * cursor — useful when the dataset is large and page-based pagination
761
+ * would hit the 10 000-record cap.
762
+ *
763
+ * @example
764
+ * ```ts
765
+ * import { paginate } from '@lonca/core';
766
+ * for await (const pkg of paginate((p) =>
767
+ * client.orders.listStream({ ...p, packageItemStatuses: 'Created,Picking' }),
768
+ * )) {
769
+ * console.log(pkg.id, pkg.status);
770
+ * }
771
+ * ```
772
+ */
773
+ listStream(params?: ListOrdersStreamParams): Promise<CursorPage<ShipmentPackage>>;
774
+ /**
775
+ * Fetch the per-parcel cargo-fee breakdown for a single cargo invoice
776
+ * (Trendyol's `getCargoInvoiceItems`). Useful for reconciling Trendyol's
777
+ * cargo deductions against your shipped packages.
778
+ *
779
+ * `invoiceSerialNumber` is sourced from the Current Account Statement
780
+ * ("Cari Hesap Ekstresi") with `transactionType=DeductionInvoices`.
781
+ *
782
+ * Page-based pagination internally (cursor encodes the page index).
783
+ */
784
+ getCargoInvoiceItems(invoiceSerialNumber: string, params?: CursorPaginationParams): Promise<CursorPage<CargoInvoiceItem>>;
785
+ private packagePath;
410
786
  }
411
787
 
412
788
  /** A `{ id, name }` reference object used in product/category/brand wire types. */
@@ -470,6 +846,117 @@ interface Product {
470
846
  /** Untouched raw response — read fields we have not modeled yet. */
471
847
  raw: Record<string, unknown>;
472
848
  }
849
+ /**
850
+ * Lifecycle status of an unapproved (draft) product on Trendyol.
851
+ *
852
+ * Verified values seen on STAGE/PROD as of 2026-05-25:
853
+ * - `pendingApproval` — submitted; Trendyol content review in progress.
854
+ * - `rejected` — review failed; `rejectReasonDetails` is populated.
855
+ *
856
+ * Older docs also mention `waiting`. Treat as open-enum (`(string & {})`)
857
+ * since Trendyol can add new statuses without notice.
858
+ */
859
+ type UnapprovedProductStatus = 'pendingApproval' | 'waiting' | 'rejected' | (string & {});
860
+ interface UnapprovedProductRejectReason {
861
+ /** Short title (e.g. "Kategori Bilgisi Eksik veya Yanlış"). */
862
+ rejectReason?: string;
863
+ /** Full explanation of the rejection. */
864
+ rejectReasonDetail?: string;
865
+ }
866
+ /**
867
+ * An unapproved (draft) product as returned by `filterUnapprovedProducts`.
868
+ *
869
+ * Important: the wire shape is **flatter** than the approved-product shape
870
+ * exposed by `Product` — `barcode`, `quantity`, `salePrice`, etc. live at the
871
+ * root (no `variants[]` array). Each draft is one barcode/SKU.
872
+ *
873
+ * Verified against Trendyol STAGE on 2026-05-25. The official OpenAPI spec
874
+ * calls the image-list field `media`, but the live API returns it as
875
+ * `images`. SDK normalizes to `images`.
876
+ */
877
+ interface UnapprovedProduct {
878
+ /** Seller (supplier) ID echoed back by Trendyol. */
879
+ supplierId?: string;
880
+ productMainId: string;
881
+ /** Lifecycle status — see `UnapprovedProductStatus`. */
882
+ status?: UnapprovedProductStatus;
883
+ brand: NamedRef;
884
+ category: NamedRef;
885
+ barcode: string;
886
+ title: string;
887
+ description?: string;
888
+ /** Stock quantity at the moment of the query. */
889
+ quantity?: number;
890
+ listPrice?: number;
891
+ salePrice?: number;
892
+ /** VAT rate as a percentage (e.g. `20` for 20%). */
893
+ vatRate?: number;
894
+ dimensionalWeight?: number;
895
+ stockCode?: string;
896
+ /** Image URLs in display order (Trendyol's `images` field, spec says `media`). */
897
+ images: string[];
898
+ attributes: ProductAttribute[];
899
+ /** Populated when `status === 'rejected'`. */
900
+ rejectReasonDetails: UnapprovedProductRejectReason[];
901
+ /** Returned by Trendyol; null when the seller has not configured this. */
902
+ origin?: string | null;
903
+ locationBasedDelivery?: 'ENABLED' | 'DISABLED' | null;
904
+ lotNumber?: string | null;
905
+ /** Special consumption tax (ÖTV) where applicable. */
906
+ specialConsumptionTax?: number | null;
907
+ /** Suggested governance retail price (Suggested Government Retail price). */
908
+ sgrPrice?: number | null;
909
+ /** ISO 8601 UTC string (from `createDateTime` ms-epoch). */
910
+ createdAt?: string;
911
+ /** ISO 8601 UTC string (from `lastUpdateDate`). */
912
+ updatedAt?: string;
913
+ /** ISO 8601 UTC string (from `lastPriceChangeDate`). */
914
+ lastPriceChangedAt?: string;
915
+ /** ISO 8601 UTC string (from `lastStockChangeDate`). */
916
+ lastStockChangedAt?: string;
917
+ /** Untouched raw response. */
918
+ raw: Record<string, unknown>;
919
+ }
920
+ /**
921
+ * Basic lifecycle info for a single product, returned by `getProductBase`.
922
+ *
923
+ * Cheap to call (no body — just barcode in path) and useful as a polling
924
+ * primitive after `createProducts` to detect `approved: true`.
925
+ */
926
+ interface ProductBase {
927
+ barcode: string;
928
+ approved: boolean;
929
+ archived: boolean;
930
+ /** ISO 8601 UTC string (from `approvedDate` ms-epoch); `undefined` until approved. */
931
+ approvedAt?: string;
932
+ /** Stable listing ID assigned after approval. */
933
+ listingId?: string;
934
+ /** Trendyol's content ID — the same field on `Product.contentId`. */
935
+ contentId?: string;
936
+ /** Untouched raw response. */
937
+ raw: Record<string, unknown>;
938
+ }
939
+ /**
940
+ * Buybox status for a single barcode, returned by `getBuyboxInformation`.
941
+ *
942
+ * `buyboxOrder === 1` means you currently hold the buybox.
943
+ * `secondBuyboxPrice` / `thirdBuyboxPrice` are surfaced from live wire (not
944
+ * in the spec) so you can see what other sellers are charging.
945
+ */
946
+ interface BuyboxInfo {
947
+ barcode: string;
948
+ /** Position in the buybox ranking (1 = you hold it). */
949
+ buyboxOrder?: number;
950
+ /** Current buybox-winning price. */
951
+ buyboxPrice?: number;
952
+ hasMultipleSeller?: boolean;
953
+ /** Second-best price (when multiple sellers compete). */
954
+ secondBuyboxPrice?: number | null;
955
+ /** Third-best price. */
956
+ thirdBuyboxPrice?: number | null;
957
+ /** Untouched raw response. */
958
+ raw: Record<string, unknown>;
959
+ }
473
960
  /**
474
961
  * Status of an async batch request returned by `createProducts`,
475
962
  * `updatePriceAndInventory`, and other Trendyol bulk endpoints.
@@ -507,6 +994,140 @@ interface BatchRequestResult {
507
994
  raw: Record<string, unknown>;
508
995
  }
509
996
 
997
+ /**
998
+ * Input types for Trendyol product write endpoints (V2).
999
+ *
1000
+ * All five write endpoints (`createProducts`, `updateContentBulk`,
1001
+ * `updateVariantBulk`, `updateUnapproved`, `updateDeliveryInfoBulk`) are
1002
+ * async batch operations: SDK accepts the typed payload, Trendyol returns
1003
+ * `{ batchRequestId }`, and the caller polls via `products.getBatchStatus`.
1004
+ */
1005
+ /** Response shape for every async write endpoint in the product API. */
1006
+ interface BatchAcceptedResponse {
1007
+ /** Opaque ID — pass to `products.getBatchStatus(...)` to track. */
1008
+ batchRequestId: string;
1009
+ }
1010
+ /** A V2 product attribute payload. Mutually-exclusive value selectors. */
1011
+ interface ProductAttributeV2Input {
1012
+ attributeId: number;
1013
+ /**
1014
+ * One or more attribute value IDs (V2 supports multi-value when the
1015
+ * attribute's `allowMultipleAttributeValues` flag is true).
1016
+ */
1017
+ attributeValueIds?: number[];
1018
+ /** Free-text value (only when the attribute's `allowCustom` flag is true). */
1019
+ attributeValue?: string;
1020
+ }
1021
+ /** Image entry: just a URL (Trendyol fetches the image asynchronously). */
1022
+ interface ProductImageInput {
1023
+ /** https URL; Trendyol recommends 1200×1800, 96 DPI. */
1024
+ url: string;
1025
+ }
1026
+ /** Per-variant delivery option (used by `create` + `updateUnapproved`). */
1027
+ interface DeliveryOptionInput {
1028
+ deliveryDuration?: number;
1029
+ fastDeliveryType?: 'SAME_DAY_SHIPPING' | 'FAST_DELIVERY';
1030
+ }
1031
+ /**
1032
+ * Payload for one item in `createProducts` (V2).
1033
+ *
1034
+ * Trendyol requires all 14 fields listed in the spec — the type makes them
1035
+ * non-optional so missing required fields fail at compile time, not at
1036
+ * runtime after a failed batch.
1037
+ */
1038
+ interface CreateProductV2Input {
1039
+ /** Barcode (≤40 chars, allows `.`, `-`, `_`). */
1040
+ barcode: string;
1041
+ /** Title (≤100 chars). */
1042
+ title: string;
1043
+ /** Parent product ID for variant grouping (≤40 chars). */
1044
+ productMainId: string;
1045
+ /** Trendyol numeric brand ID (from `brands.list`). */
1046
+ brandId: number;
1047
+ /** Trendyol numeric category ID (from `categories.list`). */
1048
+ categoryId: number;
1049
+ /** Initial stock quantity. */
1050
+ quantity: number;
1051
+ /** Seller-side stock code (≤100 chars). */
1052
+ stockCode: string;
1053
+ /** Desi value used for shipping cost calculation. */
1054
+ dimensionalWeight: number;
1055
+ /** HTML-friendly product description (≤30 000 chars). */
1056
+ description: string;
1057
+ /** List price (PSF). Must be ≥ `salePrice`. */
1058
+ listPrice: number;
1059
+ /** Sale price (TSF). */
1060
+ salePrice: number;
1061
+ /** 1–8 image URLs. */
1062
+ images: ProductImageInput[];
1063
+ /** VAT rate as integer percent (0, 1, 10, 20). */
1064
+ vatRate: number;
1065
+ /** Required attributes for the category — fetch via `categories.getAttributes`. */
1066
+ attributes: ProductAttributeV2Input[];
1067
+ /** Delivery duration / fast-delivery type. */
1068
+ deliveryOption?: DeliveryOptionInput;
1069
+ /** Lot/SKT info (≤100 chars). */
1070
+ lotNumber?: string | null;
1071
+ /** Shipment warehouse ID (from `suppliers.getAddresses`). */
1072
+ shipmentAddressId?: number;
1073
+ /** Returning warehouse ID. */
1074
+ returningAddressId?: number;
1075
+ }
1076
+ /** Payload for one item in `updateContentBulk` (only `contentId` is required). */
1077
+ interface UpdateContentInput {
1078
+ /** From `Product.contentId` on `products.list` results. */
1079
+ contentId: number;
1080
+ title?: string;
1081
+ description?: string;
1082
+ images?: ProductImageInput[];
1083
+ /**
1084
+ * If you update ANY attribute, you must send ALL attributes — partial
1085
+ * attribute updates are not supported by Trendyol on this endpoint.
1086
+ */
1087
+ attributes?: ProductAttributeV2Input[];
1088
+ }
1089
+ /**
1090
+ * Payload for one item in `updateVariantBulk`. `barcode` is the identifier;
1091
+ * Trendyol does not allow updating the barcode itself via this endpoint.
1092
+ */
1093
+ interface UpdateVariantInput {
1094
+ barcode: string;
1095
+ stockCode?: string;
1096
+ vatRate?: number;
1097
+ shipmentAddressId?: number;
1098
+ returningAddressId?: number;
1099
+ dimensionalWeight?: number;
1100
+ lotNumber?: string | null;
1101
+ locationBasedDelivery?: 'ENABLED' | 'DISABLED' | null;
1102
+ }
1103
+ /** Payload for one item in `updateUnapprovedProducts` — all optional except `barcode`. */
1104
+ interface UpdateUnapprovedInput {
1105
+ barcode: string;
1106
+ title?: string;
1107
+ description?: string;
1108
+ productMainId?: string;
1109
+ brandId?: number;
1110
+ categoryId?: number;
1111
+ stockCode?: string;
1112
+ dimensionalWeight?: number;
1113
+ vatRate?: number;
1114
+ deliveryOption?: DeliveryOptionInput;
1115
+ locationBasedDelivery?: 'ENABLED' | 'DISABLED' | null;
1116
+ lotNumber?: string | null;
1117
+ shipmentAddressId?: number;
1118
+ returningAddressId?: number;
1119
+ images?: ProductImageInput[];
1120
+ attributes?: ProductAttributeV2Input[];
1121
+ }
1122
+ /** Payload for one item in `updateDeliveryInfoBulk`. */
1123
+ interface UpdateDeliveryInfoInput {
1124
+ barcode: string;
1125
+ deliveryOptions?: {
1126
+ deliveryDuration?: number;
1127
+ fastDeliveryType?: 'SAME_DAY_SHIPPING' | 'FAST_DELIVERY';
1128
+ };
1129
+ }
1130
+
510
1131
  interface ListProductsParams extends CursorPaginationParams {
511
1132
  /** Filter by a single barcode. */
512
1133
  barcode?: string;
@@ -515,22 +1136,47 @@ interface ListProductsParams extends CursorPaginationParams {
515
1136
  /** Filter products updated on or before this date. */
516
1137
  endDate?: Date;
517
1138
  }
1139
+ /** Date field to filter against on `listUnapproved` (default: server choice). */
1140
+ type UnapprovedDateQueryType = 'CREATED_DATE' | 'LAST_MODIFIED_DATE';
1141
+ interface ListUnapprovedProductsParams extends CursorPaginationParams {
1142
+ barcode?: string;
1143
+ startDate?: Date;
1144
+ endDate?: Date;
1145
+ /** Choose which date `startDate`/`endDate` apply to. */
1146
+ dateQueryType?: UnapprovedDateQueryType;
1147
+ /**
1148
+ * Optional override of the seller-scoped query (rare; defaults to the
1149
+ * client's `sellerId`).
1150
+ */
1151
+ supplierId?: number;
1152
+ }
518
1153
  /**
519
- * Trendyol product list + batch-result endpoints.
1154
+ * Trendyol product read + write + lifecycle + batch-result endpoints.
520
1155
  *
521
1156
  * Rate limits (per Trendyol service limits):
522
- * - filterProducts: 2000 req/min
1157
+ * - filterProducts (approved + unapproved + getProductBase): 2000 req/min
523
1158
  * - getBatchRequestResult: 1000 req/min
1159
+ * - getBuyboxInformation: 1000 req/min
1160
+ * - create/update/archive/unlock product writes: 1000 req/min (shared bucket)
1161
+ * - delete: 100 req/min (separate bucket)
524
1162
  */
525
1163
  declare class ProductsResource {
526
1164
  private readonly transport;
527
1165
  private readonly sellerId;
528
1166
  private readonly filterLimiter;
529
1167
  private readonly batchLimiter;
1168
+ private readonly buyboxLimiter;
1169
+ private readonly writeLimiter;
1170
+ private readonly deleteLimiter;
530
1171
  constructor(transport: TrendyolTransport, sellerId: number, options?: {
531
1172
  filterLimiter?: TokenBucketRateLimiter;
532
1173
  batchLimiter?: TokenBucketRateLimiter;
1174
+ buyboxLimiter?: TokenBucketRateLimiter;
1175
+ writeLimiter?: TokenBucketRateLimiter;
1176
+ deleteLimiter?: TokenBucketRateLimiter;
533
1177
  });
1178
+ private validateBarcodes;
1179
+ private submitWrite;
534
1180
  /**
535
1181
  * List approved products. Use `paginate()` from `@lonca/core` to iterate
536
1182
  * lazily across pages.
@@ -561,6 +1207,142 @@ declare class ProductsResource {
561
1207
  * @param batchRequestId The opaque ID returned by the originating call.
562
1208
  */
563
1209
  getBatchStatus(batchRequestId: string): Promise<BatchRequestResult>;
1210
+ /**
1211
+ * List **unapproved** (draft / rejected / pending-review) products.
1212
+ *
1213
+ * Wire shape is intentionally flatter than the approved-product shape:
1214
+ * each barcode is one top-level item with `barcode`, `quantity`, `salePrice`
1215
+ * etc. at the root. Rejected drafts carry `rejectReasonDetails` so you can
1216
+ * surface why Trendyol's content team turned them down.
1217
+ *
1218
+ * Pagination follows the same convention as `list()`: `cursor` from the
1219
+ * previous response forwards as `nextPageToken`.
1220
+ *
1221
+ * @example
1222
+ * ```ts
1223
+ * const page = await client.products.listUnapproved({ limit: 50 });
1224
+ * for (const draft of page.items) {
1225
+ * if (draft.status === 'rejected') {
1226
+ * console.warn(draft.barcode, draft.rejectReasonDetails);
1227
+ * }
1228
+ * }
1229
+ * ```
1230
+ */
1231
+ listUnapproved(params?: ListUnapprovedProductsParams): Promise<CursorPage<UnapprovedProduct>>;
1232
+ /**
1233
+ * Fetch the basic lifecycle status of a single product by barcode.
1234
+ *
1235
+ * Cheap and useful as a polling primitive after `createProducts`: poll
1236
+ * this endpoint until `approved` flips to `true` (or use
1237
+ * `client.products.getBatchStatus()` to track the originating batch).
1238
+ *
1239
+ * @param barcode The product barcode to look up.
1240
+ */
1241
+ getBase(barcode: string): Promise<ProductBase>;
1242
+ /**
1243
+ * Fetch buybox information for up to 10 barcodes in one call.
1244
+ *
1245
+ * Returns rank (`buyboxOrder === 1` means you hold the buybox), the
1246
+ * current buybox price, and — beyond the spec — the second and third
1247
+ * competing prices when other sellers are present.
1248
+ *
1249
+ * @param barcodes 1–10 product barcodes.
1250
+ * @throws {ValidationError} when `barcodes` is empty or longer than 10.
1251
+ */
1252
+ getBuyboxInfo(barcodes: string[]): Promise<BuyboxInfo[]>;
1253
+ /**
1254
+ * Create products (V2). Async batch — returns a `batchRequestId` you can
1255
+ * poll with `getBatchStatus`. Max 1000 items per call.
1256
+ *
1257
+ * Trendyol requires the full V2 attribute payload — fetch via
1258
+ * `categories.getAttributes` (and `categories.getAttributeValues` for
1259
+ * values when `allowCustom === false`). Shipment / returning warehouse
1260
+ * IDs come from `suppliers.getAddresses`.
1261
+ *
1262
+ * @throws {ValidationError} when `items` is empty or longer than 1000.
1263
+ */
1264
+ create(items: CreateProductV2Input[]): Promise<BatchAcceptedResponse>;
1265
+ /**
1266
+ * Update **content** of approved products (title, description, images,
1267
+ * attributes). Identified by `contentId`. Partial update is supported
1268
+ * except for attributes — if you update ANY attribute, send ALL of them.
1269
+ *
1270
+ * @throws {ValidationError} when `items` is empty or longer than 1000.
1271
+ */
1272
+ updateContent(items: UpdateContentInput[]): Promise<BatchAcceptedResponse>;
1273
+ /**
1274
+ * Update **variant** fields of approved products (stockCode, vatRate,
1275
+ * dimensionalWeight, warehouse IDs, location-based delivery, lot). Identified
1276
+ * by `barcode`. The barcode itself cannot be changed via this endpoint.
1277
+ *
1278
+ * @throws {ValidationError} when `items` is empty or longer than 1000.
1279
+ */
1280
+ updateVariants(items: UpdateVariantInput[]): Promise<BatchAcceptedResponse>;
1281
+ /**
1282
+ * Update **unapproved** (draft) products. Identified by `barcode`. All
1283
+ * other fields are optional partial updates. Use this to fix drafts that
1284
+ * Trendyol rejected — `client.products.listUnapproved` surfaces the
1285
+ * `rejectReasonDetails` you need to act on.
1286
+ *
1287
+ * **Gotcha (verified live STAGE 2026-05-25):** Trendyol's V2 spec claims
1288
+ * only `barcode` is required, but the endpoint returns HTTP 500
1289
+ * (`TrendyolSystemException` / `TypeError`) when too many optional fields
1290
+ * are omitted. In practice, send at least `title`, `description`,
1291
+ * `productMainId`, `brandId`, `categoryId`, `stockCode`,
1292
+ * `dimensionalWeight`, `vatRate`, `images[]`, and `attributes[]` (an
1293
+ * empty array is OK for the latter). The SDK forwards your payload
1294
+ * as-is; trim fields only if you have verified the server accepts it.
1295
+ *
1296
+ * @throws {ValidationError} when `items` is empty or longer than 1000.
1297
+ */
1298
+ updateUnapproved(items: UpdateUnapprovedInput[]): Promise<BatchAcceptedResponse>;
1299
+ /**
1300
+ * Update product **delivery information** (deliveryDuration,
1301
+ * fastDeliveryType). Identified by `barcode`.
1302
+ *
1303
+ * @throws {ValidationError} when `items` is empty or longer than 1000.
1304
+ */
1305
+ updateDeliveryInfo(items: UpdateDeliveryInfoInput[]): Promise<BatchAcceptedResponse>;
1306
+ /**
1307
+ * Delete products by barcode. Trendyol allows deletion of unapproved
1308
+ * products and approved products that have been archived for more than a
1309
+ * day (and have not been sales-stopped by Trendyol).
1310
+ *
1311
+ * Async batch — returns `{ batchRequestId }` to poll via `getBatchStatus`.
1312
+ * Separately rate-limited at **100 req/min** (much tighter than create/update).
1313
+ *
1314
+ * @param barcodes 1–1000 barcodes.
1315
+ * @throws {ValidationError} when `barcodes` is empty or longer than 1000.
1316
+ */
1317
+ delete(barcodes: string[]): Promise<BatchAcceptedResponse>;
1318
+ /**
1319
+ * Archive products by barcode (Trendyol's `archived=true` state).
1320
+ * Archived products are not visible to customers; pair with `delete`
1321
+ * after the 24-hour archive cool-down to remove them entirely.
1322
+ *
1323
+ * Async batch — returns `{ batchRequestId }`.
1324
+ *
1325
+ * @throws {ValidationError} when `barcodes` is empty or longer than 1000.
1326
+ */
1327
+ archive(barcodes: string[]): Promise<BatchAcceptedResponse>;
1328
+ /**
1329
+ * Unarchive products by barcode (Trendyol's `archived=false` state).
1330
+ * Restores visibility for previously-archived products.
1331
+ *
1332
+ * @throws {ValidationError} when `barcodes` is empty or longer than 1000.
1333
+ */
1334
+ unarchive(barcodes: string[]): Promise<BatchAcceptedResponse>;
1335
+ private setArchivedState;
1336
+ /**
1337
+ * Unlock products whose sale was paused by Trendyol due to pricing
1338
+ * issues (under/over-pricing, critical price error, supplier issues).
1339
+ * Restores selling status for the listed barcodes.
1340
+ *
1341
+ * Async batch — returns `{ batchRequestId }`.
1342
+ *
1343
+ * @throws {ValidationError} when `barcodes` is empty or longer than 1000.
1344
+ */
1345
+ unlock(barcodes: string[]): Promise<BatchAcceptedResponse>;
564
1346
  }
565
1347
 
566
1348
  /**
@@ -688,4 +1470,4 @@ interface TrendyolClient {
688
1470
  */
689
1471
  declare function createTrendyolClient(opts: CreateClientOptions): TrendyolClient;
690
1472
 
691
- export { type BatchRequestItemResult, type BatchRequestResult, type BatchRequestStatus, type Brand, BrandsResource, CategoriesResource, type Category, type CategoryAttribute, type CategoryAttributeValue, type CreateClientOptions, InventoryResource, type ListOrdersParams, type ListProductsParams, type NamedRef, type OrderAddress, type OrderAddressLines, type OrderCustomer, type OrderLine, type OrderLineDiscountDetail, OrdersResource, type PackageHistoryEntry, type PriceInventoryUpdate, type Product, type ProductAttribute, type ProductVariant, ProductsResource, type ShipmentPackage, type ShipmentPackageStatus, type SupplierAddress, type SupplierAddressType, SuppliersResource, type SuppliersResourceOptions, type TrendyolClient, type TrendyolEnvironment, type UpdatePriceInventoryResponse, createTrendyolClient };
1473
+ export { type BarcodeCategoryLookup, type BatchAcceptedResponse, type BatchRequestItemResult, type BatchRequestResult, type BatchRequestStatus, type Brand, BrandsResource, type BuyboxInfo, type CancelPackageItemInput, type CargoInvoiceItem, CategoriesResource, type Category, type CategoryAttribute, type CategoryAttributeValue, type CreateClientOptions, type CreateProductV2Input, type DeliveryOptionInput, InventoryResource, type LaborCostInput, type ListCategoryAttributeValuesParams, type ListOrdersParams, type ListOrdersStreamParams, type ListProductsParams, type ListUnapprovedProductsParams, type NamedRef, type OrderAddress, type OrderAddressLines, type OrderCustomer, type OrderLine, type OrderLineDiscountDetail, OrdersResource, type PackageDetail, type PackageHistoryEntry, type PackageLineUpdate, type PriceInventoryUpdate, type ProcessAlternativeDeliveryInput, type Product, type ProductAttribute, type ProductAttributeV2Input, type ProductBase, type ProductImageInput, type ProductVariant, ProductsResource, type QuantitySplit, type ShipmentPackage, type ShipmentPackageStatus, type SplitGroup, type SplitPackagePlan, type SupplierAddress, type SupplierAddressType, SuppliersResource, type SuppliersResourceOptions, type TrendyolCargoProvider, type TrendyolClient, type TrendyolEnvironment, type UnapprovedDateQueryType, type UnapprovedProduct, type UnapprovedProductRejectReason, type UnapprovedProductStatus, type UpdateBoxInfoInput, type UpdateContentInput, type UpdateDeliveryInfoInput, type UpdatePackageStatusInput, type UpdatePriceInventoryResponse, type UpdateUnapprovedInput, type UpdateVariantInput, createTrendyolClient };