Romania (ANAF)
Romania’s e-invoicing system is operated by ANAF (Agentia Nationala de Administrare Fiscala) through the e-Factura platform. Since January 2024, all B2B transactions between Romanian VAT-registered entities must be reported through e-Factura.
Mandato handles the entire ANAF integration: OAuth2 authentication, JSON-to-UBL conversion, XML submission, status polling, and error translation.
Overview
Section titled “Overview”| Property | Value |
|---|---|
| Government system | ANAF e-Factura |
| XML format | UBL 2.1 CIUS-RO |
| Profile | urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1 |
| Authentication | OAuth2 + digital certificate |
| Token lifetime | Access token: 90 days, Refresh token: 365 days |
| Upload endpoint | POST /upload/FACT1/{vatNumber} |
| Status endpoint | GET /listaMesajeFactura?cif={vatNumber}&zile=60 |
Prerequisites
Section titled “Prerequisites”Before you can submit invoices to ANAF through Mandato, you need:
- A Romanian company with a valid CUI (Cod Unic de Identificare)
- An ANAF SPV account (Spatiul Privat Virtual) for the company
- A digital certificate — either a USB hardware token or a cloud-based certificate registered with ANAF
- A Mandato account with an API key
-
Register your company in Mandato
Create a company with your Romanian VAT details:
import { MandatoClient } from "@getmandato/sdk";const mandato = new MandatoClient({apiKey: "sk_live_your_production_key",});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",}); -
Initialize the ANAF connection
Create a connection for Romania. This returns an OAuth authorization URL.
const { data: connection } = await mandato.connections.create({companyId: company.id,countryCode: "RO",});console.log("Authorize at:", connection.authorizationUrl);// https://logincert.anaf.ro/anaf-oauth2/v1/authorize?response_type=code&client_id=...&state=... -
Complete the OAuth flow
Redirect the company administrator to the
authorizationUrl. They will:- Access the ANAF login page with their digital certificate inserted
- Authenticate using the certificate PIN
- Authorize Mandato to access e-Factura on behalf of the company
- Be redirected back to Mandato’s callback endpoint
After the callback is processed, the connection status changes to
active.// Poll until the connection is activeconst { data: activeConnection } = await mandato.connections.get(connection.id);console.log(activeConnection.status); // "active"console.log(activeConnection.tokenExpiresAt); // ~90 days from now -
Submit your first invoice
Once the connection is active, you can submit invoices:
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 Corp SRL",address: {street: "Bulevardul Unirii 10",city: "Cluj-Napoca",postalCode: "400001",country: "RO",},},lines: [{description: "Servicii dezvoltare software - Ianuarie 2025",quantity: 160,unitPrice: 50,vatRate: 19,unitCode: "HUR", // Hours},],currency: "RON",issueDate: "2025-01-31",dueDate: "2025-02-28",paymentMeans: {bankAccount: "RO49AAAA1B31007593840000",bic: "AAABROBU",paymentTerms: "Net 30",},});
CUI / VAT number format
Section titled “CUI / VAT number format”Romanian VAT numbers (CUI — Cod Unic de Identificare) follow this format:
| Format | Example | Notes |
|---|---|---|
RO + 2-10 digits | RO12345678 | Standard format, always include the RO prefix |
| Without prefix | 12345678 | Mandato also accepts this and prepends RO |
The CUI is validated against ANAF’s records. In the sandbox, any correctly formatted CUI is accepted.
Romanian VAT rates
Section titled “Romanian VAT rates”| Rate | Code | Description |
|---|---|---|
| 19% | S | Standard rate (most goods and services) |
| 9% | S | Reduced rate (food, water, medicine, books, hotels) |
| 5% | S | Super-reduced rate (housing under certain conditions) |
| 0% | Z | Zero rate (exports, intra-community supplies) |
| Exempt | E | VAT exempt (financial, insurance, education, medical) |
Set the vatRate on each invoice line to the numeric value (e.g., 19, 9, 5, or 0).
UBL CIUS-RO format
Section titled “UBL CIUS-RO format”Mandato converts your JSON invoice to UBL 2.1 XML conforming to the CIUS-RO (Core Invoice Usage Specification for Romania) profile. The conversion handles:
- EN 16931 base structure and rules
- Romanian-specific business rules (BR-RO-xxx)
- Correct XML namespaces and schema references
- Romanian diacritics in party names and addresses
- Proper decimal formatting for amounts
You do not need to understand or write XML. Mandato generates valid UBL from your JSON input.
Required fields for Romanian invoices
Section titled “Required fields for Romanian invoices”Beyond the standard required fields, Romanian invoices need:
| Field | Requirement | Notes |
|---|---|---|
| Supplier VAT | Required | Must be a valid Romanian CUI |
| Supplier address | Required | Street, city, postal code, and country |
| Customer VAT | Required | Romanian CUI for B2B, or foreign VAT for exports |
| Customer address | Required | Street, city, postal code, and country |
| Currency | Required | RON for domestic, EUR/USD for international |
| Issue date | Required | Cannot be more than 5 days in the past |
Validation rules
Section titled “Validation rules”Mandato validates Romanian invoices against three rule sets:
- EN 16931 — EU base semantic rules (BR-01 through BR-65)
- CIUS-RO — Romanian-specific rules (BR-RO-001 through BR-RO-080)
- ANAF business rules — Additional rules enforced by the ANAF system
The POST /v1/invoices/validate endpoint runs all three rule sets without submitting.
ANAF error codes
Section titled “ANAF error codes”When ANAF rejects an invoice, the raw error is in Romanian. Mandato translates these to English and suggests fixes.
Common rejection reasons
Section titled “Common rejection reasons”| ANAF error | Meaning | Fix |
|---|---|---|
CUI invalid | The supplier or customer CUI is not registered in ANAF | Verify the VAT number at ANAF online |
XML invalid conform schemei | The XML does not conform to the UBL schema | Usually a Mandato bug — contact support |
Factura duplicata | An invoice with the same number was already submitted | Use a different invoice number or check the externalId |
Certificat digital expirat | The OAuth token or certificate has expired | Re-authorize the connection |
Cod CAEN invalid | Invalid activity code reference | Check the item classification codes |
Adresa fiscala incompleta | Incomplete fiscal address | Ensure all address fields are provided (street, city, postal code) |
Cota TVA invalida | Invalid VAT rate | Use one of the valid Romanian rates: 19%, 9%, 5%, 0% |
Error translation
Section titled “Error translation”Every rejected invoice has three error-related fields:
{ "errorMessage": "CUI cumparator invalid - nu este inregistrat in scopuri de TVA", "errorTranslated": "Customer VAT number is not registered for VAT purposes in Romania", "errorFix": "Verify the customer's CUI at anaf.ro. If the customer is not VAT-registered, use a B2C invoice format instead."}errorMessage— Raw error from ANAF (in Romanian)errorTranslated— AI-translated error in EnglisherrorFix— AI-suggested action to resolve the issue
Token management
Section titled “Token management”Mandato manages ANAF OAuth tokens automatically:
| Event | Behavior |
|---|---|
| Token nearing expiry | Auto-refreshed using the refresh token |
| Refresh token expired | Connection status changes to expired, connection.expired webhook fires |
| Refresh fails | Retried 3 times with backoff, then marked error |
| Manual re-auth needed | Create a new connection to get a fresh authorization URL |
Monitoring token health
Section titled “Monitoring token health”Check the tokenExpiresAt field on your connection:
const { data: connection } = await mandato.connections.get("conn_id");
const expiresAt = new Date(connection.tokenExpiresAt!);const daysUntilExpiry = Math.floor( (expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
console.log(`Token expires in ${daysUntilExpiry} days`);Subscribe to the connection.expired webhook event to be notified when re-authorization is needed.
Testing with the sandbox
Section titled “Testing with the sandbox”In the test environment (sk_test_ keys):
- No real ANAF OAuth is needed — connections are auto-activated
- Invoices are validated with the same CIUS-RO rules
- XML is generated but not submitted to ANAF
- The simulated acceptance takes ~10 seconds
- Use
RO12345678andRO87654321as test VAT numbers
// Sandbox testing -- no OAuth neededconst mandato = new MandatoClient({ apiKey: "sk_test_your_test_key",});
const { data: company } = await mandato.companies.create({ name: "Test SRL", vatNumber: "RO12345678", country: "RO",});
// Connection is auto-activated in sandboxconst { data: connection } = await mandato.connections.create({ companyId: company.id, countryCode: "RO",});console.log(connection.status); // "active" immediatelyANAF environments
Section titled “ANAF environments”| Environment | Base URL | Usage |
|---|---|---|
| ANAF Test | https://api.anaf.ro/test/FCTEL/rest | ANAF’s own sandbox (used by Mandato internally) |
| ANAF Production | https://api.anaf.ro/prod/FCTEL/rest | Live production system |
You do not need to configure these URLs. Mandato routes to the correct ANAF environment based on your API key prefix (sk_test_ vs sk_live_).
Frequently asked questions
Section titled “Frequently asked questions”Do I need a digital certificate for every invoice?
Section titled “Do I need a digital certificate for every invoice?”No. The digital certificate is only needed once, during the initial OAuth2 authorization flow. After that, Mandato uses OAuth tokens that are automatically refreshed.
Can I submit invoices for multiple CUIs?
Section titled “Can I submit invoices for multiple CUIs?”Yes. Create a separate company and connection for each CUI. Each company has its own OAuth tokens.
What happens if ANAF is down?
Section titled “What happens if ANAF is down?”Mandato queues the invoice and retries submission automatically. The invoice stays in submitting status until ANAF responds. You are notified via webhook when the status changes.
Is there a delay between submission and acceptance?
Section titled “Is there a delay between submission and acceptance?”Typically ANAF processes invoices within 1-15 minutes. During peak periods (end of month), processing can take up to several hours.
Can I submit credit notes?
Section titled “Can I submit credit notes?”Yes. Set invoiceType: "credit_note" in the request body. Credit notes follow the same CIUS-RO rules with additional requirements for referencing the original invoice.