@lonca/trendyol 0.3.0 → 0.5.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 +172 -14
- package/dist/index.cjs +1182 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +959 -1
- package/dist/index.d.ts +959 -1
- package/dist/index.js +1175 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
# @lonca/trendyol
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@lonca/trendyol)
|
|
4
|
+
|
|
3
5
|
Type-safe TypeScript SDK for the [Trendyol Marketplace API](https://developers.trendyol.com).
|
|
4
6
|
|
|
5
|
-
>
|
|
7
|
+
> **`0.3.0` — bottom-up Phase 2 complete.** The full product surface is now wire-verified: brands · categories · suppliers · products (read + write + lifecycle) · inventory · orders.
|
|
8
|
+
|
|
9
|
+
## Coverage
|
|
10
|
+
|
|
11
|
+
Each entry is a method on the client. Endpoints behind `★` 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` | `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` | `list({...})` (shipment packages) ★ |
|
|
23
|
+
|
|
24
|
+
Trendyol's product API has 12 V2 read/write/lifecycle endpoints — all 12 are covered. Categories V2 (tree + attributes + values + barcode→category lookup) is complete. V1 endpoints are intentionally skipped (Trendyol sunsets them August 2026). Order sub-endpoints (`updatePackageStatus`, `splitShipmentPackage`, returns, claims, webhooks) land in subsequent phases.
|
|
6
25
|
|
|
7
26
|
## Install
|
|
8
27
|
|
|
9
28
|
```bash
|
|
10
|
-
pnpm add @lonca/trendyol
|
|
29
|
+
pnpm add @lonca/trendyol @lonca/core
|
|
30
|
+
# or npm install / yarn add
|
|
11
31
|
```
|
|
12
32
|
|
|
33
|
+
`@lonca/core` is a peer dependency (provides `paginate`, `CursorPage`, error classes, the token-bucket limiter).
|
|
34
|
+
|
|
13
35
|
## Quick start
|
|
14
36
|
|
|
15
37
|
```ts
|
|
@@ -24,24 +46,151 @@ const client = createTrendyolClient({
|
|
|
24
46
|
integratorName: 'MyCompany', // optional; defaults to 'SelfIntegration'
|
|
25
47
|
});
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
|
|
49
|
+
// Iterate every product page-by-page.
|
|
50
|
+
for await (const product of paginate((p) => client.products.list(p))) {
|
|
51
|
+
for (const variant of product.variants) {
|
|
52
|
+
console.log(variant.barcode, product.title, variant.stock ?? '?');
|
|
53
|
+
}
|
|
29
54
|
}
|
|
30
55
|
```
|
|
31
56
|
|
|
32
|
-
##
|
|
57
|
+
## End-to-end: create a product
|
|
33
58
|
|
|
34
|
-
|
|
59
|
+
The chain `brand → category → category attributes (+ values) → addresses → create → poll → verify` is the canonical "create a real listing" flow.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createTrendyolClient } from '@lonca/trendyol';
|
|
35
63
|
|
|
36
|
-
|
|
64
|
+
const client = createTrendyolClient({ ... });
|
|
65
|
+
|
|
66
|
+
// 1. Resolve brand + category IDs.
|
|
67
|
+
const [brand] = await client.brands.search('TRENDYOLMİLLA');
|
|
68
|
+
const tree = await client.categories.list();
|
|
69
|
+
const category = findLeaf(tree, /Elbise/); // your own walker
|
|
70
|
+
|
|
71
|
+
// 2. Fetch required attributes + a value (Renk = Kırmızı).
|
|
72
|
+
const attrs = await client.categories.getAttributes(category.id);
|
|
73
|
+
const renk = attrs.find((a) => a.name === 'Renk')!;
|
|
74
|
+
const renkValues = await client.categories.getAttributeValues(category.id, renk.id);
|
|
75
|
+
const kirmizi = renkValues.items.find((v) => v.name === 'Kırmızı')!;
|
|
76
|
+
|
|
77
|
+
// 3. Resolve shipment / returning warehouse IDs.
|
|
78
|
+
const addresses = await client.suppliers.getAddresses();
|
|
79
|
+
const shipment = addresses.find((a) => a.isShipmentAddress)!;
|
|
80
|
+
const returning = addresses.find((a) => a.isReturningAddress)!;
|
|
81
|
+
|
|
82
|
+
// 4. Submit the create (async batch).
|
|
83
|
+
const { batchRequestId } = await client.products.create([
|
|
84
|
+
{
|
|
85
|
+
barcode: 'MY-SKU-001',
|
|
86
|
+
title: 'Kırmızı Elbise',
|
|
87
|
+
productMainId: 'MY-MAIN-001',
|
|
88
|
+
brandId: Number(brand.id),
|
|
89
|
+
categoryId: Number(category.id),
|
|
90
|
+
quantity: 10,
|
|
91
|
+
stockCode: 'MY-SC-001',
|
|
92
|
+
dimensionalWeight: 1,
|
|
93
|
+
description: '<p>...</p>',
|
|
94
|
+
listPrice: 299.9,
|
|
95
|
+
salePrice: 199.9,
|
|
96
|
+
images: [{ url: 'https://cdn.example.com/dress.jpg' }],
|
|
97
|
+
vatRate: 20,
|
|
98
|
+
attributes: [{ attributeId: Number(renk.id), attributeValueIds: [Number(kirmizi.id)] }],
|
|
99
|
+
shipmentAddressId: Number(shipment.id),
|
|
100
|
+
returningAddressId: Number(returning.id),
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
// 5. Poll until Trendyol finishes content review.
|
|
105
|
+
let result;
|
|
106
|
+
do {
|
|
107
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
108
|
+
result = await client.products.getBatchStatus(batchRequestId);
|
|
109
|
+
} while (result.items[0]?.status === 'PROCESSING');
|
|
110
|
+
|
|
111
|
+
// 6. Detect approval (or surface a rejection reason).
|
|
112
|
+
if (result.items[0]?.status === 'SUCCESS') {
|
|
113
|
+
const base = await client.products.getBase('MY-SKU-001');
|
|
114
|
+
console.log('Approved:', base.approved, 'contentId:', base.contentId);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
37
117
|
|
|
38
|
-
##
|
|
118
|
+
## Per-resource cheat sheet
|
|
39
119
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
120
|
+
```ts
|
|
121
|
+
// brands
|
|
122
|
+
await client.brands.list({ limit: 1000 });
|
|
123
|
+
await client.brands.search('TRENDYOLMİLLA'); // substring + case-insensitive
|
|
124
|
+
|
|
125
|
+
// categories
|
|
126
|
+
const tree = await client.categories.list();
|
|
127
|
+
const attrs = await client.categories.getAttributes(catId);
|
|
128
|
+
const values = await client.categories.getAttributeValues(catId, attrId);
|
|
129
|
+
await client.categories.getByBarcodes(['BC1', 'BC2']); // requires AutoFT enrollment
|
|
130
|
+
|
|
131
|
+
// suppliers (cached 1h)
|
|
132
|
+
await client.suppliers.getAddresses();
|
|
133
|
+
await client.suppliers.getAddresses({ forceRefresh: true });
|
|
134
|
+
|
|
135
|
+
// products — read
|
|
136
|
+
await client.products.list({ barcode: 'BC1' });
|
|
137
|
+
await client.products.listUnapproved({ limit: 50 });
|
|
138
|
+
await client.products.getBase('BC1');
|
|
139
|
+
await client.products.getBuyboxInfo(['BC1', 'BC2']); // max 10 per call
|
|
140
|
+
await client.products.getBatchStatus(batchRequestId);
|
|
141
|
+
|
|
142
|
+
// products — write (all return { batchRequestId }; max 1000 items)
|
|
143
|
+
await client.products.create([...]);
|
|
144
|
+
await client.products.updateContent([{ contentId: 123, title: '...' }]);
|
|
145
|
+
await client.products.updateVariants([{ barcode: 'BC1', stockCode: 'NEW' }]);
|
|
146
|
+
await client.products.updateUnapproved([{ barcode: 'BC1', title: '...', /* fuller payload */ }]);
|
|
147
|
+
await client.products.updateDeliveryInfo([{ barcode: 'BC1', deliveryOptions: { deliveryDuration: 3 } }]);
|
|
148
|
+
|
|
149
|
+
// products — lifecycle
|
|
150
|
+
await client.products.delete(['BC1']); // separately rate-limited (100/min)
|
|
151
|
+
await client.products.archive(['BC1']); // PUT archived=true
|
|
152
|
+
await client.products.unarchive(['BC1']); // PUT archived=false
|
|
153
|
+
await client.products.unlock(['BC1']); // restore after Trendyol price-lock
|
|
154
|
+
|
|
155
|
+
// inventory — async batch
|
|
156
|
+
await client.inventory.update([{ barcode: 'BC1', quantity: 50, salePrice: 199.9, listPrice: 299.9 }]);
|
|
157
|
+
|
|
158
|
+
// orders — shipment packages
|
|
159
|
+
for await (const pkg of paginate((p) => client.orders.list(p))) {
|
|
160
|
+
console.log(pkg.id, pkg.status, pkg.lines.length);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Async batch + polling
|
|
165
|
+
|
|
166
|
+
Every write endpoint (`products.create`, `updateContent`, `updateVariants`, `updateUnapproved`, `updateDeliveryInfo`, `delete`, `archive`, `unarchive`, `unlock`, `inventory.update`) is **asynchronous**: Trendyol accepts the batch and returns a `{ batchRequestId }`. Poll the result with:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const status = await client.products.getBatchStatus(batchRequestId);
|
|
170
|
+
// status.status: 'PROCESSING' | 'COMPLETED' | 'FAILED'
|
|
171
|
+
// status.items[].status: per-item outcome
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Important:** Trendyol's overall batch `status` can lag at `PROCESSING` even after each `items[].status` has settled. Trust the per-item status, or re-read the affected products via `list({ barcode })` / `getBase(barcode)` to verify the change landed. Batch results are retained for **4 hours** on Trendyol's side.
|
|
175
|
+
|
|
176
|
+
## Discovery-first wire fixes
|
|
177
|
+
|
|
178
|
+
`@lonca/trendyol` was built by hitting the live Trendyol STAGE for every endpoint before writing types. Several places where the official OpenAPI spec disagrees with the live wire are normalized automatically:
|
|
179
|
+
|
|
180
|
+
- `categories.getAttributeValues`: spec says `attributeValueName`, wire returns `attributeValue` → SDK normalizes to `{ id, name }`
|
|
181
|
+
- `products.listUnapproved`: spec says `media: [{url}]`, wire returns `images: [{url}]` → SDK exposes `images: string[]`
|
|
182
|
+
- `products.getBuyboxInfo`: wire returns extra `secondBuyboxPrice` / `thirdBuyboxPrice` fields beyond spec → both surfaced
|
|
183
|
+
- `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
|
+
- `brands.search`: docs claim case-sensitive exact match, live is substring + case-insensitive → documented in JSDoc
|
|
185
|
+
- `getBatchRequestResult`: returns `PROCESSING + empty items` for unknown batch IDs (not 404)
|
|
186
|
+
|
|
187
|
+
Each fix is pinned by a regression mock test using the exact STAGE shape.
|
|
188
|
+
|
|
189
|
+
## Authentication
|
|
190
|
+
|
|
191
|
+
Trendyol uses HTTP Basic Auth. Get your `sellerId`, `apiKey`, and `apiSecret` from the [Trendyol Partner Panel → Account Info → Integration Information](https://partner.trendyol.com/account/info?tab=integrationInformation) (master-user only).
|
|
192
|
+
|
|
193
|
+
**Production vs Stage have different credentials.** Stage also requires IP whitelisting — register your CI/server IP with Trendyol support (0850 258 58 00). The SDK auto-sends the 5 mandatory headers (`Authorization`, `x-clientip`, `x-correlationid`, `x-agentname`, `User-Agent`).
|
|
45
194
|
|
|
46
195
|
## Environments
|
|
47
196
|
|
|
@@ -50,9 +199,18 @@ Trendyol uses HTTP Basic Auth. Get your `sellerId`, `apiKey`, and `apiSecret` fr
|
|
|
50
199
|
| `prod` | `https://apigw.trendyol.com` | No IP whitelist |
|
|
51
200
|
| `stage` | `https://stageapigw.trendyol.com` | IP whitelist required — call Trendyol support |
|
|
52
201
|
|
|
202
|
+
## Built-in robustness
|
|
203
|
+
|
|
204
|
+
- **Retry with exponential backoff** on 429 (respects `Retry-After`) and 5xx
|
|
205
|
+
- **Per-endpoint rate limiting** (token bucket) sized to Trendyol's documented limits — separate buckets for filter (2000/min), batch read (1000/min), buybox (1000/min), writes (1000/min), and delete (100/min)
|
|
206
|
+
- **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
|
+
- **Correlation ID** auto-generated per request for Trendyol-side log tracing
|
|
209
|
+
- **`AbortSignal` support** throughout
|
|
210
|
+
|
|
53
211
|
## Stability
|
|
54
212
|
|
|
55
|
-
`0.x` — alpha.
|
|
213
|
+
`0.x` — alpha. The product surface is feature-complete and STAGE-verified, but public types may still adjust between minor versions until `1.0.0`.
|
|
56
214
|
|
|
57
215
|
## License
|
|
58
216
|
|