@taxora/sdk 1.0.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/LICENSE +21 -0
- package/README.md +436 -0
- package/dist/index.cjs +1046 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +315 -0
- package/dist/index.d.ts +315 -0
- package/dist/index.js +991 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 theconcept-technologies
|
|
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,436 @@
|
|
|
1
|
+
<p>
|
|
2
|
+
<img src="https://taxora.io/assets/logo/taxora_logo.svg" alt="Taxora Logo" width="220"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @taxora/sdk
|
|
6
|
+
|
|
7
|
+
> **Official Node.js/TypeScript SDK for the [Taxora VAT Validation API](https://taxora.io)**
|
|
8
|
+
> Validate EU VAT numbers, generate compliance certificates, and integrate VAT checks into your Node.js services โ with clean, modern TypeScript and zero runtime dependencies.
|
|
9
|
+
|
|
10
|
+
[](https://github.com/theconcept-technologies/taxora-sdk-node/actions)
|
|
11
|
+
[](https://www.npmjs.com/package/@taxora/sdk)
|
|
12
|
+
[](#-testing)
|
|
13
|
+
[](https://nodejs.org)
|
|
14
|
+
[](https://www.typescriptlang.org)
|
|
15
|
+
[](LICENSE)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ๐ Overview
|
|
20
|
+
|
|
21
|
+
The **Taxora SDK** provides a clean, type-safe interface to the [Taxora API](https://taxora.io), supporting:
|
|
22
|
+
|
|
23
|
+
- โ
Secure **API-Key** and **Bearer Token** authentication with auto-refresh
|
|
24
|
+
- โ
Single & multiple VAT validation with AI-based company name matching
|
|
25
|
+
- โ
VAT state history and full-text search endpoints
|
|
26
|
+
- โ
Certificate generation (PDF) and bulk/list exports (ZIP or PDF)
|
|
27
|
+
- โ
Strict TypeScript โ full type coverage, zero `any`
|
|
28
|
+
- โ
Native `fetch` (Node 18+) โ **zero runtime dependencies**
|
|
29
|
+
- โ
Dual **ESM + CJS** output for maximum compatibility
|
|
30
|
+
|
|
31
|
+
> ๐ The SDK itself is free to use, but a **Taxora API subscription** is required.
|
|
32
|
+
> Obtain your `x-api-key` from your [Taxora account developer settings](https://app.taxora.io).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ๐งฎ Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @taxora/sdk
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Requirements:** Node.js โฅ 18 (uses native `fetch` โ no polyfill needed)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## โ๏ธ Quick Start
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { TaxoraClientFactory, Environment } from '@taxora/sdk';
|
|
50
|
+
|
|
51
|
+
const client = TaxoraClientFactory.create({
|
|
52
|
+
apiKey: 'YOUR_X_API_KEY',
|
|
53
|
+
environment: Environment.SANDBOX, // or Environment.PRODUCTION
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 1๏ธโฃ Authenticate
|
|
57
|
+
await client.auth.login('user@example.com', 'superSecret');
|
|
58
|
+
|
|
59
|
+
// 2๏ธโฃ Validate a VAT number
|
|
60
|
+
const vat = await client.vat.validate('ATU12345678', 'Example GmbH');
|
|
61
|
+
console.log(vat.state); // 'valid' | 'invalid' | 'fraud' | 'unknown'
|
|
62
|
+
console.log(vat.companyName); // Official company name from registry
|
|
63
|
+
console.log(vat.score); // Overall confidence score (0.0 โ 1.0)
|
|
64
|
+
|
|
65
|
+
for (const step of vat.breakdown ?? []) {
|
|
66
|
+
console.log(`${step.stepName} โ ${step.scoreContribution}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Optional: typed address input for fallback name scoring
|
|
70
|
+
import { VatValidationAddressInput } from '@taxora/sdk';
|
|
71
|
+
|
|
72
|
+
const addressInput = new VatValidationAddressInput({
|
|
73
|
+
addressLine1: 'Ringstraรe 1',
|
|
74
|
+
postalCode: '1010',
|
|
75
|
+
city: 'Vienna',
|
|
76
|
+
countryCode: 'AT',
|
|
77
|
+
});
|
|
78
|
+
const vatWithAddress = await client.vat.validate('ATU12345678', 'Example GmbH', 'vies', addressInput);
|
|
79
|
+
|
|
80
|
+
// 3๏ธโฃ Access company context
|
|
81
|
+
const company = await client.company.get();
|
|
82
|
+
|
|
83
|
+
// 4๏ธโฃ Export certificates
|
|
84
|
+
const exportJob = await client.vat.certificatesBulkExport('2024-01-01', '2024-12-31');
|
|
85
|
+
const zip = await client.vat.downloadBulkExport(exportJob.exportId);
|
|
86
|
+
import { writeFileSync } from 'fs';
|
|
87
|
+
writeFileSync('certificates.zip', zip);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`vat.validate()` returns a `VatResource` with the canonical VAT UID, status, company data, and optional scoring details. The `score` reflects overall confidence (higher is better), while `breakdown` is an array of `ScoreBreakdown` objects describing each validation step, its score contribution, and metadata (e.g. matched addresses or mismatched fields).
|
|
91
|
+
|
|
92
|
+
Need a custom HTTP client (e.g. for logging or retries)? Pass it via the factory:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { TaxoraClientFactory, HttpClientInterface } from '@taxora/sdk';
|
|
96
|
+
|
|
97
|
+
class LoggingHttpClient implements HttpClientInterface {
|
|
98
|
+
async request(method: string, url: string, options?: RequestInit): Promise<Response> {
|
|
99
|
+
console.log(`โ ${method} ${url}`);
|
|
100
|
+
return fetch(url, { ...options, method });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const client = TaxoraClientFactory.create({
|
|
105
|
+
apiKey: 'YOUR_X_API_KEY',
|
|
106
|
+
httpClient: new LoggingHttpClient(),
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## ๐งฉ Architecture
|
|
113
|
+
|
|
114
|
+
The SDK follows clean separation of concerns:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
TaxoraClientFactory.create()
|
|
118
|
+
โโโ TaxoraClient
|
|
119
|
+
โโโ auth โ AuthEndpoint (login, loginWithClientId, refresh)
|
|
120
|
+
โโโ vat โ VatEndpoint (validate, history, search, certificates)
|
|
121
|
+
โโโ company โ CompanyEndpoint (company info)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Each endpoint handles:
|
|
125
|
+
|
|
126
|
+
- Request signing with `x-api-key`
|
|
127
|
+
- Bearer token injection and proactive refresh if expired
|
|
128
|
+
- JSON/binary response parsing into typed DTOs
|
|
129
|
+
|
|
130
|
+
`VatEndpoint` and `CompanyEndpoint` use the `AuthRetryHttpClient` wrapper, which transparently handles preemptive token refresh and 401 retry โ `AuthEndpoint` always uses the raw HTTP client to avoid circular refresh calls.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## ๐ฆ DTOs
|
|
135
|
+
|
|
136
|
+
| Class | Description |
|
|
137
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
138
|
+
| `VatResource` | Single VAT validation result: state, score, breakdown, company name, address, provider document |
|
|
139
|
+
| `VatCollection` | Iterable collection of `VatResource` objects with optional pagination `self` link |
|
|
140
|
+
| `ScoreBreakdown` | Per-step scoring fragment: step name, score contribution, and metadata context |
|
|
141
|
+
| `CompanyAddress` | Structured company address; assembles `fullAddress` from components or parses JSON strings |
|
|
142
|
+
| `VatValidationAddressInput` | Typed input for address-based fallback scoring (validates lengths, country code format) |
|
|
143
|
+
| `Token` | Access token with type and expiry; 15-second buffer for proactive refresh |
|
|
144
|
+
| `VatCertificateExport` | Bulk export job reference (`exportId` + optional message) |
|
|
145
|
+
| `ProviderDocument` | Attached provider document metadata (type, date, MIME, hash, nested line item) |
|
|
146
|
+
|
|
147
|
+
All DTOs expose `toArray()` for serialization and a static `fromArray()` factory.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const dto = await client.vat.validate('ATU12345678');
|
|
151
|
+
console.log(dto.toArray());
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## ๐ Authentication Flow
|
|
157
|
+
|
|
158
|
+
### 1. Login
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
// Email + password
|
|
162
|
+
await client.auth.login('user@example.com', 'password', 'my-server-01');
|
|
163
|
+
// device_name is optional; omitted value falls back to os.hostname()
|
|
164
|
+
|
|
165
|
+
// Client ID + secret
|
|
166
|
+
await client.auth.loginWithClientId('client_abc123', 'client-secret', 'integration-box');
|
|
167
|
+
|
|
168
|
+
// Explicit identifier enum
|
|
169
|
+
import { LoginIdentifier } from '@taxora/sdk';
|
|
170
|
+
await client.auth.login('client_abc123', 'client-secret', undefined, LoginIdentifier.CLIENT_ID);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
โ Stores and returns a `Token` DTO (valid for ~3600 seconds).
|
|
174
|
+
|
|
175
|
+
### 2. Auto-refresh
|
|
176
|
+
|
|
177
|
+
The SDK automatically refreshes the token in two scenarios:
|
|
178
|
+
|
|
179
|
+
| Trigger | Behaviour |
|
|
180
|
+
| -------------------------------------------- | ----------------------------------------------- |
|
|
181
|
+
| **Preemptive** (token expired + 15 s buffer) | Refreshes before the next request |
|
|
182
|
+
| **Reactive** (API returns `401`) | Refreshes once and retries the original request |
|
|
183
|
+
|
|
184
|
+
You never need to call `refresh()` manually in normal usage.
|
|
185
|
+
|
|
186
|
+
### 3. Manual refresh
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
await client.auth.refresh();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 4. Custom token storage
|
|
193
|
+
|
|
194
|
+
By default, tokens are stored in-memory and lost on process restart. Provide your own storage for persistence:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { TokenStorageInterface, Token } from '@taxora/sdk';
|
|
198
|
+
|
|
199
|
+
class RedisTokenStorage implements TokenStorageInterface {
|
|
200
|
+
get(): Token | null {
|
|
201
|
+
/* read from Redis */ return null;
|
|
202
|
+
}
|
|
203
|
+
set(token: Token): void {
|
|
204
|
+
/* write with TTL = token.expiresAt */
|
|
205
|
+
}
|
|
206
|
+
clear(): void {
|
|
207
|
+
/* delete key */
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const client = TaxoraClientFactory.create({
|
|
212
|
+
apiKey: 'YOUR_X_API_KEY',
|
|
213
|
+
tokenStorage: new RedisTokenStorage(),
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## ๐งพ VAT Validation
|
|
220
|
+
|
|
221
|
+
### Single Validation
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// Basic โ state only
|
|
225
|
+
const vat = await client.vat.validate('ATU12345678');
|
|
226
|
+
|
|
227
|
+
// With company name matching
|
|
228
|
+
const vat = await client.vat.validate('ATU12345678', 'Alpha Handels GmbH');
|
|
229
|
+
|
|
230
|
+
// With address input for enhanced score
|
|
231
|
+
const vat = await client.vat.validate('ATU12345678', 'Alpha Handels GmbH', 'vies', {
|
|
232
|
+
address_line_1: 'Ringstraรe 1',
|
|
233
|
+
postal_code: '1010',
|
|
234
|
+
city: 'Wien',
|
|
235
|
+
country_code: 'AT',
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
console.log(vat.state); // VatState value
|
|
239
|
+
console.log(vat.score); // 0.0 โ 1.0
|
|
240
|
+
console.log(vat.companyAddress?.toString()); // Full address string
|
|
241
|
+
console.log(vat.getBackendLink()); // https://app.taxora.io/vat/history/{uuid}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Schema Validation (format check only)
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const result = await client.vat.validateSchema('ATU12345678');
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Batch Validation
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
const collection = await client.vat.validateMultiple(
|
|
254
|
+
['ATU12345678', 'DE123456789'],
|
|
255
|
+
['Alpha Handels GmbH', 'Beta Technik GmbH'],
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
for (const vat of collection) {
|
|
259
|
+
console.log(vat.vatUid, vat.state);
|
|
260
|
+
}
|
|
261
|
+
console.log(collection.length); // 2
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### VAT State Snapshot
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
const vat = await client.vat.state('ATU12345678');
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### History
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
const history = await client.vat.history(); // all entries
|
|
274
|
+
const history = await client.vat.history('ATU12345678'); // filtered
|
|
275
|
+
|
|
276
|
+
for (const entry of history) {
|
|
277
|
+
console.log(entry.checkedAt, entry.state);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Search
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
const results = await client.vat.search('Alpha Handels', 25 /* perPage */);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## ๐ Certificates
|
|
290
|
+
|
|
291
|
+
### Single Certificate (PDF)
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import { Language } from '@taxora/sdk';
|
|
295
|
+
import { writeFileSync } from 'fs';
|
|
296
|
+
|
|
297
|
+
const pdf = await client.vat.certificate('uuid-123', Language.GERMAN);
|
|
298
|
+
writeFileSync('certificate.pdf', pdf);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Bulk Export (ZIP)
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
const exportJob = await client.vat.certificatesBulkExport(
|
|
305
|
+
'2024-01-01', // or new Date('2024-01-01')
|
|
306
|
+
'2024-12-31',
|
|
307
|
+
['AT', 'DE'], // optional: filter by country
|
|
308
|
+
Language.ENGLISH, // optional: language
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
console.log(exportJob.exportId); // poll or use directly
|
|
312
|
+
|
|
313
|
+
const zip = await client.vat.downloadBulkExport(exportJob.exportId);
|
|
314
|
+
writeFileSync('certificates.zip', zip);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### List Export
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const exportJob = await client.vat.certificatesListExport(new Date('2024-01-01'), new Date('2024-12-31'));
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## ๐ Environments
|
|
326
|
+
|
|
327
|
+
| Environment | Base URL |
|
|
328
|
+
| ------------------------ | ------------------------------ |
|
|
329
|
+
| `Environment.SANDBOX` | `https://sandbox.taxora.io/v1` |
|
|
330
|
+
| `Environment.PRODUCTION` | `https://api.taxora.io/v1` |
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
const client = TaxoraClientFactory.create({
|
|
334
|
+
apiKey: 'YOUR_X_API_KEY',
|
|
335
|
+
environment: Environment.PRODUCTION,
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Need sandbox sample data? Known VAT UIDs with deterministic responses live in `tests/fixtures/SandboxVatFixtures.ts`.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## โ ๏ธ Error Handling
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
import { AuthenticationException, HttpException, ValidationException } from '@taxora/sdk';
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
await client.vat.validate('ATU12345678');
|
|
350
|
+
} catch (err) {
|
|
351
|
+
if (err instanceof AuthenticationException) {
|
|
352
|
+
// HTTP 401 โ credentials invalid or token refresh failed
|
|
353
|
+
console.error('Auth failed:', err.message);
|
|
354
|
+
} else if (err instanceof ValidationException) {
|
|
355
|
+
// HTTP 422 โ request payload rejected by the API
|
|
356
|
+
console.error('Validation errors:', err.getErrors()); // Record<string, string[]>
|
|
357
|
+
} else if (err instanceof HttpException) {
|
|
358
|
+
// Any other HTTP error
|
|
359
|
+
console.error(`HTTP ${err.getStatusCode()}:`, err.getResponseBody());
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## ๐ VatState Helpers
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { VatState, getFailedVatStates, describeVatState } from '@taxora/sdk';
|
|
370
|
+
|
|
371
|
+
VatState.VALID; // 'valid'
|
|
372
|
+
VatState.INVALID; // 'invalid'
|
|
373
|
+
VatState.FRAUD; // 'fraud'
|
|
374
|
+
VatState.UNKNOWN; // 'unknown'
|
|
375
|
+
|
|
376
|
+
getFailedVatStates(); // ['invalid', 'fraud']
|
|
377
|
+
describeVatState(VatState.VALID); // 'The VAT number is valid and active.'
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## ๐งช Testing
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
npm test # Run all 129 tests
|
|
386
|
+
npm run test:coverage # Run with v8 coverage report
|
|
387
|
+
npm run test:watch # Watch mode during development
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
CI runs on **Node 18**, **20**, and **22**, verifying:
|
|
391
|
+
|
|
392
|
+
- 129 Vitest unit tests across 18 test files
|
|
393
|
+
- 97.8 % statement coverage / 87 % branch coverage / 100 % function coverage
|
|
394
|
+
- Strict TypeScript compilation
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## ๐๏ธ Build
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
npm run build
|
|
402
|
+
# โ dist/index.js (ESM)
|
|
403
|
+
# โ dist/index.cjs (CommonJS)
|
|
404
|
+
# โ dist/index.d.ts (TypeScript declarations)
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## โ ๏ธ Deprecations
|
|
410
|
+
|
|
411
|
+
So fresh there aren't even any deprecated features yet. Check back in a few months when we're on v47 and have made some regrettable decisions. ๐
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## ๐ชช License
|
|
416
|
+
|
|
417
|
+
Licensed under the **MIT License** ยฉ 2025 [theconcept technologies](https://www.theconcept-technologies.com).
|
|
418
|
+
The SDK is open-source, but API usage requires a valid **Taxora subscription**.
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## ๐ค Contributing
|
|
423
|
+
|
|
424
|
+
Contributions and pull requests are welcome!
|
|
425
|
+
|
|
426
|
+
- Follow the existing TypeScript code style (strict mode, no `any`).
|
|
427
|
+
- Run `npm test` before submitting a PR.
|
|
428
|
+
- Ensure new endpoints include DTOs + tests.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## ๐ฌ Support
|
|
433
|
+
|
|
434
|
+
Need help or enterprise support?
|
|
435
|
+
๐ง **[support@taxora.io](mailto:support@taxora.io)**
|
|
436
|
+
๐ [https://taxora.io](https://taxora.io)
|