@getmandato/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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrei Cotaga
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,80 @@
1
+ # @getmandato/sdk
2
+
3
+ Official Node.js SDK for the [Mandato](https://getmandato.dev) EU e-invoicing API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @getmandato/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { MandatoClient } from "@getmandato/sdk";
15
+
16
+ const mandato = new MandatoClient({
17
+ apiKey: "sk_live_...",
18
+ });
19
+
20
+ // Create and submit an invoice
21
+ const invoice = await mandato.invoices.create({
22
+ country: "RO",
23
+ supplier: {
24
+ vatNumber: "RO12345678",
25
+ name: "Acme SRL",
26
+ address: { street: "Str. Exemplu 1", city: "Bucharest", postalCode: "010101", country: "RO" },
27
+ },
28
+ customer: {
29
+ vatNumber: "RO87654321",
30
+ name: "Client SRL",
31
+ },
32
+ lines: [
33
+ { description: "Software license", quantity: 1, unitPrice: 500, vatRate: 19 },
34
+ ],
35
+ });
36
+
37
+ console.log(invoice.id); // "inv_abc123"
38
+ console.log(invoice.status); // "submitted"
39
+
40
+ // Check status
41
+ const status = await mandato.invoices.get(invoice.id);
42
+ console.log(status.status); // "accepted"
43
+ console.log(status.govId); // ANAF index number
44
+ ```
45
+
46
+ ## Features
47
+
48
+ - Full TypeScript support with detailed types
49
+ - Automatic retries on 429 and 5xx errors
50
+ - Idempotency key generation
51
+ - Webhook signature verification
52
+ - Test and live environment support
53
+
54
+ ## Webhook Verification
55
+
56
+ ```typescript
57
+ import { verifyWebhookSignature } from "@getmandato/sdk";
58
+
59
+ app.post("/webhooks/mandato", (req) => {
60
+ const isValid = verifyWebhookSignature(
61
+ JSON.stringify(req.body),
62
+ req.headers["x-mandato-signature"],
63
+ process.env.WEBHOOK_SECRET,
64
+ );
65
+
66
+ if (!isValid) {
67
+ return res.status(401).send("Invalid signature");
68
+ }
69
+
70
+ // Handle the event
71
+ });
72
+ ```
73
+
74
+ ## Documentation
75
+
76
+ Full API documentation is available at [getmandato.dev/docs](https://getmandato.dev/docs).
77
+
78
+ ## License
79
+
80
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,441 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AuthenticationError: () => AuthenticationError,
24
+ ConflictError: () => ConflictError,
25
+ ForbiddenError: () => ForbiddenError,
26
+ MandatoClient: () => MandatoClient,
27
+ MandatoError: () => MandatoError,
28
+ MandatoHttpClient: () => MandatoHttpClient,
29
+ NotFoundError: () => NotFoundError,
30
+ RateLimitError: () => RateLimitError,
31
+ ServerError: () => ServerError,
32
+ ValidationError: () => ValidationError,
33
+ constructWebhookEvent: () => constructWebhookEvent,
34
+ verifyWebhookSignature: () => verifyWebhookSignature
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/config.ts
39
+ var DEFAULTS = {
40
+ baseUrl: "https://api.getmandato.dev",
41
+ timeout: 3e4,
42
+ maxRetries: 3
43
+ };
44
+
45
+ // src/errors.ts
46
+ var MandatoError = class extends Error {
47
+ /** HTTP status code */
48
+ status;
49
+ /** Error type from the API response */
50
+ type;
51
+ /** Request ID for support reference */
52
+ requestId;
53
+ constructor(message, status, type, requestId) {
54
+ super(message);
55
+ this.name = "MandatoError";
56
+ this.status = status;
57
+ this.type = type;
58
+ this.requestId = requestId;
59
+ }
60
+ };
61
+ var ValidationError = class extends MandatoError {
62
+ details;
63
+ constructor(message, details = [], requestId) {
64
+ super(message, 400, "validation_error", requestId);
65
+ this.name = "ValidationError";
66
+ this.details = details;
67
+ }
68
+ };
69
+ var AuthenticationError = class extends MandatoError {
70
+ constructor(message = "Invalid API key", requestId) {
71
+ super(message, 401, "authentication_error", requestId);
72
+ this.name = "AuthenticationError";
73
+ }
74
+ };
75
+ var ForbiddenError = class extends MandatoError {
76
+ constructor(message = "Forbidden", requestId) {
77
+ super(message, 403, "forbidden_error", requestId);
78
+ this.name = "ForbiddenError";
79
+ }
80
+ };
81
+ var NotFoundError = class extends MandatoError {
82
+ constructor(message = "Resource not found", requestId) {
83
+ super(message, 404, "not_found_error", requestId);
84
+ this.name = "NotFoundError";
85
+ }
86
+ };
87
+ var ConflictError = class extends MandatoError {
88
+ constructor(message = "Idempotency conflict", requestId) {
89
+ super(message, 409, "conflict_error", requestId);
90
+ this.name = "ConflictError";
91
+ }
92
+ };
93
+ var RateLimitError = class extends MandatoError {
94
+ /** Requests per minute allowed */
95
+ limit;
96
+ /** Seconds until the rate limit resets */
97
+ retryAfter;
98
+ constructor(message, limit, retryAfter, requestId) {
99
+ super(message, 429, "rate_limit_error", requestId);
100
+ this.name = "RateLimitError";
101
+ this.limit = limit;
102
+ this.retryAfter = retryAfter;
103
+ }
104
+ };
105
+ var ServerError = class extends MandatoError {
106
+ constructor(message = "Internal server error", status = 500, requestId) {
107
+ super(message, status, "server_error", requestId);
108
+ this.name = "ServerError";
109
+ }
110
+ };
111
+
112
+ // src/http.ts
113
+ var MandatoHttpClient = class {
114
+ apiKey;
115
+ baseUrl;
116
+ timeout;
117
+ maxRetries;
118
+ constructor(config) {
119
+ this.apiKey = config.apiKey;
120
+ this.baseUrl = (config.baseUrl ?? DEFAULTS.baseUrl).replace(/\/+$/, "");
121
+ this.timeout = config.timeout ?? DEFAULTS.timeout;
122
+ this.maxRetries = config.maxRetries ?? DEFAULTS.maxRetries;
123
+ }
124
+ /** Make a GET request */
125
+ async get(path, params) {
126
+ const url = this.buildUrl(path, params);
127
+ return this.request("GET", url);
128
+ }
129
+ /** Make a POST request */
130
+ async post(path, body, headers) {
131
+ const url = this.buildUrl(path);
132
+ return this.request("POST", url, body, headers);
133
+ }
134
+ /** Make a PATCH request */
135
+ async patch(path, body) {
136
+ const url = this.buildUrl(path);
137
+ return this.request("PATCH", url, body);
138
+ }
139
+ /** Make a DELETE request */
140
+ async delete(path) {
141
+ const url = this.buildUrl(path);
142
+ return this.request("DELETE", url);
143
+ }
144
+ /** Build full URL with optional query params */
145
+ buildUrl(path, params) {
146
+ const url = new URL(`${this.baseUrl}${path}`);
147
+ if (params) {
148
+ for (const [key, value] of Object.entries(params)) {
149
+ if (value !== void 0) {
150
+ url.searchParams.set(key, String(value));
151
+ }
152
+ }
153
+ }
154
+ return url.toString();
155
+ }
156
+ /** Execute an HTTP request with retries and error mapping */
157
+ async request(method, url, body, extraHeaders) {
158
+ let lastError;
159
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
160
+ if (attempt > 0) {
161
+ const delay = Math.min(500 * 2 ** (attempt - 1), 1e4);
162
+ await sleep(delay);
163
+ }
164
+ try {
165
+ const controller = new AbortController();
166
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
167
+ const headers = {
168
+ Authorization: `Bearer ${this.apiKey}`,
169
+ "Content-Type": "application/json",
170
+ ...extraHeaders
171
+ };
172
+ const init = {
173
+ method,
174
+ headers,
175
+ signal: controller.signal
176
+ };
177
+ if (body !== void 0 && method !== "GET") {
178
+ init.body = JSON.stringify(body);
179
+ }
180
+ const response = await fetch(url, init);
181
+ clearTimeout(timeoutId);
182
+ if (response.ok) {
183
+ return await response.json();
184
+ }
185
+ const errorBody = await this.parseErrorBody(response);
186
+ const requestId = errorBody?.error?.requestId;
187
+ if (response.status === 400) {
188
+ throw new ValidationError(
189
+ errorBody?.error?.message ?? "Validation error",
190
+ errorBody?.error?.details,
191
+ requestId
192
+ );
193
+ }
194
+ if (response.status === 401) {
195
+ throw new AuthenticationError(errorBody?.error?.message, requestId);
196
+ }
197
+ if (response.status === 403) {
198
+ throw new ForbiddenError(errorBody?.error?.message, requestId);
199
+ }
200
+ if (response.status === 404) {
201
+ throw new NotFoundError(errorBody?.error?.message, requestId);
202
+ }
203
+ if (response.status === 409) {
204
+ throw new ConflictError(errorBody?.error?.message, requestId);
205
+ }
206
+ if (response.status === 429) {
207
+ const limitHeader = response.headers.get("X-RateLimit-Limit");
208
+ const retryAfter = response.headers.get("Retry-After");
209
+ lastError = new RateLimitError(
210
+ errorBody?.error?.message ?? "Rate limit exceeded",
211
+ limitHeader ? Number(limitHeader) : void 0,
212
+ retryAfter ? Number(retryAfter) : void 0,
213
+ requestId
214
+ );
215
+ continue;
216
+ }
217
+ if (response.status >= 500) {
218
+ lastError = new ServerError(
219
+ errorBody?.error?.message ?? "Server error",
220
+ response.status,
221
+ requestId
222
+ );
223
+ continue;
224
+ }
225
+ throw new MandatoError(
226
+ errorBody?.error?.message ?? `HTTP ${response.status}`,
227
+ response.status,
228
+ errorBody?.error?.type ?? "unknown_error",
229
+ requestId
230
+ );
231
+ } catch (error) {
232
+ if (error instanceof MandatoError && !(error instanceof RateLimitError) && !(error instanceof ServerError)) {
233
+ throw error;
234
+ }
235
+ if (error instanceof Error && error.name === "AbortError") {
236
+ lastError = new MandatoError("Request timeout", 0, "timeout_error");
237
+ continue;
238
+ }
239
+ if (error instanceof MandatoError) {
240
+ lastError = error;
241
+ continue;
242
+ }
243
+ lastError = error instanceof Error ? error : new Error(String(error));
244
+ }
245
+ }
246
+ throw lastError ?? new MandatoError("Request failed after retries", 0, "retry_exhausted");
247
+ }
248
+ /** Safely parse error response body */
249
+ async parseErrorBody(response) {
250
+ try {
251
+ return await response.json();
252
+ } catch {
253
+ return null;
254
+ }
255
+ }
256
+ };
257
+ function sleep(ms) {
258
+ return new Promise((resolve) => setTimeout(resolve, ms));
259
+ }
260
+
261
+ // src/resources/api-keys.ts
262
+ var ApiKeys = class {
263
+ constructor(http) {
264
+ this.http = http;
265
+ }
266
+ /** List API keys (masked) */
267
+ async list() {
268
+ return this.http.get("/v1/api-keys");
269
+ }
270
+ /** Create a new API key (full key returned once) */
271
+ async create(data) {
272
+ return this.http.post("/v1/api-keys", data);
273
+ }
274
+ /** Revoke an API key */
275
+ async revoke(id) {
276
+ return this.http.delete(`/v1/api-keys/${id}`);
277
+ }
278
+ };
279
+
280
+ // src/resources/companies.ts
281
+ var Companies = class {
282
+ constructor(http) {
283
+ this.http = http;
284
+ }
285
+ /** Create a company */
286
+ async create(data) {
287
+ return this.http.post("/v1/companies", data);
288
+ }
289
+ /** List all companies */
290
+ async list() {
291
+ return this.http.get("/v1/companies");
292
+ }
293
+ /** Get a single company by ID */
294
+ async get(id) {
295
+ return this.http.get(`/v1/companies/${id}`);
296
+ }
297
+ /** Update a company */
298
+ async update(id, data) {
299
+ return this.http.patch(`/v1/companies/${id}`, data);
300
+ }
301
+ /** Delete a company */
302
+ async delete(id) {
303
+ return this.http.delete(`/v1/companies/${id}`);
304
+ }
305
+ };
306
+
307
+ // src/resources/connections.ts
308
+ var Connections = class {
309
+ constructor(http) {
310
+ this.http = http;
311
+ }
312
+ /** Initialize a government connection */
313
+ async create(data) {
314
+ return this.http.post("/v1/connections", data);
315
+ }
316
+ /** List all connections */
317
+ async list() {
318
+ return this.http.get("/v1/connections");
319
+ }
320
+ /** Get a single connection by ID */
321
+ async get(id) {
322
+ return this.http.get(`/v1/connections/${id}`);
323
+ }
324
+ /** Remove a connection */
325
+ async delete(id) {
326
+ return this.http.delete(`/v1/connections/${id}`);
327
+ }
328
+ };
329
+
330
+ // src/resources/invoices.ts
331
+ var Invoices = class {
332
+ constructor(http) {
333
+ this.http = http;
334
+ }
335
+ /** Create and submit an invoice */
336
+ async create(data, idempotencyKey) {
337
+ const headers = {};
338
+ if (idempotencyKey) {
339
+ headers["X-Idempotency-Key"] = idempotencyKey;
340
+ }
341
+ return this.http.post("/v1/invoices", data, headers);
342
+ }
343
+ /** List invoices with optional filters */
344
+ async list(params) {
345
+ return this.http.get(
346
+ "/v1/invoices",
347
+ params
348
+ );
349
+ }
350
+ /** Get a single invoice by ID */
351
+ async get(id) {
352
+ return this.http.get(`/v1/invoices/${id}`);
353
+ }
354
+ /** Dry-run validation without submission */
355
+ async validate(data) {
356
+ return this.http.post("/v1/invoices/validate", data);
357
+ }
358
+ /** Get invoice event timeline */
359
+ async getEvents(id) {
360
+ return this.http.get(`/v1/invoices/${id}/events`);
361
+ }
362
+ };
363
+
364
+ // src/resources/webhooks.ts
365
+ var Webhooks = class {
366
+ constructor(http) {
367
+ this.http = http;
368
+ }
369
+ /** Register a webhook URL */
370
+ async create(data) {
371
+ return this.http.post("/v1/webhooks", data);
372
+ }
373
+ /** Get current webhook configuration */
374
+ async get() {
375
+ return this.http.get("/v1/webhooks");
376
+ }
377
+ /** Remove webhook */
378
+ async delete() {
379
+ return this.http.delete("/v1/webhooks");
380
+ }
381
+ };
382
+
383
+ // src/client.ts
384
+ var MandatoClient = class {
385
+ /** Invoice operations */
386
+ invoices;
387
+ /** Company operations */
388
+ companies;
389
+ /** Connection operations */
390
+ connections;
391
+ /** Webhook operations */
392
+ webhooks;
393
+ /** API key operations */
394
+ apiKeys;
395
+ http;
396
+ constructor(config) {
397
+ this.http = new MandatoHttpClient(config);
398
+ this.invoices = new Invoices(this.http);
399
+ this.companies = new Companies(this.http);
400
+ this.connections = new Connections(this.http);
401
+ this.webhooks = new Webhooks(this.http);
402
+ this.apiKeys = new ApiKeys(this.http);
403
+ }
404
+ /** Check API health */
405
+ async health() {
406
+ return this.http.get("/v1/health");
407
+ }
408
+ };
409
+
410
+ // src/webhooks.ts
411
+ var import_node_crypto = require("crypto");
412
+ function verifyWebhookSignature(payload, signature, secret) {
413
+ const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(payload).digest("hex");
414
+ const sigBuffer = Buffer.from(signature, "utf-8");
415
+ const expectedBuffer = Buffer.from(expected, "utf-8");
416
+ if (sigBuffer.length !== expectedBuffer.length) {
417
+ return false;
418
+ }
419
+ return (0, import_node_crypto.timingSafeEqual)(sigBuffer, expectedBuffer);
420
+ }
421
+ function constructWebhookEvent(payload, signature, secret) {
422
+ if (!verifyWebhookSignature(payload, signature, secret)) {
423
+ throw new Error("Invalid webhook signature");
424
+ }
425
+ return JSON.parse(payload);
426
+ }
427
+ // Annotate the CommonJS export names for ESM import in node:
428
+ 0 && (module.exports = {
429
+ AuthenticationError,
430
+ ConflictError,
431
+ ForbiddenError,
432
+ MandatoClient,
433
+ MandatoError,
434
+ MandatoHttpClient,
435
+ NotFoundError,
436
+ RateLimitError,
437
+ ServerError,
438
+ ValidationError,
439
+ constructWebhookEvent,
440
+ verifyWebhookSignature
441
+ });