Node.js SDK
The official Mandato Node.js SDK provides a typed, ergonomic interface for the Mandato API. It includes automatic retries, error handling, and webhook signature verification.
Installation
Section titled “Installation”npm install @getmandato/sdkpnpm add @getmandato/sdkyarn add @getmandato/sdkRequirements: Node.js 18+ (uses the built-in fetch API and crypto module).
Quick start
Section titled “Quick start”import { MandatoClient } from "@getmandato/sdk";
const mandato = new MandatoClient({ apiKey: process.env.MANDATO_API_KEY!,});
// Create an invoiceconst { data: invoice } = await mandato.invoices.create({ country: "RO", supplier: { vatNumber: "RO12345678", name: "TechVision SRL", }, customer: { vatNumber: "RO87654321", name: "Client SRL", }, lines: [ { description: "Consulting services", unitPrice: 1000, vatRate: 19 }, ],});
console.log(invoice.id, invoice.status);Configuration
Section titled “Configuration”import { MandatoClient } from "@getmandato/sdk";
const mandato = new MandatoClient({ // Required: your API key apiKey: "sk_test_your_key",
// Optional: override the base URL (default: https://api.getmandato.dev) baseUrl: "https://api.getmandato.dev",
// Optional: request timeout in milliseconds (default: 30000) timeout: 30_000,
// Optional: max retries for 429/5xx errors (default: 3) maxRetries: 3,});Configuration options
Section titled “Configuration options”| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | (required) | API key (sk_test_... or sk_live_...) |
baseUrl | string | https://api.getmandato.dev | API base URL |
timeout | number | 30000 | Request timeout in milliseconds |
maxRetries | number | 3 | Max retries for retryable errors |
Resource namespaces
Section titled “Resource namespaces”The client is organized into resource namespaces that match the API structure:
mandato.invoices // Invoice operationsmandato.companies // Company operationsmandato.connections // Connection operationsmandato.webhooks // Webhook operationsmandato.apiKeys // API key operationsmandato.health() // Health checkInvoices
Section titled “Invoices”invoices.create(data, idempotencyKey?)
Section titled “invoices.create(data, idempotencyKey?)”Create and submit an invoice.
const { data: invoice } = await mandato.invoices.create({ country: "RO", invoiceType: "invoice", // "invoice" | "credit_note" | "debit_note" companyId: "comp_abc123", // optional, auto-matched by country if omitted externalId: "order-12345", // optional, your reference ID supplier: { vatNumber: "RO12345678", name: "Supplier SRL", address: { street: "Strada Exemplu 42", city: "Bucharest", postalCode: "010101", country: "RO", }, email: "billing@supplier.ro", }, customer: { vatNumber: "RO87654321", name: "Customer SRL", address: { street: "Bulevardul Unirii 10", city: "Cluj-Napoca", postalCode: "400001", country: "RO", }, }, lines: [ { description: "Software development", quantity: 160, unitPrice: 50, vatRate: 19, unitCode: "HUR", // UN/ECE unit code (hours) }, { description: "Cloud hosting", quantity: 1, unitPrice: 200, vatRate: 19, discount: 20, // discount per unit }, ], currency: "RON", issueDate: "2025-01-15", dueDate: "2025-02-15", note: "Thank you for your business.", paymentMeans: { bankAccount: "RO49AAAA1B31007593840000", bic: "AAABROBU", paymentTerms: "Net 30", }, attachments: [ { filename: "timesheet.pdf", content: "base64encodedcontent...", mimeType: "application/pdf", }, ],});
// With idempotency key (prevents duplicate submissions)const { data: invoice2 } = await mandato.invoices.create( invoiceData, "unique-idempotency-key-123",);Returns: SingleResponse<Invoice> — { data: Invoice }
invoices.list(params?)
Section titled “invoices.list(params?)”List invoices with optional filters and cursor-based pagination.
// List all invoicesconst page1 = await mandato.invoices.list();
// With filtersconst filtered = await mandato.invoices.list({ country: "RO", status: "accepted", companyId: "comp_abc123", search: "order-12345", dateFrom: "2025-01-01", dateTo: "2025-01-31", limit: 50,});
// Paginationconst page2 = await mandato.invoices.list({ cursor: page1.nextCursor!, limit: 20,});
// Iterate through all pageslet cursor: string | undefined;do { const page = await mandato.invoices.list({ cursor, limit: 100 }); for (const invoice of page.data) { console.log(invoice.id, invoice.status); } cursor = page.nextCursor ?? undefined;} while (cursor);Parameters:
| Param | Type | Description |
|---|---|---|
country | CountryCode | Filter by country |
status | InvoiceStatus | Filter by status |
companyId | string | Filter by company |
search | string | Search VAT numbers, external ID |
dateFrom | string | ISO 8601 date lower bound |
dateTo | string | ISO 8601 date upper bound |
cursor | string | Pagination cursor |
limit | number | Results per page (default 20, max 100) |
Returns: PaginatedResponse<Invoice> — { data: Invoice[], hasMore: boolean, nextCursor: string | null }
invoices.get(id)
Section titled “invoices.get(id)”Get a single invoice by ID.
const { data: invoice } = await mandato.invoices.get("inv_abc123");console.log(invoice.status, invoice.govId);Returns: SingleResponse<Invoice> — { data: Invoice }
invoices.validate(data)
Section titled “invoices.validate(data)”Dry-run validation without creating or submitting.
const result = await mandato.invoices.validate({ country: "RO", supplier: { vatNumber: "RO12345678", name: "Test SRL" }, customer: { vatNumber: "RO87654321", name: "Customer SRL" }, lines: [{ description: "Item", unitPrice: 100, vatRate: 19 }],});
if (result.valid) { console.log("Valid -- safe to submit");} else { console.log("Errors:", result.errors); // ["BR-RO-010: Supplier address is required for Romanian invoices"]}Returns: ValidationResult — { valid: boolean, errors: string[] }
invoices.getEvents(id)
Section titled “invoices.getEvents(id)”Get the full event timeline for an invoice.
const { data: events } = await mandato.invoices.getEvents("inv_abc123");
for (const event of events) { console.log(`${event.createdAt} ${event.event} -> ${event.status}`);}Returns: SingleResponse<InvoiceEvent[]> — { data: InvoiceEvent[] }
Companies
Section titled “Companies”companies.create(data)
Section titled “companies.create(data)”Create a new company.
const { data: company } = await mandato.companies.create({ name: "TechVision SRL", vatNumber: "RO12345678", country: "RO", registrationNumber: "J40/1234/2020", address: { street: "Strada Exemplu 42", city: "Bucharest", postalCode: "010101", country: "RO", }, currency: "RON", contacts: { email: "billing@techvision.ro", phone: "+40712345678", },});Returns: SingleResponse<Company> — { data: Company }
companies.list()
Section titled “companies.list()”List all companies in your account.
const { data: companies } = await mandato.companies.list();
for (const company of companies) { console.log(`${company.name} (${company.vatNumber}) - ${company.country}`);}Returns: SingleResponse<Company[]> — { data: Company[] }
companies.get(id)
Section titled “companies.get(id)”Get a single company by ID.
const { data: company } = await mandato.companies.get("comp_abc123");Returns: SingleResponse<Company> — { data: Company }
companies.update(id, data)
Section titled “companies.update(id, data)”Update a company’s mutable fields. VAT number and country cannot be changed.
const { data: updated } = await mandato.companies.update("comp_abc123", { name: "TechVision Romania SRL", address: { street: "Strada Noua 100", city: "Bucharest", postalCode: "020202", country: "RO", }, contacts: { email: "invoices@techvision.ro", },});Returns: SingleResponse<Company> — { data: Company }
companies.delete(id)
Section titled “companies.delete(id)”Soft-delete a company.
const result = await mandato.companies.delete("comp_abc123");console.log(result.data.deleted); // trueReturns: DeletedResponse — { data: { id: string, deleted: boolean } }
Connections
Section titled “Connections”connections.create(data)
Section titled “connections.create(data)”Initialize a government connection for a company.
const { data: connection } = await mandato.connections.create({ companyId: "comp_abc123", countryCode: "RO",});
// For Romania, redirect the user to complete OAuthif (connection.authorizationUrl) { console.log("Redirect to:", connection.authorizationUrl);}Returns: SingleResponse<Connection> — { data: Connection }
connections.list()
Section titled “connections.list()”List all connections across all companies.
const { data: connections } = await mandato.connections.list();
for (const conn of connections) { console.log(`${conn.countryCode} - ${conn.status}`); if (conn.tokenExpiresAt) { console.log(` Token expires: ${conn.tokenExpiresAt}`); }}Returns: SingleResponse<Connection[]> — { data: Connection[] }
connections.get(id)
Section titled “connections.get(id)”Get a single connection by ID.
const { data: connection } = await mandato.connections.get("conn_abc123");console.log(connection.status); // "active" | "pending_setup" | "expired" | "error"Returns: SingleResponse<Connection> — { data: Connection }
connections.delete(id)
Section titled “connections.delete(id)”Remove a connection and its stored credentials.
const result = await mandato.connections.delete("conn_abc123");console.log(result.data.deleted); // trueReturns: DeletedResponse — { data: { id: string, deleted: boolean } }
Webhooks
Section titled “Webhooks”webhooks.create(data)
Section titled “webhooks.create(data)”Register or update the webhook URL. Generates a new signing secret each time.
const { data: webhook } = await mandato.webhooks.create({ url: "https://your-app.com/webhooks/mandato", events: ["invoice.accepted", "invoice.rejected", "invoice.error"],});
// Store the secret -- shown only onceconsole.log("Secret:", webhook.secret);Returns: SingleResponse<WebhookCreated> — { data: WebhookCreated }
webhooks.get()
Section titled “webhooks.get()”Get the current webhook configuration. The secret is masked.
const { data: config } = await mandato.webhooks.get();console.log("URL:", config.url);console.log("Events:", config.events);Returns: SingleResponse<WebhookConfig> — { data: WebhookConfig }
webhooks.delete()
Section titled “webhooks.delete()”Remove the webhook configuration.
const { data: result } = await mandato.webhooks.delete();console.log(result.deleted); // trueReturns: SingleResponse<{ deleted: boolean }> — { data: { deleted: boolean } }
API Keys
Section titled “API Keys”apiKeys.list()
Section titled “apiKeys.list()”List all API keys (masked).
const { data: keys } = await mandato.apiKeys.list();
for (const key of keys) { console.log(`${key.name} (${key.keyPrefix}...) - ${key.environment}`);}Returns: SingleResponse<ApiKey[]> — { data: ApiKey[] }
apiKeys.create(data)
Section titled “apiKeys.create(data)”Create a new API key. The full key is returned only once.
const { data: newKey } = await mandato.apiKeys.create({ name: "CI/CD pipeline", environment: "test", expiresAt: "2026-01-01T00:00:00.000Z",});
// Store immediately -- full key shown only onceconsole.log("Key:", newKey.key);Returns: SingleResponse<ApiKeyCreated> — { data: ApiKeyCreated }
apiKeys.revoke(id)
Section titled “apiKeys.revoke(id)”Revoke an API key.
const { data: result } = await mandato.apiKeys.revoke("key_abc123");console.log(result.revoked); // trueReturns: SingleResponse<{ id: string, revoked: boolean }> — { data: { id: string, revoked: boolean } }
Health check
Section titled “Health check”const health = await mandato.health();console.log(health.status); // "ok" | "degraded"console.log(health.checks.database); // "ok"console.log(health.checks.redis); // "ok"Returns: HealthStatus
Webhook verification
Section titled “Webhook verification”The SDK includes functions for verifying webhook signatures. These are standalone functions, not methods on the client.
verifyWebhookSignature(payload, signature, secret)
Section titled “verifyWebhookSignature(payload, signature, secret)”Verifies an HMAC-SHA256 signature. Returns true or false.
import { verifyWebhookSignature } from "@getmandato/sdk";
const isValid = verifyWebhookSignature( rawBody, // raw request body string signature, // X-Mandato-Signature header secret, // your webhook secret);constructWebhookEvent(payload, signature, secret)
Section titled “constructWebhookEvent(payload, signature, secret)”Verifies the signature and parses the event payload. Throws an error if the signature is invalid.
import { constructWebhookEvent, type WebhookEvent } from "@getmandato/sdk";
try { const event: WebhookEvent = constructWebhookEvent(rawBody, signature, secret); console.log(event.type); // "invoice.accepted" console.log(event.data); // invoice or connection object} catch (err) { // Invalid signature}WebhookEvent type
Section titled “WebhookEvent type”interface WebhookEvent { id: string; // unique event ID type: string; // event type createdAt: string; // ISO 8601 timestamp data: Record<string, unknown>; // event-specific data}Error handling
Section titled “Error handling”The SDK throws typed errors for different HTTP status codes. All errors extend MandatoError.
import { MandatoError, ValidationError, AuthenticationError, ForbiddenError, NotFoundError, ConflictError, RateLimitError, ServerError,} from "@getmandato/sdk";
try { await mandato.invoices.create(data);} catch (error) { if (error instanceof ValidationError) { console.log("Fields:", error.details); // [{ path, message }] } else if (error instanceof RateLimitError) { console.log("Retry after:", error.retryAfter, "seconds"); console.log("Limit:", error.limit); } else if (error instanceof MandatoError) { console.log("Status:", error.status); console.log("Type:", error.type); console.log("Request ID:", error.requestId); }}Error classes
Section titled “Error classes”| Class | HTTP Status | Extra properties |
|---|---|---|
ValidationError | 400 | details: Array<{ path: string, message: string }> |
AuthenticationError | 401 | — |
ForbiddenError | 403 | — |
NotFoundError | 404 | — |
ConflictError | 409 | — |
RateLimitError | 429 | limit?: number, retryAfter?: number |
ServerError | 500+ | — |
All classes share these base properties from MandatoError:
| Property | Type | Description |
|---|---|---|
message | string | Error message |
status | number | HTTP status code |
type | string | Machine-readable error type |
requestId | string | Request ID for support |
TypeScript types
Section titled “TypeScript types”All request and response types are exported for use in your application:
import type { // Country and status types CountryCode, InvoiceStatus, InvoiceType, ConnectionStatus, ApiKeyEnvironment, WebhookEventType,
// Request types CreateInvoiceRequest, ValidateInvoiceRequest, CreateCompanyRequest, UpdateCompanyRequest, CreateConnectionRequest, CreateWebhookRequest, CreateApiKeyRequest, ListInvoicesParams,
// Response types Invoice, InvoiceEvent, Company, Connection, ApiKey, ApiKeyCreated, WebhookConfig, WebhookCreated, ValidationResult, HealthStatus,
// Shared types Party, Address, InvoiceLine, PaymentMeans, Attachment,
// Wrappers SingleResponse, PaginatedResponse, DeletedResponse,} from "@getmandato/sdk";Key type definitions
Section titled “Key type definitions”type CountryCode = "RO" | "IT" | "BE" | "PL" | "FR" | "DE";
type InvoiceStatus = | "created" | "validated" | "converting" | "submitting" | "submitted" | "accepted" | "rejected" | "error";
type ConnectionStatus = "pending_setup" | "active" | "expired" | "error";
type WebhookEventType = | "invoice.created" | "invoice.validated" | "invoice.submitted" | "invoice.accepted" | "invoice.rejected" | "invoice.error" | "connection.active" | "connection.expired";
interface PaginatedResponse<T> { data: T[]; hasMore: boolean; nextCursor: string | null;}
interface SingleResponse<T> { data: T;}Full example
Section titled “Full example”A complete example showing invoice creation, status polling, and webhook handling:
import { MandatoClient, MandatoError, constructWebhookEvent } from "@getmandato/sdk";import type { Invoice, WebhookEvent } from "@getmandato/sdk";
// Initialize clientconst mandato = new MandatoClient({ apiKey: process.env.MANDATO_API_KEY!,});
// Create a companyconst { data: company } = await mandato.companies.create({ name: "TechVision SRL", vatNumber: "RO12345678", country: "RO", currency: "RON",});
// Connect to ANAFconst { data: connection } = await mandato.connections.create({ companyId: company.id, countryCode: "RO",});
// Submit an invoicetry { const { data: invoice } = await mandato.invoices.create( { country: "RO", companyId: company.id, supplier: { vatNumber: "RO12345678", name: "TechVision SRL", address: { street: "Strada Exemplu 42", city: "Bucharest", postalCode: "010101", country: "RO", }, }, customer: { vatNumber: "RO87654321", name: "Client SRL", address: { street: "Bulevardul Unirii 10", city: "Cluj-Napoca", postalCode: "400001", country: "RO", }, }, lines: [ { description: "Software development", quantity: 160, unitPrice: 50, vatRate: 19, unitCode: "HUR", }, ], currency: "RON", issueDate: "2025-01-31", dueDate: "2025-02-28", }, "unique-idempotency-key", );
console.log("Invoice created:", invoice.id);
// Poll for status (alternatively, use webhooks) let current: Invoice = invoice; while (current.status !== "accepted" && current.status !== "rejected" && current.status !== "error") { await new Promise((r) => setTimeout(r, 5000)); const response = await mandato.invoices.get(current.id); current = response.data; console.log("Status:", current.status); }
if (current.status === "accepted") { console.log("Government ID:", current.govId); } else { console.log("Error:", current.errorTranslated); console.log("Fix:", current.errorFix); }} catch (error) { if (error instanceof MandatoError) { console.error(`API error: ${error.message} (${error.requestId})`); }}