@taxora/sdk 1.0.7 → 1.2.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 +388 -1
- package/dist/index.cjs +2372 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1084 -6
- package/dist/index.d.ts +1084 -6
- package/dist/index.js +2334 -125
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -301,6 +301,331 @@ const results = await client.vat.search('Alpha Handels', 25 /* perPage */);
|
|
|
301
301
|
|
|
302
302
|
---
|
|
303
303
|
|
|
304
|
+
## 🔎 Smart Enrichment (reverse VAT lookup)
|
|
305
|
+
|
|
306
|
+
Resolve a company **name + address + country → VAT number + confidence**. A confident match
|
|
307
|
+
returns synchronously; harder cases (and all bulk batches) are processed asynchronously and
|
|
308
|
+
delivered via the `enrichment.completed` webhook — poll with `get(jobId)` in the meantime.
|
|
309
|
+
Requires the Smart Enrichment add-on to be active on your account.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
// Single lookup
|
|
313
|
+
let job = await client.smartEnrichment.lookup({
|
|
314
|
+
companyName: 'Example Company GmbH',
|
|
315
|
+
country: 'AT',
|
|
316
|
+
street: 'Main Street 10',
|
|
317
|
+
postalCode: '1010',
|
|
318
|
+
city: 'Vienna',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (job.isProcessing()) {
|
|
322
|
+
// Resolved asynchronously — poll until done (or wait for the webhook)
|
|
323
|
+
job = await client.smartEnrichment.waitForCompletion(job.jobId, {
|
|
324
|
+
pollIntervalMs: 2000, // optional, default 2000
|
|
325
|
+
timeoutMs: 120_000, // optional, default 120000 — throws TaxoraException on timeout
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const result = job.result();
|
|
330
|
+
console.log(result?.status); // 'found' | 'no_vat_exists' | 'not_found'
|
|
331
|
+
console.log(result?.vatNumber); // e.g. 'ATU12345678'
|
|
332
|
+
console.log(result?.confidence); // 0–100
|
|
333
|
+
|
|
334
|
+
// Search mode: 'default' searches with one AI provider and only consults a second one when
|
|
335
|
+
// the first finds nothing. 'complex' has two independent providers search from the start and
|
|
336
|
+
// puts every number either of them proposes through the tax authority's checks. Complex finds
|
|
337
|
+
// companies a single search misses — typically ones whose VAT only appears in a website
|
|
338
|
+
// Impressum — and costs more per lookup, so it is opt-in.
|
|
339
|
+
const deep = await client.smartEnrichment.lookup({
|
|
340
|
+
companyName: 'Vodafone D2 GmbH',
|
|
341
|
+
country: 'DE',
|
|
342
|
+
postalCode: '40547',
|
|
343
|
+
mode: SmartEnrichmentMode.COMPLEX,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// When more than one provider searched, you can see what each one concluded on its own.
|
|
347
|
+
// Two of them reporting the same number is the strongest confirmation this layer produces.
|
|
348
|
+
for (const verdict of deep.result()?.providerVerdicts ?? []) {
|
|
349
|
+
console.log(`${verdict.provider}: ${verdict.vatNumber ?? 'nothing'}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// The API also grades the address you sent. Worth writing back into your own records: a
|
|
353
|
+
// 'postal_code_city_mismatch' / 'postal_code_unassigned' warning means the postal code in
|
|
354
|
+
// your source data is wrong, and `derivedPlace` is the area it actually points at.
|
|
355
|
+
const quality = deep.result()?.addressQuality;
|
|
356
|
+
if (quality && quality.warnings.length > 0) {
|
|
357
|
+
console.log(quality.warnings.join(', '), '→', quality.derivedPlace);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Bulk lookup (always async → webhook + polling)
|
|
361
|
+
// The batch-level mode applies to every row; a row carrying its own `mode` overrides it,
|
|
362
|
+
// so you can pay for 'complex' on just the rows that need it.
|
|
363
|
+
const bulk = await client.smartEnrichment.bulkLookup([
|
|
364
|
+
{ companyName: 'A GmbH', country: 'DE', city: 'Berlin' },
|
|
365
|
+
{ companyName: 'B SARL', country: 'FR' },
|
|
366
|
+
]);
|
|
367
|
+
const done = await client.smartEnrichment.waitForCompletion(bulk.jobId);
|
|
368
|
+
for (const r of done.results) {
|
|
369
|
+
console.log(r.status, r.vatNumber ?? '—', r.matchedAddress ?? '');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Lookup history (paginated, newest first; perPage is capped at 100 server-side)
|
|
373
|
+
const history = await client.smartEnrichment.history(1, 25, 'Example GmbH' /* optional free-text search */);
|
|
374
|
+
console.log(`${history.total} lookups`);
|
|
375
|
+
console.log(history.stats?.total, history.stats?.found); // account-wide tiles (independent of the search filter)
|
|
376
|
+
for (const row of history) {
|
|
377
|
+
console.log(row.queryCompanyName, '→', row.result?.vatNumber ?? row.status);
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Quota usage & billing history
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
const usage = await client.smartEnrichment.usage();
|
|
385
|
+
console.log(usage.period, `${usage.used}/${usage.included}`, 'remaining:', usage.remaining);
|
|
386
|
+
console.log(usage.overageCount, '×', usage.overagePrice, '=', usage.overageAmount);
|
|
387
|
+
for (const entry of usage.history) {
|
|
388
|
+
console.log(entry.period, entry.matches, entry.amount, entry.state); // 'billed' | 'pending'
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### CSV export
|
|
393
|
+
|
|
394
|
+
Download your lookups as CSV in the bulk-input shape plus the resolved VAT columns (bulk jobs
|
|
395
|
+
are flattened to one line per input row). All filters are optional.
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
import { SmartEnrichmentStatus } from '@taxora/sdk';
|
|
399
|
+
import { writeFileSync } from 'fs';
|
|
400
|
+
|
|
401
|
+
const csv = await client.smartEnrichment.export({
|
|
402
|
+
dateFrom: '2026-01-01', // job created_at lower bound (YYYY-MM-DD)
|
|
403
|
+
dateTo: '2026-06-30', // job created_at upper bound (YYYY-MM-DD)
|
|
404
|
+
minConfidence: 80, // 0–100
|
|
405
|
+
status: [SmartEnrichmentStatus.FOUND, SmartEnrichmentStatus.NO_VAT_EXISTS],
|
|
406
|
+
onlyFound: true, // only rows that resolved to a VAT number
|
|
407
|
+
});
|
|
408
|
+
writeFileSync('smart-enrichment.csv', csv);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Statistics
|
|
412
|
+
|
|
413
|
+
Aggregated lookup statistics: headline totals, a time series and breakdowns by source, outcome
|
|
414
|
+
and confidence band. Defaults to the last 12 months and a monthly interval (server-side).
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
const stats = await client.smartEnrichment.statistics({
|
|
418
|
+
dateFrom: '2026-01-01', // optional (YYYY-MM-DD)
|
|
419
|
+
dateTo: '2026-06-30', // optional (YYYY-MM-DD)
|
|
420
|
+
interval: 'month', // 'day' | 'week' | 'month'
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
console.log(stats.totals.items, 'items,', stats.totals.found, `found (${stats.totals.foundRate}%)`);
|
|
424
|
+
console.log('avg confidence:', stats.totals.avgConfidence);
|
|
425
|
+
for (const bucket of stats.timeSeries) {
|
|
426
|
+
console.log(`${bucket.bucket}: ${bucket.found}/${bucket.items}`);
|
|
427
|
+
}
|
|
428
|
+
for (const band of stats.confidenceBuckets) {
|
|
429
|
+
console.log(band.bucket, band.count); // high / medium / low
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
> Note: `no_vat_exists` means the company was identified but legitimately has **no** VAT/UID
|
|
434
|
+
> number (common for purely-domestic German firms). It is a definitive answer — not a failure —
|
|
435
|
+
> and is not billed.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## 🇫🇷 E-Reporting / Compliance (DGFiP Flux 10)
|
|
440
|
+
|
|
441
|
+
Full French e-reporting integration: enroll a company with the compliance provider, record and
|
|
442
|
+
submit transactions (individually or via CSV import), and track the resulting DGFiP tax reports.
|
|
443
|
+
All `compliance` routes require an active E-Reporting subscription / feature grant on your
|
|
444
|
+
account — except `requestEReportingAccess()`, which is how you ask for one.
|
|
445
|
+
|
|
446
|
+
### Request feature activation
|
|
447
|
+
|
|
448
|
+
Not enabled yet? This route is deliberately **not** gated — it requests activation of the
|
|
449
|
+
E-Reporting feature for your account (the authenticated user's name/email take precedence over
|
|
450
|
+
the form values):
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
await client.eReporting.requestEReportingAccess({
|
|
454
|
+
company: 'Example GmbH',
|
|
455
|
+
phone: '+43 660 1234567',
|
|
456
|
+
message: 'Please activate e-reporting for our account.',
|
|
457
|
+
language: 'de', // locale of the confirmation mail
|
|
458
|
+
});
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### 1. SIRENE lookup (prefill enrollment data)
|
|
462
|
+
|
|
463
|
+
Resolve company data by SIRET, SIREN or French VAT number via the French SIRENE registry.
|
|
464
|
+
**Rate-limited to 3 requests/minute per user** (protects the external French government API).
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
const company = await client.eReporting.sireneLookup('FR32123456789'); // or a SIRET/SIREN
|
|
468
|
+
|
|
469
|
+
console.log(company.companyName, company.siret);
|
|
470
|
+
console.log(company.nafCode, company.enterpriseSize); // e.g. "47", "pme"
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### 2. Create an enrollment
|
|
474
|
+
|
|
475
|
+
Creates the local enrollment, provisions the provider account and (by default) activates the
|
|
476
|
+
DGFiP regime. Either `siret` or `siren` is required. On provider failure the API responds 502
|
|
477
|
+
(an `HttpException`) and the enrollment is persisted in an error state for retries.
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
const enrollment = await client.eReporting.createEnrollment({
|
|
481
|
+
siret: company.siret!, // or siren: company.siren
|
|
482
|
+
companyName: company.companyName,
|
|
483
|
+
address: company.address ?? undefined,
|
|
484
|
+
city: company.city ?? undefined,
|
|
485
|
+
postalcode: company.postalcode ?? undefined,
|
|
486
|
+
email: 'tax@acme.fr',
|
|
487
|
+
nafCode: company.nafCode!, // 2-digit NAF division, e.g. "47"
|
|
488
|
+
enterpriseSize: 'pme', // 'micro' | 'pme' | 'eti' | 'ge'
|
|
489
|
+
typeOperation: 'mixed', // 'services' | 'goods' | 'mixed'
|
|
490
|
+
reportingStartDate: '2026-09-01', // or a Date
|
|
491
|
+
autoActivate: true, // optional — false creates the account without regime activation
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
console.log(enrollment.id, enrollment.status, enrollment.statusLabel);
|
|
495
|
+
|
|
496
|
+
const page = await client.eReporting.listEnrollments(1, 25); // paginated
|
|
497
|
+
const single = await client.eReporting.getEnrollment(enrollment.id);
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### 3. Record & submit transactions
|
|
501
|
+
|
|
502
|
+
Records the transaction and (by default) submits it right away; pass `submitNow: false` to defer.
|
|
503
|
+
Creation is **idempotent** on the invoice's natural key (enrollment, type, invoice number,
|
|
504
|
+
invoice date, counterparty VAT) — a client retry returns the existing transaction instead of
|
|
505
|
+
creating a duplicate.
|
|
506
|
+
|
|
507
|
+
> **Note:** Immediate per-invoice submission (and `submitTransaction()`) requires the
|
|
508
|
+
> **e-invoicing** feature on your account. In pure e-reporting mode transactions are only
|
|
509
|
+
> recorded here and reported automatically via the aggregated daily ledgers —
|
|
510
|
+
> `submitTransaction()` then returns a 422 `ValidationException` explaining this.
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
const tx = await client.eReporting.createTransaction({
|
|
514
|
+
complianceEnrollmentId: enrollment.id,
|
|
515
|
+
transactionType: 'b2c_outbound', // | 'b2b_domestic_outbound' | 'b2b_domestic_inbound'
|
|
516
|
+
// | 'crossborder_outbound' | 'crossborder_inbound'
|
|
517
|
+
invoiceNumber: 'TKT-2026-00001',
|
|
518
|
+
invoiceDate: '2026-07-01', // or a Date
|
|
519
|
+
currency: 'EUR', // optional, default EUR
|
|
520
|
+
subtotal: 100,
|
|
521
|
+
taxAmount: 20,
|
|
522
|
+
total: 120,
|
|
523
|
+
invoiceLines: [
|
|
524
|
+
{
|
|
525
|
+
description: 'Ticket',
|
|
526
|
+
quantity: 1,
|
|
527
|
+
price: 100,
|
|
528
|
+
taxes: [{ name: 'TVA', percent: 20, category: 'S' }], // category E needs a VATEX `comment`
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
submitNow: false, // record now, submit later
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Submit (or retry) a pending/error transaction later — replays the stored invoice lines
|
|
535
|
+
const submitted = await client.eReporting.submitTransaction(tx.id);
|
|
536
|
+
console.log(submitted.state, submitted.stateLabel); // e.g. "submitted"
|
|
537
|
+
|
|
538
|
+
// Manage recorded transactions (only `pending` ones can be edited/deleted)
|
|
539
|
+
const transactions = await client.eReporting.listTransactions({
|
|
540
|
+
dateFrom: '2026-07-01', // or a Date
|
|
541
|
+
dateTo: '2026-07-31',
|
|
542
|
+
state: 'pending', // 'pending' | 'sending' | 'submitted' | 'error'
|
|
543
|
+
transactionType: 'b2c_outbound',
|
|
544
|
+
page: 1,
|
|
545
|
+
perPage: 25,
|
|
546
|
+
});
|
|
547
|
+
const one = await client.eReporting.getTransaction(tx.id);
|
|
548
|
+
await client.eReporting.updateTransaction(tx.id, { total: 130, taxAmount: 21.67 });
|
|
549
|
+
await client.eReporting.deleteTransaction(tx.id); // resolves void on 204
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### CSV import
|
|
553
|
+
|
|
554
|
+
Bulk-import from a semicolon-separated CSV (one row per invoice line; rows sharing an
|
|
555
|
+
`invoice_number` are grouped into one transaction). Idempotent: re-uploading skips rows whose
|
|
556
|
+
invoice already exists.
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
import { readFileSync } from 'fs';
|
|
560
|
+
|
|
561
|
+
const result = await client.eReporting.importTransactions(
|
|
562
|
+
enrollment.id,
|
|
563
|
+
readFileSync('transactions.csv', 'utf8'),
|
|
564
|
+
'transactions.csv', // optional filename
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
console.log(result.created, result.skippedDuplicates);
|
|
568
|
+
for (const error of result.errors) console.warn(error); // row-level errors
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### 4. Track tax reports
|
|
572
|
+
|
|
573
|
+
Every submitted transaction is tracked through the aggregated DGFiP ledger lifecycle
|
|
574
|
+
(`new → sent → acknowledged → registered` or `refused`/`error`).
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
const reports = await client.eReporting.listTaxReports(1, 25, 'refused'); // state filter optional
|
|
578
|
+
for (const report of reports) {
|
|
579
|
+
console.log(report.state, report.stateLabel, report.isTerminal, report.refusalReason);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const report = await client.eReporting.getTaxReport(reports.rows[0]!.id);
|
|
583
|
+
console.log(report.baseAmount, report.taxAmount, report.totalAmount); // precision-safe strings
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### VAT rates & DGFiP tax categories
|
|
587
|
+
|
|
588
|
+
The canonical rates for a reporting country (the seller's reporting-country rates). Use these to
|
|
589
|
+
build the `taxes` entries of your invoice lines.
|
|
590
|
+
|
|
591
|
+
```ts
|
|
592
|
+
const rates = await client.eReporting.getVatRates('FR'); // defaults to FR when omitted
|
|
593
|
+
|
|
594
|
+
for (const rate of rates.rates) {
|
|
595
|
+
console.log(rate.percent, rate.category, rate.taxName, rate.requiresVatex);
|
|
596
|
+
// 20 'S' 'TVA' false … 0 'E' 'TVA' true (VATEX code required in the line's comment)
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Revenue statistics
|
|
601
|
+
|
|
602
|
+
Read-only turnover aggregation over your e-reporting transactions — totals, a time series and
|
|
603
|
+
breakdowns by transaction type, counterparty country and state. All headline figures are
|
|
604
|
+
expressed in the most frequent currency in the range (`primaryCurrency`); when more than one
|
|
605
|
+
currency is present, `isMultiCurrency` is `true` and `byCurrency` lists each one. Monetary values
|
|
606
|
+
are returned as 4-decimal strings to preserve precision.
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
const stats = await client.eReporting.getRevenueStatistics({
|
|
610
|
+
dateFrom: '2026-01-01', // optional — defaults to 12 months ago (server-side)
|
|
611
|
+
dateTo: new Date(), // optional — defaults to today
|
|
612
|
+
interval: 'month', // 'day' | 'week' | 'month' (default 'month')
|
|
613
|
+
transactionType: 'b2c_outbound', // optional filter
|
|
614
|
+
state: 'submitted', // optional filter
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
console.log(stats.primaryCurrency); // e.g. EUR
|
|
618
|
+
console.log(stats.totals.total); // e.g. 120000.0000
|
|
619
|
+
for (const bucket of stats.timeSeries) {
|
|
620
|
+
console.log(`${bucket.bucket}: ${bucket.total}`); // 2026-01: 10800.0000
|
|
621
|
+
}
|
|
622
|
+
for (const row of stats.byCountry) {
|
|
623
|
+
console.log(`${row.country} → ${row.total}`); // 'unknown' groups NULL partners
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
304
629
|
## 📜 Certificates
|
|
305
630
|
|
|
306
631
|
### Single Certificate (PDF)
|
|
@@ -371,11 +696,73 @@ try {
|
|
|
371
696
|
console.error('Validation errors:', err.getErrors()); // Record<string, string[]>
|
|
372
697
|
} else if (err instanceof HttpException) {
|
|
373
698
|
// Any other HTTP error
|
|
374
|
-
console.error(`HTTP ${err.getStatusCode()}:`, err.
|
|
699
|
+
console.error(`HTTP ${err.getStatusCode()}:`, err.message);
|
|
375
700
|
}
|
|
376
701
|
}
|
|
377
702
|
```
|
|
378
703
|
|
|
704
|
+
**Exception messages are always short and safe to log.** A response body is
|
|
705
|
+
never used as the message: gateways in front of the API answer `502`/`503`/`504`
|
|
706
|
+
with a full HTML error page, and that page would otherwise land in every log
|
|
707
|
+
line of your application. The SDK uses the `message` / `error` field of a JSON
|
|
708
|
+
error body when there is one, and falls back to the status line
|
|
709
|
+
(`Taxora API request failed (HTTP 504 Gateway Timeout).`) otherwise. The
|
|
710
|
+
untouched body stays available via `err.getResponseBody()`.
|
|
711
|
+
|
|
712
|
+
### Automatic retries
|
|
713
|
+
|
|
714
|
+
The SDK retries what never produced an answer from the API itself: **3 attempts,
|
|
715
|
+
with 500 ms and 1000 ms of backoff in between**. That covers
|
|
716
|
+
|
|
717
|
+
- **gateway failures (`502`, `503`, `504`)** — the infrastructure in front of the
|
|
718
|
+
API, where a second attempt a moment later usually succeeds;
|
|
719
|
+
- **connection-level failures** — reset connections, client-side timeouts, DNS
|
|
720
|
+
hiccups (`fetch failed`, `ECONNRESET`, `UND_ERR_CONNECT_TIMEOUT`, …). Once the
|
|
721
|
+
attempts are used up, the original error is rethrown unchanged;
|
|
722
|
+
- **`429 Too Many Requests`, but only with a `Retry-After`** — then the SDK waits
|
|
723
|
+
exactly as long as the API asked instead of using its own backoff. Without that
|
|
724
|
+
header a retry would only hammer a rate limit that is already tripped, so the
|
|
725
|
+
error is surfaced immediately. A `Retry-After` longer than `maxRetryAfterMs`
|
|
726
|
+
(10 s by default) also fails immediately rather than blocking your worker — the
|
|
727
|
+
raw header stays readable via `err.getRetryAfter()`.
|
|
728
|
+
|
|
729
|
+
Everything else fails on the first attempt, including any `5xx` the API produced
|
|
730
|
+
itself (a `500` is a bug, not a hiccup — repeating it just costs time).
|
|
731
|
+
|
|
732
|
+
Retries apply to read-only calls only — VAT validation, lookups, state, history,
|
|
733
|
+
search, certificate downloads, company and e-reporting reads, login and token
|
|
734
|
+
refresh. Calls that change state or cost quota (`certificatesBulkExport()`,
|
|
735
|
+
`smartEnrichment.lookup()`, e-reporting enrollment/transaction writes) are
|
|
736
|
+
**never** retried automatically, because a gateway timeout does not tell us
|
|
737
|
+
whether the API already processed the request.
|
|
738
|
+
|
|
739
|
+
Once the attempts are used up you get one clean error:
|
|
740
|
+
|
|
741
|
+
```
|
|
742
|
+
Taxora VAT validation failed after 3 attempts (HTTP 504 Gateway Timeout).
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
Tune or disable it via `RetryPolicy`:
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
import { RetryPolicy, TaxoraClientFactory } from '@taxora/sdk';
|
|
749
|
+
|
|
750
|
+
const client = TaxoraClientFactory.create({
|
|
751
|
+
apiKey: 'YOUR_X_API_KEY',
|
|
752
|
+
retryPolicy: new RetryPolicy({
|
|
753
|
+
maxAttempts: 5,
|
|
754
|
+
initialDelayMs: 250,
|
|
755
|
+
retryableStatusCodes: [502, 503, 504], // add or drop status codes
|
|
756
|
+
respectRetryAfter: true, // false = ignore the header entirely
|
|
757
|
+
maxRetryAfterMs: 10_000, // longest wait accepted from Retry-After
|
|
758
|
+
retryOnNetworkErrors: true, // false = surface transport errors at once
|
|
759
|
+
}),
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
// or, for callers that do their own retrying (queue workers, cron jobs):
|
|
763
|
+
const noRetries = TaxoraClientFactory.create({ apiKey: '…', retryPolicy: RetryPolicy.disabled() });
|
|
764
|
+
```
|
|
765
|
+
|
|
379
766
|
---
|
|
380
767
|
|
|
381
768
|
## 🔌 VatState Helpers
|