@taxora/sdk 1.6.0 → 1.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/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to `@taxora/sdk` are documented here. Versions follow [SemVer](https://semver.org/).
4
4
 
5
+ ## [1.7.0] — 2026-10-24
6
+
7
+ ### Added
8
+
9
+ - **Smart Enrichment: `taxNumberType`, `taxNumberLabel`, `taxNumberSource`** next to `taxNumber`. `taxNumber` is now
10
+ the company's local tax / business number: on `found` it is read off the confirmed VAT ID where that country's
11
+ VAT ID contains it (`taxNumberSource` = `'vat_id'`: FR SIREN, IT Partita IVA, ES NIF, PL NIP, BE enterprise
12
+ number, DK CVR, PT, RO, CZ IČO, SK DIČ, SE/NO orgnr, FI Y-tunnus, SI, HR OIB, BG EIK, GR AFM), otherwise a
13
+ domestic number the company published (`'company_website'`, e.g. a DE/AT Steuernummer — not a VAT ID).
14
+ `taxNumberType` is the code (`pl_nip`, `fr_siren`, `de_steuernummer` …), `taxNumberLabel` its national name.
15
+ New type `SmartEnrichmentTaxNumberSource`.
16
+ - **Local tax numbers report** on `client.vat`: `localTaxNumbersCsv()` (raw CSV, UTF-8 with BOM),
17
+ `localTaxNumbers()` (parsed `LocalTaxNumberRow[]` — BOM dropped, the Excel TAB guard before digit-only tax numbers
18
+ trimmed, empty cells `null`) and `localTaxNumbersSummary()` (`{ companies, withTaxNumber }`). Filters: `dateFrom`,
19
+ `dateTo`, `countries`, `onlyWithTaxNumber`. `parseLocalTaxNumbersCsv()` parses a saved file.
20
+
21
+ ### Changed
22
+
23
+ - Bundled OpenAPI specs (`resources/`) refreshed from the current API.
24
+
5
25
  ## [1.6.0] — 2026-10-22
6
26
 
7
27
  ### Added
package/README.md CHANGED
@@ -307,6 +307,39 @@ const results = await client.vat.search('Alpha Handels', 25 /* perPage */);
307
307
 
308
308
  ---
309
309
 
310
+ ## 🧾 Local tax numbers report
311
+
312
+ Every company you checked — single and bulk VAT checks and Smart Enrichment lookups — once, with its **local tax
313
+ number** (NIP, SIREN, Partita IVA, Steuernummer …), the number's national name and where it comes from (`vat_id` =
314
+ read off the confirmed VAT ID, `company_website` = published by the company). All filters are optional.
315
+
316
+ ```typescript
317
+ import { parseLocalTaxNumbersCsv } from '@taxora/sdk';
318
+ import { writeFileSync } from 'node:fs';
319
+
320
+ // Parsed rows (BOM dropped, cells trimmed, empty cells null)
321
+ const rows = await client.vat.localTaxNumbers({
322
+ dateFrom: '2026-10-01', // optional (YYYY-MM-DD or Date)
323
+ dateTo: '2026-10-31', // optional
324
+ countries: ['PL', 'IT'], // optional (ISO-2)
325
+ onlyWithTaxNumber: true, // optional: skip companies without one
326
+ });
327
+ for (const row of rows) {
328
+ // e.g. Orlen S.A. PL 5260250995 (NIP, vat_id)
329
+ console.log(`${row.companyName} ${row.country} ${row.taxNumber} (${row.taxNumberName}, ${row.taxNumberSource})`);
330
+ }
331
+ // row.vatStatus: valid | invalid | unconfirmed | wrong_company | no_vat_id | not_found
332
+ // row.source: vat_check | smart_enrichment; row.checkId: the VAT check uuid or the lookup job id
333
+
334
+ // The raw CSV (UTF-8 with BOM) for a spreadsheet. Digit-only tax numbers carry a leading TAB so
335
+ // Excel keeps leading zeros; parseLocalTaxNumbersCsv() turns a saved file into rows.
336
+ writeFileSync('local-tax-numbers.csv', await client.vat.localTaxNumbersCsv({ countries: ['PL'] }));
337
+
338
+ // Counts for the same filters
339
+ const summary = await client.vat.localTaxNumbersSummary({ dateFrom: '2026-10-01' });
340
+ console.log(`${summary.withTaxNumber} of ${summary.companies} companies have a local tax number`);
341
+ ```
342
+
310
343
  ## 🔎 Smart Enrichment (reverse VAT lookup)
311
344
 
312
345
  Resolve a company **name + address + country → VAT number + confidence**. A confident match
@@ -377,9 +410,15 @@ if (result?.nameMismatch()) {
377
410
  console.log(result.officialName, result.officialAddress);
378
411
  }
379
412
 
380
- // A page that shows a tax number (Steuernummer) under the VAT ID heading is not a VAT ID:
381
- // the lookup stays 'not_found' and the number comes back as `taxNumber`.
382
- console.log(result?.taxNumber ?? '—');
413
+ // `taxNumber` is the company's LOCAL tax / business number — what domestic e-invoicing asks for.
414
+ // On 'found' it is read off the confirmed VAT ID where that country's VAT ID contains it
415
+ // (taxNumberSource 'vat_id': FR SIREN, IT Partita IVA, ES NIF, PL NIP, BE, DK CVR, CZ IČO, …).
416
+ // Otherwise it is a number the company published itself ('company_website', e.g. a German
417
+ // Steuernummer shown under the VAT ID heading) — that one is not a VAT ID.
418
+ console.log(result?.taxNumber ?? '—'); // e.g. '5260250995'
419
+ console.log(result?.taxNumberType ?? '—'); // e.g. 'pl_nip', 'fr_siren', 'de_steuernummer'
420
+ console.log(result?.taxNumberLabel ?? '—'); // e.g. 'NIP'
421
+ console.log(result?.taxNumberSource ?? '—'); // 'vat_id' | 'company_website'
383
422
 
384
423
  // 'not_found' doesn't always mean nothing turned up. When the research found a VAT number but
385
424
  // the official check threw it out (e.g. the tax authority reports it as invalid), the number
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  ImportResult: () => ImportResult,
47
47
  InMemoryTokenStorage: () => InMemoryTokenStorage,
48
48
  Language: () => Language,
49
+ LocalTaxNumberRow: () => LocalTaxNumberRow,
49
50
  LoginIdentifier: () => LoginIdentifier,
50
51
  NorwegianOrgNumber: () => NorwegianOrgNumber,
51
52
  PeppolLookupResult: () => PeppolLookupResult,
@@ -98,6 +99,7 @@ __export(index_exports, {
98
99
  generateWebhookSignatureHeader: () => generateWebhookSignatureHeader,
99
100
  getBaseUrl: () => getBaseUrl,
100
101
  getFailedVatStates: () => getFailedVatStates,
102
+ parseLocalTaxNumbersCsv: () => parseLocalTaxNumbersCsv,
101
103
  parseWebhookSignatureHeader: () => parseWebhookSignatureHeader,
102
104
  toComplianceDocumentType: () => toComplianceDocumentType,
103
105
  toComplianceEnrollmentStatus: () => toComplianceEnrollmentStatus,
@@ -2445,7 +2447,7 @@ function toOfficialAddress(value) {
2445
2447
  return Object.keys(address).length > 0 ? address : void 0;
2446
2448
  }
2447
2449
  var SmartEnrichmentResource = class _SmartEnrichmentResource {
2448
- constructor(status, vatNumber, vatType, confidence, matchedCompanyName, matchedAddress, country, source, mode = void 0, providerVerdicts = [], addressQuality = void 0, rejectedCandidates = [], sourceUrl = void 0, registeredOwner = void 0, sourceUrls = [], taxNumber = void 0, vatValid = void 0, aiScore = void 0, nameMatch = void 0, officialName = void 0, officialAddress = void 0) {
2450
+ constructor(status, vatNumber, vatType, confidence, matchedCompanyName, matchedAddress, country, source, mode = void 0, providerVerdicts = [], addressQuality = void 0, rejectedCandidates = [], sourceUrl = void 0, registeredOwner = void 0, sourceUrls = [], taxNumber = void 0, vatValid = void 0, aiScore = void 0, nameMatch = void 0, officialName = void 0, officialAddress = void 0, taxNumberType = void 0, taxNumberLabel = void 0, taxNumberSource = void 0) {
2449
2451
  this.status = status;
2450
2452
  this.vatNumber = vatNumber;
2451
2453
  this.vatType = vatType;
@@ -2467,6 +2469,9 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
2467
2469
  this.nameMatch = nameMatch;
2468
2470
  this.officialName = officialName;
2469
2471
  this.officialAddress = officialAddress;
2472
+ this.taxNumberType = taxNumberType;
2473
+ this.taxNumberLabel = taxNumberLabel;
2474
+ this.taxNumberSource = taxNumberSource;
2470
2475
  }
2471
2476
  status;
2472
2477
  vatNumber;
@@ -2489,6 +2494,9 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
2489
2494
  nameMatch;
2490
2495
  officialName;
2491
2496
  officialAddress;
2497
+ taxNumberType;
2498
+ taxNumberLabel;
2499
+ taxNumberSource;
2492
2500
  /** True when the register knows the number under a different name than the one requested. */
2493
2501
  nameMismatch() {
2494
2502
  return this.nameMatch === "mismatch";
@@ -2536,7 +2544,10 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
2536
2544
  num("aiScore"),
2537
2545
  data["nameMatch"] === "ok" || data["nameMatch"] === "mismatch" ? data["nameMatch"] : void 0,
2538
2546
  text("officialName"),
2539
- toOfficialAddress(data["officialAddress"])
2547
+ toOfficialAddress(data["officialAddress"]),
2548
+ text("taxNumberType"),
2549
+ text("taxNumberLabel"),
2550
+ data["taxNumberSource"] === "vat_id" || data["taxNumberSource"] === "company_website" ? data["taxNumberSource"] : void 0
2540
2551
  );
2541
2552
  }
2542
2553
  toArray() {
@@ -2561,7 +2572,10 @@ var SmartEnrichmentResource = class _SmartEnrichmentResource {
2561
2572
  aiScore: this.aiScore ?? null,
2562
2573
  nameMatch: this.nameMatch ?? null,
2563
2574
  officialName: this.officialName ?? null,
2564
- officialAddress: this.officialAddress ?? null
2575
+ officialAddress: this.officialAddress ?? null,
2576
+ taxNumberType: this.taxNumberType ?? null,
2577
+ taxNumberLabel: this.taxNumberLabel ?? null,
2578
+ taxNumberSource: this.taxNumberSource ?? null
2565
2579
  };
2566
2580
  }
2567
2581
  };
@@ -3569,6 +3583,123 @@ var VatValidationAddressInput = class _VatValidationAddressInput {
3569
3583
  }
3570
3584
  };
3571
3585
 
3586
+ // src/dto/LocalTaxNumberReport.ts
3587
+ var LocalTaxNumberRow = class _LocalTaxNumberRow {
3588
+ constructor(companyName, requestedName, country, vatNumber, vatStatus, taxNumber, taxNumberName, taxNumberSource, source, checkedAt, checkId) {
3589
+ this.companyName = companyName;
3590
+ this.requestedName = requestedName;
3591
+ this.country = country;
3592
+ this.vatNumber = vatNumber;
3593
+ this.vatStatus = vatStatus;
3594
+ this.taxNumber = taxNumber;
3595
+ this.taxNumberName = taxNumberName;
3596
+ this.taxNumberSource = taxNumberSource;
3597
+ this.source = source;
3598
+ this.checkedAt = checkedAt;
3599
+ this.checkId = checkId;
3600
+ }
3601
+ companyName;
3602
+ requestedName;
3603
+ country;
3604
+ vatNumber;
3605
+ vatStatus;
3606
+ taxNumber;
3607
+ taxNumberName;
3608
+ taxNumberSource;
3609
+ source;
3610
+ checkedAt;
3611
+ checkId;
3612
+ /** One CSV row keyed by column name. */
3613
+ static fromArray(data) {
3614
+ const cell = (key) => {
3615
+ const value = data[key];
3616
+ const trimmed = typeof value === "string" ? value.trim() : "";
3617
+ return trimmed === "" ? null : trimmed;
3618
+ };
3619
+ return new _LocalTaxNumberRow(
3620
+ cell("companyName"),
3621
+ cell("requestedName"),
3622
+ cell("country"),
3623
+ cell("vatNumber"),
3624
+ cell("vatStatus"),
3625
+ cell("taxNumber"),
3626
+ cell("taxNumberName"),
3627
+ cell("taxNumberSource"),
3628
+ cell("source"),
3629
+ cell("checkedAt"),
3630
+ cell("checkId")
3631
+ );
3632
+ }
3633
+ toArray() {
3634
+ return {
3635
+ companyName: this.companyName,
3636
+ requestedName: this.requestedName,
3637
+ country: this.country,
3638
+ vatNumber: this.vatNumber,
3639
+ vatStatus: this.vatStatus,
3640
+ taxNumber: this.taxNumber,
3641
+ taxNumberName: this.taxNumberName,
3642
+ taxNumberSource: this.taxNumberSource,
3643
+ source: this.source,
3644
+ checkedAt: this.checkedAt,
3645
+ checkId: this.checkId
3646
+ };
3647
+ }
3648
+ };
3649
+ function parseCsvRecords(csv) {
3650
+ const records = [];
3651
+ let record = [];
3652
+ let cell = "";
3653
+ let quoted = false;
3654
+ for (let i = 0; i < csv.length; i++) {
3655
+ const ch = csv[i];
3656
+ if (quoted) {
3657
+ if (ch === '"') {
3658
+ if (csv[i + 1] === '"') {
3659
+ cell += '"';
3660
+ i++;
3661
+ } else {
3662
+ quoted = false;
3663
+ }
3664
+ } else {
3665
+ cell += ch;
3666
+ }
3667
+ } else if (ch === '"') {
3668
+ quoted = true;
3669
+ } else if (ch === ",") {
3670
+ record.push(cell);
3671
+ cell = "";
3672
+ } else if (ch === "\n" || ch === "\r") {
3673
+ if (ch === "\r" && csv[i + 1] === "\n") i++;
3674
+ record.push(cell);
3675
+ records.push(record);
3676
+ record = [];
3677
+ cell = "";
3678
+ } else {
3679
+ cell += ch;
3680
+ }
3681
+ }
3682
+ if (cell !== "" || record.length > 0) {
3683
+ record.push(cell);
3684
+ records.push(record);
3685
+ }
3686
+ return records.filter((r) => !(r.length === 1 && r[0] === ""));
3687
+ }
3688
+ function parseLocalTaxNumbersCsv(csv) {
3689
+ const [header, ...records] = parseCsvRecords(csv.replace(/^\uFEFF/, ""));
3690
+ if (!header) {
3691
+ return [];
3692
+ }
3693
+ const names = header.map((name) => name.trim());
3694
+ return records.map((cells) => {
3695
+ const row = {};
3696
+ names.forEach((name, i) => {
3697
+ row[name] = cells[i];
3698
+ });
3699
+ return LocalTaxNumberRow.fromArray(row);
3700
+ });
3701
+ }
3702
+
3572
3703
  // src/endpoints/VatEndpoint.ts
3573
3704
  var DATE_REGEX2 = /^\d{4}-\d{2}-\d{2}$/;
3574
3705
  var VatEndpoint = class {
@@ -3679,6 +3810,69 @@ var VatEndpoint = class {
3679
3810
  return new Uint8Array(buffer);
3680
3811
  });
3681
3812
  }
3813
+ /**
3814
+ * Local tax numbers report as raw CSV (UTF-8 with BOM, comma-separated): every company checked —
3815
+ * single and bulk VAT checks and Smart Enrichment lookups — once, with its local tax number
3816
+ * (taxNumber, taxNumberName, taxNumberSource). Digit-only taxNumber cells carry a leading TAB so
3817
+ * Excel keeps them as text; `localTaxNumbers()` strips it for you.
3818
+ */
3819
+ async localTaxNumbersCsv(options = {}) {
3820
+ const params = this.localTaxNumbersParams(options);
3821
+ if (options.onlyWithTaxNumber !== void 0) {
3822
+ params.set("only_with_tax_number", options.onlyWithTaxNumber ? "1" : "0");
3823
+ }
3824
+ const url = this.withQuery(`${this.baseUrl}/reports/local-tax-numbers`, params);
3825
+ return withRetries(this.retryPolicy, "Local tax numbers report", async () => {
3826
+ const headers = this.buildHeaders();
3827
+ delete headers["Content-Type"];
3828
+ headers["Accept"] = "text/csv, application/json";
3829
+ const response = await this.httpClient.request("GET", url, { headers });
3830
+ if (response.status === 422) {
3831
+ await this.parseJsonResponse(response);
3832
+ }
3833
+ const text = new TextDecoder("utf-8", { ignoreBOM: true }).decode(await response.arrayBuffer());
3834
+ if (!response.ok) {
3835
+ throw new HttpException(
3836
+ describeApiError(text, response.status),
3837
+ response.status,
3838
+ text,
3839
+ {},
3840
+ response.headers.get("retry-after")
3841
+ );
3842
+ }
3843
+ return text;
3844
+ });
3845
+ }
3846
+ /** The local tax numbers report, parsed: one row per company (same filters as `localTaxNumbersCsv()`). */
3847
+ async localTaxNumbers(options = {}) {
3848
+ return parseLocalTaxNumbersCsv(await this.localTaxNumbersCsv(options));
3849
+ }
3850
+ /** How many companies the report would list for the filters, and how many have a tax number. */
3851
+ async localTaxNumbersSummary(options = {}) {
3852
+ const url = this.withQuery(
3853
+ `${this.baseUrl}/reports/local-tax-numbers/summary`,
3854
+ this.localTaxNumbersParams(options)
3855
+ );
3856
+ return withRetries(this.retryPolicy, "Local tax numbers summary", async () => {
3857
+ const response = await this.sendRequest("GET", url);
3858
+ const data = await this.parseJsonResponse(response);
3859
+ const count = (key) => typeof data[key] === "number" ? data[key] : 0;
3860
+ return { companies: count("companies"), withTaxNumber: count("withTaxNumber") };
3861
+ });
3862
+ }
3863
+ localTaxNumbersParams(options) {
3864
+ const params = new URLSearchParams();
3865
+ if (options.dateFrom !== void 0) params.set("date_from", this.formatDate(options.dateFrom));
3866
+ if (options.dateTo !== void 0) params.set("date_to", this.formatDate(options.dateTo));
3867
+ for (const country of options.countries ?? []) {
3868
+ params.append("countries[]", country.trim().toUpperCase());
3869
+ }
3870
+ return params;
3871
+ }
3872
+ withQuery(url, params) {
3873
+ const queryString = params.toString();
3874
+ return queryString ? `${url}?${queryString}` : url;
3875
+ }
3682
3876
  buildHeaders() {
3683
3877
  const headers = {
3684
3878
  "Content-Type": "application/json",
@@ -3839,7 +4033,7 @@ var AuthRetryHttpClient = class {
3839
4033
  };
3840
4034
 
3841
4035
  // src/version.ts
3842
- var SDK_VERSION = "1.6.0";
4036
+ var SDK_VERSION = "1.7.0";
3843
4037
 
3844
4038
  // src/http/SdkVersionHttpClient.ts
3845
4039
  var SDK_VERSION_HEADER = "X-Taxora-SDK-Version";
@@ -4053,6 +4247,7 @@ function toBytes(rawBody) {
4053
4247
  ImportResult,
4054
4248
  InMemoryTokenStorage,
4055
4249
  Language,
4250
+ LocalTaxNumberRow,
4056
4251
  LoginIdentifier,
4057
4252
  NorwegianOrgNumber,
4058
4253
  PeppolLookupResult,
@@ -4105,6 +4300,7 @@ function toBytes(rawBody) {
4105
4300
  generateWebhookSignatureHeader,
4106
4301
  getBaseUrl,
4107
4302
  getFailedVatStates,
4303
+ parseLocalTaxNumbersCsv,
4108
4304
  parseWebhookSignatureHeader,
4109
4305
  toComplianceDocumentType,
4110
4306
  toComplianceEnrollmentStatus,