Skip to main content
DualEntry files your invoices and customer credits with the tax authority or the Peppol network of the company country. An e-invoice filing is the record of that submission: its status, its faults, and the legal files that the authority returns. Use the /public/v2/e-invoicing/ routes to read the setup, set customer identifiers, and follow each filing. This guide shows the full flow: check the subscription, read the setup, set the customer identifiers, post the invoice, follow the filing, act on a stopped filing, and send the invoice. For the request and response shapes and the permission of each route, see the E-invoicing section of the V2 API reference.
The e-invoicing routes are in V2 and V3-alpha. They are not in V1. All examples in this guide use V2.

Prerequisites

Before you start, make sure that you have these items:

Supported countries

DualEntry files e-invoices in seven countries. In four of them, the authority must clear the e-invoice before the customer gets it. In the Peppol countries, DualEntry delivers the e-invoice to the inbox of the customer on the Peppol network. GET /public/v2/e-invoicing/countries/ returns the same list, with the identity fields that each country asks for. enabled_for_organization is true when a company of your organization files in that country. Read the countries from this route, not from the table above, because DualEntry can add a country.

The subscription rule

The e-invoicing subscription controls the e-invoicing routes. It does not control invoicing: without it, invoices and customer credits post, send, and archive as before, and DualEntry files nothing. Every e-invoicing route except GET /public/v2/e-invoicing/ then answers 402, before it checks the permission. A record that DualEntry filed before the subscription ended keeps its filing. For that record, the filing route (GET /public/v2/e-invoicing/invoices/{number}/ or GET /public/v2/e-invoicing/customer-credits/{number}/) and its files/{attachment_id}/ route still answer. The send rules of that filing also stay: an invoice that waits for clearance still answers 422 e_invoicing_send_held on send, and the e_invoice field stays on the invoice or customer credit. The 402 body has no error_code:

Step 1: Check the subscription and the setup

Read the subscription first, then the setup of each company. The setup is read-only on the API. A user changes it in the DualEntry app. Check the subscription:
The response is {"subscription_enabled": true} or {"subscription_enabled": false}. Stop here when the value is false. Then read the setup: Each company country has a decision (enabled, disabled, or null until a user decides) and a list of checks. Each check has a code, passed, a message, and params. The registration shows status, the company identifiers, the missing required fields, the agency faults, and waiting_for (decision or fields). blocked_reason is set while a broken setup of the country holds the registration. DualEntry sends the held registration after the setup is fixed.

Step 2: Set the customer identifiers

Each country asks for identity fields of the customer, such as a tax ID. Read the field keys, patterns, and allowed values from identifier_fields.customer of GET /public/v2/e-invoicing/countries/. Then write them with one call:
Each item is one row:
  • Country row: scope is regime and country is the country code. identifiers holds the fields of that country.
  • Network row: scope is network:peppol and country is null. The Peppol participant ID (peppol_id, in the form <scheme>:<code>) applies in every Peppol country.
The route writes all rows in one transaction and returns every row of the customer. The values of a row replace the stored row, and a blank value clears its field. A row that you leave out keeps its values. A peppol_id in a country row goes to the Peppol network row. An unknown field or a value that does not match its pattern answers 422 with error_code e_invoicing_error. DualEntry then changes nothing. Give each country and each network one time only. Read the identifiers with GET /public/v2/e-invoicing/customers/{customer_id}/identifiers/. A network row also shows the participant that DualEntry last found on the network. Vendor identifiers are read-only on the API: GET /public/v2/e-invoicing/vendors/{vendor_id}/identifiers/.

Step 3: Check a Peppol customer before you post

In a Peppol country, DualEntry delivers the e-invoice only to a customer that is on the Peppol network. Check the customer before you post the invoice:
The check can take up to 10 seconds. The response has country, network, participant_id, and status: The route needs the invoice:create permission. A company or a customer that your key does not reach answers 404.

Step 4: Post the invoice

Post the invoice with POST /public/v2/invoices/, as you do without e-invoicing. When the company files e-invoices in its country, DualEntry checks the e-invoice with the authority and queues the filing. The check can take up to 10 seconds. A post that e-invoicing refuses answers 422. The reasons are in the e_invoicing key of errors, for example a missing required identifier of the customer. When the body has error_code, the value is e_invoicing_validation. Fix the record or the customer identifiers, then post again. A customer credit for an invoice that DualEntry filed becomes a credit note filing. A credit to an invoice that the authority has not cleared answers 422 with the reason in the e_invoicing key. Post the credit after the authority clears the invoice. A filed record has locks. A change or an archive of an invoice whose filing runs, or that the authority cleared, answers 422 with the reason in the e_invoicing key. These record save refusals have no error_code.

Step 5: Follow the filing

Each invoice and customer credit in V2 has an e_invoice field. It is null when DualEntry did not file the record. Otherwise it holds a summary of the filing, including its status and needs_attention. The invoice and customer credit list routes also take an e_invoice_status filter: one or more filing statuses, needs_attention, or none (records without a filing). The status of a filing is one of these values. The same values appear in from_status and to_status of events and webhooks. lifecycle tells what a cleared filing means: authority_clearance and ordered_records are confirmed by the authority, and network_delivery reached the inbox of the receiver. blocked_reason is one of workspace_missing, workspace_rejected, workflow_missing, or configuration. For the full filing, read GET /public/v2/e-invoicing/invoices/{number}/ or GET /public/v2/e-invoicing/customer-credits/{number}/. {number} is the record number. These are the fields that you act on:
  • faults: why the filing stopped, each with code, message, and paths
  • actions: what your key can do now (retry or refresh)
  • files and legal_pdf: the legal files, such as the authority XML and PDF
For every field of the filing, see Get the e-invoice filing of an invoice in the API reference. To learn about changes, use one of these methods:
  • Subscribe to the core.e_invoice_filing/updated webhook (see The filing webhook).
  • List the events of the organization with GET /public/v2/e-invoicing/events/?needs_attention=true, newest first.
  • List the events of one record with GET /public/v2/e-invoicing/invoices/{number}/events/, oldest first.
  • Filter the invoice or customer credit list with ?e_invoice_status=needs_attention.
A filing needs attention when it failed, when the authority rejected it, when DualEntry did not file it, or when a broken setup holds it.

Step 6: Act on a stopped filing

A filing in failed or rejected status stops. Read faults to find the cause, fix the record, the customer identifiers, or the setup, then retry:
The route returns the filing with its new status. It needs the record edit permission (invoice:edit or customer_credit:edit). A filing in any other status, or a record without a filing, answers 422 with error_code e_invoicing_not_retryable. Check actions before you call: it lists retry only when the retry route accepts it for your key. DualEntry reads the result of a started filing on its own. When you cannot wait, call POST .../refresh/ to read the newest result from the authority now. A filing that DualEntry has not yet started to send stays as it is. A filing with blocked_reason set waits for a fix of the country setup. You do not have to retry it. DualEntry sends it after the setup is fixed.

Step 7: Send the invoice

Send the invoice by email with POST /public/v2/invoices/{record_number}/send/. In Argentina, Italy, Mexico, and Spain, the authority must clear the e-invoice before the customer gets it. Until then, the send answers 422: send_held and send_held_reason on the filing tell you before you call. send_held_reason is one of awaiting_clearance, waiting_for_setup, filing_failed, or filing_rejected. To download a legal file, read its attachment_id from files, then call GET /public/v2/e-invoicing/invoices/{number}/files/{attachment_id}/. The response holds a short-lived url.

Step 8: Correct a cleared invoice

The authority does not accept a change to a cleared invoice. When corrections on the invoice filing holds credit_note, post a customer credit for that invoice with POST /public/v2/customer-credits/. DualEntry files the credit as a credit note. Follow the credit note filing with GET /public/v2/e-invoicing/customer-credits/{number}/. After these steps, each invoice that you post shows its filing in e_invoice, and you send it once the filing allows.

The filing webhook

DualEntry sends the core.e_invoice_filing/updated webhook for each filing event: the first queued state, each status change, and each new setup hold. A hold keeps the status, so its from_status equals its to_status. Subscribe with POST /public/v2/webhooks/ and put core.e_invoice_filing/updated in topics. GET /public/v2/webhooks/topics/ lists every topic. The delivery body is the standard webhook envelope. object_type is core.EInvoiceSubmission, and object holds the filing event:
The object fields: The payload is small on purpose. Read the full filing with GET /public/v2/e-invoicing/invoices/{record_number}/ or GET /public/v2/e-invoicing/customer-credits/{record_number}/. A redelivery of the same filing event keeps its envelope event_id, so remove duplicates by that value.

Troubleshooting

A refused e-invoicing request has a stable error_code next to errors. Branch on error_code, not on the message text. For the error body format, see the error handling guide. These answers have no error_code:
  • 402: no e-invoicing subscription on the organization (see The subscription rule)
  • 404: a record, company, or customer that your key does not reach, for example because a key that the CLI sign-in made reaches only the companies of its user
  • 422: a record save that e-invoicing refuses, with the reason under the e_invoicing key, such as a change of a cleared invoice or a credit to an invoice that the authority has not cleared
Treat an unknown error_code, fault code, or enum value as a general value. Stored filings keep old fault codes, and DualEntry can add values. For the request and response shapes and the permission of each route, see the E-invoicing section of the V2 API reference. For the webhook envelope and its signature, see the webhooks reference. For tax on the invoice lines before you post, see How to Calculate Tax Before Creating an Invoice or Bill.
Last modified on October 14, 2026