@cynco/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/dist/index.cjs +1222 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1169 -0
- package/dist/index.d.ts +1169 -0
- package/dist/index.js +1193 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-03-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release of the Cynco TypeScript SDK.
|
|
13
|
+
- Resource clients: invoices, customers, vendors, bills, items, accounts, journal entries, bank accounts, reports, webhooks.
|
|
14
|
+
- Auto-pagination with `for await...of` support.
|
|
15
|
+
- Automatic retry with exponential backoff for rate limits and server errors.
|
|
16
|
+
- Idempotency key support for safe mutation retries.
|
|
17
|
+
- Webhook signature verification with HMAC-SHA256.
|
|
18
|
+
- Full TypeScript types for all API resources and operations.
|
|
19
|
+
- ESM and CommonJS dual-package output.
|
|
20
|
+
- Configurable base URL, timeout, max retries, and custom fetch.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cynco Sdn Bhd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# cynco
|
|
2
|
+
|
|
3
|
+
The official TypeScript SDK for the [Cynco](https://cynco.io) REST API.
|
|
4
|
+
|
|
5
|
+
Cynco is an AI-native business platform for accounting, invoicing, payments, and operations. This SDK provides full typed access to the Cynco API from any Node.js or TypeScript environment.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install cynco
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add cynco
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
yarn add cynco
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Requirements:** Node.js 18 or later.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import Cynco from 'cynco';
|
|
27
|
+
|
|
28
|
+
const cynco = new Cynco('cak_your_api_key');
|
|
29
|
+
|
|
30
|
+
// List invoices
|
|
31
|
+
const page = await cynco.invoices.list({ status: 'paid', limit: 20 });
|
|
32
|
+
console.log(page.data); // Invoice[]
|
|
33
|
+
|
|
34
|
+
// Create a customer
|
|
35
|
+
const customer = await cynco.customers.create({
|
|
36
|
+
name: 'Acme Corp',
|
|
37
|
+
email: 'billing@acme.com',
|
|
38
|
+
type: 'company',
|
|
39
|
+
currency: 'MYR',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Retrieve a single resource
|
|
43
|
+
const invoice = await cynco.invoices.retrieve('inv_abc123');
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const cynco = new Cynco('cak_your_api_key', {
|
|
50
|
+
baseUrl: 'https://app.cynco.io/api/v1', // Default
|
|
51
|
+
timeout: 30_000, // 30 seconds (default)
|
|
52
|
+
maxRetries: 3, // Automatic retries (default)
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Option | Type | Default | Description |
|
|
57
|
+
|--------|------|---------|-------------|
|
|
58
|
+
| `baseUrl` | `string` | `https://app.cynco.io/api/v1` | API base URL |
|
|
59
|
+
| `timeout` | `number` | `30000` | Request timeout in milliseconds |
|
|
60
|
+
| `maxRetries` | `number` | `3` | Max retries on transient failures |
|
|
61
|
+
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation |
|
|
62
|
+
|
|
63
|
+
## Resources
|
|
64
|
+
|
|
65
|
+
All resources follow a consistent pattern:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
cynco.{resource}.list(params?) // Paginated list (also async iterable)
|
|
69
|
+
cynco.{resource}.retrieve(id) // Get by ID
|
|
70
|
+
cynco.{resource}.create(data, opts) // Create (where applicable)
|
|
71
|
+
cynco.{resource}.update(id, data) // Update (where applicable)
|
|
72
|
+
cynco.{resource}.delete(id) // Delete (where applicable)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Invoices
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// List with filters
|
|
79
|
+
const page = await cynco.invoices.list({
|
|
80
|
+
status: 'overdue',
|
|
81
|
+
customerId: 'cust_abc',
|
|
82
|
+
dateFrom: '2026-01-01',
|
|
83
|
+
limit: 50,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Create
|
|
87
|
+
const invoice = await cynco.invoices.create({
|
|
88
|
+
customerId: 'cust_abc',
|
|
89
|
+
issueDate: '2026-03-22',
|
|
90
|
+
dueDate: '2026-04-22',
|
|
91
|
+
lineItems: [
|
|
92
|
+
{ description: 'Consulting', quantity: 10, unitPrice: 500 },
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Actions
|
|
97
|
+
await cynco.invoices.send('inv_123');
|
|
98
|
+
await cynco.invoices.markPaid('inv_123', { paidDate: '2026-03-25' });
|
|
99
|
+
await cynco.invoices.void('inv_123');
|
|
100
|
+
|
|
101
|
+
// PDF
|
|
102
|
+
const { url } = await cynco.invoices.getPdf('inv_123');
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Customers
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const page = await cynco.customers.list({ search: 'acme', type: 'company' });
|
|
109
|
+
const customer = await cynco.customers.create({ name: 'Acme Corp' });
|
|
110
|
+
const updated = await cynco.customers.update('cust_123', { name: 'Acme Inc' });
|
|
111
|
+
await cynco.customers.delete('cust_123');
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Vendors
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const page = await cynco.vendors.list({ search: 'supplier' });
|
|
118
|
+
const vendor = await cynco.vendors.create({
|
|
119
|
+
name: 'Office Supplies Co',
|
|
120
|
+
email: 'accounts@supplier.com',
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Bills
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const page = await cynco.bills.list({ status: 'received', vendorId: 'vnd_123' });
|
|
128
|
+
const bill = await cynco.bills.create({
|
|
129
|
+
vendorId: 'vnd_123',
|
|
130
|
+
issueDate: '2026-03-20',
|
|
131
|
+
dueDate: '2026-04-20',
|
|
132
|
+
lineItems: [{ description: 'Paper supplies', quantity: 100, unitPrice: 5 }],
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await cynco.bills.markPaid('bill_123');
|
|
136
|
+
await cynco.bills.void('bill_123');
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Items
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const page = await cynco.items.list({ type: 'service' });
|
|
143
|
+
const item = await cynco.items.create({
|
|
144
|
+
name: 'Consulting Hour',
|
|
145
|
+
type: 'service',
|
|
146
|
+
unitPrice: 500,
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Chart of Accounts
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const page = await cynco.accounts.list({ type: 'revenue' });
|
|
154
|
+
const account = await cynco.accounts.create({
|
|
155
|
+
name: 'Sales Revenue',
|
|
156
|
+
code: '4000',
|
|
157
|
+
type: 'revenue',
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Journal Entries
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const page = await cynco.journalEntries.list({ status: 'posted' });
|
|
165
|
+
const entry = await cynco.journalEntries.create({
|
|
166
|
+
date: '2026-03-22',
|
|
167
|
+
description: 'Monthly depreciation',
|
|
168
|
+
lines: [
|
|
169
|
+
{ accountId: 'acc_dep_exp', debit: 1000, credit: 0 },
|
|
170
|
+
{ accountId: 'acc_accum_dep', debit: 0, credit: 1000 },
|
|
171
|
+
],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await cynco.journalEntries.post('je_123');
|
|
175
|
+
await cynco.journalEntries.void('je_123');
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Bank Accounts
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const page = await cynco.bankAccounts.list();
|
|
182
|
+
const account = await cynco.bankAccounts.create({
|
|
183
|
+
name: 'Operating Account',
|
|
184
|
+
accountNumber: '1234567890',
|
|
185
|
+
bankName: 'Maybank',
|
|
186
|
+
currency: 'MYR',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// List transactions for a bank account
|
|
190
|
+
const txns = await cynco.bankAccounts.listTransactions('ba_123', {
|
|
191
|
+
status: 'cleared',
|
|
192
|
+
dateFrom: '2026-03-01',
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Reports
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const bs = await cynco.reports.balanceSheet({
|
|
200
|
+
endDate: '2026-03-31',
|
|
201
|
+
currency: 'MYR',
|
|
202
|
+
});
|
|
203
|
+
console.log(bs.totalAssets, bs.totalLiabilities, bs.totalEquity);
|
|
204
|
+
|
|
205
|
+
const pnl = await cynco.reports.profitAndLoss({
|
|
206
|
+
startDate: '2026-01-01',
|
|
207
|
+
endDate: '2026-03-31',
|
|
208
|
+
});
|
|
209
|
+
console.log(pnl.netIncome);
|
|
210
|
+
|
|
211
|
+
const tb = await cynco.reports.trialBalance({ endDate: '2026-03-31' });
|
|
212
|
+
console.log(tb.totalDebits, tb.totalCredits);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Webhooks
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const page = await cynco.webhooks.list();
|
|
219
|
+
const webhook = await cynco.webhooks.create({
|
|
220
|
+
url: 'https://example.com/webhooks/cynco',
|
|
221
|
+
events: ['invoice.paid', 'customer.created'],
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await cynco.webhooks.update('wh_123', { isActive: false });
|
|
225
|
+
const { secret } = await cynco.webhooks.rotateSecret('wh_123');
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Pagination
|
|
229
|
+
|
|
230
|
+
### Standard (single page)
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const page = await cynco.invoices.list({ limit: 20, offset: 40 });
|
|
234
|
+
|
|
235
|
+
console.log(page.data); // Invoice[]
|
|
236
|
+
console.log(page.pagination.total); // Total count
|
|
237
|
+
console.log(page.pagination.hasMore); // More pages?
|
|
238
|
+
|
|
239
|
+
// Manual pagination
|
|
240
|
+
const nextPage = await page.nextPage(); // Page<Invoice> | null
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Auto-pagination
|
|
244
|
+
|
|
245
|
+
Iterate over all items across all pages automatically:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
for await (const invoice of cynco.invoices.list({ limit: 50 })) {
|
|
249
|
+
console.log(invoice.id);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Collect all items
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const allCustomers: Customer[] = [];
|
|
257
|
+
for await (const customer of cynco.customers.list({ limit: 100 })) {
|
|
258
|
+
allCustomers.push(customer);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Error Handling
|
|
263
|
+
|
|
264
|
+
All API errors are instances of `CyncoError` with specific subclasses:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import Cynco, {
|
|
268
|
+
CyncoError,
|
|
269
|
+
AuthenticationError,
|
|
270
|
+
ValidationError,
|
|
271
|
+
NotFoundError,
|
|
272
|
+
RateLimitError,
|
|
273
|
+
} from 'cynco';
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
await cynco.customers.create({ name: '' });
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (error instanceof ValidationError) {
|
|
279
|
+
console.log(error.status); // 422
|
|
280
|
+
console.log(error.code); // "validation_error"
|
|
281
|
+
console.log(error.message); // "Validation failed"
|
|
282
|
+
console.log(error.details); // [{ field: "name", message: "is required" }]
|
|
283
|
+
console.log(error.requestId); // UUID for support
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
| Error Class | Status | When |
|
|
289
|
+
|-------------|--------|------|
|
|
290
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
291
|
+
| `PermissionError` | 403 | Insufficient permissions |
|
|
292
|
+
| `NotFoundError` | 404 | Resource does not exist |
|
|
293
|
+
| `ConflictError` | 409 | Conflicting request |
|
|
294
|
+
| `ValidationError` | 422 | Invalid request body |
|
|
295
|
+
| `RateLimitError` | 429 | Rate limit exceeded (after retries) |
|
|
296
|
+
| `InternalError` | 5xx | Server error (after retries) |
|
|
297
|
+
| `TimeoutError` | - | Request timed out |
|
|
298
|
+
| `ConnectionError` | - | Network unreachable |
|
|
299
|
+
|
|
300
|
+
## Retries
|
|
301
|
+
|
|
302
|
+
The SDK automatically retries on rate limits (429) and server errors (5xx) with exponential backoff and jitter.
|
|
303
|
+
|
|
304
|
+
- **GET, HEAD, DELETE** requests are always retried.
|
|
305
|
+
- **POST, PATCH, PUT** requests are only retried if an idempotency key is provided (except for 429, which is always safe to retry since the request was never processed).
|
|
306
|
+
- Retries respect the `Retry-After` header.
|
|
307
|
+
- Maximum retry count is configurable via `maxRetries` (default: 3).
|
|
308
|
+
|
|
309
|
+
## Idempotency
|
|
310
|
+
|
|
311
|
+
For safe retries on mutations, pass an idempotency key:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const customer = await cynco.customers.create(
|
|
315
|
+
{ name: 'Acme Corp' },
|
|
316
|
+
{ idempotencyKey: 'create-acme-20260322' },
|
|
317
|
+
);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
If a request with the same idempotency key was already processed, the API returns the original response instead of creating a duplicate.
|
|
321
|
+
|
|
322
|
+
## Webhook Verification
|
|
323
|
+
|
|
324
|
+
Verify incoming webhook signatures using the static `Cynco.webhooks` utility. No client instantiation needed.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import Cynco from 'cynco';
|
|
328
|
+
|
|
329
|
+
// In your webhook handler (e.g. Express)
|
|
330
|
+
app.post('/webhooks/cynco', (req, res) => {
|
|
331
|
+
const payload = req.body; // Raw string body
|
|
332
|
+
const signature = req.headers['x-webhook-signature'];
|
|
333
|
+
const timestamp = req.headers['x-webhook-timestamp'];
|
|
334
|
+
|
|
335
|
+
const isValid = Cynco.webhooks.verify(
|
|
336
|
+
payload,
|
|
337
|
+
signature,
|
|
338
|
+
timestamp,
|
|
339
|
+
process.env.CYNCO_WEBHOOK_SECRET,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if (!isValid) {
|
|
343
|
+
return res.status(401).send('Invalid signature');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const event = JSON.parse(payload);
|
|
347
|
+
// Handle the event...
|
|
348
|
+
|
|
349
|
+
res.sendStatus(200);
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Verify or throw
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
try {
|
|
357
|
+
Cynco.webhooks.verifyOrThrow(payload, signature, timestamp, secret);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
// "Invalid webhook signature" or "Webhook timestamp is too old"
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Custom tolerance
|
|
364
|
+
|
|
365
|
+
By default, timestamps older than 5 minutes are rejected. You can customise this:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
Cynco.webhooks.verify(payload, signature, timestamp, secret, {
|
|
369
|
+
tolerance: 600, // 10 minutes
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Testing webhooks
|
|
374
|
+
|
|
375
|
+
Generate test signatures for development:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
const { signature, timestamp } = Cynco.webhooks.sign(
|
|
379
|
+
JSON.stringify({ event: 'invoice.paid', data: {} }),
|
|
380
|
+
'whsec_your_test_secret',
|
|
381
|
+
);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## TypeScript
|
|
385
|
+
|
|
386
|
+
Every resource, parameter, and response is fully typed. Import individual types as needed:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
import type {
|
|
390
|
+
Invoice,
|
|
391
|
+
Customer,
|
|
392
|
+
InvoiceCreateInput,
|
|
393
|
+
CustomerListParams,
|
|
394
|
+
PaginatedResponse,
|
|
395
|
+
WebhookEvent,
|
|
396
|
+
} from 'cynco';
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## CommonJS
|
|
400
|
+
|
|
401
|
+
The SDK ships as a dual ESM/CJS package:
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
const { default: Cynco } = require('cynco');
|
|
405
|
+
const cynco = new Cynco('cak_your_api_key');
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## License
|
|
409
|
+
|
|
410
|
+
MIT - Cynco Sdn Bhd
|