> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dualentry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to File and Track E-invoices with the API

> How to read e-invoice filings of invoices and customer credits through the V2 API: setup, customer identifiers, routes, webhook, and error codes.

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](/developers/api/resources-v2/e-invoicing/get-the-e-invoicing-status-of-the-organization).

<Info>
  The e-invoicing routes are in V2 and V3-alpha. They are not in V1. All examples in this guide use V2.
</Info>

## Prerequisites

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

* V2 API (`/public/v2/...`)
* `X-API-KEY` header with your organization's API key ([authenticating your requests](/developers/guides/api/api-keys/how-to-authenticate-your-requests))
* An API key with the permissions of the routes that you call (each route in the [V2 API reference](/developers/api/resources-v2/e-invoicing/get-the-e-invoicing-status-of-the-organization) names its permission; see also [giving an integration access](/developers/guides/api/api-keys/how-to-give-an-integration-access))
* The e-invoicing subscription on the organization (see [The subscription rule](#the-subscription-rule))
* A company that files e-invoices in a [supported country](#supported-countries), turned on by a user in the DualEntry app

## 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.

| Country | Code | Agency | Standard | Send held until clearance |
| - | - | - | - | - |
| Argentina | `AR` | ARCA | Factura electrónica | Yes |
| Belgium | `BE` | Peppol | Peppol BIS Billing 3.0 | No |
| Germany | `DE` | Peppol | Peppol BIS Billing 3.0 or XRechnung | No |
| Italy | `IT` | SDI | FatturaPA | Yes |
| Mexico | `MX` | SAT | CFDI 4.0 | Yes |
| Spain | `ES` | AEAT | VERI\*FACTU | Yes |
| Sweden | `SE` | Peppol | Peppol BIS Billing 3.0 | No |

`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`:

```json theme={null}
{
  "success": false,
  "errors": {
    "__all__": ["Subscription required for this feature"]
  }
}
```

## 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:

```bash theme={null}
curl https://api.dualentry.com/public/v2/e-invoicing/ \
  -H "X-API-KEY: YOUR_API_KEY"
```

The response is `{"subscription_enabled": true}` or `{"subscription_enabled": false}`. Stop here when the value is `false`.

Then read the setup:

| Need | Route |
| - | - |
| Countries and their identity fields | `GET /public/v2/e-invoicing/countries/` |
| Decision and readiness checks of each company country | `GET /public/v2/e-invoicing/companies/?limit=&offset=` |
| One company | `GET /public/v2/e-invoicing/companies/{company_id}/` |
| Registration of a company with the agency of one country | `GET /public/v2/e-invoicing/companies/{company_id}/countries/{country}/registration/` |

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:

```bash theme={null}
curl -X PUT https://api.dualentry.com/public/v2/e-invoicing/customers/2/identifiers/ \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "scope": "network:peppol",
        "country": null,
        "identifiers": {"peppol_id": "0208:0123456789"}
      }
    ]
  }'
```

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:

```bash theme={null}
curl "https://api.dualentry.com/public/v2/e-invoicing/preflight/?company_id=1&customer_id=2" \
  -H "X-API-KEY: YOUR_API_KEY"
```

The check can take up to 10 seconds. The response has `country`, `network`, `participant_id`, and `status`:

| `status` | Meaning |
| - | - |
| `on_network` | DualEntry delivers the e-invoice on the network |
| `not_on_network` | DualEntry does not file the invoice, and the customer gets the DualEntry PDF |
| `unknown` | The network did not answer in time |
| `null` | No network applies to this company |

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.

| `status` | Meaning |
| - | - |
| `queued` | DualEntry queued the filing |
| `processing` | DualEntry processes the filing |
| `pending_clearance` | The authority must clear the filing |
| `cleared` | The authority cleared the filing, or the network delivered it. `lifecycle` tells which |
| `rejected` | The authority rejected the filing. Read `faults`, fix the cause, and retry |
| `failed` | The filing failed. Read `faults`, fix the cause, and retry |
| `not_filed` | DualEntry did not file the record. The customer gets the DualEntry PDF. `not_filed_reason` tells why |

`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](/developers/api/resources-v2/e-invoicing/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](#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:

```bash theme={null}
curl -X POST https://api.dualentry.com/public/v2/e-invoicing/invoices/1043/retry/ \
  -H "X-API-KEY: YOUR_API_KEY"
```

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`:

| `error_code` | Cause | What to do |
| - | - | - |
| `e_invoicing_send_held` | The authority has not cleared the e-invoice | Send again after the filing status is `cleared` |
| `e_invoicing_legal_pdf_missing` | The country sends the authority PDF, and DualEntry does not hold it yet | Send again after `legal_pdf` is set on the filing |

`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:

```json theme={null}
{
  "event_id": "8d1f5c2e-3b7a-5e9c-9a41-0f6b2d7c4e10",
  "topic": "core.e_invoice_filing/updated",
  "object_type": "core.EInvoiceSubmission",
  "object": {
    "id": 812,
    "event_id": 4410,
    "kind": "invoice",
    "record_number": 1043,
    "country": "IT",
    "from_status": "processing",
    "to_status": "pending_clearance",
    "blocked_reason": null,
    "not_filed_reason": null,
    "occurred_at": "2026-10-12T09:15:00+00:00"
  },
  "webhook_uuid": "3a7e9b10-2c4d-4f6a-8b1e-5d9c0a7f6e21",
  "created_at": "2026-10-12T09:15:01+00:00",
  "api_version": "2026-06-27"
}
```

The `object` fields:

| `object` field | Meaning |
| - | - |
| `id` | The filing ID. It equals `filing_id` of the `e_invoice` summary |
| `event_id` | The filing event ID. It equals `id` of an item of `GET /public/v2/e-invoicing/events/` |
| `kind` | `invoice` or `credit_note` |
| `record_number` | The `number` of the invoice or customer credit |
| `from_status` | The status before the event. `null` on the first event |
| `to_status` | The status after the event |
| `blocked_reason` | Why a broken setup holds the filing. Set only on a hold event |
| `not_filed_reason` | Why DualEntry did not file the record |

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](/developers/guides/api/core-concepts/errors).

| `error_code` | HTTP status | Cause | What to do |
| - | - | - | - |
| `e_invoicing_validation` | `422` | The authority refuses the document data. The reasons are in `errors.e_invoicing` | Fix the record or the customer identifiers, then post again |
| `e_invoicing_send_held` | `422` | The authority has not cleared the e-invoice | Send again after the filing status is `cleared` |
| `e_invoicing_legal_pdf_missing` | `422` | DualEntry does not hold the authority PDF yet | Send again after `legal_pdf` is set on the filing |
| `e_invoicing_not_retryable` | `422` | The filing is not `failed` or `rejected`, or the record has no filing | Check `actions` on the filing before you retry |
| `e_invoicing_error` | `422` | Any other refusal, such as an unknown identifier field, a value that does not match its pattern, or a country that DualEntry does not file in | Fix the cause in `errors`, then send the request again |
| `e_invoicing_setup` | `503` | The setup of the country is broken | Try again later |
| `e_invoicing_engine` | `503` or `502` | The e-invoicing engine did not answer (`503`) or refused the request (`502`) | Try again later |

These answers have no `error_code`:

* **`402`**: no e-invoicing subscription on the organization (see [The subscription rule](#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.

## Related resources

For the request and response shapes and the permission of each route, see the [E-invoicing section of the V2 API reference](/developers/api/resources-v2/e-invoicing/get-the-e-invoicing-status-of-the-organization). For the webhook envelope and its signature, see the [webhooks reference](/developers/api/resources-v2/webhooks/create-webhook). For tax on the invoice lines before you post, see [How to Calculate Tax Before Creating an Invoice or Bill](./calculate-tax-before-invoicing).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.