Skip to content

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.

Terminal window
npm install @getmandato/sdk

Requirements: Node.js 18+ (uses the built-in fetch API and crypto module).

import { MandatoClient } from "@getmandato/sdk";
const mandato = new MandatoClient({
apiKey: process.env.MANDATO_API_KEY!,
});
// Create an invoice
const { 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);
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,
});
OptionTypeDefaultDescription
apiKeystring(required)API key (sk_test_... or sk_live_...)
baseUrlstringhttps://api.getmandato.devAPI base URL
timeoutnumber30000Request timeout in milliseconds
maxRetriesnumber3Max retries for retryable errors

The client is organized into resource namespaces that match the API structure:

mandato.invoices // Invoice operations
mandato.companies // Company operations
mandato.connections // Connection operations
mandato.webhooks // Webhook operations
mandato.apiKeys // API key operations
mandato.health() // Health check

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 }

List invoices with optional filters and cursor-based pagination.

// List all invoices
const page1 = await mandato.invoices.list();
// With filters
const 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,
});
// Pagination
const page2 = await mandato.invoices.list({
cursor: page1.nextCursor!,
limit: 20,
});
// Iterate through all pages
let 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:

ParamTypeDescription
countryCountryCodeFilter by country
statusInvoiceStatusFilter by status
companyIdstringFilter by company
searchstringSearch VAT numbers, external ID
dateFromstringISO 8601 date lower bound
dateTostringISO 8601 date upper bound
cursorstringPagination cursor
limitnumberResults per page (default 20, max 100)

Returns: PaginatedResponse<Invoice>{ data: Invoice[], hasMore: boolean, nextCursor: string | null }

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 }

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[] }

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[] }


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 }

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[] }

Get a single company by ID.

const { data: company } = await mandato.companies.get("comp_abc123");

Returns: SingleResponse<Company>{ data: Company }

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 }

Soft-delete a company.

const result = await mandato.companies.delete("comp_abc123");
console.log(result.data.deleted); // true

Returns: DeletedResponse{ data: { id: string, deleted: boolean } }


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 OAuth
if (connection.authorizationUrl) {
console.log("Redirect to:", connection.authorizationUrl);
}

Returns: SingleResponse<Connection>{ data: Connection }

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[] }

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 }

Remove a connection and its stored credentials.

const result = await mandato.connections.delete("conn_abc123");
console.log(result.data.deleted); // true

Returns: DeletedResponse{ data: { id: string, deleted: boolean } }


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 once
console.log("Secret:", webhook.secret);

Returns: SingleResponse<WebhookCreated>{ data: WebhookCreated }

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 }

Remove the webhook configuration.

const { data: result } = await mandato.webhooks.delete();
console.log(result.deleted); // true

Returns: SingleResponse<{ deleted: boolean }>{ data: { deleted: boolean } }


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[] }

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 once
console.log("Key:", newKey.key);

Returns: SingleResponse<ApiKeyCreated>{ data: ApiKeyCreated }

Revoke an API key.

const { data: result } = await mandato.apiKeys.revoke("key_abc123");
console.log(result.revoked); // true

Returns: SingleResponse<{ id: string, revoked: boolean }>{ data: { id: string, revoked: boolean } }


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


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
}
interface WebhookEvent {
id: string; // unique event ID
type: string; // event type
createdAt: string; // ISO 8601 timestamp
data: Record<string, unknown>; // event-specific data
}

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);
}
}
ClassHTTP StatusExtra properties
ValidationError400details: Array<{ path: string, message: string }>
AuthenticationError401
ForbiddenError403
NotFoundError404
ConflictError409
RateLimitError429limit?: number, retryAfter?: number
ServerError500+

All classes share these base properties from MandatoError:

PropertyTypeDescription
messagestringError message
statusnumberHTTP status code
typestringMachine-readable error type
requestIdstringRequest ID for support

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";
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;
}

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 client
const mandato = new MandatoClient({
apiKey: process.env.MANDATO_API_KEY!,
});
// Create a company
const { data: company } = await mandato.companies.create({
name: "TechVision SRL",
vatNumber: "RO12345678",
country: "RO",
currency: "RON",
});
// Connect to ANAF
const { data: connection } = await mandato.connections.create({
companyId: company.id,
countryCode: "RO",
});
// Submit an invoice
try {
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})`);
}
}