@lonca/trendyol 0.6.0 → 0.7.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/README.md +268 -46
- package/dist/index.cjs +3 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,24 +4,38 @@
|
|
|
4
4
|
|
|
5
5
|
Type-safe TypeScript SDK for the [Trendyol Marketplace API](https://developers.trendyol.com).
|
|
6
6
|
|
|
7
|
-
> **`0.
|
|
7
|
+
> **`0.6.0` — Trendyol surface complete.** 14 resources, ~70 typed methods, plus a `parseWebhookEvent` helper for inbound event handling. Every endpoint a non-AutoFT non-V1 seller can hit is covered.
|
|
8
8
|
|
|
9
9
|
## Coverage
|
|
10
10
|
|
|
11
|
-
Each entry is a method on the client. Endpoints
|
|
12
|
-
|
|
13
|
-
| Resource | Methods
|
|
14
|
-
| ---------------- |
|
|
15
|
-
| `brands` | `list()`, `search(name)` ★
|
|
16
|
-
| `categories` | `list()`, `getAttributes(id)`, `getAttributeValues(catId, attrId)` ★, `getByBarcodes(barcodes)` (AutoFT)
|
|
17
|
-
| `suppliers` | `getAddresses({forceRefresh?})` (1-hour cache; rate-limited 1 req/hour on Trendyol)
|
|
18
|
-
| `products`
|
|
19
|
-
| `products` write | `create(items)`, `updateContent(items)`, `updateVariants(items)`, `updateUnapproved(items)` ★, `updateDeliveryInfo(items)`
|
|
20
|
-
| `products` life | `delete(barcodes)`, `archive(barcodes)`, `unarchive(barcodes)`, `unlock(barcodes)`
|
|
21
|
-
| `inventory` | `update(items)` (stock + price, async batch)
|
|
22
|
-
| `orders`
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
Each entry is a method on the client. Endpoints marked `★` are discovery-first wire-verified against live Trendyol STAGE — the SDK normalizes any spec/wire mismatch.
|
|
12
|
+
|
|
13
|
+
| Resource | Methods |
|
|
14
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `brands` | `list()`, `search(name)` ★ |
|
|
16
|
+
| `categories` | `list()`, `getAttributes(id)`, `getAttributeValues(catId, attrId)` ★, `getByBarcodes(barcodes)` (AutoFT) |
|
|
17
|
+
| `suppliers` | `getAddresses({forceRefresh?})` (1-hour cache; rate-limited 1 req/hour on Trendyol) |
|
|
18
|
+
| `products` read | `list({...})` ★, `listUnapproved({...})` ★, `getBase(barcode)` ★, `getBuyboxInfo(barcodes)` ★, `getBatchStatus(id)` ★ |
|
|
19
|
+
| `products` write | `create(items)`, `updateContent(items)`, `updateVariants(items)`, `updateUnapproved(items)` ★, `updateDeliveryInfo(items)` |
|
|
20
|
+
| `products` life | `delete(barcodes)`, `archive(barcodes)`, `unarchive(barcodes)`, `unlock(barcodes)` |
|
|
21
|
+
| `inventory` | `update(items)` ★ (stock + price, async batch) |
|
|
22
|
+
| `orders` read | `list({...})` ★, `listStream({...})` ★ (opaque cursor for >10K), `getCargoInvoiceItems(serial, {...})` |
|
|
23
|
+
| `orders` write | `updatePackageStatus(id, {...})`, `cancelPackageItem(id, {...})`, `extendDeliveryDate(id, 1\|2\|3)`, `processAlternativeDelivery(id, {...})` |
|
|
24
|
+
| `orders` split | `splitPackage`, `splitPackageByQuantity`, `multiSplitPackage`, `splitMultiPackagesByQuantity` (4 variants) |
|
|
25
|
+
| `orders` cargo | `changeCargoProvider(id, code)`, `manualDeliverByPackageId(id)`, `manualDeliverByTrackingNumber(trk)`, `markDeliveredByService(id)` |
|
|
26
|
+
| `orders` ops | `updateBoxInfo(id, {...})`, `updateLaborCosts(id, items)`, `updateWarehouse(id, warehouseId)` |
|
|
27
|
+
| `orders` returns | `manualReturnByPackageId(id)`, `manualReturnByTrackingNumber(trk)`, `getCompensationTickets({...})` (TEX) |
|
|
28
|
+
| `claims` | `create({...})`, `createIssue(id, {...})` (multipart), `approveLineItems(id, {...})`, `list({...})`, `getIssueReasons()` ★, `getItemAudits(itemId)` ★ |
|
|
29
|
+
| `webhooks` | `create({...})`, `list()`, `update(id, {...})`, `delete(id)`, `activate(id)`, `deactivate(id)` |
|
|
30
|
+
| `questions` | `get(id)`, `list({...})` ★, `answer(id, text)` |
|
|
31
|
+
| `invoices` | `uploadFile({shipmentPackageId, file, ...})` (multipart), `sendLink({...})`, `deleteLink({...})` |
|
|
32
|
+
| `finance` | `getSettlements({...})`, `getOtherFinancials({...})` — both return typed `FinancialTransaction[]` ★ |
|
|
33
|
+
| `labels` | `createCommon(trackingNumber, {format: 'ZPL', ...})`, `getCommon(trackingNumber)` ★ |
|
|
34
|
+
| `testOrders` | `create({...})`, `updateStatus(id, status)`, `setClaimsWaitingInAction()` — **STAGE-only utility** |
|
|
35
|
+
| `locations` | `getCountries()` ★, `getTurkeyCities()` ★, `getTurkeyDistricts(cityCode)`, `getTurkeyNeighborhoods(cityCode, districtCode)`, `getAzerbaijanCities()`, `getAzerbaijanDistricts(...)`, `getCitiesByCountry/getDistrictsByCity(...)` |
|
|
36
|
+
| **top-level** | `parseWebhookEvent(rawBody)`, `normalizeShipmentPackage(rawNode)` — for inbound webhook handlers |
|
|
37
|
+
|
|
38
|
+
**Intentionally excluded:** V1 endpoints (Trendyol sunsets them August 2026), AutoFT (Export Center) program-specific endpoints other than `getByBarcodes`, `processAlternativeDeliveryDigital` (digital products only).
|
|
25
39
|
|
|
26
40
|
## Install
|
|
27
41
|
|
|
@@ -43,10 +57,9 @@ const client = createTrendyolClient({
|
|
|
43
57
|
apiKey: process.env.TRENDYOL_API_KEY!,
|
|
44
58
|
apiSecret: process.env.TRENDYOL_API_SECRET!,
|
|
45
59
|
env: 'stage', // or 'prod'
|
|
46
|
-
integratorName: 'MyCompany', //
|
|
60
|
+
integratorName: 'MyCompany', // required; use 'SelfIntegration' if the seller owns the integration code
|
|
47
61
|
});
|
|
48
62
|
|
|
49
|
-
// Iterate every product page-by-page.
|
|
50
63
|
for await (const product of paginate((p) => client.products.list(p))) {
|
|
51
64
|
for (const variant of product.variants) {
|
|
52
65
|
console.log(variant.barcode, product.title, variant.stock ?? '?');
|
|
@@ -54,32 +67,26 @@ for await (const product of paginate((p) => client.products.list(p))) {
|
|
|
54
67
|
}
|
|
55
68
|
```
|
|
56
69
|
|
|
57
|
-
## End-to-end
|
|
58
|
-
|
|
59
|
-
The chain `brand → category → category attributes (+ values) → addresses → create → poll → verify` is the canonical "create a real listing" flow.
|
|
70
|
+
## End-to-end flows
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
import { createTrendyolClient } from '@lonca/trendyol';
|
|
72
|
+
### Create a product
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
The chain `brand → category → attributes (+ values) → addresses → create → poll → verify`:
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
```ts
|
|
67
77
|
const [brand] = await client.brands.search('TRENDYOLMİLLA');
|
|
68
78
|
const tree = await client.categories.list();
|
|
69
79
|
const category = findLeaf(tree, /Elbise/); // your own walker
|
|
70
80
|
|
|
71
|
-
// 2. Fetch required attributes + a value (Renk = Kırmızı).
|
|
72
81
|
const attrs = await client.categories.getAttributes(category.id);
|
|
73
82
|
const renk = attrs.find((a) => a.name === 'Renk')!;
|
|
74
83
|
const renkValues = await client.categories.getAttributeValues(category.id, renk.id);
|
|
75
84
|
const kirmizi = renkValues.items.find((v) => v.name === 'Kırmızı')!;
|
|
76
85
|
|
|
77
|
-
// 3. Resolve shipment / returning warehouse IDs.
|
|
78
86
|
const addresses = await client.suppliers.getAddresses();
|
|
79
87
|
const shipment = addresses.find((a) => a.isShipmentAddress)!;
|
|
80
88
|
const returning = addresses.find((a) => a.isReturningAddress)!;
|
|
81
89
|
|
|
82
|
-
// 4. Submit the create (async batch).
|
|
83
90
|
const { batchRequestId } = await client.products.create([
|
|
84
91
|
{
|
|
85
92
|
barcode: 'MY-SKU-001',
|
|
@@ -101,20 +108,111 @@ const { batchRequestId } = await client.products.create([
|
|
|
101
108
|
},
|
|
102
109
|
]);
|
|
103
110
|
|
|
104
|
-
//
|
|
111
|
+
// Poll the batch → detect approval.
|
|
105
112
|
let result;
|
|
106
113
|
do {
|
|
107
114
|
await new Promise((r) => setTimeout(r, 2000));
|
|
108
115
|
result = await client.products.getBatchStatus(batchRequestId);
|
|
109
116
|
} while (result.items[0]?.status === 'PROCESSING');
|
|
110
117
|
|
|
111
|
-
// 6. Detect approval (or surface a rejection reason).
|
|
112
118
|
if (result.items[0]?.status === 'SUCCESS') {
|
|
113
119
|
const base = await client.products.getBase('MY-SKU-001');
|
|
114
120
|
console.log('Approved:', base.approved, 'contentId:', base.contentId);
|
|
115
121
|
}
|
|
116
122
|
```
|
|
117
123
|
|
|
124
|
+
### Handle inbound webhooks (Express)
|
|
125
|
+
|
|
126
|
+
Trendyol POSTs the same body shape as `getShipmentPackages` to your endpoint on status events. `parseWebhookEvent` returns typed `ShipmentPackage[]`:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import express from 'express';
|
|
130
|
+
import { parseWebhookEvent } from '@lonca/trendyol';
|
|
131
|
+
|
|
132
|
+
const app = express();
|
|
133
|
+
|
|
134
|
+
app.post('/trendyol/webhook', express.json(), (req, res) => {
|
|
135
|
+
// Authenticate Trendyol against your endpoint here (Basic or x-api-key,
|
|
136
|
+
// matching the auth method you configured on the subscription).
|
|
137
|
+
|
|
138
|
+
const event = parseWebhookEvent(req.body);
|
|
139
|
+
for (const pkg of event.packages) {
|
|
140
|
+
// pkg: typed ShipmentPackage — same shape as orders.list()
|
|
141
|
+
await myQueue.enqueue({
|
|
142
|
+
packageId: pkg.id,
|
|
143
|
+
orderNumber: pkg.orderNumber,
|
|
144
|
+
status: pkg.status,
|
|
145
|
+
createdBy: pkg.raw.createdBy, // 'order-creation' | 'cancel' | 'split' | 'transfer'
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
res.sendStatus(200);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Register the subscription once.
|
|
152
|
+
await client.webhooks.create({
|
|
153
|
+
url: 'https://my-app.example.com/trendyol/webhook',
|
|
154
|
+
authenticationType: 'API_KEY',
|
|
155
|
+
apiKey: process.env.TRENDYOL_WEBHOOK_API_KEY!,
|
|
156
|
+
subscribedStatuses: ['CREATED', 'SHIPPED', 'DELIVERED'],
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
> **Important:** Trendyol authenticates against _your_ endpoint with the auth method you choose. There's no HMAC signature — pick `API_KEY` over `BASIC_AUTHENTICATION` so you can rotate the secret without redeploying. Trendyol retries failed deliveries every 5 minutes and auto-deactivates the subscription after persistent failures (you'll get 2 emails). Call `webhooks.activate(id)` to bring it back online once your endpoint is healthy.
|
|
161
|
+
|
|
162
|
+
### Handle a return / claim
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// 1. New customer-filed claims arrive via list().
|
|
166
|
+
const claims = await client.claims.list({ claimItemStatus: 'WaitingInAction' });
|
|
167
|
+
|
|
168
|
+
for (const claim of claims.items) {
|
|
169
|
+
// 2a. Approve all the line items in the claim → triggers refund flow.
|
|
170
|
+
await client.claims.approveLineItems(claim.id, {
|
|
171
|
+
claimLineItemIdList: claim.raw.items.map((i: any) => i.id),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// 2b. OR reject the claim with a documented reason + supporting docs.
|
|
175
|
+
const reasons = await client.claims.getIssueReasons();
|
|
176
|
+
await client.claims.createIssue(claim.id, {
|
|
177
|
+
claimIssueReasonId: reasons.find((r) => r.name.includes('kullanılmış'))!.id,
|
|
178
|
+
claimItemIdList: claim.raw.items.map((i: any) => i.id),
|
|
179
|
+
description: 'Ürün kullanılmış olarak iade edildi, retten kaynaklı reddediliyor.',
|
|
180
|
+
files: [pdfBlob, photoBlob],
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 3. After you've received the physical package back, mark it:
|
|
185
|
+
await client.orders.manualReturnByPackageId(packageId);
|
|
186
|
+
// or, if you only have the cargo tracking number:
|
|
187
|
+
await client.orders.manualReturnByTrackingNumber(trackingNumber);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Reconcile settlements
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const start = new Date('2026-05-01');
|
|
194
|
+
const end = new Date('2026-05-31');
|
|
195
|
+
|
|
196
|
+
for await (const tx of paginate((p) =>
|
|
197
|
+
client.finance.getSettlements({ ...p, startDate: start, endDate: end }),
|
|
198
|
+
)) {
|
|
199
|
+
// tx is a typed FinancialTransaction — no .raw drill required for documented fields
|
|
200
|
+
if (tx.transactionType === 'Satış' && tx.orderNumber) {
|
|
201
|
+
await db.recordSale({
|
|
202
|
+
orderNumber: tx.orderNumber,
|
|
203
|
+
revenue: tx.sellerRevenue ?? 0,
|
|
204
|
+
commission: tx.commissionAmount ?? 0,
|
|
205
|
+
transactionDate: tx.transactionDate,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// "Other financials" (cargo deductions, labor adjustments) share the same shape.
|
|
211
|
+
const cargoDeductions = await client.finance.getOtherFinancials({
|
|
212
|
+
transactionType: 'DeductionInvoices',
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
118
216
|
## Per-resource cheat sheet
|
|
119
217
|
|
|
120
218
|
```ts
|
|
@@ -140,25 +238,127 @@ await client.products.getBuyboxInfo(['BC1', 'BC2']); // max 10 per call
|
|
|
140
238
|
await client.products.getBatchStatus(batchRequestId);
|
|
141
239
|
|
|
142
240
|
// products — write (all return { batchRequestId }; max 1000 items)
|
|
143
|
-
await client.products.create([
|
|
241
|
+
await client.products.create([
|
|
242
|
+
/* CreateProductV2Input */
|
|
243
|
+
]);
|
|
144
244
|
await client.products.updateContent([{ contentId: 123, title: '...' }]);
|
|
145
245
|
await client.products.updateVariants([{ barcode: 'BC1', stockCode: 'NEW' }]);
|
|
146
|
-
await client.products.updateUnapproved([{ barcode: 'BC1', title: '...'
|
|
147
|
-
await client.products.updateDeliveryInfo([
|
|
246
|
+
await client.products.updateUnapproved([{ barcode: 'BC1', title: '...' /* fuller payload */ }]);
|
|
247
|
+
await client.products.updateDeliveryInfo([
|
|
248
|
+
{ barcode: 'BC1', deliveryOptions: { deliveryDuration: 3 } },
|
|
249
|
+
]);
|
|
148
250
|
|
|
149
251
|
// products — lifecycle
|
|
150
|
-
await client.products.delete(['BC1']);
|
|
151
|
-
await client.products.archive(['BC1']);
|
|
152
|
-
await client.products.unarchive(['BC1']);
|
|
153
|
-
await client.products.unlock(['BC1']);
|
|
252
|
+
await client.products.delete(['BC1']); // separately rate-limited (100/min)
|
|
253
|
+
await client.products.archive(['BC1']); // PUT archived=true
|
|
254
|
+
await client.products.unarchive(['BC1']); // PUT archived=false
|
|
255
|
+
await client.products.unlock(['BC1']); // restore after Trendyol price-lock
|
|
154
256
|
|
|
155
257
|
// inventory — async batch
|
|
156
|
-
await client.inventory.update([
|
|
258
|
+
await client.inventory.update([
|
|
259
|
+
{ barcode: 'BC1', quantity: 50, salePrice: 199.9, listPrice: 299.9 },
|
|
260
|
+
]);
|
|
157
261
|
|
|
158
|
-
// orders —
|
|
159
|
-
for await (const pkg of paginate((p) => client.orders.list(p))) {
|
|
160
|
-
|
|
161
|
-
|
|
262
|
+
// orders — read
|
|
263
|
+
for await (const pkg of paginate((p) => client.orders.list(p))) { ... }
|
|
264
|
+
for await (const pkg of paginate((p) => client.orders.listStream({ ...p, packageItemStatuses: 'Created,Picking' }))) { ... }
|
|
265
|
+
await client.orders.getCargoInvoiceItems('INV-2026-001');
|
|
266
|
+
|
|
267
|
+
// orders — status / cargo
|
|
268
|
+
await client.orders.updatePackageStatus(pkgId, { status: 'Picking' });
|
|
269
|
+
await client.orders.updatePackageStatus(pkgId, { status: 'Invoiced' });
|
|
270
|
+
await client.orders.cancelPackageItem(pkgId, { lines: [{ lineId: 1, quantity: 1 }], reasonId: 577 });
|
|
271
|
+
await client.orders.extendDeliveryDate(pkgId, 2);
|
|
272
|
+
await client.orders.processAlternativeDelivery(pkgId, {
|
|
273
|
+
isPhoneNumber: false,
|
|
274
|
+
trackingInfo: 'https://my-cargo/track/abc',
|
|
275
|
+
params: { provider: 'EXAMPLE_CARGO' },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// orders — splits (4 variants — see JSDoc)
|
|
279
|
+
await client.orders.splitPackage(pkgId, [lineId1, lineId2]);
|
|
280
|
+
await client.orders.splitPackageByQuantity(pkgId, [{ orderLineId: 100, quantities: [2, 2, 1] }]);
|
|
281
|
+
await client.orders.multiSplitPackage(pkgId, [{ orderLineIds: [3, 5] }, { orderLineIds: [7, 8] }]);
|
|
282
|
+
await client.orders.splitMultiPackagesByQuantity(pkgId, [
|
|
283
|
+
{ packageDetails: [{ orderLineId: 12345, quantities: 2 }] },
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
// orders — cargo + manual delivery
|
|
287
|
+
await client.orders.changeCargoProvider(pkgId, 'ARASMP'); // open enum (see TrendyolCargoProvider)
|
|
288
|
+
await client.orders.manualDeliverByPackageId(pkgId);
|
|
289
|
+
await client.orders.manualDeliverByTrackingNumber(trackingNumber);
|
|
290
|
+
await client.orders.markDeliveredByService(pkgId);
|
|
291
|
+
|
|
292
|
+
// orders — operational metadata
|
|
293
|
+
await client.orders.updateBoxInfo(pkgId, { deci: 2.5, boxQuantity: 1 });
|
|
294
|
+
await client.orders.updateLaborCosts(pkgId, [{ orderLineId: 100, laborCostPerItem: 32.12 }]);
|
|
295
|
+
await client.orders.updateWarehouse(pkgId, warehouseId);
|
|
296
|
+
|
|
297
|
+
// orders — returns + compensation
|
|
298
|
+
await client.orders.manualReturnByPackageId(pkgId);
|
|
299
|
+
await client.orders.manualReturnByTrackingNumber(trackingNumber);
|
|
300
|
+
const tickets = await client.orders.getCompensationTickets({ startDate: lastMonth }); // TEX-only
|
|
301
|
+
|
|
302
|
+
// claims
|
|
303
|
+
await client.claims.create({
|
|
304
|
+
orderNumber: 'ORD-1',
|
|
305
|
+
claimItems: [{ barcode: 'BC1', quantity: 1, reasonId: 401 }],
|
|
306
|
+
});
|
|
307
|
+
await client.claims.createIssue(claimId, {
|
|
308
|
+
claimIssueReasonId: 5,
|
|
309
|
+
claimItemIdList: ['item-1', 'item-2'],
|
|
310
|
+
description: '...',
|
|
311
|
+
files: [pdfBlob],
|
|
312
|
+
});
|
|
313
|
+
await client.claims.approveLineItems(claimId, { claimLineItemIdList: ['line-1'] });
|
|
314
|
+
const claims = await client.claims.list({ claimItemStatus: 'WaitingInAction' });
|
|
315
|
+
const reasons = await client.claims.getIssueReasons();
|
|
316
|
+
const audits = await client.claims.getItemAudits(claimItemId);
|
|
317
|
+
|
|
318
|
+
// webhooks
|
|
319
|
+
await client.webhooks.create({
|
|
320
|
+
url: 'https://my-app/hook',
|
|
321
|
+
authenticationType: 'API_KEY',
|
|
322
|
+
apiKey: 'rotatable-secret',
|
|
323
|
+
subscribedStatuses: ['CREATED', 'SHIPPED'],
|
|
324
|
+
});
|
|
325
|
+
const subs = await client.webhooks.list();
|
|
326
|
+
await client.webhooks.update(id, { ...updated });
|
|
327
|
+
await client.webhooks.delete(id);
|
|
328
|
+
await client.webhooks.activate(id);
|
|
329
|
+
await client.webhooks.deactivate(id);
|
|
330
|
+
|
|
331
|
+
// questions
|
|
332
|
+
const q = await client.questions.get(questionId);
|
|
333
|
+
const pending = await client.questions.list({ status: 'WAITING_FOR_ANSWER' });
|
|
334
|
+
await client.questions.answer(questionId, 'Cevap metni (10–2000 chars).');
|
|
335
|
+
|
|
336
|
+
// invoices
|
|
337
|
+
await client.invoices.uploadFile({ shipmentPackageId: 100, file: pdfBlob });
|
|
338
|
+
await client.invoices.sendLink({ shipmentPackageId: 100, invoiceLink: 'https://x/i.pdf' });
|
|
339
|
+
await client.invoices.deleteLink({ serviceSourceId: 1, channelId: 2, customerId: 3 });
|
|
340
|
+
|
|
341
|
+
// finance — typed FinancialTransaction[]
|
|
342
|
+
await client.finance.getSettlements({ startDate, endDate });
|
|
343
|
+
await client.finance.getOtherFinancials({ transactionType: 'DeductionInvoices' });
|
|
344
|
+
|
|
345
|
+
// labels
|
|
346
|
+
await client.labels.createCommon(trackingNumber, { format: 'ZPL', boxQuantity: 2 });
|
|
347
|
+
const label = await client.labels.getCommon(trackingNumber);
|
|
348
|
+
console.log(label.labels[0]?.label); // ZPL string
|
|
349
|
+
|
|
350
|
+
// test orders (STAGE-only)
|
|
351
|
+
await client.testOrders.create({
|
|
352
|
+
/* CreateTestOrderInput */
|
|
353
|
+
});
|
|
354
|
+
await client.testOrders.updateStatus(pkgId, 'Shipped');
|
|
355
|
+
await client.testOrders.setClaimsWaitingInAction();
|
|
356
|
+
|
|
357
|
+
// locations (no sellerId — utility lookup)
|
|
358
|
+
const countries = await client.locations.getCountries();
|
|
359
|
+
const cities = await client.locations.getTurkeyCities();
|
|
360
|
+
const districts = await client.locations.getTurkeyDistricts(cityCode);
|
|
361
|
+
const neighborhoods = await client.locations.getTurkeyNeighborhoods(cityCode, districtCode);
|
|
162
362
|
```
|
|
163
363
|
|
|
164
364
|
## Async batch + polling
|
|
@@ -175,7 +375,7 @@ const status = await client.products.getBatchStatus(batchRequestId);
|
|
|
175
375
|
|
|
176
376
|
## Discovery-first wire fixes
|
|
177
377
|
|
|
178
|
-
`@lonca/trendyol` was built by hitting the live Trendyol STAGE for every endpoint before writing types.
|
|
378
|
+
`@lonca/trendyol` was built by hitting the live Trendyol STAGE for every endpoint before writing types. Places where the official OpenAPI spec disagrees with the live wire are normalized automatically:
|
|
179
379
|
|
|
180
380
|
- `categories.getAttributeValues`: spec says `attributeValueName`, wire returns `attributeValue` → SDK normalizes to `{ id, name }`
|
|
181
381
|
- `products.listUnapproved`: spec says `media: [{url}]`, wire returns `images: [{url}]` → SDK exposes `images: string[]`
|
|
@@ -183,6 +383,12 @@ const status = await client.products.getBatchStatus(batchRequestId);
|
|
|
183
383
|
- `products.updateUnapproved`: spec marks only `barcode` required, but live endpoint returns HTTP 500 (`TypeError`) when too many optional fields are omitted → documented in JSDoc
|
|
184
384
|
- `brands.search`: docs claim case-sensitive exact match, live is substring + case-insensitive → documented in JSDoc
|
|
185
385
|
- `getBatchRequestResult`: returns `PROCESSING + empty items` for unknown batch IDs (not 404)
|
|
386
|
+
- `orders.listStream` returns package ID as `id`, regular `orders.list` returns it as `shipmentPackageId` → normalizer accepts both
|
|
387
|
+
- `orders.updateLaborCosts`: body is a **raw array** (no `{ items: [...] }` envelope) — only endpoint in the surface that does this
|
|
388
|
+
- `getCompensationTickets`: spec says `{ data: { items: [] } }`, but SDK also accepts `{ data: [] }` and `{ content: [] }` defensively
|
|
389
|
+
- `labels.getCommon`: response is `{ data: [{ label, format }] }` → SDK surfaces `labels[]` for ergonomic access
|
|
390
|
+
- `finance.*`: both `getSettlements` and `getOtherFinancials` share the same `FinancialTransaction` wire schema → unified typed surface
|
|
391
|
+
- `webhooks.list`: SDK accepts 3 envelope shapes (`[]` raw, `{ webhooks: [] }`, `{ content: [] }`) and 3 active-flag spellings (`active`, `isActive`, `status: 'ACTIVE'`)
|
|
186
392
|
|
|
187
393
|
Each fix is pinned by a regression mock test using the exact STAGE shape.
|
|
188
394
|
|
|
@@ -202,15 +408,31 @@ Trendyol uses HTTP Basic Auth. Get your `sellerId`, `apiKey`, and `apiSecret` fr
|
|
|
202
408
|
## Built-in robustness
|
|
203
409
|
|
|
204
410
|
- **Retry with exponential backoff** on 429 (respects `Retry-After`) and 5xx
|
|
205
|
-
- **Per-endpoint rate limiting** (token bucket) sized to Trendyol's documented limits —
|
|
411
|
+
- **Per-endpoint rate limiting** (token bucket) sized to Trendyol's documented limits — see defaults below; override per resource
|
|
412
|
+
- **Per-request correlation ID** — every call gets a UUID surfaced in log messages and the `x-correlationid` header for Trendyol-side log tracing
|
|
206
413
|
- **Structured errors** via `@lonca/core` (`AuthError`, `RateLimitError`, `NotFoundError`, `ServerError`, `ValidationError`, `NetworkError`, `TimeoutError`)
|
|
207
|
-
- **Client-side validation** before the network: empty batches, oversized batches (>1000 items), >10 buybox barcodes throw `ValidationError`
|
|
208
|
-
- **
|
|
414
|
+
- **Client-side validation** before the network: empty batches, oversized batches (>1000 items), >10 buybox barcodes, ≤500-char claim descriptions, 10–2000-char Q&A answers — all throw `ValidationError`
|
|
415
|
+
- **Multipart upload support** — `claims.createIssue` and `invoices.uploadFile` build `FormData` internally and the transport handles `Content-Type` correctly
|
|
209
416
|
- **`AbortSignal` support** throughout
|
|
210
417
|
|
|
418
|
+
### Rate-limiter defaults
|
|
419
|
+
|
|
420
|
+
| Bucket | Default capacity | Interval | Used by |
|
|
421
|
+
| ------------ | :--------------: | :------: | --------------------------------------------- |
|
|
422
|
+
| `filter` | 2000 | 60 s | products filter / list |
|
|
423
|
+
| `batch read` | 1000 | 60 s | products batch read, orders list, finance |
|
|
424
|
+
| `buybox` | 1000 | 60 s | buybox lookups |
|
|
425
|
+
| `writes` | 1000 | 60 s | most write endpoints (create / update) |
|
|
426
|
+
| `delete` | 100 | 60 s | DELETE endpoints |
|
|
427
|
+
| `categories` | 50 | 60 s | categories list (cached by callers) |
|
|
428
|
+
| `webhooks` | 50 | 60 s | webhook config CRUD |
|
|
429
|
+
| `suppliers` | 1 | 1 h | suppliers list (Trendyol caps this at 1/hour) |
|
|
430
|
+
|
|
431
|
+
Override per resource by passing a `TokenBucketRateLimiter` from `@lonca/core` when constructing the resource directly.
|
|
432
|
+
|
|
211
433
|
## Stability
|
|
212
434
|
|
|
213
|
-
`0.x` — alpha. The
|
|
435
|
+
`0.x` — alpha. The Trendyol surface is feature-complete and STAGE-verified, but public types may still adjust between minor versions until `1.0.0`.
|
|
214
436
|
|
|
215
437
|
## License
|
|
216
438
|
|
package/dist/index.cjs
CHANGED
|
@@ -2226,7 +2226,7 @@ function buildAuthHeader(apiKey, apiSecret) {
|
|
|
2226
2226
|
const token = Buffer.from(`${apiKey}:${apiSecret}`, "utf8").toString("base64");
|
|
2227
2227
|
return `Basic ${token}`;
|
|
2228
2228
|
}
|
|
2229
|
-
function buildUserAgent(sellerId, integratorName
|
|
2229
|
+
function buildUserAgent(sellerId, integratorName) {
|
|
2230
2230
|
return `${sellerId} - ${integratorName}`;
|
|
2231
2231
|
}
|
|
2232
2232
|
function mapHttpError(status, body, retryAfterMs) {
|
|
@@ -2387,13 +2387,12 @@ var TrendyolTransport = class {
|
|
|
2387
2387
|
return url.toString();
|
|
2388
2388
|
}
|
|
2389
2389
|
buildHeaders(correlationId) {
|
|
2390
|
-
const integratorName = this.config.integratorName ?? "SelfIntegration";
|
|
2391
2390
|
return {
|
|
2392
2391
|
Authorization: buildAuthHeader(this.config.apiKey, this.config.apiSecret),
|
|
2393
2392
|
"x-clientip": this.config.clientIp ?? "127.0.0.1",
|
|
2394
2393
|
"x-correlationid": correlationId,
|
|
2395
|
-
"x-agentname": integratorName,
|
|
2396
|
-
"User-Agent": buildUserAgent(this.config.sellerId, integratorName),
|
|
2394
|
+
"x-agentname": this.config.integratorName,
|
|
2395
|
+
"User-Agent": buildUserAgent(this.config.sellerId, this.config.integratorName),
|
|
2397
2396
|
"Content-Type": "application/json",
|
|
2398
2397
|
Accept: "application/json"
|
|
2399
2398
|
};
|