Since January 2023, e-invoicing through the Serbian Ministry of Finance's SEF (Sistem e-Faktura) has been mandatory for B2B transactions in Serbia. We've now integrated the SEF API for three clients with very different existing systems, and the experience has taught us a few things the official documentation doesn't make obvious.

What the SEF API actually covers

The SEF system handles two document types: e-fakture (electronic invoices) and e-otpremnice (electronic delivery notes). The API surface for each is similar but not identical, and the business rules differ in ways that matter.

For e-fakture, the system handles creation, sending, status tracking, and cancellation. The status lifecycle is particularly important: an invoice moves through states (draft → sent → accepted/rejected/cancelled), and your integration needs to handle all of them, including the edge cases where an invoice is accepted and then a credit note is required.

Authentication and the sandbox environment

The API uses API key authentication. Each client has their own key tied to their PIB (tax identification number). The sandbox environment at https://efaktura.mfin.gov.rs/api/publicApi is where you test, but be aware: sandbox data doesn't always behave identically to production. We've encountered at least two cases where something worked in sandbox and failed in production due to stricter validation rules on the production side.

Test in sandbox, but don't trust sandbox to catch everything. Build your own test suite that covers all the edge cases you care about.

The XML schema is where people struggle

SEF uses UBL 2.1 XML for document structure. The schema is well-defined, but the local extensions Serbia has added are documented inconsistently. The things that trip people up most:

Practical advice from our integrations

Build a dedicated SEF service class in your application - don't scatter API calls across your codebase. This service should handle authentication, request formatting, response parsing, and error mapping to your domain's exception types.

Log everything, at least initially. The SEF API's error messages can be cryptic, and having the full request/response cycle in your logs is invaluable when debugging.

Build idempotency in from the start. Network failures happen, and you need to be able to retry failed submissions without creating duplicate invoices. The SEF API has its own deduplication, but your application layer should not depend on it.

If you're building this into an existing ERP or accounting system, plan for the status sync carefully. The typical pattern is a webhook for real-time status changes plus a periodic polling job as a fallback - the SEF webhook delivery is reliable but not guaranteed.