AnafIntegration 1.0.5
dotnet add package AnafIntegration --version 1.0.5
NuGet\Install-Package AnafIntegration -Version 1.0.5
<PackageReference Include="AnafIntegration" Version="1.0.5" />
<PackageVersion Include="AnafIntegration" Version="1.0.5" />
<PackageReference Include="AnafIntegration" />
paket add AnafIntegration --version 1.0.5
#r "nuget: AnafIntegration, 1.0.5"
#:package AnafIntegration@1.0.5
#addin nuget:?package=AnafIntegration&version=1.0.5
#tool nuget:?package=AnafIntegration&version=1.0.5
AnafIntegration
.NET 7 class library for integrating with the ANAF e-Factura API (RO e-Factura). Handles OAuth 2.0 authentication, invoice conversion from C# objects to UBL 2.1 XML (CIUS-RO), and all e-Factura API operations.
Designed to be referenced from a Blazor WASM Server application.
Installation
Add a project reference from your Blazor Server project:
<ProjectReference Include="..\AnafIntegration\AnafIntegration.csproj" />
NuGet dependencies (restored automatically):
UblSharp1.1.1Microsoft.Extensions.Http7.0.0Microsoft.Extensions.DependencyInjection.Abstractions7.0.0Microsoft.Extensions.Options7.0.0
Setup
1. Register services in Program.cs
using AnafIntegration;
using AnafIntegration.Extensions;
builder.Services.AddAnafIntegration(options =>
{
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.RedirectUri = "https://yourapp.com/anaf-callback";
options.Environment = AnafEnvironment.Test; // or AnafEnvironment.Production
});
This registers all services: AnafOAuthService, AnafApiClient, InMemoryTokenStore, and InvoiceToUblMapper.
2. ANAF prerequisites
Before using the API you must:
- Register as a developer at anaf.ro > Servicii Online > Inregistrare utilizatori > Dezvoltatori Aplicatii.
- Authenticate and go to "Editare profil Oauth".
- Register your application (name + e-Factura service) to receive your Client ID and Client Secret.
- Users of your app must be registered in SPV with a qualified digital certificate and SPV PJ rights.
OAuth 2.0 Authentication
The library implements the full OAuth 2.0 Authorization Code flow with JWT tokens.
Step 1 -- Redirect the user to ANAF login
@inject AnafOAuthService OAuthService
var authUrl = OAuthService.GetAuthorizationUrl(state: "optional-csrf-token");
NavigationManager.NavigateTo(authUrl, forceLoad: true);
The user authenticates with their qualified digital certificate on logincert.anaf.ro, then ANAF redirects back to your RedirectUri with a ?code= query parameter.
Step 2 -- Exchange the code for tokens
In your callback page/endpoint:
@inject AnafOAuthService OAuthService
@inject InMemoryTokenStore TokenStore
// Extract the authorization code from the callback URL
var code = NavigationManager.Uri.Split("code=")[1].Split("&")[0];
// Exchange for tokens
var tokenInfo = await OAuthService.ExchangeCodeForTokenAsync(code);
// Store the token so AnafApiClient can use it
await TokenStore.SetTokenAsync(tokenInfo);
Step 3 -- Refresh tokens when needed
Access tokens are valid for 90 days, refresh tokens for 365 days.
var currentToken = await TokenStore.GetTokenAsync();
if (currentToken != null && currentToken.IsExpired)
{
var newToken = await OAuthService.RefreshTokenAsync(currentToken.RefreshToken);
await TokenStore.SetTokenAsync(newToken);
}
Persisting tokens across restarts
InMemoryTokenStore holds tokens in memory. Subscribe to OnTokenChanged to persist them to your own storage:
// In Program.cs or a startup service
var tokenStore = app.Services.GetRequiredService<InMemoryTokenStore>();
tokenStore.OnTokenChanged += token =>
{
if (token != null)
{
// Save to database, file, etc.
MyDatabase.SaveToken(token.AccessToken, token.RefreshToken, token.ExpiresAt);
}
};
// On app startup, restore previously saved tokens
var saved = MyDatabase.LoadToken();
if (saved != null)
{
await tokenStore.SetTokenAsync(new TokenInfo
{
AccessToken = saved.AccessToken,
RefreshToken = saved.RefreshToken,
ExpiresAt = saved.ExpiresAt
});
}
Sending an Invoice
Full workflow example
@inject InvoiceToUblMapper Mapper
@inject AnafApiClient ApiClient
// 1. Build the invoice
var invoice = new InvoiceData
{
InvoiceNumber = "FV-2024-001",
IssueDate = new DateTime(2024, 3, 15),
DueDate = new DateTime(2024, 4, 15),
CurrencyCode = "RON",
Supplier = new PartyInfo
{
Name = "Firma Mea SRL",
VatNumber = "RO12345678",
Cif = "12345678",
RegistrationNumber = "J40/1234/2020",
StreetAddress = "Str. Exemplu nr. 1",
City = "Bucuresti",
County = "RO-B",
PostalCode = "010101",
CountryCode = "RO",
Email = "contact@firmamea.ro",
Phone = "+40212345678"
},
Customer = new PartyInfo
{
Name = "Client SRL",
VatNumber = "RO87654321",
Cif = "87654321",
RegistrationNumber = "J40/5678/2019",
StreetAddress = "Bd. Clientului nr. 10",
City = "Cluj-Napoca",
County = "RO-CJ",
PostalCode = "400001",
CountryCode = "RO"
},
Lines = new List<InvoiceLine>
{
new InvoiceLine
{
Description = "Servicii consultanta IT",
Quantity = 10,
UnitCode = "HUR", // hours
UnitPrice = 150.00m,
VatPercent = 19,
VatCategoryCode = "S"
},
new InvoiceLine
{
Description = "Licenta software",
Quantity = 1,
UnitCode = "C62", // piece
UnitPrice = 500.00m,
VatPercent = 19,
VatCategoryCode = "S",
ItemId = "SW-LIC-001"
}
},
Payment = new PaymentInfo
{
Iban = "RO49AAAA1B31007593840000",
BankName = "Banca Exemplu",
PaymentMeansCode = "30", // bank transfer
PaymentTerms = "30 zile de la data facturii"
}
};
// 2. Convert to UBL 2.1 XML (CIUS-RO)
var xml = Mapper.MapToXml(invoice);
// 3. Validate (free, no authentication required)
var validationResult = await ApiClient.ValidateInvoiceAsync(xml, "FACT1");
// 4. Upload to ANAF
var uploadResult = await ApiClient.UploadInvoiceAsync(xml, "UBL", invoice.Supplier.Cif);
if (uploadResult.IsSuccess)
{
Console.WriteLine($"Uploaded successfully. Index: {uploadResult.UploadIndex}");
}
// 5. Check processing status
var status = await ApiClient.GetMessageStatusAsync(uploadResult.UploadIndex!.Value);
// status.Stare: "ok", "nok", "in prelucrare"
// 6. Download the signed response when ready
if (status.IsOk && status.IdDescarcare.HasValue)
{
byte[] zip = await ApiClient.DownloadResponseAsync(status.IdDescarcare.Value);
File.WriteAllBytes("response.zip", zip);
// ZIP contains: original invoice XML + Ministry of Finance digital signature XML
}
API Reference
All authenticated methods require a valid token stored via InMemoryTokenStore.SetTokenAsync().
AnafApiClient
| Method | Description | Auth |
|---|---|---|
UploadInvoiceAsync(xml, standard, cif, flags?) |
Upload a B2B invoice | Yes |
UploadB2CInvoiceAsync(xml, standard, cif, flags?) |
Upload a B2C invoice | Yes |
GetMessageStatusAsync(idIncarcare) |
Check invoice processing status | Yes |
ListMessagesAsync(days, cif, filter?) |
List messages for the last N days (1-60) | Yes |
ListMessagesPaginatedAsync(startMs, endMs, cif, page, filter?) |
List messages by date range | Yes |
DownloadResponseAsync(id) |
Download response ZIP (invoice + signature) | Yes |
ValidateInvoiceAsync(xml, standard?) |
Validate XML without uploading | No |
ConvertToPdfAsync(xml, standard?, skipValidation?) |
Convert invoice XML to PDF | No |
TestOauthAsync(name?) |
Test that your OAuth token works | Yes |
Upload standards
| Value | Usage |
|---|---|
UBL |
Standard invoice |
CN |
Credit note |
CII |
CII format |
RASP |
Buyer response message |
Upload flags
Use UploadFlags for special cases:
// Foreign buyer (no Romanian CUI/NIF)
await ApiClient.UploadInvoiceAsync(xml, "UBL", cif, new UploadFlags { Extern = true });
// Self-billing (buyer issues invoice on behalf of supplier)
await ApiClient.UploadInvoiceAsync(xml, "UBL", cif, new UploadFlags { AutoFactura = true });
Validation standards
| Value | Usage |
|---|---|
FACT1 |
Invoice validation |
FCN |
Credit note validation |
Message list filters
| Value | Meaning |
|---|---|
E |
Errors (rejected invoices) |
T |
Sent invoices |
P |
Received invoices |
R |
Buyer messages |
Invoice Model Reference
InvoiceData
| Property | Type | Default | Description |
|---|---|---|---|
InvoiceNumber |
string | "" |
Invoice number (e.g., "FV-2024-001") |
IssueDate |
DateTime | Today | Invoice issue date |
DueDate |
DateTime? | null | Payment due date |
CurrencyCode |
string | "RON" |
ISO 4217 currency code |
InvoiceTypeCode |
string | "380" |
380=Invoice, 381=Credit Note, 389=Self-billing |
Supplier |
PartyInfo | Seller/supplier details | |
Customer |
PartyInfo | Buyer/customer details | |
Lines |
List<InvoiceLine> | Invoice line items | |
Note |
string? | null | Free-text note |
Payment |
PaymentInfo? | null | Payment details (IBAN, bank, terms) |
DeliveryDate |
DateTime? | null | Actual delivery date |
OrderReference |
string? | null | Purchase order reference |
PartyInfo
| Property | Type | Default | Description |
|---|---|---|---|
Name |
string | "" |
Company name |
VatNumber |
string | "" |
VAT number with country prefix (e.g., "RO12345678") |
Cif |
string | "" |
Numeric CIF only (e.g., "12345678") |
RegistrationNumber |
string? | null | Trade register number (e.g., "J40/1234/2020") |
StreetAddress |
string | "" |
Street address |
City |
string | "" |
City name |
County |
string? | null | County/region (e.g., "RO-B") |
PostalCode |
string? | null | Postal code |
CountryCode |
string | "RO" |
ISO 3166-1 alpha-2 country code |
Email |
string? | null | Contact email |
Phone |
string? | null | Contact phone |
InvoiceLine
| Property | Type | Default | Description |
|---|---|---|---|
Description |
string | "" |
Item/service description |
Quantity |
decimal | 1 | Quantity |
UnitCode |
string | "C62" |
UN/ECE unit code (C62=piece, HUR=hour, KGM=kg, MON=month) |
UnitPrice |
decimal | 0 | Unit price excluding VAT |
VatPercent |
decimal | 19 | VAT rate percentage |
VatCategoryCode |
string | "S" |
S=Standard, Z=Zero, E=Exempt, AE=Reverse charge |
ItemId |
string? | null | Optional seller item identifier |
LineTotal |
decimal | computed | Quantity * UnitPrice (read-only) |
VatAmount |
decimal | computed | LineTotal * VatPercent / 100 (read-only) |
PaymentInfo
| Property | Type | Default | Description |
|---|---|---|---|
Iban |
string? | null | IBAN account number |
BankName |
string? | null | Bank name |
Bic |
string? | null | Bank BIC/SWIFT code |
PaymentMeansCode |
string | "30" |
30=Bank transfer, 10=Cash, 48=Card |
PaymentTerms |
string? | null | Payment terms text |
Project Structure
AnafIntegration/
AnafEnvironment.cs URL constants (Test / Production)
Auth/
AnafOAuthOptions.cs ClientId, ClientSecret, RedirectUri, Environment
AnafOAuthService.cs OAuth flow: authorization URL, code exchange, token refresh
TokenInfo.cs AccessToken, RefreshToken, ExpiresAt, IsExpired
Storage/
InMemoryTokenStore.cs Thread-safe token store with OnTokenChanged event
Api/
AnafApiClient.cs All e-Factura API endpoints
Models/
UploadResponse.cs Upload result (upload_index, errors)
MessageStatus.cs Processing status (ok, nok, in prelucrare)
MessageListResponse.cs Message list with pagination
UploadFlags.cs Extern, AutoFactura, Executare flags
Invoice/
InvoiceModel.cs POCO models: InvoiceData, PartyInfo, InvoiceLine, PaymentInfo
InvoiceToUblMapper.cs Converts InvoiceData to UBL 2.1 XML (CIUS-RO)
Extensions/
ServiceCollectionExtensions.cs AddAnafIntegration() DI registration
ANAF API Limits
| Parameter | Value |
|---|---|
| Rate limit | 1000 requests / minute |
| Access token lifetime | 90 days |
| Refresh token lifetime | 365 days |
| Token acquisition window | 60 seconds |
| Message list lookback | 1-60 days |
HTTP Status Codes
| Code | Meaning |
|---|---|
200 OK |
Success |
403 Forbidden |
Unauthorized (invalid/expired token or insufficient permissions) |
429 Too Many Requests |
Rate limit exceeded (>1000 requests/minute) |
Environment URLs
| Environment | API Base | Auth |
|---|---|---|
| Test | https://api.anaf.ro/test/FCTEL/rest |
https://logincert.anaf.ro/anaf-oauth2/v1/authorize |
| Production | https://api.anaf.ro/prod/FCTEL/rest |
https://logincert.anaf.ro/anaf-oauth2/v1/authorize |
| Validation (no auth) | https://api.anaf.ro/prod/FCTEL/rest/validare |
N/A |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net7.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
- Microsoft.Extensions.Http (>= 7.0.0)
- Microsoft.Extensions.Options (>= 7.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 7.0.0)
- UblSharp (>= 1.1.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.