# Flowie Exchange — full documentation # Generated by scripts/build_llm_docs.py # Each section below is one page from https://docs.get-flowie.com ======================================================================== # Introduction # Source: https://docs.get-flowie.com/index.html ======================================================================== --- title: "Flowie Exchange — One API for every e-invoicing network" description: "The developer platform for electronic invoicing across every network — Peppol, France PPF, Italy SDI, KSA Fatoora, IL ITA, IN GST IRP, MY MyInvois. 47 countries across Europe, MENA, and Asia-Pacific. White-label ready." canonical: "https://docs.get-flowie.com/" source: "https://docs.get-flowie.com/index.html" --- # Flowie Exchange — One API for every e-invoicing network API v3.0 · Released April 2026 # Flowie Exchange API — one API for every e-invoicing network Search the documentation Search **[Developer portal]()** — get an API key with no signup, read the [OpenAPI spec](), browse the [API reference](), or connect over [MCP](<.well-known/mcp/server-card.json>) / [A2A](<.well-known/agent-card.json>). Send and receive e-invoices across [**47 countries**]() — Europe, MENA, and Asia-Pacific. Peppol, France PPF, Italy SDI, KSA Fatoora, India GST IRP, Malaysia MyInvois — one integration, every network handled. ⚡ Get a test API key (free, no signup) [Start in 5 minutes →](<#quickstart>) [Browse the API]() **🤖 Hand this URL to your LLM** — it does the integration. [How it works →]() Copy link `https://back.flowie.ink/exchange/docs-public/agent-onboarding.html` An agent that fetches this URL is auto-authenticated against a fresh sandbox. **Personalize** — bind to your org so the agent operates as your account. Paste any existing API key (`flw_test_…` / `flw_live_…`). We mint a **single-use, 10-min handoff token** scoped to `send`, `receive`, `documents.read`, `companies.read`, `stats`. The key never leaves your browser. Generate `` Copy personalized link **🇫🇷 On another Plateforme Agréée? Port your taxpayers to Flowie.** Your SIREN/SIRET keeps routing, so nothing downstream is re-addressed. Import a taxpayer from its SIRET — or a whole client book in bulk — and we build the normalised inter-PA message: 24 h to acknowledge, 5 _jours ouvrés_ to decide, _silence vaut accord_ after that. **France (PPF) only.** [Portability guide →]() [Elsewhere in Europe →]() ## Platform capabilities ### Universal Peppol access point Coverage across [47 countries]() on four continents — single integration for Europe, MENA, and Asia-Pacific. Auto-SMP registration and directory verification included. ### Compliance on autopilot PPF (FR) and SDI (IT) are reported automatically when you update invoice status. Belgium runs pure Peppol — no separate report needed. Zero extra wiring. ### Platform & white-label Manage thousands of tenant companies under one account. Scoped keys, per-tenant quotas, custom branding. ### Reliable webhooks Signed deliveries, exponential retries, at-least-once guarantees. Event replay through the Events API. ### Structured or raw Send invoices as JSON and we generate valid UBL 2.1. Or send your own UBL/CII — we validate and deliver it. ### AFNOR XP Z12-013 ready French PDP-compliant adapter, cXML PunchOut, SIRET/SIREN directory — all behind the same account. ## Send your first invoice in 5 minutes Sign up, grab a test API key, and fire three requests. No SDK required — it's just JSON over HTTPS. 1. ### Authenticate Every request carries a bearer token — either a Flowie JWT (if you already use the dashboard) or an Exchange API key (`flw_live_…` / `flw_test_…`). [code] export FLOWIE_KEY="flw_test_your_key_here" curl https://back.p2p-flowie.com/exchange/v1/companies \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] 2. ### Register the sending company Pass a VAT number. We enrich the legal name, address, and Peppol identifier for you, then publish the company to the Peppol SMP. [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/companies \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{"vatNumber": "BE0123456789"}' [/code] 3. ### Send an invoice Describe the invoice in JSON, set an `Idempotency-Key`, and we deliver it — UBL-XML-formatted and Peppol-signed — to the recipient's access point. [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: inv-2026-001" \ -d '{ "type": "invoice", "from": "comp_abc123", "to": "0208:9876543210", "document": { "number": "INV-2026-001", "issueDate": "2026-04-25", "dueDate": "2026-05-25", "currency": "EUR", "lines": [{ "description": "Consulting, April 2026", "quantity": 10, "unit": "hours", "unitPrice": 150.00, "vatRate": 21 }] } }' [/code] ✓ That's it — your invoice is live on Peppol. You'll receive a `document.delivered` webhook once the recipient's access point confirms. ## Complete starter programs The same three steps, packaged as a single runnable file in your language. Copy, set `FLOWIE_KEY`, and you have a working integration. [code] # pip install httpx import os, uuid, httpx BASE = "https://back.p2p-flowie.com/exchange/v1" KEY = os.environ["FLOWIE_KEY"] api = httpx.Client(base_url=BASE, headers={"Authorization": f"Bearer {KEY}"}) # 1. Register sender sender = api.post("/companies", json={"vatNumber": "BE0123456789"}).json() print("sender:", sender["id"], sender["peppolId"]) # 2. Verify recipient before sending ver = api.post("/directory/verify", json={ "peppolId": "0208:9876543210", "documentType": "INVOICE", }).json() assert ver["canReceive"], f"recipient unreachable: {ver}" # 3. Send doc = api.post( "/documents/send", headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "type": "invoice", "from": sender["id"], "to": "0208:9876543210", "document": { "number": "INV-2026-0417", "issueDate": "2026-04-25", "dueDate": "2026-05-25", "currency": "EUR", "lines": [{ "description": "Consulting — April 2026", "quantity": 10, "unit": "hours", "unitPrice": 150.00, "vatRate": 21, }], }, }, ).json() print("invoice:", doc["id"], doc["status"], doc["deliveryStatus"]) [/code] Need another language? Every example follows the same pattern: bearer auth, JSON body, `Idempotency-Key`. Open a PR for your language at [github.com/flowie-fr/exchange-api-docs](). ## Where to go next Pick the track that matches what you're building. ### [API Reference → Every endpoint, every field, every error. With runnable examples in four languages. ]() ### [Integration Guides → Playbooks for sending, receiving, going live, and building white-label products. ]() ### [🇫🇷 Portability — change of PA → Already with another Plateforme Agréée? Migrate in France without losing your SIRET addressing: SIRET import, bulk import, and the normalised inter-PA message with its 24 h / 5-day clocks. France (PPF) only. ]() ### [Compliance · 47 countries → Per-country deep-dives across Europe, MENA, and Asia-Pacific. Mandates, formats, deadlines, and primary government sources for France PPF, Italy SDI, KSA Fatoora, India GST, Malaysia MyInvois, Singapore InvoiceNow, and more. ]() ### [Webhook Cookbook → Event catalog, signing, retry policy, and idempotency patterns for robust listeners. ]() ### [Build with AI → Plug Claude Desktop, Claude Code, Cursor, or your own Python agent into the API as native tools — over MCP, with agent-ready docs and self-service onboarding. ]() ### [Agent onboarding → Two paths for an agent to self-provision: zero-friction sandbox bootstrap (no human in the loop) or OAuth-style consent flow with PKCE for production-grade scope grants. ]() ### [Error Catalog → Every error code with a remediation. Because "500 Internal Server Error" isn't a diagnosis. ]() ### [Sandbox → Every test scenario as a row. Force any error, advance any clock, simulate any recipient. ]() ### [Platform Onboarding Kit → Build under your own brand. Onboard 1 tenant or 1,000 with the same playbook. ]() ### [Data Model → One diagram that makes the whole API click. Read this first, thank yourself later. ]() ### [Webhook Fixtures → Real JSON payloads for every event. Drop them into your handler tests. ]() ======================================================================== # Change platform (portability app) # Source: https://docs.get-flowie.com/portability/index.html ======================================================================== --- title: "Change platform" description: "Move a company from one e-invoicing platform to another, in any country we cover: type a name, we resolve the company and its directory line, one company or five thousand, with the deadlines that country sets." canonical: "https://docs.get-flowie.com/portability/" source: "https://docs.get-flowie.com/portability/index.html" --- # Change platform Portability # Change platform Type a company name, or paste a tax ID — in any country we cover. We identify the company against its national register or the Peppol network, read the routing directory where the country has one, and tell you what its move requires _there_ : one company in thirty seconds, or five thousand in an afternoon. ## One company No account, no API key — the page mints a throwaway sandbox key and forgets it when you close the tab. Company name, tax ID, VAT number or Peppol id Look it up Three letters are enough. Names are matched against the national register where a country publishes one, and against the Peppol network everywhere else — so a Belgian, Italian or British company answers to its name too. A tax ID, a VAT number or a Peppol id always works. ## Many companies at once A platform migration is rarely one company, and rarely one country. Paste the list — names, tax IDs, VAT numbers or Peppol ids, one per line, straight out of a spreadsheet — and every line is resolved the same way the single lookup above resolves, four at a time so nothing is throttled. What comes back is a table you can act on: who each line really is, who routes them today, and what is still missing before their move can be filed in their own country. One company per line Resolve the list Copy as CSV Copy the import payload You typed| Company| Company ID| Routed today by| Still needed ---|---|---|---|--- **Then file them in one call.** The _Copy the import payload_ button gives you the body for [`POST /v1/companies/import/batch`](<../reference/index.html#import-companies-batch>) — up to 500 companies per call, each idempotent on its identifier, each reporting its own outcome so a partial failure never costs you the batch. Put the agreed `effectiveDate` on the items and the routing address is created for _that_ date rather than the moment the call lands. What the list does not do by itself Resolving a list tells you who these companies are and what their move needs. It does not send anything: the inter-platform messages go out when you open the requests, so you can look at the table, fix the three lines that came back incomplete, and only then commit. ## How a change of platform actually works Four steps, the same in every country we cover — what changes is the paperwork each one demands, the deadlines it runs on, and whether there is a public directory to re-point at all. The [country-by-country matrix](<../guides/portability-europe.html>) has the differences; the [French walkthrough](<../guides/portability.html>) has one regime end to end. This is the shape they share. 1 ### Identify the taxpayer One identifier — or a name, resolved above — becomes the legal name, the country, the national identifiers, the Peppol id and, where that country publishes a directory, the line it routes on. Whatever cannot be resolved comes back in `stillNeeded` instead of being guessed. 2 ### Open the request Opening one produces the agreement that country's rules require, its deadlines computed on that country's own business calendar, and a hash-linked evidence chain — so the proof survives the argument, not just the happy path. 3 ### Tell the other platform Where a country prescribes a format between platforms — France's normalised subject line and 18-field CSV, for one — it is built and sent to the outgoing platform, addressed from the register of approved platforms. It is logged with the hash of what was sent, whether or not it left. 4 ### Switch the routing On the agreed effective date — not before — the routing address moves. The outgoing platform stops sending that day and keeps receiving for as long as its country requires (twelve months in France), so nothing in flight is lost. The clocks belong to the country, not to us In France a request must be acknowledged within **24 hours** and processed within **5 jours ouvrés** , past which silence counts as agreement — in both directions, so the deadline protects you as the incoming platform and exposes you as the outgoing one. Other countries set different windows, and some set none at all: `resolve` returns the regime that applies to the company you looked up, and the [per-country matrix](<../guides/portability-europe.html>) spells the rest out. ## Drive it from your own system Everything on this page is the public API with a sandbox key. The same calls, with your own key, are what an integrator or an agent uses. They are country-agnostic except where the table says otherwise: Call| What it does ---|--- `GET /v1/portability/companies?q=`| Type a name, get the company — what the field above runs on. National register first where one is connected (France), then the Peppol network; identifiers resolve on either. `POST /v1/portability/resolve`| One identifier in, the whole taxpayer out, plus what its country requires. `POST /v1/portability/requests`| Open the request: designation agreement, deadlines, evidence chain. `POST /v1/portability/messages`| Send the normalised inter-platform message and keep the proof. `GET /v1/portability/annuaire/{siren}`| 🇫🇷 Who routes this taxpayer today, since when, until when — the French _annuaire_. `POST /v1/portability/routing`| Move the routing on the agreed date. `POST /v1/companies/import/batch`| Onboard the whole list, per-item results. Full parameters in the [API reference](<../reference/index.html#portability>), the country-by-country rules in [changing platform in Europe](<../guides/portability-europe.html>), and one regime end to end in the [French portability guide](<../guides/portability.html>). ======================================================================== # API reference # Source: https://docs.get-flowie.com/reference/index.html ======================================================================== --- title: "API Reference" description: "Complete API reference for Flowie Exchange. Endpoints, schemas, parameters, errors, and runnable examples." canonical: "https://docs.get-flowie.com/reference/" source: "https://docs.get-flowie.com/reference/index.html" --- # API Reference API Reference · v3.0.0 # Flowie Exchange API The Flowie Exchange API is a single REST API for **sending, receiving, and managing electronic invoices over the Peppol network**. It covers [47 countries](<../compliance/index.html>) across Europe, MENA, and Asia-Pacific, handles regulatory compliance reporting automatically, and scales from a freelancer sending one invoice per month to a white-label platform managing thousands of tenant companies. Base URL `https://back.p2p-flowie.com/exchange` in production · `https://back.flowie.ink/exchange` in sandbox. Every path below is prefixed with `/v1`. Pick an environment in the session menu (top right) and each endpoint header below shows the full URL for it, ready to copy. ### Quick index * [Postman collection](<#postman>) * [Authentication1](<#authentication>) * [Idempotency](<#idempotency>) * [Pagination](<#pagination>) * [Rate limits & quotas](<#rate-limits>) * [Errors1](<#errors>) * [Versioning](<#versioning>) * [Sandbox mode6](<#sandbox>) * [Agent auth6](<#agent-auth>) * [Companies14](<#companies>) * [Documents10](<#documents>) * [Lifecycle4](<#lifecycle>) * [Directory3](<#directory>) * [Partners7](<#partners>) * [Purchase orders1](<#purchase-orders>) * [Webhooks4](<#webhooks>) * [Events4](<#events>) * [Compliance2](<#compliance>) * [Stats1](<#stats>) * [Platform8](<#platform>) * [API keys3](<#api-keys>) * [Categorization6](<#categorization>) * [Payments3](<#payments>) * [Request log3](<#request-log>) * [Portability9](<#portability>) * [UBL generator (France)7](<#ubl-generator>) * [AFNOR XP Z12-01318](<#afnor>) * [PunchOut cart callback2](<#punchout>) * [Health3](<#health>) * [Appendices](<#appendices>) ## Postman collection Prefer to explore the API in [Postman]()? Download the ready-made collection — every endpoint, pre-filled with a working example body — and import it in seconds. [⬇ Download Postman collection](<../postman_collection.json>) [⬇ OpenAPI 3.1 spec](<../openapi.json>) In Postman: **Import** → drop the file, or paste the URL `https://docs.get-flowie.com/postman_collection.json`. Then set the collection variables: * `baseUrl` — `https://back.flowie.ink/exchange` (sandbox) or `https://back.p2p-flowie.com/exchange` (production). * `token` — your API key or JWT. It is sent as `Authorization: Bearer {{token}}` on every request (collection-level bearer auth). Authenticating with the [client-credentials grant](<#m2m>) instead? Leave `token` empty, fill in `tokenUrl`, `clientId`, `clientSecret`, `audience` and `organizationId`, and send **auth → Get an access token** : it stores the token in `{{token}}` for every other request, and each request carries `X-Flowie-Organization-Id: {{organizationId}}` — which a machine token cannot do without. Hit **Send** on any request to call the sandbox straight away. The collection is regenerated on every release, so it always matches this reference. Prefer to generate your own client? Import the [`openapi.json`](<../openapi.json>) spec instead. ## Authentication Every request must carry a bearer token. Flowie Exchange supports three kinds of credentials; pick whichever matches your caller. ### Flowie JWT If the caller is a Flowie dashboard user, pass the Auth0-issued JWT you already use elsewhere. The organization is resolved from the `_permissions` claim. #### Switching organizations JWTs typically grant access to multiple organizations (the user's `_permissions` claim is a dict of `org_id → permissions`). By default the API picks the first one in that dict. To act as a specific organization, pass the `X-Flowie-Organization-Id` header on every request: [code] curl https://back.p2p-flowie.com/exchange/v1/documents \ -H "Authorization: Bearer eyJhbGc..." \ -H "X-Flowie-Organization-Id: 685a5670efafaa26ebf0128e" [/code] The header is validated against the JWT's `_permissions`: passing an org the token doesn't grant returns `403`. `Organization-Id` (the legacy name used by the AFNOR routes) is also accepted as an alias. To list every org a caller can switch to, hit [`GET /v1/me`](<#get-me>). The Flowie docs auth widget uses this endpoint to render the org-picker dropdown next to your email. **API keys** are bound to a single org at creation time and ignore this header — a key issued for one org, sent with a header naming another, answers `200` with the _key's_ rows and no warning. Confirm which org a key acts as with [`GET /v1/me`](<#get-me>). ### OAuth 2.0 client credentials (machine-to-machine) For a backend that runs unattended — an ERP or D365 connector, a nightly sync, a webhook consumer — with no user to sign in. Flowie issues a client id and secret per integration, backed by a technical account made a member of your organization with a role; that membership is what grants access. Staging and production are separate applications with separate credentials. [code] curl -X POST https://login.flowieapp.io/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "client_credentials", "client_id": "...", "client_secret": "...", "audience": "https://auth.flowie.me" }' [/code] Token endpoint: `https://login.flowieapp.io/oauth/token` in sandbox, `https://login.flowie.me/oauth/token` in production. The response is `{"access_token": "eyJ…", "token_type": "Bearer", "expires_in": 86400}` — put `access_token` in the `Authorization` header. Cache it and renew it on `expires_in` rather than minting one per call. The `audience` is required and is _not_ the API base URL. A wrong one is refused at the token endpoint with `403 access_denied` — which reads like a credentials problem and is not one: it means the client is not authorized for that audience, either because it is misspelled or because it has not been granted access to the Flowie API. A machine token carries no organization An Auth0 `client_credentials` token has an empty `_permissions` claim and names no organization, so [`X-Flowie-Organization-Id`](<#org-switching>) is not an override here — it is the only thing that tells the API which tenant you mean. Send it on **every** call. Omit it and you get `403 No organization found in token`; name an organization your technical account is not a member of and you get `403 Token does not grant access to organization '…'. Available: none.` (`Available` reads `none` for every machine token — that part is normal.) Getting a token proves the audience grant only; the organization membership is a second, separate grant. [code] curl https://back.p2p-flowie.com/exchange/v1/documents \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "X-Flowie-Organization-Id: 019b47ba-..." [/code] The whole grant is in the [Postman collection](<#postman>) — send _auth → Get an access token_ and every other request is authenticated. Full walkthrough, including provisioning and rate limits: [the authentication guide](<../auth.md>). ### Exchange API keys For programmatic access, issue an Exchange API key from the dashboard or [via the API](<#create-api-key>). Keys are prefixed so you can tell them apart at a glance: * flw_live_…Personal key Scoped to a single company. Use for server-to-server calls from your own stack. * flw_plat_live_…Platform key Scoped to an organization that manages other companies. Combine with `X-Flowie-Company` to act on behalf of a tenant. * flw_wl_live_…White-label key Same as a platform key, plus the ability to customize branding, quotas, and settings per tenant. * flw_test_…Sandbox key Any of the above with `_test_` in the prefix hits sandbox. No real Peppol delivery. 🔒 Keys are shown once The full key string is returned exactly once, at creation. After that, only the key prefix is visible. Rotate a compromised key immediately — revoke it at [DELETE /v1/api-keys/{id}](<#revoke-api-key>). ### Scopes Keys carry a list of scopes. Use `*` only for full-access keys you control end-to-end; prefer the narrowest set your workload needs. `send` `receive` `documents.read` `documents.search` `documents.write` `companies.read` `companies.write` `directory` `partners` `payments` `lifecycle` `compliance` `stats` `platform` `*` [code] curl https://back.p2p-flowie.com/exchange/v1/companies \ -H "Authorization: Bearer flw_live_abc123" [/code] ##### Acting on a managed company (platform keys) [code] Authorization: Bearer flw_plat_live_xyz789 X-Flowie-Company: comp_abc123def456 [/code] ### Get caller identity + accessible orgs GET/v1/me Returns who the caller is, which organizations they can act as, and the active org for the current request. Works with both JWT and API-key auth. Used by the docs auth widget to render the organization-switcher dropdown. #### Returns [code] { "authMethod": "jwt", "userId": "user_…", "email": "alice@example.com", "keyType": "jwt", "organizationId": "org_685a5670efafaa26ebf0128e", "organizationIds": ["org_685a…", "org_72b1…"], "organizations": [ { "id": "org_685a…", "name": "PMU", "country": "FR", "vatNumber": "FR12345678901" }, { "id": "org_72b1…", "name": "Subsidiary", "country": "FR", "vatNumber": "FR98765432109" } ], "scopes": ["*"], "isTestMode": false } [/code] [code] curl https://back.p2p-flowie.com/exchange/v1/me \ -H "Authorization: Bearer eyJhbGc..." [/code] ## Idempotency Network calls are imperfect. Any `POST` in this API accepts an `Idempotency-Key` header; if a request with that key has already completed in the last 24 hours, we return the original response byte-for-byte instead of acting again. * Keys are strings, up to 255 characters. UUID v4 works great. * Cache TTL is 24 hours. After that, a repeated key is treated as new. * If you retry _before_ the first response has finished processing, you'll get a `409 idempotency_in_progress`. Retry in a moment. * Mutating a request under the same key is never allowed. We compare the full body hash — mismatched retries return `422 idempotency_body_mismatch`. Best practice Generate the idempotency key _before_ the first attempt — typically from your database row ID, not a random UUID on retry. That way, a crash between generation and HTTP call can still be recovered. [code] curl -X POST …/v1/documents/send \ -H "Authorization: Bearer $KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -d @invoice.json [/code] ## Pagination All list endpoints are cursor-paginated. Don't hard-code offsets — the cursor is an opaque server-issued token and will change format without notice. * limitintegeroptional Page size. Default `20`, max `100`. * cursorstringoptional Pass the `cursor` value returned by the previous page. Omit to start at the first page. Every list response has the same envelope: [code] { "data": [ /* records */ ], "hasMore": true, "cursor": "eyJpZCI6ImRvY19YLi4uIn0" } [/code] ##### Iterate all pages [code] cursor = None while True: params = {"limit": 100} if cursor: params["cursor"] = cursor page = api.get("/documents", params=params).json() for doc in page["data"]: process(doc) if not page["hasMore"]: break cursor = page["cursor"] [/code] ## Rate limits & quotas Rate limits are enforced with a 60-second sliding window per key. Quotas are enforced monthly per organization. Both depend on your plan: Plan| Requests / min| Documents / month ---|---|--- Free| 60| 50 Starter| 120| 500 Pro| 300| 5,000 Platform| 600| 50,000 White-label| 1,200| Unlimited Every response includes the current state: * X-RateLimit-Limitinteger Requests allowed in the current 60-second window. * X-RateLimit-Remaininginteger Requests left before you're throttled. * X-RateLimit-Resetunix timestamp When the window rolls over. * Retry-Afterseconds Present only on `429`. How long to wait before retrying. Exponential backoff On `429` or `503`, wait `Retry-After` seconds (or `2ⁿ × 250ms` jittered) and try again. Don't retry `4xx` client errors — they'll always fail. ##### 429 response [code] HTTP/1.1 429 Too Many Requests Retry-After: 37 X-RateLimit-Limit: 300 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1714046400 { "error": { "type": "rate_limit_error", "code": "RATE_LIMITED", "message": "You have exceeded 300 req/min. Retry in 37s.", "requestId": "req_01HXYZ…" } } [/code] ## Errors Every error response uses the same envelope (RFC 7807 + Flowie extensions). See the [error catalog]() for a full list of codes and how to fix them. Status| Meaning ---|--- `400`| The request is malformed or fails validation. `401`| Missing, expired, or invalid credentials. `403`| Credentials are valid but lack the required scope or company access. `404`| The resource doesn't exist (or isn't visible to you). `409`| Conflict — typically an idempotency or state transition issue. `422`| Semantically invalid (e.g. VAT not in directory, unreachable recipient). `429`| Rate-limited. Honor `Retry-After`. `500`| Internal error. Report `requestId` to support. `502 / 503`| Upstream service unavailable. Circuit breaker may be open. ### Request inspector GET/v1/requests/{request_id} Every response carries an `X-Request-Id` header (and a `requestId` field on errors). Pass it back to this endpoint to retrieve the full request trace: timing, intermediate upstream calls, validation diff, and final status. Mirrors what you see at [requests.html](<../playground/requests.html>). ##### Error shape [code] { "error": { "type": "validation_error", "code": "INVALID_REQUEST", "message": "Request validation failed", "details": [ { "field": "document.lines[0].vatRate", "rule": "range", "message":"Must be between 0 and 100" } ], "requestId": "req_01HXYZ2K3M4N5P6Q7R", "docUrl": "https://docs.get-flowie.com/errors#INVALID_REQUEST" } } [/code] ## Versioning The API version is baked into the URL (`/v1/…`). We follow semantic versioning with these commitments: * **Breaking changes** ship under a new path (`/v2/…`). Old paths stay alive for at least 12 months. * **Additive changes** — new fields, new enum values, new endpoints — land in `/v1/` without notice. * **Deprecations** are announced in the [changelog](<../changelog.html>) and flagged with the `Sunset` response header 6+ months before removal. Forward-compatible parsers Ignore unknown fields. Treat enum values as opaque strings. That way your integration survives any additive change automatically. ##### Sunset header example [code] Sunset: Wed, 01 Oct 2026 00:00:00 GMT Deprecation: true Link: ; rel="deprecation" [/code] ## Sandbox mode Use a `flw_test_…` key with the staging base URL. Sandbox behaves identically to live with these differences: * Documents are **not** delivered to real Peppol access points — they're routed to an internal echo endpoint. * Compliance reporting goes to a mock PPF/SDI that always accepts. * Webhooks fire the same events with `"livemode": false` in the payload. * There are no quotas; rate limits remain. ### Bootstrap a sandbox key POST/v1/sandbox/bootstrap Public, unauthenticated. Mints a fresh `flw_test_*` API key bound to a brand-new throwaway organization plus a Belgian sandbox company (`BE0000000001`, peppolId `0208:0000000001`). Returns the key only once. Rate-limited per IP; meant for the docs Playground and CI smoke tests. #### Request body * labelstringoptional Free-form tag for the issued key — appears in the dashboard. Default `quickstart`. Max 64 chars. * emailstringoptional Optional contact email (we may follow up with usage tips). * keyTypeenumoptional `personal``platform``white_label` Defaults to `personal` (token prefix `flw_test_`). Pass `platform` to mint a multi-tenant key (`flw_plat_test_`) that satisfies the platform-key gate on `/v1/platform/*` ops, or `white_label` for the branding-enabled variant (`flw_wl_test_`). See [Sandbox · Key types](<../sandbox/index.html#key-types>). ### Reset sandbox state POST/v1/sandbox/reset Wipes events, idempotency cache, and pending scheduled events for the calling organization. Test-mode key only. #### Request body * confirmenumrequired Type the literal string `yes` to acknowledge the wipe. * scopeenumoptional `all``documents``events``idempotency` What to wipe. Defaults to `all`. ### Advance virtual clock POST/v1/sandbox/clock/advance Move the company-scoped virtual clock forward — used to test 60-day overdue flows, retry escalations, etc. Wakes any scheduled events whose virtual fire-time is now in the past. #### Request body * companyIdstringrequired Company whose virtual clock should be advanced. * bystringrequired How far to jump. Accepts compact units: `1h`, `3d`, `2w`, `1m`, `1y`. ### Reset virtual clock POST/v1/sandbox/clock/reset Snap the virtual clock back to wall-clock time for a company. #### Request body * companyIdstringrequired ### Force rate-limit POST/v1/sandbox/rate-limit/exhaust Make every subsequent request from this organization return `429`. Use to validate your client's retry/backoff path against a real `Retry-After`. #### Request body * durationSecondsintegeroptional How long the forced `429` should last. Default `60`, range 1–3600 (max 1 hour). ### Flush idempotency cache POST/v1/sandbox/idempotency/flush Drop the 24h idempotency cache for the calling key — useful when you want to re-issue a request that previously succeeded under the same `Idempotency-Key`. No request body. ##### Base URL [code] https://back.flowie.ink/exchange/v1 [/code] ## Agent auth — OAuth & handoff Three ways an AI agent gets a key. **Handoff** is the fastest: a human generates a single-use link and pastes it to the agent, which redeems it in one call. **Sandbox bootstrap** ([below](<#sandbox-bootstrap>)) needs no human at all. **OAuth with PKCE** is the full consent flow when the agent must act on a real user's account and you want an approval screen. The end-to-end walkthrough lives in the [agent onboarding guide](<../build-with-ai/agent-onboarding.html>). ### List grantable scopes GET/v1/oauth/scopes **Authentication:** none — this endpoint is public. Every grantable scope with a human-readable description. Agents call this once at boot to render an honest scope-selection UI before starting the consent flow. [code] { "scopes": [ { "id": "send", "description": "Send documents" }, { "id": "documents.read", "description": "Read documents" }, { "id": "lifecycle", "description": "Advance lifecycle statuses" } ] } [/code] ### Start consent (PKCE) POST/v1/oauth/authorize **Authentication:** none — this endpoint is public. Step 1 of the consent flow. Returns the URL the agent shows the user. RFC 7636 PKCE: the agent keeps a random `code_verifier` secret and sends only its SHA-256 challenge. #### Request body * client_namestringrequired Agent display name, shown on the consent screen. * scopesstring[]required Scopes requested, from [the catalogue](<#oauth-scopes>). * code_challengestringrequired `BASE64URL(SHA256(code_verifier))`, no padding. * code_challenge_methodstringoptional `S256`. The plain method is not accepted. * redirect_uristringoptional Omit for out-of-band: the code is shown on screen for the user to paste. * statestringoptional Echoed back on redirect. [code] { "client_name": "My Agent", "scopes": ["send", "documents.read"], "code_challenge": "E9Melhoa2Ow…", "code_challenge_method": "S256" } [/code] [code] { "request_id": "areq_01HY…", "consent_url": "https://back.flowie.ink/exchange/consent?request=areq_01HY…", "expires_in": 600 } [/code] ### Exchange the code for a key POST/v1/oauth/token **Authentication:** none — this endpoint is public. Final step of the consent flow. The server hashes `code_verifier` and checks it against the challenge recorded at [/authorize](<#oauth-authorize>). The code is single-use and expires 5 minutes after consent. #### Request body * grant_typestringrequired `authorization_code`. * codestringrequired The one-time code from the consent screen. * code_verifierstringrequired The 43–128 character secret whose SHA-256 was sent as the challenge. [code] { "grant_type": "authorization_code", "code": "ac_01HY…", "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" } [/code] [code] { "access_token": "flw_test_…", "scopes": ["send", "documents.read"], "expires_in": 604800, "organization_id": "org_01HY…", "company_id": "comp_01HY…" } [/code] ### Mint a handoff link POST/v1/oauth/handoff **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Generate a single-use, pre-approved link to paste to an agent you already trust. The agent redeems the embedded token at [/handoff/exchange](<#oauth-handoff-exchange>) and gets a key bound to _your_ organization — no consent screen. You can only grant scopes you hold yourself. #### Request body * scopesstring[]optional Defaults to the scopes of the calling key. * labelstringoptional Shown in the API-key list so you can revoke the right one later. * ttl_secondsintegeroptional Token lifetime, 60 minutes maximum. [code] { "scopes": ["send"], "label": "claude-desktop", "ttl_seconds": 900 } [/code] [code] { "handoff_url": "https://docs.get-flowie.com/build-with-ai/agent-onboarding.html?handoff=hand_AbC…", "handoff_token": "hand_AbC…", "expires_in": 900 } [/code] ### Anonymous sandbox handoff POST/v1/oauth/handoff/sandbox **Authentication:** none — this endpoint is public. Bootstraps a fresh sandbox organization _and_ mints a handoff token in one call. Rate-limited to 120 requests per IP per hour, like [sandbox bootstrap](<#sandbox-bootstrap>). This is what lets the docs home page hand an agent a URL that is already authenticated. [code] { "handoff_token": "hand_AbC…", "organization_id": "org_sbx_01HY…", "company_id": "comp_sbx_01HY…", "expires_in": 3600 } [/code] ### Redeem a handoff token POST/v1/oauth/handoff/exchange **Authentication:** none — this endpoint is public. Redeem the token for an API key. Single-use: a second attempt returns `400 invalid_grant`. This is the whole of path 1 — one POST, no PKCE, no consent UI. #### Request body * handoff_tokenstringrequired The `hand_…` value from the URL you were given. [code] { "handoff_token": "hand_AbC…" } [/code] [code] { "access_token": "flw_test_…", "scopes": ["send"], "expires_in": 604800, "organization_id": "org_01HY…", "company_id": "comp_01HY…" } [/code] ## Companies A **company** represents a legal entity that can send or receive documents on Peppol. Create one per VAT number you operate under. Flowie auto-enriches the legal name, address, and Peppol identifier, then registers the company with the Peppol SMP so other access points can route messages to it. The company object * idstring Unique identifier, `comp_…`. * name / legalNamestring Display name and registered legal name. * vatNumberstring Normalized `^[A-Z]{2}[A-Z0-9]+$`. * countryISO 3166-1 α-2 Derived from the VAT prefix. * peppolIdstring Scheme-prefixed Peppol participant identifier, e.g. `0208:0123456789`. * additionalIdentifiersobject[] Extra identifiers (GLN, DUNS, SIRET…). * addressAddress Postal address. See [Address](<#address-object>). * capabilitiesobject Which document types the company can send/receive. * statusstring `active`, `inactive`, or `suspended`. * smpRegisteredboolean True once the SMP record is live. * smpRegisteredAttimestamp When SMP registration completed. * complianceobject Per-country compliance status (PPF for FR, SDI for IT). Belgium has no regulator-side report; the field is empty for BE companies. * settingsobject Sending preferences, default currency, auto-reporting toggles. * statsobject Summary counters (documents sent, received). * metadataobject Your free-form key-value store. * createdAt / updatedAttimestamp ISO 8601 UTC. ### Create a company POST/v1/companies Registers a new company. Only `vatNumber` is strictly required — everything else is auto-enriched from the national registry (INSEE, KBO, Camera di Commercio, …) and the Peppol directory. #### Request body * vatNumberstringrequired Country prefix + number, e.g. `BE0123456789`. Pattern `^[A-Z]{2}[A-Z0-9]+$`. * namestringoptional Display name. Defaults to the enriched legal name. * addressAddressoptional Overrides the auto-enriched address. * additionalIdentifiersobject[]optional Extra routing identifiers. `{ "scheme": "0088", "value": "1234567890128" }` for GLN, etc. * capabilitiesobjectoptional `{"send": ["invoice","credit-note"], "receive": ["invoice"]}`. Default: full set. * settingsobjectoptional Default currency, auto-compliance toggles, preferred contact. * complianceobjectoptional Per-country compliance configuration overrides (e-reporting enrolment, PPF/SDI routing hints). * metadataobjectoptional Free-form key-value (max 40 keys, 500 chars each). #### Returns The [company object](<#companies>) with status `201`. SMP registration happens asynchronously — listen for `company.smp_registered` via webhook. Duplicates Calling create with a `vatNumber` already owned by your organization returns `409 duplicate` with the existing `companyId`. Use that as your idempotent upsert. ##### Request [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/companies \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: upsert-acme-be" \ -d '{ "vatNumber": "BE0123456789", "capabilities": { "send": ["invoice","credit-note"], "receive": ["invoice"] }, "metadata": { "tenantId": "t_acme" } }' [/code] ##### Response 201 Created 409 Duplicate 422 Unknown VAT [code] { "id": "comp_01HXYZ123ABC", "name": "ACME Business Solutions BVBA", "legalName": "ACME Business Solutions BVBA", "vatNumber": "BE0123456789", "country": "BE", "peppolId": "0208:0123456789", "additionalIdentifiers": [], "address": { "street": "Rue de la Loi 16", "city": "Bruxelles", "postalCode": "1000", "country": "BE" }, "capabilities": { "send": ["invoice","credit-note"], "receive": ["invoice"] }, "status": "active", "smpRegistered": false, "smpRegisteredAt": null, "compliance": {}, "settings": { "defaultCurrency": "EUR" }, "stats": { "sent": 0, "received": 0 }, "metadata": { "tenantId": "t_acme" }, "createdAt": "2026-04-25T10:00:00Z", "updatedAt": "2026-04-25T10:00:00Z" } [/code] [code] { "error": { "type": "conflict", "code": "COMPANY_EXISTS", "message": "A company with this VAT already exists in your organization.", "details": [{ "field": "vatNumber", "value": "BE0123456789", "existingId": "comp_01HXYZ…" }], "requestId":"req_01HXYZ…" } } [/code] [code] { "error": { "type": "invalid_request_error", "code": "VAT_NOT_FOUND", "message": "VAT BE0000000000 is not in the national registry.", "requestId":"req_01HXYZ…" } } [/code] ### List companies GET/v1/companies Returns all companies you own or manage, most-recently created first. #### Query parameters * countryISO 3166-1 α-2optional Filter by country. * statusstringoptional `active`, `inactive`, or `suspended`. * searchstringoptional Full-text over name, legal name, VAT, and Peppol ID. * include_addressbooleanoptional Resolve each row's `legalAddressId` into a full `address` object (adds round-trips). Default `true` — set `false` for a faster, lighter list. * limit / cursorpaginationoptional See [Pagination](<#pagination>). [code] curl "https://back.p2p-flowie.com/exchange/v1/companies?country=BE&status=active&limit=50" \ -H "Authorization: Bearer $KEY" [/code] [code] { "data": [ { "id": "comp_01HXYZ…", "name": "ACME BVBA", "vatNumber": "BE0123456789", "country": "BE", "peppolId": "0208:0123456789", "status": "active" } ], "hasMore": false, "cursor": null } [/code] ### Resolve by VAT / SIREN GET/v1/companies/resolve Looks up any company, anywhere, by legal identifier — returns the same shape as the company object but synthesized from national registries and the Peppol directory. Use it to pre-fill forms, verify recipients, or check Peppol reachability. #### Query parameters * countryCodeISO 3166-1 α-2required * vatNumberstringone of * registrationNumberstringone of SIREN, KBO, CF, … depending on `countryCode`. [code] curl "https://back.p2p-flowie.com/exchange/v1/companies/resolve?countryCode=FR®istrationNumber=797978996" \ -H "Authorization: Bearer $KEY" [/code] ### Search companies GET/v1/companies/search Autocomplete over your managed companies. Optimized for < 80 ms response time. Use for dropdowns in UIs. * qstringrequired Query fragment (min 2 chars). * countryCodeISO 3166-1 α-2optional * limitintegeroptional Default `10`, max `50`. [code] [ { "id": "comp_…", "name": "ACME BVBA", "vatNumber": "BE0123456789", "country": "BE", "peppolId": "0208:0123456789" } ] [/code] ### Retrieve a company GET/v1/companies/{company_id} Returns the [company object](<#companies>). The path parameter accepts three forms: * `comp_01HXYZ…` — the canonical id * `vat:BE0123456789` — VAT-scoped lookup * `peppol:0208:0123456789` — Peppol-ID lookup [code] curl https://back.p2p-flowie.com/exchange/v1/companies/vat:BE0123456789 \ -H "Authorization: Bearer $KEY" [/code] ### Update a company PATCH/v1/companies/{company_id} Partial update. System-managed attributes (`peppolId`, `status`, timestamps, stats) are read-only. Merging rules: * Top-level keys are replaced wholesale. * `metadata` is shallow-merged. Set a key to `null` to delete it. * Changing `capabilities.send` or `capabilities.receive` may trigger an SMP re-registration (you'll see a `company.smp_registered` event). #### Request body All fields optional — send only what you want to change. * namestringoptional Display name. * addressAddressoptional * capabilitiesobjectoptional `{"send": [...], "receive": [...]}`. May trigger SMP re-registration. * settingsobjectoptional * complianceobjectoptional * metadataobjectoptional Shallow-merged. Set a key to `null` to delete it. ### Deregister a company DEL/v1/companies/{company_id} Permanently removes the SMP record and marks the company inactive. Historical documents remain queryable. Returns `204 No Content`. ### Join requests If a Flowie user wants to connect to an already-registered company, they hit `POST /companies/{id}/join`. The company's organization admins see pending requests via: GET/v1/companies/join-requests and accept or reject with: POST/v1/companies/{company_id}/join Issue a join request as the calling user. POST/v1/companies/{company_id}/join-requests/{request_id}/accept POST/v1/companies/{company_id}/join-requests/{request_id}/reject [code] curl -X PATCH \ https://back.p2p-flowie.com/exchange/v1/companies/comp_abc \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "settings": { "defaultCurrency": "EUR" }, "metadata": { "tier": "premium", "oldKey": null } }' [/code] ### Import a company (portability) POST/v1/companies/import **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Onboard a company for a portability migration, keyed on the taxpayer's **SIRET**. Flowie derives the SIREN, country and Peppol id (`0009:`), resolves the legal name and current PA from the PPF annuaire, then attaches the company to your organization. Idempotent on SIRET. #### Request body * siretstringrequired 14-digit SIRET of the taxpayer. Supply `siren` instead only when the establishment is unknown. * companyNamestringoptional Overrides the legal name resolved from the annuaire. * countryCodestringoptional ISO-3166 alpha-2. Defaults to `FR`. * modeenumoptional Migration mode. Governs whether the existing provider connection is reused or re-provisioned. * sovosOrganizationIdstringoptional Existing provider organization id, when migrating a company already live elsewhere. [code] { "siret": "55210055400013", "mode": "portability" } [/code] [code] { "id": "comp_01HY7AB9C2DE3FG", "siren": "552100554", "peppolId": "0009:552100554", "name": "ACME SAS", "country": "FR", "imported": true } [/code] ### Import companies in bulk POST/v1/companies/import/batch **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Import a list of companies in one call. Items are processed concurrently and idempotently; a per-item failure is reported in that item's result row rather than failing the whole batch, so a partial batch still onboards everything that was valid. #### Request body * itemsCompanyImportRequest[]required Each item takes the same fields as [Import a company](<#import-company>). [code] { "items": [ { "siret": "55210055400013" }, { "siret": "39876543200025" } ] } [/code] [code] { "results": [ { "ok": true, "siret": "55210055400013", "id": "comp_01…" }, { "ok": false, "siret": "39876543200025", "error": { "code": "SIRET_NOT_FOUND" } } ], "imported": 1, "failed": 1 } [/code] ### Register a company on Peppol POST/v1/companies/{company_id}/register **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Deploy the company on Peppol and activate its registration so it can send and receive. This is what publishes the participant to the SMP: until it succeeds, [directory verification](<#verify-recipient>) of your own id returns `canReceive: false`. Idempotent. For an organization already provisioned this re-syncs and re-activates the local registration; for a new one it provisions the provider customer config and managed connection first. #### Path parameters * company_idstringrequired The company to register, e.g. `comp_01HY7AB9C2DE3FG`. No request body. [code] POST /v1/companies/comp_01HY7AB9C2DE3FG/register Authorization: Bearer flw_live_… [/code] [code] { "id": "comp_01HY7AB9C2DE3FG", "peppolId": "0208:0123456789", "registered": true, "smpStatus": "active", "activatedAt": "2026-04-25T10:05:00Z" } [/code] ## Documents The **document** resource represents an invoice, credit note, debit note, or purchase order. Flowie accepts a structured JSON body (we'll render valid UBL 2.1) or a raw UBL/CII XML payload. Either way, we validate, sign, deliver over Peppol, and track lifecycle status through to payment. The document object * idstring `doc_…` * typeenum `invoice``credit-note``debit-note``purchase-order``purchase-request``sales-order``quote``goods-receipt``event` * directionenum `incoming``outgoing` * numberstring Your external document number. * issueDate / dueDatedate (YYYY-MM-DD) * currencyISO 4217 * grossAmount / netAmount / vatAmountdecimal * sender / receiverParty * statusenum `draft``sent``delivered``rejected` * deliveryStatusenum `pending``delivered``failed``rejected` * lifecycleStatusenum Business-level state. See [Lifecycle](<#update-lifecycle>). * documentobject The full structured body (lines, tax, payment, …). * xmlstring Rendered UBL (populated on delivery). * metadataobject * receivedAt / sentAttimestamp ### Send a document POST/v1/documents/send Delivers a document over Peppol to the `to` participant. Always set `Idempotency-Key` — duplicate sends to SDI or PPF can create regulatory headaches. **Doubles as Flowie's inbound integration point.** Wire any ERP / accounting system / iPaaS webhook directly here — see [Inbound: ERP webhooks](<../guides/index.html#ingest>) for the full matrix of payload shapes (structured JSON · UBL XML · PDF / Factur-X / image / proprietary file). #### Body * typeenumrequired `invoice``credit-note``debit-note``purchase-order``purchase-request``sales-order``quote``goods-receipt``event` * formatenumoptional `json``ubl-xml``cii-xml``auto``raw` `json` (default) — we render UBL. `ubl-xml` / `cii-xml` — provide your own XML in `xml`; a `cii-xml` payload we cannot read is refused with `422` rather than recorded as an empty document. `auto` — supply a `file`; the server sniffs the bytes and routes to the right pipeline. A Factur-X PDF or a CII (UN/CEFACT `CrossIndustryInvoice`) is read into a structured document — number, dates, parties, lines, totals and the type from BT-3 — with the CII kept as the original. `raw` — supply a `file`; the server stores it as-is on the document file API and returns `deliveryStatus="stored"` (no Peppol routing). * fromstringrequired Your sender company. Accepts a bare Peppol id (`0208:0123456789`) or the prefixed forms `peppol:…`, `vat:…`, `comp_…` / `org:…`. Whatever you pass is normalised to the sender's canonical Peppol id before delivery — the response always echoes the bare `0208:…` form. * tostringrequired when type ≠ event Recipient. A Peppol participant id (`0208:0123456789` or `peppol:…`) — used as-is — or any other identifier we can resolve to one: `vat:…`, `siren:…` / `siret:…`, `duns:…`, `gln:…`, `lei:…`, `eori:…`, `registration:…`, `email:…`, `domain:…`, `name:…`, `org:…` / `id:…` (or the bare unprefixed form of any of these). Non-Peppol identifiers are resolved against org-v2 + the PPF Annuaire (FR) + the Peppol Directory, and provisioned if never seen, so they route to a real participant. A French reception point (_ligne annuaire_) can be addressed with the composed identifier `{siren}_{siret}[_{suffix}]` (e.g. `75297877500027_001`) — see [Reception-point addressing](<#reception-point-addressing>). Optional (omit) when `type=event` — events are pure observability/audit records and have no recipient. **`name:` needs a country, and refuses to guess.** With one — taken from the counterparty in your payload, so you rarely state it separately — a name is searched against the legal registries, and an unknown company is found and provisioned. Without one it resolves nothing: a name is not a legal identity. “SAFRAN NACELLES MOROCCO” is both a French SIREN and a Moroccan registration, two different legal persons, and binding an invoice to the wrong one is a compliance defect. When several companies match, the API answers `409` listing them rather than picking one. When your name and the registry’s name differ, use the number Registries hold the _legal_ name, which is often not the one in your customer master: a Chinese customer filed as `BEIJING GE HUALUN MEDICAL EQUIPMENT CO, LTD` is registered as `GE Hualun Medical Systems Co., Ltd`. No name search bridges that, and none should try. **`registration:`** takes a national registration number in any jurisdiction — a Chinese Unified Social Credit Code, a UK company number, a Moroccan RC — and resolves it exactly. Company registries are searched one jurisdiction at a time, so the country has to be known: it is taken from the recipient's address in the document, or stated inline with a trailing `@`: [code] "to": "registration:91110302621705062U@CN" [/code] It is accepted only when the registry returns exactly one company carrying that number, so a loose search never becomes a wrong match. Without a country, from either source, the call fails with a 400 that says so rather than reporting the company as unknown. `siren:` and `siret:` remain the French forms. **Better still, route on your own reference.** If your ERP already holds a stable code for each customer, store it on the partnership as a custom field and address the recipient with `cf:FIELD_NAME=VALUE` — for example `cf:CODE_CLIENT=C-4471`. That is an exact match on a value you control, so it does not depend on how either side spells the company name, and it keeps working when the legal name changes. #### Addressing by your own reference Every ERP already has a stable code for each customer. Stored on the partnership as a custom field, that code becomes a routing key: `cf:FIELD_NAME=VALUE`, e.g. `cf:CODE_CLIENT=C-4471` (aliases: `ref:`, `customfield:`). It is matched exactly against custom fields scoped to your organization, so unlike `name:` it does not depend on spelling, accents or locale, and it survives a change of legal name. [code] { "type": "invoice", "to": "cf:CODE_CLIENT=C-4471", "document": { "number": "INV-2026-0042", "...": "..." } } [/code] Two deliberate limits, both there so a routing key can never silently send a document to the wrong company: * **The field name is required.** `cf:C-4471` is rejected with `400`: Flowie will not guess which custom field holds your reference. * **No match is an error, never a fallback.** An unknown reference returns `404` and nothing is created — it does not degrade into a name search. If the reference matches more than one partner you get `409` rather than an arbitrary pick; de-duplicate it, or address that recipient by `org:`. Set the field on the partnership through the partners API (or the UI) before you route on it. * documentDocumentBodyrequired when format=json See schema below. * numberstringrequired Invoice number — **BT-1**. * issueDatedaterequired **BT-2**. * dueDatedateoptional **BT-9**. * currencyISO 4217optional **BT-5**. Default `EUR`. * buyerReferencestringoptional **BT-10**. Required by many public-sector buyers (e.g. Service Executant / Code Service in FR). * orderReferencestringoptional PO number — **BT-13**. * despatchAdviceReferencestringoptional Delivery-note number — **BT-16**. * incotermsstringoptional Delivery terms code — **EXT-FR-FE-185**. `1`, `2`, or an Incoterms 2020 code (`EXW`, `FCA`, `CPT`, `CIP`, `DAP`, `DPU`, `DDP`, `FAS`, `FOB`, `CFR`, `CIF`). * incotermsLocationstringoptional Named place the delivery terms refer to — **EXT-FR-FE-186**. Requires `incoterms`. * notestringoptional **BT-22**. * seller / buyerPartyoptional Overrides the auto-derived seller/buyer. A `Party` object: * namestring **BT-27** (seller) / **BT-44** (buyer). * vatNumberstring **BT-31** (seller) / **BT-48** (buyer). * addressAddress Billing address — see the [Address](<#address-object>) object. Carried to the party's `billingAddress` and rendered as the party's postal-address group: **BG-5** for the seller (`BT-35` street, `BT-36` street 2, `BT-37` city, `BT-38` post code, `BT-39` subdivision, `BT-40` country) and **BG-8** for the buyer (`BT-50`…`BT-55`). * shippingAddressAddress Same shape as `address`. * contactobject `{ name?, email?, phone?: string }`. The `email` is added to the party's `contacts`. Rendered as the contact group — **BT-41/42/43** (seller), **BT-56/57/58** (buyer). * contactsstring[] Contact email addresses, e.g. `["ap@acme.example"]`. * partiesPartyRef[]optional Explicit, role-tagged party list for documents with **more than two parties** (a `payer`/`payee` distinct from `buyer`/`seller`) and for self-billing. **Exactly one** entry must set `initiator: true` (the org the key acts as). When present it **overrides** the default seller/buyer derivation. See [Multiple parties](<#multiple-parties>). Each entry: * roleenumrequired `seller``buyer``payer``payee` * idstring Any resolvable id (same grammar as `to`). * name / vatNumberstring * address / shippingAddressAddress See the [Address](<#address-object>) object. * contact / contactsobject / string[] Same as on `seller/buyer` above. * initiatorboolean Exactly one entry must be `true`. * paymentPaymentInfooptional A `PaymentInfo` object: * meansstring How the payment is made. A UNTDID 4461 code is sent as **BT-81** : `"30"` credit transfer, `"42"` payment to bank account, `"48"` bank card, `"49"` direct debit, `"58"` SEPA credit transfer, `"97"` clearing between partners (the netting a customer and supplier settle against each other), or `"ZZZ"` for a means the two of you defined between yourselves — the list runs 1 to 97 plus `ZZZ`. Anything else, e.g. `"Virement SEPA"` or `"credit_transfer"`, is sent as **BT-82** , the means description; a number outside the code list goes there too rather than being passed off as a code, since free text in BT-81 fails `BR-CL-16`. * ibanstring Account credited — **BT-84**. The holder name (**BT-85**) is taken from the seller. * bicstring Account provider — **BT-86**. * referencestring Remittance / structured communication — **BT-83**. Also sent as `paymentReferenceNumber`. * discountTermsarray `[{ days: int, percent: number, note?: string }]`. _Accepted but not yet emitted to the e-invoice._ * deliveryobjectoptional Delivery details (**BG-13**), e.g. `{ actualDeliveryDate?: "YYYY-MM-DD", deliveryLocation?: Address }`. Two parts are emitted: the delivery date (**BT-72** , also read from `deliveryDate` / `date`) and the deliver-to country (**BT-80** , from `deliveryLocation.country` — required by `BR-IC-12` on intra-community supplies, and defaulted to the buyer's country there when omitted). The rest of the group is accepted but not yet emitted. * linesInvoiceLine[]required **VAT is per line** — a document with several rates is several lines (see [Multiple VAT rates](<#multiple-vat>)). Each line: * descriptionstringrequired * quantitynumberrequired * unitPricenumberrequired Excl. VAT. * vatRatenumberrequired Percent, e.g. `21`, `6`, `0` — **BT-152**. * unitstring UN/ECE Rec 20 code, e.g. `"HUR"`, `"C62"`. * vatCategorystring UNCL5305 code; defaults to `S`. See [Tax exemption & zero rate](<#tax-exemption>). * vatExemptionReasonstring Free-text reason why the line carries no VAT (**BT-120**), e.g. `"TVA non applicable, art. 293 B du CGI"`. Required by EN 16931 whenever `vatCategory` is a zero-VAT category. Ignored on standard-rated lines. * vatExemptionCodestring VATEX code backing the reason (**BT-121**), e.g. `VATEX-EU-IC`, `VATEX-EU-AE`, `VATEX-FR-FRANCHISE`. Optional under EN 16931, **mandatory for the French franchise en base**. * itemCodestring Your identifier for the item — **BT-155**. * customFieldsobject Keyed by field name or UUID. See [Custom fields & templates](<#custom-fields>). * periodobject Billing period the line covers (**BG-26**): `{ startDate: "YYYY-MM-DD", endDate: "YYYY-MM-DD" }` → **BT-134** / **BT-135**. The pairs `start`/`end` and `from`/`to` are also accepted. * allowances / chargesarrayoptional Document-level discounts (`allowances`) / surcharges (`charges`). Each item: `{ reason?: string, amount?: number, percent?: number, vatRate?: number }`. _Accepted but not yet emitted to the e-invoice — for document-level allowances/charges today, send`format=ubl-xml`._ * attachmentsarrayoptional Embedded attachments. Each item: `{ filename: string, contentType: string, content: }`. _Accepted but not yet emitted to the e-invoice_ — to attach a file today use `format=auto`/`raw` with the top-level `file`. * totalsobjectoptional Pre-computed totals — **overrides** the values computed from lines. `{ netAmount?: number, vatAmount?: number, grossAmount?: number }` (aliases `net`/`vat`/`gross` also accepted). If omitted, all three are computed from the lines. **Recommended when your ERP has already posted the invoice** — see [Totals](<#totals>). * templateIduuidoptional Template to file the document under — it declares which `customFields` are valid. See [Custom fields & templates](<#custom-fields>). * customFieldsobjectoptional Document-level custom field values, keyed by field **name or UUID**. Attached to your party. See [Custom fields & templates](<#custom-fields>). * xmlstringrequired when format=ubl-xml Raw UBL 2.1 or CII XML. We validate against the Peppol BIS 3.0 schematron before delivery. * fileFileAttachmentrequired when format=auto or raw Arbitrary file payload (PDF, image, ZIP, proprietary format). `{ content: , contentType?, filename? }`. Max 5 MiB. With `format=auto` we sniff magic bytes — if the file is UBL XML it routes through the regular pipeline (`deliveryStatus="pending"`); if it is a Factur-X PDF or a CII and `type` is an invoice, credit note or debit note, the CII is read into a structured document (`deliveryStatus="pending"`, the CII stored as the original, the PDF as the readable copy); otherwise the bytes are persisted on the document file API and the response carries `deliveryStatus="stored"`, `fileId`, and `storedFormat`. Use `format=raw` to archive a Factur-X without it being read. * selfBilledbooleanoptional Self-billed invoice (_autofacturation_): the acting org (`from`) is the **customer** issuing on the supplier's behalf, so `to` becomes the Seller and `from` the Buyer / initiator. Tags the document with UNCL1001 subtype `389`. Only valid for `type=invoice`. Default `false`. For self-billing that also involves a third party, use [`document.parties`](<#multiple-parties>) instead. #### Query parameters — raw-body mode Instead of a JSON body, you can POST the **native ERP payload verbatim** (UBL/CII XML, PDF, image, proprietary file) as the raw request body and carry the wrapper constants in the URL. Triggered whenever `type` is present as a query param. Handy for wiring an ERP / iPaaS webhook straight at this endpoint. * typeenumrequired `invoice``credit-note``debit-note``purchase-order``sales-order``quote``goods-receipt``event` Same enum as the body `type`. Its presence is what switches the endpoint into raw-body mode. * fromstringoptional Sender company. Defaults to the key's organization (`org:`) when omitted. * contentTypestringoptional MIME type of the raw body. Falls back to the `Content-Type` header, then a magic-byte sniff. * filenamestringoptional Filename persisted on the file record. Defaults to `.` or an auto-generated `event-*.`. #### Address object Used by `seller`/`buyer`/`parties[].address` and `shippingAddress`. All fields are optional strings, and every one of them reaches the e-invoice as the party's postal-address group — **BG-5** for the seller, **BG-8** for the buyer. All but `state` are also carried to the party's `billingAddress` record, as `street`, `street2`, `city`, `zipCode` and `country`. * streetstring Street and number — the first address line. → `BT-35` (seller) / `BT-50` (buyer). * streetLine2string Second address line (suite, box…). → `street2`, `BT-36` / `BT-51`. * citystring Town or city. → `BT-37` / `BT-52`. * postalCodestring → `zipCode`, `BT-38` / `BT-53`. * countryISO 3166-1 alpha-2 e.g. `"FR"`, `"BE"`. → `BT-40` / `BT-55`. It decides whose VAT rules the invoice is judged by; absent it, Flowie falls back to the country prefix of the party's VAT number. * statestring Country subdivision — province, région, state. → `BT-39` (seller) / `BT-54` (buyer). Also accepted as `region`. Required by some jurisdictions to fix which local tax applies; optional in the EU. This is the one address field that does not also land on the `billingAddress` record. #### Totals: let your ERP be the source of truth Omit `totals` and Flowie derives every amount from the lines, rounding the VAT once per rate as EN 16931 **BR-CO-17** requires. Send `totals` and **your** figures win outright. [code] "totals": { "netAmount": 1267.16, "vatAmount": 253.43, "grossAmount": 1520.59 } [/code] The short keys `net` / `vat` / `gross` are accepted too. Send them if your ERP has already posted the invoice Your accounting system is the source of truth for what the customer owes, not us. Two systems computing the same total independently will eventually disagree by a cent — rounding a hundred lines is not associative — and then you have to explain which one is right. Sending `totals` removes the question: we carry your amounts through to the UBL unchanged. If you do send them, they must reconcile with the per-line sums, or validation fails. #### Multiple VAT rates VAT is carried **per line** : every `InvoiceLine` has its own `vatRate` and optional `vatCategory`. A document spanning several rates is simply several lines with different `vatRate` values — Flowie sums each line's tax, groups the totals by rate, and renders one `cac:TaxSubtotal` per rate. There is no document-level VAT array (none is accepted). If you also send `totals`, they must reconcile with the per-line sums or validation fails. [code] "lines": [ { "description": "Consulting", "quantity": 10, "unitPrice": 150.00, "vatRate": 21.0, "vatCategory": "S" }, { "description": "E-book (reduced)", "quantity": 1, "unitPrice": 40.00, "vatRate": 6.0, "vatCategory": "S" }, { "description": "Intra-EU goods", "quantity": 1, "unitPrice": 500.00, "vatRate": 0.0, "vatCategory": "K" } ] [/code] Exempt / reverse-charge categories (`E`, `AE`, `K`, `G`, `O`) additionally need a VAT exemption reason — see [Tax exemption & zero rate](<#tax-exemption>). #### Multiple parties The common seller→buyer case needs no `parties` block — `from`/`to` (or `document.seller`/`document.buyer`) are enough, and Flowie injects a Payer party mirroring the buyer automatically. Supply `document.parties` only when the document has **more than two roles** (a `payer`/`payee` distinct from buyer/seller) or when the issuer is not the seller. * Each entry is a `PartyRef`: `role` (`seller`·`buyer`·`payer`·`payee`), `id` (any resolvable id, same grammar as `to`), `name`, `vatNumber`, `initiator`. * **Exactly one** entry must set `initiator: true` — the org the calling key is acting as (tx-docs requires the acting org to be a party). * **Give every party a resolvable identity** — `id` (peppol / vat / siren / siret / duns / gln) or a `vatNumber`. tx-docs requires an organization on every party, so each is resolved to one (auto-created if new); if an id can't be resolved it falls back to the acting org so the document is still accepted. * When `parties` is present it **overrides** the default seller/buyer derivation; Flowie injects nothing and your list is authoritative. * Roles beyond these four aren't modelled by the structured pipeline — use `format=ubl-xml` for those. [code] "parties": [ { "role": "seller", "id": "0009:FR86797978996", "name": "ACME FRANCE", "initiator": true }, { "role": "buyer", "id": "0208:0123456789", "name": "MEGACORP BE" }, { "role": "payee", "vatNumber": "FR90123456789", "name": "ACME FACTORING SAS" } ] [/code] For **self-billing** (the customer issues on the supplier's behalf), prefer the top-level `selfBilled: true` flag — Flowie flips the roles and tags the document UNCL1001 `389`. Use an explicit `parties` list only when self-billing also involves a third party. #### Reception-point addressing (France) In the French PPF/AFNOR model a recipient is not just a legal unit (SIREN) or an establishment (SIRET) — it is a specific **reception point** (_ligne annuaire_). A reception point is addressed with a composed identifier `{siren}_{siret}[_{suffix}]`, where the trailing `suffixeAdressage` selects which reception point inside the SIRET receives the document. The routing platform itself (`identifiantRoutage` — a declared PDP or the default public PPF) is a separate directory concept, resolved for you; you do not encode it here. * **Auto-detected.** Pass the composed form as `to` with no prefix (e.g. `752978775_75297877500027_100003`, or just `75297877500027_001`) and Flowie recognises it by shape — an underscore-joined string carrying a 14-digit SIRET and/or a 9-digit SIREN. You can also be explicit with a `routage:` / `addressing:` prefix (aliases: `adressage:`, `routing:`, `adr:`). * **The participant resolves as usual.** The SIRET (preferred, most specific) or SIREN drives recipient resolution through the ordinary layers — the suffix does not change _who_ the participant is. * **The suffix is business routing, not part of the Peppol id.** It is never folded into `receiverPeppolId`. Instead it travels as document metadata under `metadata.recipientRouting` (`{ "addressingIdentifier": …, "addressingSuffix": … }`) and is echoed back on the response `to` object alongside `peppolId`. An explicit `metadata.recipientRouting` you send yourself is preserved and takes precedence. * **Org ids are safe.** `org_…` / `comp_…` ids also contain an underscore; they are excluded from this detection and never mistaken for a SIREN/SIRET. [code] // request "to": "752978775_75297877500027_100003" // response — participant unchanged, suffix carried alongside "to": { "peppolId": "0009:75297877500027", "addressingIdentifier": "752978775_75297877500027_100003", "addressingSuffix": "100003" } [/code] #### Custom fields & templates Custom fields carry organization-specific data (cost centre, GL account, internal references…) on a document. They are defined by a **template** in your organization and are always scoped to **your own party** : document-level fields attach to your party (the acting org / initiator), line-level fields to a per-line party on your org. * `document.templateId` — UUID of the template to file the document under. It declares the valid custom fields, their types, and whether each is document- or line-level. Omit to use your org's default template for the type. * `document.customFields` — document-level values, an object keyed by the field's **name** (e.g. `"Cost Center"`) or its **definition UUID**. Names are resolved to UUIDs against your org's field definitions; a UUID key is forwarded as-is, while a **name that matches no declared field is rejected with a`400`** — pass the field's UUID or declare it on the `templateId` first. * `line.customFields` — line-level values on each `InvoiceLine`, same key rules. Value shapes follow each field's declared type: a bare string for text/date/number fields, `{ "currency": "EUR", "amount": 1000.00 }` for monetary fields, or an address object (`{ street, street2, city, zipCode, country }`). [code] "document": { "number": "INV-2026-0042", "issueDate": "2026-04-15", "templateId": "8b1f…-template-uuid", "customFields": { "Cost Center": "CC-42", "9f3a…-budget-uuid": { "currency": "EUR", "amount": 1000.00 } }, "lines": [ { "description": "Consulting", "quantity": 10, "unitPrice": 150.00, "vatRate": 21.0, "customFields": { "GL Account": "606100" } } ] } [/code] Custom fields are carried only on the structured `format=json` pipeline — for `ubl-xml`/`cii-xml`, embed them in the XML yourself. #### Which BT fields are sent **What a “BT” is.** An e-invoice is not a picture of an invoice — it is a list of named values that the recipient's software reads. The European standard EN 16931 gives each of those values a number: `BT-1` is the invoice number, `BT-9` the due date, `BT-120` the wording that explains why a line carries no VAT. Your accountant knows these as the mandatory mentions of an invoice; your customer's system knows them as the fields it matches and pays on; your developers see them as the codes a validator quotes when it rejects a document. They are the same thing under three names, and the table below lines all three up. The table below is the subset this endpoint maps; [the full list of all 164 business terms](<../compliance/fr/business-terms.html>) says what France requires of each one. How to read the table **Keeping the books?** Column 2 is the invoice mention you already know, and column 4 tells you when it is legally required. **Running the business?** Column 4 is what happens when the value is missing — a rejected invoice, or one nobody can match and pay. **Building the integration?** Column 1 is the JSON field you send, column 3 the EN 16931 code your customer's validator will name. What you send| What it is on the invoice| BT| Why it matters ---|---|---|--- `number`| Invoice number| BT-1| Mandatory. The reference both sides quote for the life of the invoice. `issueDate`| Invoice date| BT-2| Mandatory. Decides the VAT period the invoice falls in. `dueDate`| Payment due date| BT-9| Drives the customer's payment run, and your late-payment rights. `currency`| Invoice currency| BT-5| Mandatory. A non-EUR invoice must also report its VAT in EUR (`BR-FR-CO-12`). `note`| Free-text mention| BT-22| Where legal wording goes that no coded field carries. `buyerReference`| Customer's own reference (“Service exécutant” for public buyers)| BT-10| Public-sector buyers route on it; without it Chorus Pro refuses the invoice. `orderReference`| Purchase order number| BT-13| Large customers match invoice to order before paying. No order number, no payment. `despatchAdviceReference`| Delivery-note number| BT-16| Same match, against what was actually delivered. `billingReference`, `billingReferenceDate`| The original invoice a credit note corrects, and its date| BT-25, BT-26| Mandatory on credit and debit notes under the French reform (`BR-FR-CO-04`/`05`). `seller.name`, `buyer.name`| Legal name of each party| BT-27, BT-44| Mandatory identification of who sold and who bought. `seller.vatNumber`, `buyer.vatNumber`| VAT identification number| BT-31, BT-48| Mandatory. Your customer reclaims its VAT against this number. `seller.address`, `buyer.address`| Billing address (street, city, post code, country)| BG-5 (BT-35…BT-40), BG-8 (BT-50…BT-55)| Mandatory, and the country decides whose VAT rules apply. `contact` on either party| Contact name, phone, e-mail| BT-41/42/43, BT-56/57/58| Where the customer's platform sends questions about the invoice. `payment.means`| Method of payment| BT-81 or BT-82| Tells the customer how you expect to be paid. `payment.reference`| Payment reference to quote on the transfer| BT-83| What lets you tie an incoming transfer back to this invoice. `payment.iban`| Bank account to credit, and its holder| BT-84, BT-85| Where the money actually lands. `payment.bic`| Bank identifier| BT-86| Required by some banks for cross-border transfers. `delivery.actualDeliveryDate`| Delivery date| BT-72| On goods, this can be the date the VAT becomes chargeable. `delivery.deliveryLocation.country`| Country the goods were delivered to| BT-80| Mandatory on intra-community supplies (`BR-IC-12`). `incoterms`, `incotermsLocation`| Delivery terms code, and the named place it refers to| EXT-FR-FE-185, EXT-FR-FE-186| Who bears carriage and risk — what your customer reconciles freight charges against. AFNOR's French extension, not part of the EN 16931 core, so a recipient on the core profile may ignore it. One of `1`, `2` (UNTDID 4053) or an Incoterms 2020 code: `EXW`, `FCA`, `CPT`, `CIP`, `DAP`, `DPU`, `DDP`, `FAS`, `FOB`, `CFR`, `CIF`. The place is optional; the code is not. line — `description`, `quantity`, `unit`, `unitPrice`| Description, quantity, unit, unit price excl. VAT| BT-153, BT-129, BT-130, BT-146| Mandatory. The line detail every invoice has to show. line — `vatRate`, `vatCategory`| VAT rate and VAT category of the line| BT-152, BT-151| Mandatory. Sets the VAT charged, line by line. line — `vatExemptionReason`, `vatExemptionCode`| Wording that justifies charging no VAT, and its official code| BT-120, BT-121| An exempt or reverse-charge invoice without it is rejected outright. See [Tax exemption & zero rate](<#tax-exemption>). line — `period`| Period the line covers| BT-134, BT-135| How a subscription or a service billed per period states what it covers. line — `itemCode`, `buyerItemCode`, `itemDescription`| Your item reference, your customer's item reference, long description| BT-155, BT-156, BT-154| Your customer's system matches on _its own_ part number, not yours. line — `orderLineReference`, `objectIdentifier` \+ `objectIdentifierScheme`| Order line the line answers, and a document it refers to with the kind of document that is| BT-132, BT-128 + BT-128-1| Line-by-line matching when one invoice covers several orders or deliveries. EN 16931 requires the scheme whenever the identifier is present (`BR-CO-24`). line — `priceBaseQuantity` \+ `priceBaseUnit`| Number of units the unit price applies to, and their unit| BT-149 + BT-150| For a price quoted per batch — « per 1000 pieces » — rather than per single unit. Without it the price reads as a per-unit price and the line total looks wrong by orders of magnitude. line — `netAmount`| The line's own net amount, VAT excluded| BT-131| Stops Flowie recomputing the line from `quantity × unitPrice`. Reach for it when the line carries a discount or a surcharge, or when its price is quoted per batch: 519.1 ML at 12.60 less a 3 % line discount is 6344.44, not the 6540.66 the multiplication gives. line — `despatchAdviceReference`, `despatchAdviceLineReference`| Delivery note the line arrived on, and the line within it| EXT-FR-FE-140, EXT-FR-FE-141| AFNOR's French extension to EN 16931, for an invoice spanning several deliveries. Defaults to the document's `despatchAdviceReference`. `totals`| Pre-computed net, VAT and gross for the whole invoice| BT-109, BT-112, BT-115, BT-117| Overrides the sums Flowie takes from your lines. Send it when your accounting system is the authority on the figures; omit it and the lines are summed for you. `exchangeRate`| Rate your books posted the invoice at| feeds BT-111| A non-EUR invoice must also report its VAT in euros (`BR-FR-CO-12`). Stating your own rate keeps that figure tied to your ledger instead of a market feed. `parties`| The same parties, stated as an explicit role-tagged list| BG-4 / BG-7, exactly as `seller` / `buyer` above| How you state a `payer` or `payee` distinct from the buyer, or self-bill. Overrides `seller`/`buyer` when present. `contacts` on either party| Contact e-mail addresses| BT-43 / BT-58| The first entry becomes the party's contact e-mail when `contact.email` is absent. ##### Fields that do not become a BT Everything else the `document` body accepts is listed here, so that no field you can send is left unaccounted for. Two of these are worth an accountant's attention: `allowances`/`charges` and `payment.discountTerms` are accepted today and **not yet placed on the e-invoice** , so a discount stated only there will not be visible to your customer's software. What you send| What it is for| What happens to it ---|---|--- `templateId`| Which of your organization's e-invoicing templates to file the document under| Decides which BT slots exist at all, so it governs the table above. Not itself a value on the invoice. `customFields`, and `customFields` on a line| Your own data on the document — cost centre, GL account, an internal reference| Carried on the document, attached to your own party. Organization-specific by definition, so outside EN 16931 and invisible to your customer's validator. `allowances`, `charges`| Discounts and surcharges for the whole invoice (BG-20 / BG-21) — a loyalty discount, shipping| **Accepted, not yet rendered.** Send `format=ubl-xml` to carry them today, or fold the amount into a line. `attachments`| Supporting documents embedded in the invoice (BG-24) — a timesheet, a signed delivery note| **Accepted, not yet rendered.** Post the file with `format=auto`/`raw` and the top-level `file`, or embed it in `format=ubl-xml`. `payment.discountTerms`| Early-payment discount — « 2 % if paid within 10 days »| **Accepted, not yet rendered.** EN 16931 carries it in the payment terms (BT-20); put the wording in `note` (BT-22) if your customer has to read it. `shippingAddress` on either party| That party's delivery address| Accepted and stored, but the deliver-to country the invoice states (BT-80) is read from `delivery.deliveryLocation`, not from here. `type`, `format`, `from`, `to`, `file`| The envelope — what kind of document, in what shape, from whom, to whom| Routing and recognition, not invoice content. `from` and `to` resolve to the Peppol participants that deliver the document, and the parties they resolve to fill the party BTs above. ##### What decides whether a field is actually sent Your e-invoicing template declares a slot for each BT it supports and marks each one _required_ or _optional_. Two rules decide what fills them: * **Anything you state is sent** , required slot or optional one. If you put a value in the request — the customer's order number, your IBAN, the exemption wording on a line — it reaches its BT. This is worth stating because it used to be false: an optional slot was skipped, so a value you sent could be accepted with `200` and then carried by nothing. Real French e-invoicing templates mark almost everything past the EN 16931 core optional, so that silence covered a lot of ground. * **Anything Flowie works out for you is sent only where the template requires it.** Totals summed from your lines, the document type code (`BT-3`), the line numbering (`BT-126`), the VAT category of the breakdown (`BT-118`): these are our inference, not your statement, so they fill a required slot and stay out of an optional one. Put plainly — we will complete an invoice for you, but we will not put words in your mouth where you did not have to speak. Where both apply, what you stated wins: `BT-40`, the seller's country, comes from `seller.address.country` when you send an address, and falls back to the country prefix of the VAT number when you don't. One limit worth knowing: **a BT only renders if your template declares a slot for it.** The mapping fills the slots your template has; it does not create new ones. If a value you send is not appearing on the e-invoice, ask your Flowie contact which BT-* fields your template declares. #### Tax exemption & zero rate Each line's `vatCategory` is a UNCL5305 code. Use `S` for normal taxable supplies. The categories below carry `vatRate: 0` and cover zero-rate, exemption, reverse charge, and out-of-scope supplies: Code| Meaning| Typical use| Exemption reason required? ---|---|---|--- `S`| Standard rate| Normal VAT (e.g. 20%, 21%)| No `Z`| Zero rated| Taxable at 0%| No `E`| Exempt| VAT-exempt supply| **Yes** `AE`| Reverse charge| Buyer accounts for VAT (intra-EU B2B)| **Yes** `K`| Intra-community supply| Intra-EU supply of goods| **Yes** `G`| Free export item| Export outside the EU| **Yes** `O`| Not subject to VAT| Outside the scope of VAT| **Yes** Exempt categories need a reason EN16931 / Peppol BIS 3.0 schematron **rejects** an invoice that uses `E`, `AE`, `K`, `G`, or `O` unless it also carries a VAT exemption reason — a code from the [VATEX]() list (BT-121) and/or free text (BT-120). Send it **per line** with `vatExemptionReason` and `vatExemptionCode`: Flowie lifts the reason to the document VAT breakdown (BG-23) _and_ repeats it on each line’s `cac:ClassifiedTaxCategory`, which the French `BR-FREXT--08rev` reconciliation rule requires — a reason present only at document level makes that rule count zero lines and warn on BT-92 / BT-99 / BT-116 / BT-131. `Z` (zero-rated) and `S` need no reason. **French franchise en base needs the CODE, not just the text.** `BR-FR-CO-16` requires `BT-118 = "E"` _and_ `BT-121 = "VATEX-FR-FRANCHISE"`. Free text in BT-120 alone does not satisfy it. If the seller has no VAT number it must also repeat its SIREN in BT-32. **And neither survives the reform flows.** `BR-FR-MAP-08` / `-09` instruct the platform to transcode `BT-118 = "E"` \+ `VATEX-FR-FRANCHISE` to `"Z"` and to **drop BT-121 and BT-120** in flux 1 and flux 10.1. So BT-120 must not be the only carrier of anything the recipient has to read: put that wording in the document `note` (BT-22) as well, which is passed through. **Two aggregation limits, both of which drop BT-120 silently.** 1\. **One reason per document.** The breakdown has a single BT-120 slot. If two exempt lines carry _different_ `vatExemptionReason` texts, neither is emitted. Use the same wording on every exempt line. 2\. **Do not mix standard-rated and exempt lines.** When a document contains any `vatCategory: "S"` line, the breakdown collapses to `S` and BT-120 / BT-121 are not emitted at all. Split the exempt lines onto their own document, or send `format=ubl-xml` with repeated `cac:TaxSubtotal` groups. The exempt `` block to include in your UBL (both at line level under `` and in the document ``): [code] AE 0 VATEX-EU-AE Reverse charge VAT [/code] #### Returns `201 Created` with the [document object](<#documents>). For structured payloads `deliveryStatus` starts as `pending`; listen for `document.delivered` or `document.failed`. For raw uploads `deliveryStatus="stored"` and the response includes `fileId` \+ `storedFormat`. ##### Request — full invoice [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: inv-2026-0417" \ -d '{ "type": "invoice", "from": "comp_01HXYZ…", "to": "0208:9876543210", "document": { "number": "INV-2026-0417", "issueDate": "2026-04-25", "dueDate": "2026-05-25", "currency": "EUR", "buyerReference": "SERV-FIN-042", "orderReference": "PO-91234", "seller": { "name": "ACME BVBA" }, "buyer": { "name": "Globex SRL", "vatNumber": "IT01234567890" }, "payment": { "means": "credit_transfer", "iban": "BE68539007547034", "bic": "BPOTBEB1", "reference": "INV-2026-0417" }, "lines": [ { "description": "Consulting services — April 2026", "quantity": 10, "unit": "hours", "unitPrice": 150.00, "vatRate": 21, "vatCategory": "S" }, { "description": "Travel expenses", "quantity": 1, "unit": "lump", "unitPrice": 450.00, "vatRate": 21, "vatCategory": "S" } ] } }' [/code] ##### Response 201 Created 422 Unreachable [code] { "id": "doc_01HY7AB9C2DE3FG", "type": "invoice", "direction": "outgoing", "number": "INV-2026-0417", "issueDate": "2026-04-25", "dueDate": "2026-05-25", "currency": "EUR", "grossAmount": 2359.50, "netAmount": 1950.00, "vatAmount": 409.50, "sender": { "peppolId": "0208:0123456789", "name": "ACME BVBA" }, "receiver": { "peppolId": "0208:9876543210", "name": "Globex SRL" }, "status": "sent", "deliveryStatus": "pending", "lifecycleStatus":"issued", "sentAt": "2026-04-25T10:05:00Z", "createdAt": "2026-04-25T10:05:00Z" } [/code] [code] { "error": { "type": "delivery_error", "code": "RECIPIENT_NOT_FOUND", "message": "0208:9876543210 is not registered on Peppol for document type 'invoice'.", "requestId":"req_…" } } [/code] ### Batch send POST/v1/documents/send/batch Submit up to 100 documents in one request. Results come back in the same order as the input; failures don't poison successful sends. #### Request body * documentsSendItem[]required Array of send items. Each item takes the same fields as [Send a document](<#send-document>) (`type`, `format`, `from`, `to`, `document`, `xml`, `file`) plus an optional per-item `idempotencyKey`. [code] { "documents": [ { "type": "invoice", "from": "comp_…", "to": "0208:…", "document": {…} }, { "type": "invoice", "from": "comp_…", "to": "0208:…", "document": {…} } ] } [/code] [code] { "results": [ { "ok": true, "id": "doc_01…", "status": "sent" }, { "ok": false, "error": { "code": "INVALID_REQUEST", "message": "…" } } ], "sent": 1, "failed": 1 } [/code] ### Validate without sending POST/v1/documents/validate Run full Peppol BIS schematron + recipient reachability checks without delivering anything. Handy as a CI step before switching a customer live. **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. #### Request body Same shape as [Send a document](<#send-document>), minus the raw `file` upload mode. * typeenumrequired `invoice``credit-note``debit-note``purchase-order``purchase-request``sales-order``quote``goods-receipt``event` * formatenumoptional `json``ubl-xml``cii-xml` * fromstringrequired Sender company — `comp_…`, `vat:…`, or `peppol:…`. * tostringrequired Recipient Peppol participant identifier (drives the reachability check). * documentDocumentBodyrequired when format=json Same structured body as Send. See [schema](<#send-document>). * xmlstringrequired when format=ubl-xml / cii-xml [code] { "valid": false, "errors": [ { "rule": "BR-16", "message": "An Invoice shall have at least one line.", "path": "/Invoice/InvoiceLine" } ], "warnings": [], "recipientReachable": true } [/code] ### List documents GET/v1/documents Paginated list across both directions — this is the polling half of [receiving documents](<../guides/receive-invoices.html>), for integrations that cannot expose a webhook endpoint. **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Cursor-paginated like every list endpoint: the response is `{ "data": […], "hasMore": true, "cursor": "…" }`. Keep passing the returned `cursor` until `hasMore` is `false`; never hard-code an offset. #### Query parameters * directionenumoptional `incoming``outgoing` * typeenumoptional `invoice``credit-note``debit-note``purchase-order``sales-order``quote``goods-receipt``event` * statusstringoptional Exact `lifecycleStatus` — org-specific and may be localized (e.g. `draft`, `sent`). The delivery values `delivered` / `failed` are routed to `deliveryStatus`. * deliveryStatusenumoptional `pending``delivered``failed``rejected` Peppol network delivery state — use this (not `status`) to find delivered documents. * from / todateoptional Filter by `issueDate` range. * amountMin / amountMaxnumberoptional Gross amount bounds. * companyIdstringoptional * searchstringoptional Full-text over number, party names, references, note. * limit / cursorpaginationoptional ### Advanced search POST/v1/documents/search The POST counterpart of [List documents](<#list-documents>). It runs against the same index and returns the same paginated `{ "data": […], "hasMore": …, "cursor": … }` shape and the same tenant scoping — the only difference is that the query travels in a JSON body instead of the query string. Reach for it when a filter set is too large for a URL, or when you need a nested boolean predicate that the flat query params can't express. The `filters` object is a predicate tree. Each leaf is `{ "": }`, where the match is either a bare scalar (exact match) or an operator object like `{ "$gte": 1000 }`. Wrap leaves in the boolean keys `$and` and `$or` to nest them to any depth. Field names are the document's **stored field names** (e.g. `documentType`, `issuedAt`) — _not_ the friendly query-string params of the list endpoint — so consult the table below rather than reusing the `GET` parameter names. #### Request body * querystringoptional Free-text query — identical semantics to the list `search` param (matches document number and party names). * filtersobjectoptional Predicate tree over the fields in the table below, combined with the `$and` / `$or` keys. A bare value is an exact match, a bare array an any-of match; an operator object narrows it. See the operator table and example. * sortobjectoptional Map of `field → direction`, e.g. `{"issuedAt": "desc"}`. Multiple keys are applied left to right. A bare string (`"issuedAt:desc"`) is also accepted, as is a list mixing either form. Sort on any field in the table below, including `createdAt` / `updatedAt`. * limitintegeroptional Page size, clamped to `1`–`100`. Default `20`. * cursorstringoptional Opaque pagination cursor from the previous page. Keep passing it until `hasMore` is `false`. #### Filterable fields Field| Type| Notes ---|---|--- `documentType`| string| Uppercase — `INVOICE`, `CREDIT_NOTE`, `DEBIT_NOTE`, `PURCHASE_ORDER`, … `lifecycleStatus`| string| Exact business status. Org-specific and may be localized (e.g. `sent`, `Reçue par la plateforme`). `issuedAt`| date| Issue date. Use `$gte` / `$lte` for a range — this is the field behind the list endpoint's `from` / `to`. `dueDate`| date| Payment due date. `totalAmountDue`| number| Gross amount. Use `$gte` / `$lte` for bounds (the list endpoint's `amountMin` / `amountMax`). `number`| string| Document number. `currency`| string| ISO 4217 code. `sellerId` / `payerId`| string| Party org ids. Every result is already scoped to your org; add one of these to pin the direction — your org as `sellerId` is outgoing, as `payerId` is incoming. `createdAt` / `updatedAt`| datetime| Ingest / last-change timestamps. Handy as `sort` keys. #### Operators Values follow Strapi v4 filter semantics. A bare scalar is shorthand for `$eq`, a bare array for `$in`. An operator outside this table is refused with a `400` naming it. Operator| Meaning ---|--- `$eq` / `$ne`| Equals / not equal (bare scalar ⇒ `$eq`). `$gt` / `$gte`| Greater than / greater-or-equal. `$lt` / `$lte`| Less than / less-or-equal. `$in` / `$notIn`| Matches / does not match any value in a JSON array (bare array ⇒ `$in`). `$contains` / `$notContains`| Case-sensitive substring match / its negation. `$null` / `$notNull`| Field is unset / set. Takes `true`. `$and` / `$or`| Boolean combinators over an array of nested predicate blocks. There is no `$not`. `companyId` is a tenant selector, not a filter A `companyId` key inside `filters` is pulled out and used to **scope the query to that company** (validated against your token — it can narrow to a company you manage, never widen to another tenant). It is not matched as a document column. Pass it as a bare id or `{"$eq": ""}`. Two list filters have no structured equivalent The list endpoint's `direction` and `deliveryStatus` are computed conveniences, not stored columns — putting them in `filters` will not match. For direction, filter on `sellerId` / `payerId` as above; for Peppol delivery state, use `GET /v1/documents?deliveryStatus=…`. #### Filter recipes Worked `filters` for the queries people actually build. Sibling keys inside a block are **AND** ed for you — you only need an explicit `$and` when you want to group an `$or` alongside other conditions. **Invoices over €1,000, newest first.** Two sibling keys ⇒ implicit AND. [code] { "filters": { "documentType": { "$eq": "INVOICE" }, "totalAmountDue": { "$gte": 1000 } }, "sort": { "issuedAt": "desc" } } [/code] **Anything issued in Q1 2026.** A single field with two bounds is a closed range. [code] { "filters": { "issuedAt": { "$gte": "2026-01-01", "$lte": "2026-03-31" } } } [/code] **Invoices _or_ credit notes.** `$in` matches any value in the list — cleaner than an `$or` of equalities. [code] { "filters": { "documentType": { "$in": ["INVOICE", "CREDIT_NOTE"] } } } [/code] **Everything that needs attention: disputed, or high-value.** An `$or` block holds an array of alternatives. [code] { "filters": { "$or": [ { "lifecycleStatus": { "$eq": "disputed" } }, { "totalAmountDue": { "$gte": 10000 } } ] } } [/code] **Unpaid invoices in a window.** Group an `$or` with other conditions using an explicit `$and`, and exclude a status with `$ne`. [code] { "filters": { "$and": [ { "documentType": { "$eq": "INVOICE" } }, { "lifecycleStatus": { "$ne": "paid" } }, { "issuedAt": { "$gte": "2026-01-01" } } ] }, "sort": { "dueDate": "asc" } } [/code] **Free text, then narrow structurally.** `query` runs the full-text search; `filters` refines it. [code] { "query": "ACME", "filters": { "issuedAt": { "$gte": "2026-01-01" } } } [/code] **One company in a multi-tenant account.** `companyId` scopes the query to that company (validated against your token); the rest still filters normally. [code] { "filters": { "companyId": "comp_abc123", "documentType": { "$eq": "INVOICE" } } } [/code] **Outgoing only.** There's no `direction` field — pin the direction by matching your own org as the seller (use `payerId` for incoming). Results are already scoped to your org, so this just narrows the role. [code] { "filters": { "sellerId": { "$eq": "org_685a5670efafaa26ebf0128e" } } } [/code] ### Retrieve a document GET/v1/documents/{document_id} ### Download XML GET/v1/documents/{document_id}/xml Returns the signed UBL XML with `Content-Type: application/xml`. ### Download PDF GET/v1/documents/{document_id}/pdf Returns a human-readable PDF rendering. ### Structured view GET/v1/documents/{document_id}/structured Flat, scalar-only representation — perfect for pushing to a data warehouse or spreadsheet. ### Document actions POST/v1/documents/{document_id}/actions Non-lifecycle operations: `mark-read`, `mark-unread`, `archive`, `unarchive`, `tag`, `untag`, `assign`, `unassign`, `add-note`, `link`. * actionenumrequired `mark-read``mark-unread``archive``unarchive``tag``untag``assign``unassign``add-note``link` * tagstringconditional Required by the tag / untag actions. * userIdstringconditional Required by the assign / unassign actions. * notestringconditional Required by the add-note action. * relatedDocumentIdstringconditional The document to link to — required by the link action. [code] curl "…/v1/documents?direction=outgoing&status=sent&from=2026-04-01&amountMin=500" \ -H "Authorization: Bearer $KEY" [/code] ##### Structured response [code] { "id": "doc_01…", "type": "invoice", "direction": "incoming", "number": "INV-2026-0417", "issueDate": "2026-04-25", "dueDate": "2026-05-25", "currency": "EUR", "grossAmount": 2359.50, "netAmount": 1950.00, "vatAmount": 409.50, "status": "delivered", "lifecycleStatus": "approved", "deliveryStatus": "delivered", "senderPeppolId": "0208:0123456789", "senderName": "ACME BVBA", "senderVatNumber": "BE0123456789", "receiverPeppolId": "0208:9876543210", "receiverName": "Globex SRL", "receiverVatNumber": "IT01234567890", "buyerReference": "SERV-FIN-042", "orderReference": "PO-91234", "paymentIban": "BE68539007547034", "paymentReference": "INV-2026-0417", "receivedAt": "2026-04-25T10:05:08Z", "sentAt": "2026-04-25T10:05:00Z", "createdAt": "2026-04-25T10:05:00Z", "updatedAt": "2026-04-25T10:05:08Z" } [/code] ## Lifecycle Once a document is delivered, it moves through a business-level state machine: `issued → under_review → approved → partially_paid → paid`, with side branches for `rejected` and `disputed`. Flowie persists the history, enforces allowed transitions, and **reports each relevant change to the national compliance platform automatically**. Put it on hold before you refuse Refusing (`rejected`) is **terminal** — in France it transmits _210 Refusée_ , which cancels the invoice for VAT and forces the supplier to issue a corrective. If the disagreement might still be resolved, **prioritize the reversible paths first** : `disputed` to contest the content, or `disputed` with `reasonCode:"suspended"` to put the invoice **on hold** pending documents — both keep it alive and can resolve back to approval. Reach for `rejected` only when you are certain the invoice must be cancelled and re-issued. [Choosing the right status & reason →](<#reason-codes>) ### Retrieve lifecycle history GET/v1/documents/{document_id}/lifecycle Full event log, current status, allowed transitions, and per-country compliance state. When the current status stems from a failed validation, `currentStatusReason` carries the failing EN 16931 / CTC-FR schematron rule ids. ### Update lifecycle status POST/v1/documents/{document_id}/lifecycle #### Body * statusenumrequired `under_review``approved``rejected``partially_paid``paid``disputed` * reasonCodeenumconditional `NON``REF``LEG``REC``QUA``DEL``PRI``QTY``ITM``PAY``UNR``FIN``PPD``OTH` Required for **rejected** and **disputed**. One of the 14 [official Peppol status reason codes](<#reason-codes>) (OPStatusReason) — full table below. 🇫🇷 France: an AFNOR motif code (XP Z12-012 annex) is forwarded verbatim as MDT-113, and `suspended` on a _disputed_ call transmits _208 Suspendue_ — see [FR refusal & rejection](<../compliance/fr/refusal-rejection.html#motifs>). * reasonstringoptional Free-text explanation shown to the counterparty (forwarded verbatim as MDT-114 in France). Always pair it with reasonCode **OTH**. * notestringoptional * paymentDatedateconditional Required for `paid` / `partially_paid`. * paymentAmount / paymentCurrency / remainingAmountnumber / ISO 4217conditional * paymentReferencestringoptional ### Batch lifecycle update POST/v1/documents/lifecycle/batch Up to 500 updates in one call. Atomic per document; failures are reported per item. #### Request body * updatesobject[]required Array of updates. Each entry is a [lifecycle update body](<#update-lifecycle>) (`status`, `reason`, `note`, `paymentDate`, …) plus the target `documentId`. Allowed transitions Trying to skip states (e.g. `issued → paid` without a prior `approved`) returns `409 invalid_transition` and a hint listing legal next states. Fetch [the history](<#get-lifecycle>) to see what's allowed now. [code] curl -X POST …/v1/documents/doc_abc/lifecycle \ -H "Authorization: Bearer $KEY" \ -d '{ "status": "paid", "paymentDate": "2026-04-25", "paymentAmount": 2359.50, "paymentCurrency": "EUR", "paymentReference": "PAY-2026-0001" }' [/code] [code] { "documentId": "doc_abc", "previousStatus": "approved", "currentStatus": "paid", "updatedAt": "2026-04-25T10:35:00Z", "compliance": { "reportedTo": ["PPF","SDI"], "status": "reported", "nextCheckAt":"2026-04-25T10:40:00Z" }, "allowedTransitions": ["disputed"] } [/code] ### Update lifecycle status by invoice number POST/v1/documents/by-number/{number}/lifecycle Move a document to a new status, targeting it by its **invoice number** (the value printed on the invoice) instead of Flowie's internal `documentId`. Integration partners often only hold the human-readable number, not our id. This route resolves the number to exactly one document **scoped to your organization** , then applies the _same_ transition as [`POST /v1/documents/{document_id}/lifecycle`](<#update-lifecycle>) — identical state-machine validation, the same transaction-documents update, the same PPF/SDI compliance reporting for FR/IT documents, and the same `lifecycle.updated` webhook. **The request body and the success response are identical to the id-based route** (see [Update lifecycle status](<#update-lifecycle>) for the full field list), so payment fields (`paymentDate`, `paymentAmount`, `paymentCurrency`, `paymentReference`) are required for `paid` / `partially_paid` here too. Because invoice numbers are **not unique** (the same number can exist as a sale and a purchase, or across periods), resolution is strict: Matches in your org| Result ---|--- 0| `404 not_found` — no document with that invoice number that your organization is a party on. exactly 1| `200` — the transition is applied and the updated document is returned. more than 1| `409 conflict` — ambiguous; re-issue the call against [`POST /v1/documents/{documentId}/lifecycle`](<#update-lifecycle>) with the specific `documentId`. Tenant-scoped resolution Matching is always confined to documents your organization is a party on — an invoice number belonging to another tenant is invisible and resolves to `404`, never another org's document. [code] curl -X POST …/v1/documents/by-number/INV-2026-0042/lifecycle \ -H "Authorization: Bearer $KEY" \ -d '{ "status": "approved", "note": "Invoice verified against PO" }' [/code] [code] { "documentId": "doc_test001", "previousStatus": "received", "currentStatus": "approved", "updatedAt": "2026-04-15T10:32:18.421Z", "compliance": {}, "allowedTransitions": ["partially_paid", "paid", "disputed"] } [/code] ## Directory Peppol's public directory lets you find any registered participant across every access point in Europe. Use these endpoints to verify reachability _before_ sending. ### Search directory GET/v1/directory/search Find any participant registered on the Peppol network. You must supply at least one search criterion — `q` or `vatNumber` — and **a free-text`q` must be scoped by `country`** (a bare SIREN/SIRET or a `vatNumber` already carries its country, so it's exempt). Matching on `q` is fuzzy (substring). By default results are collapsed to one row per legal entity — the directory lists each company once per identifier scheme. * qstringconditional Free-text company name, e.g. `epsa`. A bare 9- or 14-digit value is treated as a French SIREN/SIRET and routed to an exact lookup. **One of`q` or `vatNumber` is required.** * vatNumberstringconditional Exact VAT number, e.g. `BE0633501357` or `FR26921376265`. **One of`q` or `vatNumber` is required.** * countryISO 3166-1 α-2conditional **Required when searching by a free-text`q`**, e.g. `BE`. Optional (a filter) otherwise. * city / postalCodestringoptional Further geographic filters. * naceCodesstring[]optional Filter by NACE business-activity code(s). * documentTypesstring[]optional Only return participants that can receive these types. * includeSubEntitiesbooleanoptional Default `false` (one row per legal entity). Set `true` to return every Peppol identifier-scheme / establishment row — needed when you want the exact routable participant ID. For a French SIREN or SIRET this includes the company's Peppol addressing lines (`0225:{siren}_{suffix}`). * detailenumoptional `basic``full` Default `basic` (directory fields only). `full` enriches each row with access-point / SMP detail — slower, one lookup per result. A French row is resolved from its company's Peppol lines, read once for all the rows of the same company. * limitintegeroptional Max distinct participants to return. Default `20`. ### Lookup Peppol ID GET/v1/directory/{peppol_id} Resolve a participant ID — `0009:921376265` for a French SIREN, `0208:0123456789` for a Belgian CBE — against local registrations, the PPF annuaire (French IDs) and the Peppol Directory. Returns `404` when no source knows it. A French company is usually listed on Peppol several times: `0225:{siren}` and suffixed addressing lines such as `0225:{siren}_hrs`. `participants` lists every line of the company. For a company ID such as `0009:{siren}`, `smpStatus` and `documentTypes` describe the company across its lines; for one line looked up directly, they describe that line alone. ### Verify recipient POST/v1/directory/verify The **recommended pre-flight check** before every send. Tells you whether the recipient exists _on the Peppol network_ , whether a participant actually advertises the document type, and where to send it. Being listed in a national company register is not being reachable: a French company the PPF annuaire knows but the network does not answers `exists: false`. For a French company ID (`0009:{siren}`) the answer covers its addressing lines, and `acceptedBy` names the ones that take this document — the company ID itself is not an address. #### Request body * peppolIdstringrequired Recipient Peppol participant identifier, e.g. `0208:9876543210`. * documentTypestringrequired Document type to check reachability for, e.g. `INVOICE`. [code] curl "…/v1/directory/search?q=epsa&country=BE&limit=20" \ -H "Authorization: Bearer $KEY" [/code] [code] { "data": [ { "peppolId": "0208:0655917760", "name": "EPSA MARKETPLACE Belgium SRL", "country": "BE", "city": null, "postalCode": null, "vatNumber": null, "documentTypes": ["invoice", "credit-note"], "accessPoint": null } ], "hasMore": true, "cursor": null } [/code] [code] curl -X POST …/v1/directory/verify \ -H "Authorization: Bearer $KEY" \ -d '{ "peppolId": "0208:9876543210", "documentType":"INVOICE" }' [/code] [code] { "peppolId": "0208:9876543210", "exists": true, "canReceive": true, "recipientName": "Globex SRL", "documentType": "INVOICE", "accessPoint": "peppol.ehealth.fgov.be", "acceptedBy": [] } [/code] ## Partners A **partner** is a counterparty you regularly transact with — a customer, a supplier, or both. Partners store defaults (preferred currency, payment terms, contacts, routing ID) so you don't have to supply them on every send. ### Create a partner POST/v1/partners At least one of `peppolId` or `vatNumber` is required. * peppolIdstringconditional Pattern `^\d{4}:.+$`. * vatNumberstringconditional * roleenumoptional `supplier``buyer``both` * contactName / contactEmailstringoptional * defaultsobjectoptional `currency`, `paymentTermsDays`, `note`, `orderReference`… * tags / metadataarray / objectoptional ### List partners GET/v1/partners #### Query parameters * roleenumoptional `supplier``buyer``both` * searchstringoptional Full-text over name, VAT, and Peppol ID. * countryISO 3166-1 α-2optional * tagsstringoptional Comma-separated tag filter. * hasActivitybooleanoptional Only partners with at least one sent/received document. * peppolStatusstringoptional * sortBy / orderstringoptional Field to sort by and direction (`asc` / `desc`). * limit / cursorpaginationoptional ### Retrieve partner GET/v1/partners/{partner_id} Path accepts `part_…`, `vat:…`, or `peppol:…`. ### Update partner PATCH/v1/partners/{partner_id} #### Request body All fields optional — same shape as [create](<#create-partner>). * peppolIdstringoptional * vatNumberstringoptional * roleenumoptional `supplier``buyer``both` * contactName / contactEmailstringoptional * defaultsobjectoptional * tags / metadataarray / objectoptional ### Delete partner DEL/v1/partners/{partner_id} ### Retrieve a partner by account number GET/v1/partners/by-account-number Reverse lookup: resolve the partner behind one of your own internal customer or supplier account numbers. The value is matched against a custom field on your partner records — scoped to your organization — and the matched record is resolved to the partner’s full profile (name, VAT number, country). The custom field must be populated on the partner records you want to reach. Returns `404` when no partner carries that value. #### Query parameters * valuestringrequired The exact account number to look up. * fieldstringoptional Name of the custom field holding the account number. Defaults to `Numéro de compte interne`. * entityTypestringoptional Entity the custom field is attached to. Defaults to `PARTNERSHIP`. Requires the `partners.read` scope. Returns a [partner](<#get-partner>) object. ### List a partner’s invoices GET/v1/partners/{partner_id}/invoices Every invoice exchanged between your organization and this partner — the partner is matched as either seller or payer. Results are always scoped to your organization: you only ever see documents your organization is a party to. #### Query parameters * limitintegeroptional 1–100. Defaults to 20. * cursorstringoptional Opaque cursor returned by the previous page. Returns a paginated list of [document](<#list-documents>) summaries. [code] curl -X POST …/v1/partners \ -H "Authorization: Bearer $KEY" \ -d '{ "peppolId": "0208:9876543210", "role": "buyer", "contactName":"Laura Rossi", "contactEmail":"laura@globex.it", "defaults": { "currency": "EUR", "paymentTermsDays": 30 }, "tags": ["strategic","italy"] }' [/code] [code] { "id": "part_01HXY…", "peppolId": "0208:9876543210", "name": "Globex SRL", "vatNumber": "IT01234567890", "country": "IT", "role": "buyer", "contactName": "Laura Rossi", "contactEmail":"laura@globex.it", "peppolStatus":"active", "defaults": { "currency": "EUR", "paymentTermsDays": 30 }, "tags": ["strategic","italy"], "enrichment": { "naceCode": "70.22" }, "stats": { "documentsSent": 12, "documentsReceived": 0 }, "metadata": {}, "createdAt": "2026-04-25T10:00:00Z", "updatedAt": "2026-04-25T10:00:00Z" } [/code] ## Purchase orders A read-only view over the purchase orders already flowing through Flowie. Use it to walk from an order to the invoices billed against it — handy for reconciliation and for answering “what has been invoiced on this order so far?”. ### List a purchase order’s invoices GET/v1/purchase-orders/{purchase_order_id}/invoices Every invoice linked to the given purchase order. Results are always scoped to your organization: you only ever see documents your organization is a party to. An order with nothing billed against it returns an empty list, not a `404`. #### Query parameters * limitintegeroptional 1–100. Defaults to 20. * cursorstringoptional Opaque cursor returned by the previous page. Returns a paginated list of [document](<#list-documents>) summaries. [code] curl …/v1/purchase-orders/PO-2026-0042/invoices \ -H "Authorization: Bearer $KEY" [/code] [code] { "data": [ { "id": "doc_01HXY…", "type": "INVOICE", "number": "INV-2026-001", "issueDate":"2026-04-14", "currency": "EUR", "amount": 1210.0, "status": "received", "direction":"incoming" } ], "hasMore": false, "cursor": null } [/code] ## Webhooks Webhooks deliver events to your HTTPS endpoint. Every delivery is signed (`X-Flowie-Signature`), retried with exponential backoff, and recorded for replay. See the [Webhook cookbook]() for signing, retries, and idempotency patterns. ### Create a webhook POST/v1/webhooks * urlhttps URLrequired * eventsstring[]required `document.received``document.updated` `document.sent``document.delivered` `document.failed``lifecycle.updated` `company.smp_registered``*` * secretstringoptional Auto-generated if omitted. Used for HMAC-SHA256 signing. * companyIdstringoptional Scope events to a specific managed company. ### List webhooks GET/v1/webhooks #### Query parameters * companyIdstringoptional Only return webhooks scoped to this managed company. ### Update webhook PATCH/v1/webhooks/{webhook_id} #### Request body * urlhttps URLoptional * eventsstring[]optional * rotateSecretbooleanoptional Set `true` to mint a new signing secret (returned once in the response). ### Delete webhook DEL/v1/webhooks/{webhook_id} [code] curl -X POST …/v1/webhooks \ -H "Authorization: Bearer $KEY" \ -d '{ "url": "https://example.com/hooks/peppol", "events": ["document.received","document.delivered","document.failed"], "secret": "whsec_rotate_me" }' [/code] [code] { "id": "wh_01…", "url": "https://example.com/hooks/peppol", "events": ["document.received","document.delivered","document.failed"], "status": "active", "companyId": null, "failureCount": 0, "lastDeliveredAt": null, "createdAt": "2026-04-25T10:00:00Z" } [/code] ## Events Every webhook delivery has a durable twin in the Events API. If your endpoint was down, or you want a replay, poll `/v1/events` and acknowledge what you've processed. ### List events GET/v1/events #### Query parameters * typestringoptional Filter by event type, e.g. `document.received`. * companyIdstringoptional Scope to a managed company. * limitintegeroptional Page size. Default `20`. ### Acknowledge one event POST/v1/events/{event_id}/ack Returns `204 No Content`. Acked events are hidden from subsequent list calls. ### Batch acknowledge POST/v1/events/ack #### Request body * eventIdsstring[]required Event IDs to acknowledge, e.g. `["evt_…", "evt_…"]`. ### Replay an event POST/v1/events/{event_id}/replay Re-emits a delivered event onto every matching webhook subscription as if it had just happened. Useful for recovering from a downstream outage on your side without rewinding our delivery state. Returns `{"replayed": }` with the count of webhook deliveries scheduled. [code] { "data": [ { "id": "evt_01HY…", "type": "document.received", "createdAt": "2026-04-25T10:05:08Z", "data": { "documentId": "doc_01…", "direction": "incoming", "type": "invoice", "number": "INV-2026-0417" } } ], "hasMore": false } [/code] ## Compliance France **PPF** and Italy **SDI** require that lifecycle state changes (accepted / rejected / paid) be reported to a national platform. Flowie does this for you. These endpoints surface the current state and the underlying report records. Belgium runs pure Peppol since 2026-01-01 (HERMES decommissioned 2025-12-31) — no regulator-side report fires for BE. ### Compliance status GET/v1/compliance/status #### Query parameters * companyIdstringoptional Limit to a single managed company. * countryISO 3166-1 α-2optional ### Compliance reports GET/v1/compliance/reports Every report record has `documentId`, `reportedTo`, `platformResponse`, and an `error` if the authority rejected. #### Query parameters * companyIdstringoptional * countryISO 3166-1 α-2optional * statusstringoptional Filter by reporting status. * from / todateoptional Report-date range. * limit / cursorpaginationoptional ## Stats GET/v1/stats Usage, quota, and rate-limit status for the current period. #### Query parameters * periodenumoptional `day``week``month``year` * companyIdstringoptional [code] { "period": { "start":"2026-04-01", "end":"2026-04-30" }, "quota": { "limit": 5000, "used": 412, "remaining": 4588 }, "rateLimit": { "perMinute": 300 }, "documents": { "sent": 180, "received": 232, "delivered": 178, "failed": 2 }, "byType": { "invoice": 390, "credit-note": 22 }, "byCountry": { "FR": 150, "BE": 120, "IT": 142 }, "partners": { "total": 47, "active": 31 } } [/code] ## Platform These endpoints are for organizations running Flowie under their own brand — accounting SaaS, ERPs, public-sector aggregators. Most require a `flw_plat_live_…` or `flw_wl_live_…` key. ### Onboard a managed company POST/v1/platform/companies Registers a tenant, optionally creates a scoped API key and webhook, and registers on SMP — all in one call. * vatNumberstringrequired * namestringoptional * addressAddressoptional * metadataobjectoptional * receiveDocumentsbooleanoptional Default `true`. * autoVerifybooleanoptional * webhookobjectoptional Same shape as [webhook create](<#create-webhook>); created atomically. * apiKeyobjectoptional `{ "name": "tenant-…", "scopes": ["send","documents.read"] }`. ### List managed companies GET/v1/platform/companies ### Create API key for tenant POST/v1/platform/api-keys * namestringrequired * companyIdstringoptional Scopes the key to that tenant. * scopesstring[]optional * expiresAttimestampoptional * rateLimitobjectoptional ### List platform API keys GET/v1/platform/api-keys ### Revoke a key DEL/v1/platform/api-keys/{key_id} ### Usage breakdown GET/v1/platform/usage Returns total counters and a per-group array. #### Query parameters * periodstringoptional Reporting window, e.g. `month`. * groupByenumoptional `company``country``type` ### Update platform settings PATCH/v1/platform/settings #### Request body * brandingobjectoptional Logo, colors, sender display name for white-label delivery. * defaultsobjectoptional Default tenant settings applied at onboard time. * customDomainstringoptional Custom domain for webhook/callback URLs. ### Cross-tenant event stream GET/v1/platform/events Returns the unified event stream across every tenant managed by this platform key. Same shape as `/v1/events` with an extra `companyId` on each row so you can fan out per-tenant. Filters: `type`, `companyId`, `limit`, `cursor`. Platform / white-label keys only. [code] curl -X POST …/v1/platform/companies \ -H "Authorization: Bearer flw_plat_live_xyz" \ -d '{ "vatNumber":"FR86797978996", "receiveDocuments":true, "webhook": { "url": "https://erp.acme.fr/hooks/flowie", "events": ["*"] }, "apiKey": { "name":"erp-tenant-t001", "scopes":["send","documents.read","lifecycle"] } }' [/code] [code] { "company": { "id":"comp_01HY…", "peppolId":"0009:FR86797978996", … }, "apiKey": { "id":"key_01…", "key":"flw_live_t001_abc…", "keyPrefix":"flw_live_t001" }, "webhook": { "id":"wh_01…", "status":"active" } } [/code] ## API keys ### Create API key POST/v1/api-keys Authenticate with a [Flowie JWT (Auth0)](<#authentication>) — the same token your dashboard uses. The new key is bound to the caller's Flowie organization (resolved from the JWT's `_permissions` claim) and inherits its tier. Multi-org users should pass `X-Flowie-Organization-Id` to target a specific org. An existing `flw_live_*` key may also call this endpoint to mint additional keys for the same org. * namestringrequired * companyIdstringoptional * scopesstring[]optional See [scopes list](<#authentication>). * expiresAttimestampoptional * rateLimitintegeroptional Response includes `key` **exactly once**. Store it in your secret manager immediately. ### List API keys GET/v1/api-keys ### Revoke API key DEL/v1/api-keys/{key_id} Immediate. Any request-in-flight bearing the revoked key finishes, but new requests 401. [code] { "id": "key_01HY…", "key": "flw_live_abc123def456ghi…", // shown once "keyPrefix": "flw_live_abc123", "name": "Mobile App", "scopes": ["send","documents.read"], "companyId": null, "createdAt": "2026-04-25T10:00:00Z", "expiresAt": "2027-04-25T00:00:00Z" } [/code] ## Categorization Tag documents, partners, or other objects. Tags live in _groups_ (e.g. `business-unit`, `project`, `cost-center`). We also expose an AI suggest endpoint — feed it a document, get a ranked list of tags. ### List tag groups GET/v1/categorization/groups ### List tags in a group GET/v1/categorization/groups/{group_id}/tags ### Tags on an object GET/v1/categorization/objects/{object_id}/tags ### Assign tag POST/v1/categorization/objects/{object_id}/tags Body: `{"tagId": "tag_…", "objectType": "document"}`. ### Remove tag DEL/v1/categorization/objects/{object_id}/tags/{tag_id} ### AI tag recommendation POST/v1/categorization/objects/tags/auto Body: `{"objectId":"doc_…", "objectType":"document", "context": {…}}` → ranked list of recommended tags with confidence scores. [code] [ { "tagId": "tag_cc_rd", "name": "R&D", "groupId": "cost-center", "confidence": 0.92 }, { "tagId": "tag_proj_x1", "name": "Project X1", "groupId": "project", "confidence": 0.71 } ] [/code] ## Payments **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. ### Document payment info GET/v1/payments/documents/{documentId} Every payment recorded against the document, plus what is still owed. `balanceDue` is the document total minus everything paid — `null` when the document carries no total to subtract from. `payments` is empty, not absent, when nothing has been paid yet. [code] { "objectId": "doc_01HZXABCDEF0123456789", "objectType": "document", "organizationId": "comp_abc123", "payments": [ { "id": "pay_01HZXPAID0000001", "amount": 1200.00, "currency": "EUR", "paidAt": "2026-06-10T13:42:11Z", "method": "SEPA", "reference": "INV-2026-0042", "status": "recorded" } ], "balanceDue": 0.00, "totalPaid": 1200.00, "currency": "EUR" } [/code] ### Record a payment POST/v1/payments/documents/{documentId}/pay Records a payment against an invoice or a purchase order, and returns the created `Payment`. Recording also advances the lifecycle to `partially_paid`, or to `paid` once the recorded payments cover the document total. The advance is best-effort: the payment is always recorded, and the response’s `lifecycleStatus` is `null` if the document could not legally move to a paid state. Only `approved`, `partially_paid` and `disputed` can — a document still in `draft`/`received`/`under_review` must be approved first. Check `GET /v1/documents/{documentId}/lifecycle` → `allowedTransitions`. #### Request body * amountnumberrequired Amount paid — a positive number. * datedateoptional `YYYY-MM-DD` or an ISO 8601 timestamp. `paidAt` is accepted as an alias. * currencyISO 4217optional ISO 4217 code. Defaults to `EUR`. * methodenumoptional One of `SEPA`, `card`, `cheque`, `cash`, `wire`, `other`. * referencestringoptional Free-form external reference — a bank transaction id, for instance. * notestringoptional Free text stored on the payment. [code] { "amount": 1200.00, "date": "2026-06-17", "currency": "EUR", "method": "SEPA", "reference": "INV-2026-0042" } [/code] [code] { "id": "pay_01HZXNEW000000001", "objectId": "doc_01HZXABCDEF0123456789", "objectType": "document", "organizationId": "comp_abc123", "amount": 1200.00, "currency": "EUR", "paidAt": "2026-06-17T09:30:00Z", "method": "SEPA", "reference": "INV-2026-0042", "status": "recorded", "lifecycleStatus": "paid", "createdAt": "2026-06-17T09:30:00Z" } [/code] ### Export ISO 20022 / SEPA POST/v1/payments/export/iso20022 Generates a pain.001 SEPA credit-transfer file for a set of documents, ready for upload to your bank. The returned `content` is base64-encoded XML — decode it before saving. Sandbox keys receive a placeholder file with `transactionCount: 0`. #### Request body * documentIdsstring[]required Documents to bundle. Each needs a registered creditor bank account and a non-zero outstanding balance. * currencyISO 4217optional ISO 4217 code. Defaults to `EUR`. * executionDatedateoptional Date on which the bank should execute the payments. Defaults to the next business day. [code] { "documentIds": [ "doc_01HZXABCDEF0123456789", "doc_01HZXABCDEF0123456790" ], "currency": "EUR", "executionDate": "2026-06-18" } [/code] [code] { "messageId": "MSG-2026-06-17-0001", "organizationId": "comp_abc123", "format": "pain.001.001.09", "filename": "MSG-2026-06-17-0001.xml", "content": "PD94bWwgdmVyc2lvbj0iMS4wIiA…(base64)…", "contentType": "application/xml", "transactionCount": 2, "totalAmount": 2400.00, "currency": "EUR", "generatedAt": "2026-06-17T09:35:14Z" } [/code] ## Request log Every mutation (POST/PUT/PATCH/DELETE) and every error is captured for your organization, so you can answer "what did that integration actually send?" without adding logging of your own. Successful GETs are captured only when the server-side `REQUEST_LOG_ALL` flag is on. Individual entries are also browsable in the [request inspector](<../playground/requests.html>). ### List captured requests GET/v1/requests **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Newest first, cursor-paginated — the response carries `data`, `hasMore` and `cursor` like every other list endpoint. Filter by `apiKeyId` or `userId` to see everything a given key or user did. #### Query parameters * methodstringoptional HTTP verb, e.g. `POST`. * pathstringoptional Path prefix, e.g. `/v1/documents`. * statusintegeroptional Exact HTTP status. * apiKeyIdstringoptional Restrict to one API key. * userIdstringoptional Restrict to one JWT user. * sincedatetimeoptional ISO-8601 lower bound. * untildatetimeoptional ISO-8601 upper bound. [code] { "data": [ { "id": "req_01HY…", "method": "POST", "path": "/v1/documents/send", "status": 201, "apiKeyId": "key_01HY…", "createdAt": "2026-04-25T10:05:00Z" } ], "hasMore": false, "cursor": null } [/code] ### Usage rollup GET/v1/requests/summary **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Per-API-key (or per-user) rollup: who called, how many times, how many errors, last seen — without paging through every log line. Pass `by=user` to group by JWT user and surface their email instead of grouping by key id. Grouped by key, each row also carries rate-limit pressure: `throttledRequests` (requests refused with 429 — exact, since every error is captured), `peakRequestsPerMinute` (the busiest minute in the window) and `rateLimitPerMinute` (the budget that key is allowed). Treat the peak as a _lower bound_ : successful reads spend the budget without being logged. #### Query parameters * byenumoptional `key``user` * sincedatetimeoptional ISO-8601 lower bound. * untildatetimeoptional ISO-8601 upper bound. [code] { "by": "apiKey", "data": [ { "apiKeyId": "key_01HY…", "totalRequests": 1284, "errorRequests": 3, "throttledRequests": 0, "peakRequestsPerMinute": 96, "rateLimitPerMinute": 600, "lastRequestAt": "2026-04-25T10:05:00Z" } ] } [/code] ### Volume over time GET/v1/requests/timeseries **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. The same traffic as the rollup, but bucketed by minute or hour, so a spike has a shape instead of a number. `errors` counts every 4xx/5xx; `throttled` isolates the requests the rate limiter itself refused with a 429. `total` is a _lower bound_ unless the server captures successful reads: those spend the rate-limit budget without being logged. `errors` and `throttled` are exact. #### Query parameters * bucketenumoptional `minute``hour` * apiKeyIdstringoptional Only this API key / client id. * organizationIdstringoptional Only this organization. * sincedatetimeoptional ISO-8601 lower bound. * untildatetimeoptional ISO-8601 upper bound. [code] { "bucket": "minute", "data": [ { "ts": "2026-08-28 21:14", "total": 96, "errors": 2, "throttled": 0, "maxDurationMs": 210 }, { "ts": "2026-08-28 21:15", "total": 412, "errors": 31, "throttled": 29, "maxDurationMs": 940 } ] } [/code] ## Portability Inter-PA messaging for the French portability process: when a taxpayer moves from one Plateforme Agréée to another, the gaining and losing platforms exchange a normalised message (a strict subject line plus an 18-field CSV). These endpoints send that message to the counterparty platform, keep every one that goes out, parse the ones that come in, and list the registered platforms so you know where to write. Full walkthrough: [Portability (change of PA)](<../guides/portability.html>). ### Send an inter-PA message POST/v1/portability/messages **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Assemble the AIFE inter-PA message for a portability request, email it to the counterparty platform and log it. Name the counterparty (`losingPaName` / `gainingPaName`) and the address is resolved from the registry of registered Plateformes Agréées, or pass `to` yourself; `recipientSource` in the response says which happened. Sending is gated by a server-side kill-switch and the SMTP configuration, and a sandbox key never reaches a real platform — when the mail does not go out, `dispatched` is `false` and `reason` says why. The message is recorded either way: `GET /v1/portability/messages` lists them and `GET /v1/portability/messages/{id}` returns the CSV as sent with its SHA-256. `GET /v1/portability/platforms` lists the platforms and the address a port request goes to, and `POST /v1/portability/routing` switches the taxpayer's routing on the agreed _date d'effet_ (`validFrom` when gaining, emission stop plus a 12-month reception window when losing). `GET /v1/portability/annuaire/{siren}` reads the PPF _annuaire_ back: which platform matricule routes the taxpayer today, since when, and until when. Outside France there is no registry to read, so `GET /v1/portability/access-point/{participantId}` resolves the answer live instead — SML, then the SMP, then the endpoint certificate, which names the platform that routes that Peppol participant. And because nobody knows their own SIRET by heart, `GET /v1/portability/companies?q=` turns a company name into the company — ranked suggestions, or a direct lookup when the query already is an identifier. #### Request body * messageTypeenumrequired Which step of the portability exchange this message is. * stateenumrequired State of the request the message reports. * requestRefstringrequired Your reference for the portability request; echoed in the subject. * taxpayerSirenstringrequired 9-digit SIREN of the taxpayer being ported. * gainingPaId / losingPaIdstringoptional Platform identifiers on each side of the move. * effectiveDatedateoptional When the transfer takes effect. [code] { "messageType": "REQUEST", "state": "received", "requestRef": "POR-2026-000123", "directionRole": "GAINING_PA", "taxpayerSiren": "552100554", "losingPaName": "ESKER", "effectiveDate": "2026-10-01" } [/code] [code] { "id": "pmsg_9f2c7a1d4b8e4c0f9a6d3e2b1c7f5a80", "subject": "[PORTABILITE][REQUEST][REQ][SIREN:552100554][REF:POR-2026-000123]", "csvRow": "POR-2026-000123;REQUEST;REQ;GAINING_PA;552100554;…", "csvSha256": "6b1f…", "to": "contact-pdp@esker.com", "recipientSource": "registry:ESKER", "dispatched": true, "reason": "sent" } [/code] ### Parse an inbound message POST/v1/portability/messages/parse **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Parse a received inter-PA message back into structured fields. Validates the normalised subject grammar and, when `csvRow` is supplied, the 18-column payload. A subject that does not match the grammar returns `400` — dead-letter it rather than opening a request. #### Request body * subjectstringrequired The raw subject line as received. * csvRowstringoptional The data row, without the header line. [code] { "subject": "[PORTABILITE][REQUEST][REQ][SIREN:552100554][REF:POR-2026-000123]" } [/code] [code] { "messageType": "REQUEST", "statusCode": "REQ", "state": "received", "siren": "552100554", "requestRef": "POR-2026-000123" } [/code] ### Resolve a taxpayer POST/v1/portability/resolve **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. One identifier in, everything a migration request needs out. Accepts a SIRET, a SIREN, a VAT number, a Peppol id, a national registration number, a domain, an e-mail or `name:`, and answers with the legal name, country, identifiers and the French annuaire addressing line — plus the regime that governs the switch, what changes, and what must be re-granted. Whatever cannot be resolved is listed in `stillNeeded` rather than guessed. `404` when no layer resolves the identifier. #### Request body * taxpayerstringrequired The only value a company has to supply. * countrystring (ISO-2)optional Used only when the identifier does not carry its own country. [code] { "taxpayer": "92137626500017" } [/code] [code] { "taxpayer": { "name": "ACME SAS", "country": "FR", "registrationNumber": "92137626500017", "peppolId": "0009:921376265", "addressingIdentifier": "921376265_92137626500017_001", "resolvedFrom": "legal_base" }, "regime": "PPF · plateforme agréée", "requirements": { "addressChanges": false, "reGrant": "A dated, signed designation agreement (accord formel).", "archiveHolder": "You / your platform — the outgoing one owes 1 year of lifecycle statuses." }, "stillNeeded": [] } [/code] ### Open a migration request POST/v1/portability/requests **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Opens a platform change from the same one identifier, resolving everything else. Produces the designation agreement **art. 242 nonies E bis** requires — taxpayer, incoming platform, previous platform, effective date, scope of electronic addresses, signatory — numbers it, and starts a hash-linked evidence chain. Deadlines are computed in _jours ouvrés_ including _jours fériés_. A missing signatory does not fail the call: it comes back in `mandate.gaps`, because that gap is exactly what an outgoing platform may object to. Returns `201`. #### Request body * taxpayerstringrequired Identifier, as above. * signatorystringoptional Who signs for the company. Required by the decree; reported as a gap when absent. * outgoingPlatformstringoptional The platform being left. Resolved from the directory when omitted. * effectiveDatestring (date)optional Defaults to the first business day after the objection window could close. * addressScopestring[]optional Addresses covered. Defaults to the resolved annuaire line and Peppol id. * overridesobjectoptional Explicit values that beat anything resolved. [code] { "taxpayer": "92137626500017", "signatory": "Camille Roy, Directrice Générale" } [/code] [code] { "requestRef": "POR-2026-4F2A91C08B7D", "state": "received", "statusCode": "REQ", "mandate": { "reference": "POR-2026-4F2A91C08B7D", "effectiveDate": "2026-09-09", "addressScope": ["921376265_92137626500017_001", "0009:921376265"] }, "clocks": { "notifyBy": "2026-09-03", "objectionWindowEnds": "2026-09-08", "annuaireUpdateBy": "2026-09-29", "continuityUntil": "2027-09-09" }, "tacitApproval": false, "evidence": { "verification": { "intact": true, "length": 1 } } } [/code] ### Read a migration request GET/v1/portability/requests/{request_ref} **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. State is never stored — it is folded from the evidence chain on every read, so this is the single truth about where a port stands, and an agent that was not running when the request was opened reaches the same answer as one that was. `tacitApproval` flips to `true` by itself once the objection window lapses with no admissible objection (_le silence vaut accord_), with no scheduler involved. `404` when the reference does not belong to the calling organization. [code] curl …/v1/portability/requests/POR-2026-4F2A91C08B7D \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] [code] { "requestRef": "POR-2026-4F2A91C08B7D", "state": "auto_accepted", "statusCode": "TAC", "tacitApproval": true, "objections": [ { "ground": "unpaid_invoices", "admissible": false } ], "evidence": { "verification": { "intact": true, "length": 3, "brokenAt": null }, "manifestSha256": "b41c…" } } [/code] ### Have the agreement signed POST/v1/portability/requests/{request_ref}/mandate/signature **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Renders the designation agreement from the mandate this request already holds — the five items article 242 _nonies_ E bis requires — and opens an **approval check** on it for the users you name. They sign by deciding in Flowie; what is stored is their decision, its instant and its author, none of which this API supplies. `201` returns the text exactly as it is put in front of them, and its two digests. **The context key is what binds a decision to this agreement:** `portability::`. An approval vote is scoped to the object it hangs on, so without it a decision taken on the same document for another reason would read here as a signature. Change the mandate — a new _date d'effet_ , a different address scope — and the digest moves with it, so the earlier signature stops answering for the new agreement. That is the point: it is not the agreement that was signed. The check hangs on a **document** , never on the request — approval's `ObjectType` is a closed enum with no portability member. Leave `objectId` out and the rendered agreement is stored as one for you. `502` when the approval service does not answer: a silent failure would leave you believing somebody had been asked. `409` when the mandate is still missing a decree item (`taxpayerId`, `incomingPlatform`, `effectiveDate`, `addressScope`): a request can be opened on an identifier alone, and an agreement rendered with blanks where the decree wants values is not one anybody should be asked to sign. #### Request body * userIdsstring[]required Who is asked to sign, as Flowie user ids. The agreement is a designation _by the taxpayer_ , so this is its legal representative — not your own team. One to ten. * objectIdstringoptional The object the check hangs on. Leave it out and the rendered agreement is stored as a document and used. * objectTypestringoptional Defaults to `DocumentVersion`. Only set it if `objectId` points at another kind approval accepts. * expiresAtstringoptional ISO-8601. A mandate nobody signs should lapse rather than sit open against a _date d'effet_ that has passed. [code] curl -X POST …/v1/portability/requests/POR-2026-4F2A91C08B7D/mandate/signature \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{"userIds": ["usr_01J9ZC3K7V8QDX4M2T6NRPB5HE"]}' [/code] [code] { "requestRef": "POR-2026-4F2A91C08B7D", "checkId": "chk_01J9ZC6Q4T2VHM8B5D7KXNPR3W", "contextKey": "portability:POR-2026-4F2A91C08B7D:9f2c…", "objectId": "docv_01J9ZC4B2N7RSF5K8W3PQXTM6D", "objectType": "DocumentVersion", "mandateSha256": "9f2c…", "agreementSha256": "41ab…", "signed": false, "signature": null } [/code] ### Read the signature GET/v1/portability/requests/{request_ref}/mandate/signature **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. `signed` stays `false` while the check is open, refused or withdrawn — only a passed check carrying _this_ agreement's context key is a signature, and one taken on an earlier version of the mandate is not. The first read that finds one writes it into the evidence chain, so the request carries its `signedAt` from then on whether or not this endpoint is called again. `404` when nobody has been asked to sign this request. A mandate signed outside Flowie is recorded instead with `signedAt` and `signatureMethod` when the request is opened. [code] curl …/v1/portability/requests/POR-2026-4F2A91C08B7D/mandate/signature \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] [code] { "signed": true, "signature": { "signatory": "Yann Ravel-Sibillot", "signedAt": "2026-09-18T08:59:00+00:00", "method": "approval", "checkId": "chk_01J9ZC6Q4T2VHM8B5D7KXNPR3W", "mandateSha256": "9f2c…", "agreementSha256": "41ab…" } } [/code] ### Sign with your own document POST/v1/portability/requests/{request_ref}/mandate/signature/document **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. The third way to sign, and the one that fits a taxpayer whose representative has no Flowie account. Signing _in_ Flowie gives an authenticated actor and an observed instant; asserting a paper signature at opening gives neither and keeps nothing. This sits between them honestly: **you** state who signed and when, exactly as for paper, but the artefact is stored under your organization and hashed — so what was signed stops being a claim. The signature then replays onto the mandate like any other and `mandateGaps` closes. **`agreementSha256` is the digest of the file you uploaded**, not of the text this API would have rendered. Those are different documents — you may have signed your own wording, or your advocate’s, or ours with a scan on top — and recording ours as the one signed would be a statement we cannot support. `mandateSha256` still binds to the decree content, so amending the mandate afterwards invalidates this signature exactly as it invalidates an in-app one. `400` when `signedAt` is not an ISO-8601 instant, when it is _in the future_ (an act cannot be dated after the moment it is recorded), or when the file is empty. `409` when the agreement is _already signed_ — the chain folds signatures in order, so a second one would replace the first without trace — or when the mandate is still missing a decree item. `413` above 10 MB, `502` when the document could not be stored — the artefact is the whole point of this path, so a signature is never recorded without it. #### Form fields * filefilerequired The signed agreement, as signed. Kept under your organization; PDF, image or text. * signatorystringrequired Who signed it, for the taxpayer — name and role, as it appears on the document. * signedAtstringrequired When they signed it, ISO-8601. Refused if it is in the future. [code] curl -X POST …/v1/portability/requests/POR-2026-4F2A91C08B7D/mandate/signature/document \ -H "Authorization: Bearer $FLOWIE_KEY" \ -F "file=@mandat-signe.pdf" \ -F "signatory=Camille Roy, Directrice Générale" \ -F "signedAt=2026-09-18T09:00:00+00:00" [/code] [code] { "signed": true, "objectId": "docv_01J9ZC4B2N7RSF5K8W3PQXTM6D", "mandateSha256": "9f2c…", "agreementSha256": "7d10…", "signature": { "signatory": "Camille Roy, Directrice Générale", "signedAt": "2026-09-18T09:00:00+00:00", "method": "upload", "documentId": "docv_01J9ZC4B2N7RSF5K8W3PQXTM6D" } } [/code] ### Record a step POST/v1/portability/requests/{request_ref}/events **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Appends one step to the evidence chain and returns the re-derived state. **An objection is recorded, then judged:** the decree limits the outgoing platform to grounds questioning the taxpayer's intent — `more_recent_agreement`, `identity_mismatch`, `mandate_invalid`. Any other ground is stored verbatim and flagged `admissible: false`; the request keeps running and carries the mandate digest as the answer to it. #### Request body * kindenumrequired `notified`, `objection`, `acceptance` or `annuaire_updated`. * groundstringoptional For `objection`: the ground stated by the outgoing platform. * statementstringoptional For `objection`: their wording, kept verbatim as evidence. * channelRefstringoptional For `notified`: your reference for the message sent. * entryRefstringoptional For `annuaire_updated`: the directory entry reference. [code] { "kind": "objection", "ground": "unpaid_invoices", "statement": "Contract runs to December." } [/code] [code] { "state": "received", "objections": [ { "ground": "unpaid_invoices", "admissible": false } ] } [/code] ## UBL generator (France) A compliant French e-invoice for every business situation the reform recognises — all 45 _cas d'usage_ of AFNOR XP Z12-014 plus the foundations. Each scenario carries its business narrative, the BT fields it turns on, the 200–213 lifecycle it drives, the EN 16931 UBL 2.1 XML and the exact `POST /v1/documents/send` body. The catalogue and the generator are pure functions of the request and touch no tenant data, so they need **no API key**. Full guide: [UBL generator](<../compliance/fr/ubl-generator.html>). ### List every business case GET/v1/tools/fr/ubl/scenarios **Authentication:** none — this is reference material, not your data. Returns every scenario with its business description. Filter by `theme`, `family`, `channel` (`e-invoicing` / `e-reporting`) or `case`, or search the narratives with `q`. #### Query parameters * themestringoptional e.g. `Acompte & paiement échelonné`. * familyenumoptional `data`, `third-party`, `lifecycle` or `foundation`. * channelenumoptional `e-invoicing` or `e-reporting`. * casestringoptional XP Z12-014 case number, e.g. `20` or `19b`. * qstringoptional Free-text search across the title, the story and the French case title. ### Read one business case GET/v1/tools/fr/ubl/scenarios/{scenario_id} **Authentication:** none — this is reference material, not your data. The story, the rule that makes it its own case, the trap, and the BT fields that carry it — without generating anything. An unknown id returns `404` with the closest matches. [code] curl https://back.flowie.ink/exchange/v1/tools/fr/ubl/scenarios?case=20 [/code] [code] { "scenarios": [ { "id": "uc-20-deposit-invoice", "case": "20", "caseTitleFr": "Facture d'acompte", "title": "A builder asks for 30% up front before starting the job", "theme": "Acompte & paiement échelonné", "channel": "e-invoicing", "typeCode": "386", "cadre": "S1", "story": "Grand Client commissions a €20,000 fit-out…", "why": "A deposit invoice is a real invoice for VAT…", "watchOut": "Type code 386 is what makes it a deposit…", "keyData": [ { "bt": "BT-3", "label": "Type code 386", "why": "…" } ], "lifecycle": ["200 Déposée", "205 Approuvée", "212 Encaissée"], "sendFormat": "ubl-xml", "validatesAsEInvoice": true } ], "summary": { "total": 59, "numberedCases": 50 }, "referential": { "useCases": "AFNOR XP Z12-014 v1.4 (2026-06-30) — 45 cas d'usage" } } [/code] ### List every business term GET/v1/tools/fr/ubl/business-terms **Authentication:** none — this is a referential, not your data. The whole EN 16931 semantic model: 30 business groups, 164 business terms (`BT-1`…`BT-165`; `BT-4` is unassigned), plus the `-1`/`-2` scheme attributes the French rules lean on. Each entry says what the term is in both languages, where it lives in UBL 2.1, what France requires of it and under which `BR-FR-*` rule, and which field of [`POST /v1/documents/send`](<#send-document>) carries it. Full guide: [Business terms](<../compliance/fr/business-terms.html>). `fr` is one of `mandatory`, `conditional`, `restricted` (France narrows the allowed values), `optional` or `unused`; `apiState` is `sent` (you state it), `derived` (Flowie computes it), `accepted` (stored, not rendered yet) or `xml-only` (no JSON field — carry it with `format=ubl-xml`). The response's `legend` spells both out. #### Query parameters * groupstringoptional One business group and its members, e.g. `BG-23`. * scopeenumoptional `document` or `line`. * frenumoptional `mandatory`, `conditional`, `restricted`, `optional` or `unused`. * mappedbooleanoptional `true` for the terms `POST /v1/documents/send` has a field for; `false` for the gaps. * qstringoptional Free-text across the id, both names, the UBL path, the French note and the API field. ### Read one business term GET/v1/tools/fr/ubl/business-terms/{term_id} **Authentication:** none — this is a referential, not your data. One `BT`, `BG` or scheme attribute by id — `BT-121`, `BG-23`, `BT-29-1`. Case-insensitive. An id the standard does not assign returns `404 BUSINESS_TERM_NOT_FOUND`. [code] curl https://back.flowie.ink/exchange/v1/tools/fr/ubl/business-terms?group=BG-23 [/code] [code] { "terms": [ { "id": "BT-121", "kind": "term", "name": "VAT exemption reason code", "nameFr": "Code du motif d'exonération", "group": "BG-23", "scope": "document", "cardinality": "0..1", "ubl": ".../cac:TaxCategory/cbc:TaxExemptionReasonCode", "fr": "conditional", "frNote": "A VATEX code. Optional under EN 16931 but mandatory for the franchise en base…", "api": "lines[].vatExemptionCode", "apiState": "sent" } ], "summary": { "groups": 32, "terms": 164, "mandatoryInFrance": 37, "mappedToApi": 84 }, "legend": { "fr": { "mandatory": "Required on every French e-invoice." } }, "referential": { "semanticModel": "EN 16931-1:2017/A1:2019 — 30 business groups, 164 business terms" } } [/code] ### Generate a case POST/v1/tools/fr/ubl/generate **Authentication:** none — this is reference material, not your data. Renders the scenario as EN 16931 UBL 2.1 and builds the send call for it. Override the invoice number, the dates, the currency and either party to make the sample look like your own data; identifier _schemes_ stay fixed, so an override cannot produce a party whose SIRET and SIREN disagree (`BR-FR-09`). #### Request body * scenarioIdstringrequired Scenario id from the catalogue, e.g. `uc-20-deposit-invoice`. * numberstringoptional Invoice number (BT-1). `BR-FR-01` caps it at 35 characters. * issueDate / dueDatedateoptional `YYYY-MM-DD`. `BR-FR-03` wants a year between 2000 and 2099. * currencystringoptional Anything other than EUR also needs the VAT accounting currency (`BR-FR-CO-12`) — the generator does not add it for you. * seller / buyerobjectoptional `{name, siret, siren, vatNumber, legalForm, address, contact}` — your own party in place of the catalogue's. * formatenumoptional `json` or `ubl-xml`. Defaults to the scenario's own recommendation. ### Simple mode — your own data, no scenario id POST/v1/tools/fr/ubl/simple **Authentication:** none — the response is a pure function of the request. The catalogue answers "what does _this_ business case look like?". This answers the question people ask first — "here is my data, make it legal" — which nine out of ten French invoices need and no numbered _cas d'usage_ covers, because they are just one company billing another. From the line kinds and the VAT situation it derives the type code (`BT-3`), the _cadre de facturation_ (`BT-23`), the tax point (`BT-8`), the VAT category with the exemption reason EN 16931 demands, the three legal mentions of `BR-FR-05` and the `BAR` regime note. Every one comes back in `inferred` with the rule that forced it, so you can check what was added on your behalf instead of trusting it. `warnings` carries what is legal but probably not what you meant — a VAT rate that is not in force in France, an intra-community supply with no buyer VAT number. Two fields cannot be guessed and are asked for: each party's **SIRET** , because the routing address and the legal identifier both derive from it, and the seller's **legal form and share capital** (`BT-33`), which France makes mandatory. A request that cannot produce a compliant invoice comes back `400 CANNOT_BUILD_COMPLIANT_INVOICE` naming the field and the fix. #### Request body * seller / buyerobjectrequired `{name, siret, siren?, vatNumber?, legalForm?, address?, contact?}`. `legalForm` is required on the seller (`BT-33`) and ignored on the buyer, where `UBL-CR-244` forbids it. * linesarrayrequired `{description, quantity, unitPrice, vatRate?, kind?, unit?}`. `kind` is `goods` or `services` and decides the cadre and the tax point. * numberstringrequired Invoice number (BT-1). `BR-FR-01` caps it at 35 characters. * documentTypeenumoptional `invoice` (default) or `credit-note` — which becomes type 381, not a negative invoice. * vatRegimeenumoptional `standard`, `reverse-charge-subcontracting`, `franchise-en-base`, `intra-community`, `export`. Anything but `standard` zero-rates every line and attaches the reason. * alreadyPaid / vatOnDebitsbooleanoptional Move the cadre to B2 / S2 / M2, and make services taxable on the invoice date. * deliveryCountrystringoptional Where the goods went (`BT-80`) — required in substance on an intra-community supply (`BR-IC-12`), inferred from the buyer when omitted. * paymentobjectoptional `{iban?, bic?, means?, reference?, terms?}`. With an IBAN the invoice declares a credit transfer (code 30); without one it declares code 1, because claiming a transfer with no account fails `BR-61`. * issueDate / dueDate / currency / buyerReference / orderReference / corrects / note / formatmixedoptional The usual header fields. `corrects` is `{number, issueDate}` — what a credit note refers back to (`BG-3`). [code] curl -X POST https://back.flowie.ink/exchange/v1/tools/fr/ubl/simple \ -H 'Content-Type: application/json' \ -d '{"seller":{"name":"Ma Boîte SAS","siret":"12345678900017", "legalForm":"SAS au capital de 10 000 EUR — RCS Paris 123 456 789"}, "buyer":{"name":"Client SARL","siret":"39158000400021"}, "lines":[{"description":"Prestation de conseil","quantity":2,"unitPrice":500, "vatRate":20,"kind":"services"}], "number":"FA-2026-0001"}' [/code] ### Generate and validate POST/v1/tools/fr/ubl/generate-and-validate **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Generates the case and runs it through the official FNFE **XP Z12-012 v1.3.0** schematrons — XSD, the EN 16931 profile rules, the French `BR-FR` rules — plus the complementary CIUS-FR field checks. `errors` flattens every failing rule across all steps. Needs a key because it calls the schematron service on your behalf. [code] { "scenarioId": "uc-21-final-after-deposit", "number": "FA-2027-0042", "issueDate": "2027-03-01", "seller": { "name": "Ma Société SAS", "siret": "55210055400013" }, "buyer": { "name": "Mon Client SA", "siret": "39158000400021" } } [/code] [code] { "scenario": { "id": "uc-21-final-after-deposit", "case": "21", "cadre": "S4" }, "ubl": "\n", "totals": { "lineExtensionAmount": 20000.0, "taxExclusiveAmount": 20000.0, "taxAmount": 4000.0, "taxInclusiveAmount": 24000.0, "prepaidAmount": 7200.0, "payableAmount": 16800.0 }, "sendRequest": { "type": "invoice", "from": "0009:55210055400013", "to": "0009:39158000400021", "format": "ubl-xml", "xml": ") * [Spécifications externes B2B — DGFiP / portail PPF]() * [Chorus Pro / PPF technical specs (EN)]() * [AFNOR X12U commission — XP Z12-013 announcement]() ### Architecture & vocabulary The PPF (Portail Public de Facturation) sits as a passive concentrator and annuaire. Every B2B invoice in France must flow through at least one PDP. PDPs route to each other directly when both sides are on different platforms; flows transit the PPF only for fallback, reporting (e-Reporting), and lifecycle aggregation. * **PA** (Plateforme Acheteur) — the buyer's PDP receives the flow. * **PV** (Plateforme Vendeur) — the seller's PDP submits the flow. * **OD** (Opérateur de Dématérialisation) — non-certified upstream of a PDP; can submit but not receive. * **OPDF** — Operation Process Description Format; how flow lifecycle is described on the wire. * **MR-DG** — Mandat de Représentation côté Destinataire / côté Generic; routing-code level mandate. Every operation below is authenticated with a Flowie token (Bearer) _or_ the AFNOR-compliant `?token=` query parameter — both forms are accepted. ### Submit a flow POST/afnor/flow-service/v1/flows Multipart: `flowInfo` (JSON) + `file` (binary). Returns `202 Accepted` with a `flowId`. * flowInfo.namestringrequired * flowInfo.flowSyntaxenumrequired `CII``UBL``Factur-X``CDAR``FRR` * flowInfo.trackingIdstring (≤36)optional * flowInfo.processingRuleenumoptional `B2B``B2C``B2G` * flowInfo.flowProfileenumoptional `Basic``CIUS``Extended-CTC-FR` * flowInfo.sha256hexoptional ### Search flows POST/afnor/flow-service/v1/flows/search #### Request body `SearchFlowParams`. Filters are AND-combined; array values are OR-combined. * limitintegeroptional Page size, 1–100. Default `25`. * whereSearchFlowFiltersoptional Filter object. Fields: `updatedAfter`, `updatedBefore`, `processingRule[]`, `flowType[]`, `flowDirection[]`, `trackingId`, `ackStatus`. ### Retrieve a flow GET/afnor/flow-service/v1/flows/{flow_id} #### Query parameters * docTypeenumoptional `Metadata``Original``Converted``ReadableView` ### AFNOR webhooks Same operations as [Webhooks](<#create-webhook>) but under the AFNOR-shaped schema: GET/afnor/flow-service/v1/webhooks POST/afnor/flow-service/v1/webhooks #### Create body * callbackobjectrequired `url` (required), plus optional `headers[]`, `authentication`, `signature`. * metadataobjectrequired Subscription filters: `flowType`, `flowDirection` (required), `processingRule`, `ackStatus` (optional). GET/afnor/flow-service/v1/webhooks/{webhook_uid} PATCH/afnor/flow-service/v1/webhooks/{webhook_uid} #### Update body — technical params only * headersobject[]optional * authenticationobjectoptional * signatureobjectoptional DEL/afnor/flow-service/v1/webhooks/{webhook_uid} ### AFNOR directory (SIREN / SIRET / routing codes) Every `*/search` response uses the AFNOR envelope: `search`, `totalNumberOfResults`, `results`. POST/afnor/directory-service/v1/siren/search #### Request body * filtersobjectoptional Field → value map of search predicates. * sortingobject[]optional * fieldsstring[]optional Restrict the returned columns. * limitintegeroptional 1–100. Default `50`. * ignoreintegeroptional Offset — rows to skip. GET/afnor/directory-service/v1/siren/code-insee:{siren} #### Query parameters * fieldsstring[]optional Comma-separated columns to return. POST/afnor/directory-service/v1/siret/search #### Request body * filtersobjectoptional * sortingobject[]optional * fieldsstring[]optional * includestring[]optional Expand related rows. * limitintegeroptional 1–100. Default `50`. * ignoreintegeroptional GET/afnor/directory-service/v1/siret/code-insee:{siret} #### Query parameters * fieldsstring[]optional * includestring[]optional POST/afnor/directory-service/v1/routing-code/search #### Request body * filtersobjectoptional * includestring[]optional * limitintegeroptional 1–100. Default `50`. GET/afnor/directory-service/v1/routing-code/siret:{siret}/code:{routing_identifier} #### Query parameters * fieldsstring[]optional * includestring[]optional ### Directory-line search POST/afnor/directory-service/v1/directory-line/search Stub endpoint for _directory-line_ queries — the AFNOR aggregate row that joins SIREN + SIRET + routing-code data into a single result row, used for OD ↔ PDP onboarding flows. Response is currently empty (returns the AFNOR `search` envelope with `totalNumberOfResults: 0`) until the PDP-PDP federation handshake is wired up. #### Request body * filtersobjectoptional * sortingobject[]optional * fieldsstring[]optional * limitintegeroptional 1–100. Default `50`. ### Healthchecks GET/afnor/flow-service/v1/healthcheck GET/afnor/directory-service/v1/healthcheck Public, unauthenticated. Returns `{ "status": "ok", "version": "1.0", "service": "flow-service|directory-service" }`. Required by the AFNOR PDP certification suite. [code] curl -X POST …/afnor/flow-service/v1/flows \ -H "Authorization: Bearer $KEY" \ -F 'flowInfo={"name":"INV-2026-0417","flowSyntax":"UBL","processingRule":"B2B","flowProfile":"Extended-CTC-FR","trackingId":"t-42"};type=application/json' \ -F 'file=@invoice.xml' [/code] [code] HTTP/1.1 202 Accepted { "flowId": "flw_01HY…", "submittedAt": "2026-04-25T10:00:00Z", "name": "INV-2026-0417", "flowSyntax": "UBL", "trackingId": "t-42", "processingRule": "B2B", "flowProfile": "Extended-CTC-FR", "sha256": "e3b0c442…" } [/code] ### Get directory line by id GET/afnor/directory-service/v1/directory-line/code:{addressing_identifier} **Authentication:** `Authorization: Bearer ` with a live or test Exchange key (`flw_live_…` / `flw_test_…`) or a Flowie JWT. Resolve a single directory line by its addressing identifier — XP Z12-013 § 7.9 `getDirectoryLineById`. Same ppf-annuaire backing as [searchDirectoryLine](<#afnor-directory-line>), filtered on the identifier and reduced to one line. Note the AFNOR path grammar: the value is prefixed `code:` in the path segment. #### Path parameters * addressing_identifierstringrequired The routing code of the line, e.g. `code:0009:552100554`. #### Query parameters * includestringoptional Related resources to embed. * fieldsstringoptional Sparse fieldset. [code] GET /afnor/directory-service/v1/directory-line/code:0009:552100554 Authorization: Bearer flw_live_… [/code] [code] { "directoryLine": { "addressingIdentifier": "0009:552100554", "siren": "552100554", "name": "ACME SAS", "status": "active" } } [/code] ## PunchOut cart callback POST/document/callback The cXML PunchOut return endpoint. SAP Ariba, Coupa, Ivalua and friends POST the user's cart back here when they check out. We OCR the cXML into a Flowie request, then respond with an HTML page that redirects the user to the originating chat thread. **Authentication:** no bearer — we validate the `SharedSecret` in the cXML header against a per-partner allow-list, plus `BuyerCookie` for org scoping. #### Accepted bodies * `Content-Type: application/x-www-form-urlencoded` with a `cxml-urlencoded` or `cxml-base64` field. * `Content-Type: application/xml` with the raw cXML PunchOutOrderMessage. #### Response An HTML `` redirect — typically to `{APP_URL}/{org_slug}/ai/chat/{thread_id}` if the v2 BuyerCookie contains a thread hint, or `{APP_URL}/{org_slug}/requests` otherwise. [code]
... ***
org_01HY…:thread_abc:v2 ...
[/code] ### OCI cart callback POST/document/oci-callback The OCI return endpoint, for Mercateo, Conrad and SAP-style suppliers. Accepted as both `POST` (form post) and `GET` (supplier auto-submit), because OCI suppliers differ on which they use. Cart lines arrive as the flat `NEW_ITEM-*` field family. **Authentication:** no bearer — the supplier-facing HOOK_URL carries a `flowie_cookie` query parameter (or form field) holding the BuyerCookie `flowie:{org_id}:{thread_id}:{nonce}`. We use it to route the cart to the right organization and to redirect the user back to the originating thread. [code] POST /document/oci-callback?flowie_cookie=flowie:org_01HY…:thr_01HY…:9f3c Content-Type: application/x-www-form-urlencoded NEW_ITEM-DESCRIPTION[1]=Laptop stand&NEW_ITEM-QUANTITY[1]=2&NEW_ITEM-PRICE[1]=49.00 [/code] ## Health Public, unauthenticated. Great for load balancers and synthetic monitors. ### Liveness GET/health/liveness Returns `{"status":"ok"}` as long as the process can serve requests. ### Readiness GET/health/readiness Includes circuit-breaker state for every upstream. ### Contracts GET/health/contracts Actively probes upstreams (SMP, national directories). Slower; don't call from a hot path. [code] { "status": "ok", "circuits": { "peppol-smp": { "state": "closed", "failures": 0 }, "ppf-annuaire": { "state": "closed", "failures": 0 }, "document-service":{ "state": "closed", "failures": 0 } } } [/code] ## Appendices ### Address object Every field is optional. Defined in full — with the BT number each one maps to — under [Send a document → Address object](<#address-object>). ### Party object `{ "name":"…", "vatNumber":"…", "address":[Address](<#address-object>), "contact": {"name":"…", "email":"…", "phone":"…"} }` ### PaymentInfo object * meansenum `credit_transfer``direct_debit``card``cash``cheque` * ibanIBAN * bicSWIFT BIC * referencestring * discountTermsarray ### Status reason codes (Peppol BIS · OPStatusReason) The coded vocabulary for `reasonCode` on [lifecycle updates](<#update-lifecycle>) is the official OpenPeppol _Status Clarification Reason_ list ([OPStatusReason](), Peppol BIS Invoice Response 3). All **14** codes — nothing else is part of the official list: Code| Label| Use it when… ---|---|--- `NON`| No issue| Pure status update — nothing is wrong (e.g. with `under_review`). `REF`| References incorrect| A required reference (PO number, buyer reference, contract) is missing or wrong. `LEG`| Legal information incorrect| The document doesn't meet legal requirements (mandatory mentions, VAT identifiers…). `REC`| Receiver unknown| The invoice is not addressed to this party. `QUA`| Item quality insufficient| Unacceptable or incorrect quality of the delivered goods / services. `DEL`| Delivery issues| Goods / services not delivered, or the delivery is not acceptable. `PRI`| Prices incorrect| Price differs from the order, quote or contract. `QTY`| Quantity incorrect| Quantity differs from what was ordered or delivered. `ITM`| Items incorrect| The invoiced items don't match what was ordered / delivered. `PAY`| Payment terms incorrect| Payment terms differ from the agreement. `UNR`| Not recognized| The commercial transaction is not recognized (unknown order / relation). `FIN`| Finance incorrect| Financing terms differ from expectations. `PPD`| Partially paid| The invoice is only partially paid. `OTH`| Other| No code fits — **always** pair with a free-text `reason`. #### Rejecting vs putting on hold — pick the reversible path first You want to…| Send| Terminal?| What the reason must say ---|---|---|--- **Pause / on hold** — something is missing (delivery note, PO reference, supporting document)| `{"status":"disputed","reasonCode":"suspended","reason":"…"}`| No — supplier answers with the material and processing resumes| Exactly _what is missing_ , so the supplier can supply it and lift the hold. **Contest** — you disagree with part of the content but it may be resolved| `{"status":"disputed","reasonCode":"…"}`| No — resolves to approval or refusal| The code that names the disagreement (`PRI`, `QTY`, `ITM`…), plus free text with the specifics (line, expected value). **Refuse / reject** — the invoice must be cancelled and re-issued| `{"status":"rejected","reasonCode":"…","reason":"…"}`| **Yes** — the supplier must issue a corrective| The code that justifies a definitive refusal, plus free text precise enough for the supplier to re-invoice correctly first time. **Prioritize on hold / dispute over refusing directly.** A rejection cannot be undone: the supplier has to start over. A hold or dispute keeps the invoice alive, tells the supplier exactly what to fix, and costs nothing if the answer is satisfactory. Whatever the status, make the reason _actionable_ : code for the machine, free text for the human — a rejection or hold whose reason the supplier can't act on just moves the problem to email. France — AFNOR motifs, not Peppol codes On the French DGFiP leg the `reasonCode` is forwarded _verbatim_ as the CDAR's `MDT-113`: for _210 Refusée_ / _213 Rejetée_ use a code from the official AFNOR XP Z12-012 motif annex (« Tableau des motifs de STATUTS »), and the special value `suspended` on a `disputed` call is the discriminator that transmits _208 Suspendue_. See [FR refusal, rejection & on-hold](<../compliance/fr/refusal-rejection.html#motifs>). ======================================================================== # Data model # Source: https://docs.get-flowie.com/reference/data-model.html ======================================================================== --- title: "Data model" description: "Entity-relationship diagram and field-level reference for every resource in Flowie Exchange." canonical: "https://docs.get-flowie.com/reference/data-model" source: "https://docs.get-flowie.com/reference/data-model.html" --- # Data model Data model # How the resources fit together If you read one page in this whole reference, make it this one. Once you see the relationships, the rest of the API becomes obvious. ## Entity-relationship diagram Organization id (org_…) name, brand plan (free|starter|pro|platform|wl) API key id (key_…) organizationId → companyId? → Company scopes[], keyType, expiresAt Company id (comp_…) organizationId → vatNumber, peppolId country, status, smpRegistered capabilities {send[], receive[]} compliance, settings, metadata createdAt, updatedAt Webhook id (wh_…) organizationId → companyId? → Company url, events[], secret Partner id (part_…) companyId → Company peppolId, vatNumber, role defaults, tags, contactEmail Document id (doc_…) senderCompanyId → Company receiverPeppolId type, direction, number status, deliveryStatus lifecycleStatus, currency grossAmount, document {…} Event id (evt_…) organizationId → type, createdAt, livemode data {…} (snapshot) Lifecycle event documentId → Document previous, current, at setBy, reason, payment {…} Compliance report documentId → Document platform (PPF|SDI) status, code, reportedAt 1 : N 1 : N 1 : N 1 : N 1 : N (sent) scopes scoped 1 : N 1 : N emits delivers ## Legend * **Solid arrow** : synchronous foreign-key relationship (the child belongs to the parent). * **Dashed arrow** : asynchronous "emits an event" relationship (state change creates an Event record). * **Bold field** : primary key. * **Blue field** : foreign key. ## Organization Top-level tenant in the Flowie system. Holds plan, branding, and ownership of every other resource. You'll never CRUD an Organization through the public API — they're created at sign-up. ## Company A legal entity that can send/receive on Peppol. [Full reference](). Note that `peppolId` is auto-derived from `vatNumber` \+ country scheme; you can override with `additionalIdentifiers[]`. ## Partner A counterparty (customer or supplier) of one of your companies. Stores defaults so you don't repeat them on every send. Partners are scoped to a single company. ## Document An invoice, credit note, debit note, or purchase order. Has three orthogonal status fields: * `status`: _protocol-level_ — has it been validated, signed, sent. * `deliveryStatus`: _transport-level_ — has the recipient AP confirmed. * `lifecycleStatus`: _business-level_ — has the buyer approved, paid, or rejected. You can have `status=sent, deliveryStatus=delivered, lifecycleStatus=disputed`. They're independent. ## Lifecycle event Append-only log of business-level transitions on a document. The current `lifecycleStatus` on a document is materialized from the latest entry. ## Compliance report One per (document, platform) pair where Flowie reported a status to a national authority (PPF for FR, SDI for IT). Updated on every retry. Belgium has no regulator-side report since the HERMES platform was decommissioned on 2025-12-31 — BE invoices don't create rows here. Historical HERMES rows from before that date are retained for audit. ## API key Three flavors (personal, platform, white-label) and an optional scope to a single Company. [Full reference](). ## Webhook Subscription to one or more event types. Optional company scoping. Failures auto-pause after 8 consecutive errors. ## Event Durable record of every state change worth notifying about. Webhook deliveries are derived from these. Available for replay through the [Events API]() for 30 days. ## Cardinality summary From| To| Cardinality| Note ---|---|---|--- Organization| Company| 1 : N| Platform orgs typically have N in the thousands. Organization| API key| 1 : N| One per integration. Organization| Webhook| 1 : N| Up to 100 active webhooks per org. Company| Partner| 1 : N| Free, no upper limit. Company| Document| 1 : N (as sender)| Or as receiver — direction stored on doc. Document| Lifecycle event| 1 : N| One per status transition. Document| Compliance report| 1 : N| One per (platform, retry). Webhook| Event| N : N| Many webhooks consume; one event matches whoever subscribes. ======================================================================== # Document & invoice types # Source: https://docs.get-flowie.com/reference/document-types.html ======================================================================== --- title: "Document & invoice types" description: "Every document type Flowie sends — invoice, credit note, debit note, orders, quotes, events — plus every invoice subtype (prepayment, corrected, self-billed) and how self-billing and self-invoicing work." canonical: "https://docs.get-flowie.com/reference/document-types" source: "https://docs.get-flowie.com/reference/document-types.html" --- # Document & invoice types API Reference # Document & invoice types Every document you send flows through one endpoint — [`POST /v1/documents/send`]() — and a single `type` field tells Flowie what it is. This page is the complete referential: the eight [document types](<#document-types>), the four [invoice subtypes](<#invoice-subtypes>) (including **self-billed** invoices), and how the two flavours of _self-invoice_ — [self-billing](<#self-billing>) and [reverse-charge self-invoicing](<#self-invoice>) — differ and how to emit each. The one field that decides everything: `type` `type` is required on every send (except `event`, which needs no recipient). It picks the document class and the Peppol document type Flowie routes on. Invoice _sub_ -kinds (prepayment, corrected, self-billed) are a second, optional axis — [`documentSubtype`](<#invoice-subtypes>) — layered on top of `type: "invoice"`. ## Test any use case Every scenario on this page has a ready-to-send example body for [`POST /v1/documents/send`](). Expand one and hit **Try in Playground** — it opens the request builder prefilled with the payload, and the Playground loads your stored sandbox key automatically — or copy the JSON or a ready-made curl. Every example uses the sandbox test identifiers, so it runs as-is. Standard invoice 380 Ordinary sale of goods or services — the default. [code] { "type": "invoice", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "INV-2026-0042", "issueDate": "2026-04-15", "currency": "EUR", "lines": [ { "description": "Consulting services", "quantity": 10, "unitPrice": 150.00, "vatRate": 21.0 } ] } } [/code] Multi-line invoice (mixed VAT rates) lines Several lines at different VAT rates — standard, reduced and an exempt intra-EU line carrying its reason. VAT is summed per rate. See [Multiple VAT rates](). [code] { "type": "invoice", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "INV-2026-0500", "issueDate": "2026-04-15", "currency": "EUR", "lines": [ { "description": "Consulting (standard rate)", "quantity": 10, "unit": "HUR", "unitPrice": 150.00, "vatRate": 21.0, "vatCategory": "S" }, { "description": "E-book (reduced rate)", "quantity": 3, "unit": "C62", "unitPrice": 40.00, "vatRate": 6.0, "vatCategory": "S" }, { "description": "Support plan (per month)", "quantity": 12, "unit": "MON", "unitPrice": 99.00, "vatRate": 21.0, "vatCategory": "S" }, { "description": "Intra-EU goods (exempt)", "quantity": 1, "unit": "C62", "unitPrice": 500.00, "vatRate": 0.0, "vatCategory": "K", "vatExemptionReason": "Intra-Community supply, art. 138 Directive 2006/112/EC", "vatExemptionCode": "VATEX-EU-IC" } ] } } [/code] Line detail (units & item codes) lines Per-line unit of measure (`unit`, UN/ECE Rec 20 — HUR hour, MON month, KGM kg, C62 unit), item reference (`itemCode`) and VAT category. [code] { "type": "invoice", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "INV-2026-0501", "issueDate": "2026-04-30", "currency": "EUR", "orderReference": "PO-2026-0042", "lines": [ { "description": "Managed hosting", "quantity": 1, "unit": "MON", "unitPrice": 1200.00, "vatRate": 21.0, "vatCategory": "S", "itemCode": "SKU-HOST-PRO" }, { "description": "Steel bar", "quantity": 250, "unit": "KGM", "unitPrice": 3.20, "vatRate": 21.0, "vatCategory": "S", "itemCode": "SKU-STEEL-16" } ] } } [/code] Prepayment invoice / acompte 386 Advance billed before delivery. See [Prepayment invoices](<#prepayment>). [code] { "type": "invoice", "documentSubtype": "PREPAYMENT_INVOICE", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "ACPT-2026-0042", "issueDate": "2026-04-15", "currency": "EUR", "orderReference": "PO-2026-0042", "note": "Acompte 30 percent - commande PO-2026-0042", "lines": [ { "description": "Advance - 30 percent of project fee", "quantity": 1, "unitPrice": 3000.00, "vatRate": 21.0 } ] } } [/code] Corrected invoice 384 Replaces a prior invoice with corrected content; references the original. [code] { "type": "invoice", "documentSubtype": "CORRECTED_INVOICE", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "INV-2026-0042-R1", "issueDate": "2026-04-20", "currency": "EUR", "billingReference": "INV-2026-0042", "billingReferenceDate": "2026-04-15", "lines": [ { "description": "Consulting services (corrected quantity)", "quantity": 8, "unitPrice": 150.00, "vatRate": 21.0 } ] } } [/code] Credit note 381 Reduces or cancels a prior invoice. See [Credit & debit notes](<#credit-debit>). [code] { "type": "credit-note", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "CN-2026-0007", "issueDate": "2026-05-02", "currency": "EUR", "billingReference": "INV-2026-0042", "billingReferenceDate": "2026-04-15", "lines": [ { "description": "Refund - consulting services", "quantity": 2, "unitPrice": 150.00, "vatRate": 21.0 } ] } } [/code] Debit note 383 Increases a prior invoice with an extra charge. [code] { "type": "debit-note", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "DN-2026-0003", "issueDate": "2026-05-05", "currency": "EUR", "billingReference": "INV-2026-0042", "billingReferenceDate": "2026-04-15", "lines": [ { "description": "Late-delivery surcharge", "quantity": 1, "unitPrice": 90.00, "vatRate": 21.0 } ] } } [/code] Self-billing / autofacturation 389 You (the customer) issue for the supplier; roles flip. See [Self-billing](<#self-billing>). [code] { "type": "invoice", "selfBilled": true, "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "SB-2026-0100", "issueDate": "2026-04-15", "currency": "EUR", "lines": [ { "description": "Grain delivery - March", "quantity": 12, "unitPrice": 210.00, "vatRate": 6.0 } ] } } [/code] Reverse charge (self-account VAT) AE Cross-border supply where the buyer accounts for the VAT. See [Self-invoicing](<#self-invoice>). [code] { "type": "invoice", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "RC-2026-0055", "issueDate": "2026-04-15", "currency": "EUR", "note": "Reverse charge - VAT to be accounted for by the customer", "lines": [ { "description": "Cross-border consulting (reverse charge)", "quantity": 5, "unitPrice": 200.00, "vatRate": 0.0, "vatCategory": "AE", "vatExemptionReason": "Reverse charge, art. 196 Directive 2006/112/EC", "vatExemptionCode": "VATEX-EU-AE" } ] } } [/code] Multiple parties (factoring payee) parties A payee distinct from the seller. See [Multiple parties](). [code] { "type": "invoice", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "INV-2026-0200", "issueDate": "2026-04-15", "currency": "EUR", "parties": [ { "role": "seller", "id": "0009:FR86797978996", "name": "ACME FRANCE", "initiator": true }, { "role": "buyer", "id": "0009:BE0123456789", "name": "MEGACORP BE" }, { "role": "payee", "vatNumber": "FR90123456789", "name": "ACME FACTORING SAS" } ], "lines": [ { "description": "Consulting services", "quantity": 10, "unitPrice": 150.00, "vatRate": 21.0 } ] } } [/code] Purchase request / requisition order The buyer's internal request to authorise a purchase, ahead of the order. See [Orders, quotes & requisitions](<#orders>). [code] { "type": "purchase-request", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "PR-2026-0042", "issueDate": "2026-04-08", "currency": "EUR", "lines": [ { "description": "Office chairs (requisition)", "quantity": 20, "unitPrice": 120.00, "vatRate": 21.0 } ] } } [/code] Purchase order order An order sent by the buyer to the seller. [code] { "type": "purchase-order", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "PO-2026-0042", "issueDate": "2026-04-10", "currency": "EUR", "lines": [ { "description": "Office chairs", "quantity": 20, "unitPrice": 120.00, "vatRate": 21.0 } ] } } [/code] Sales order order The seller's order acknowledgement back to the buyer. [code] { "type": "sales-order", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "SO-2026-0042", "issueDate": "2026-04-11", "currency": "EUR", "orderReference": "PO-2026-0042", "lines": [ { "description": "Office chairs", "quantity": 20, "unitPrice": 120.00, "vatRate": 21.0 } ] } } [/code] Quote quote A quotation ahead of any order. [code] { "type": "quote", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "Q-2026-0042", "issueDate": "2026-04-01", "currency": "EUR", "lines": [ { "description": "Annual support plan", "quantity": 1, "unitPrice": 5000.00, "vatRate": 21.0 } ] } } [/code] Event event An observability record — no recipient, so `to` is omitted. [code] { "type": "event", "from": "0009:FR86797978996", "document": { "number": "INV-2026-0042" } } [/code] Where's the expense payload? There isn't one — an [expense](<#expenses>) is _received_ , not sent. The invoice-backed case is just the supplier's `invoice` arriving inbound; the no-invoice case is declared via e-reporting. Neither is a `POST /v1/documents/send` you make. ## Two axes: document type & invoice subtype It helps to keep two concepts separate: * **Document type** (`type`) — _what kind of document_ this is: an invoice, a credit note, an order, a quote. It is a fixed enum and it drives Peppol routing (which document type the recipient must be able to receive). * **Invoice subtype** (`documentSubtype`) — _which kind of invoice_ , when `type: "invoice"`. It is rendered as the UBL `InvoiceTypeCode` (BT-3) using the UNCL1001 code list — `380` for a plain commercial invoice, `386`/`384`/`389` for prepayment / corrected / self-billed. A credit note carries its own UNCL1001 code (`381`) derived from `type: "credit-note"`; you do not set `documentSubtype` for it. The subtype axis exists only to distinguish sub-kinds _of an invoice_. ## Document types (`type`) The `type` enum on [`POST /v1/documents/send`](). The first six are Peppol-routed business documents and require a `to`; `event` is a pure observability record and has no recipient. `type`| What it is| UNCL1001 code (BT-3)| Notes ---|---|---|--- `invoice`| Commercial invoice — a demand for payment for goods/services (B2B, B2C, B2G).| `380` (default; overridable via [`documentSubtype`](<#invoice-subtypes>))| The workhorse. See [invoice subtypes](<#invoice-subtypes>) for prepayment / corrected / self-billed. `credit-note`| Reduces or cancels a previously issued invoice (a return, a rebate, an error).| `381`| Link the original with `document.billingReference` — **required** under the FR reform. See [below](<#credit-debit>). `debit-note`| Increases a previously issued invoice (an extra charge after the fact).| `383`| Also requires `document.billingReference` under the FR reform. `purchase-order`| An order sent by the buyer to the seller.| —| Ordering document, not a fiscal invoice. See [Purchase orders](). `purchase-request`| A purchase requisition — the buyer's internal request to authorise a purchase, ahead of the order.| —| Maps to the transaction-documents `PURCHASE_REQUEST`. See [Orders, quotes & requisitions](<#orders>). `sales-order`| The seller's order acknowledgement / confirmation back to the buyer.| —| Pairs with `purchase-order` in an order-to-invoice flow. `quote`| A quotation / proposal, ahead of any order.| —| No fiscal effect; the first step of the quote → order → invoice chain. `goods-receipt`| The buyer's record that the goods actually arrived — quantities received against what was ordered.| —| Closes the order loop: it is what a three-way match checks the invoice against, alongside the `purchase-order`. No fiscal effect. The _seller's_ side of the same delivery is not a document type — reference the delivery note on the invoice with `document.despatchAdviceReference` (BT-16) instead. `event`| An observability / audit record about a document — no transport, no recipient.| —| The only type where `to` is optional. Carries just `document.number` and metadata. A minimal invoice send: [code] curl -X POST https://api.flowie.ink/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "invoice", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "INV-2026-0042", "issueDate": "2026-04-15", "currency": "EUR", "lines": [ { "description": "Consulting services", "quantity": 10, "unitPrice": 150.00, "vatRate": 21.0 } ] } }' [/code] ## Invoice subtypes (`documentSubtype`) When `type: "invoice"`, the optional `documentSubtype` field selects the UNCL1001 `InvoiceTypeCode` (BT-3) rendered on the UBL. It accepts the `CAPITAL_SNAKE_CASE` name or the raw numeric code (e.g. `"386"`). It is only valid for `type: "invoice"` — sending it on any other type is a `400`. The four below are the named, modelled sub-kinds; because the field is a UNCL1001 pass-through, any other valid BT-3 code you send is tagged and rendered as-is. Name| Code| Meaning| How to send ---|---|---|--- (default)| `380`| Commercial invoice — an ordinary sale.| Omit `documentSubtype`. `PREPAYMENT_INVOICE`| `386`| Prepayment / down-payment invoice (_facture d'acompte_) — billed before delivery; netted out by the final invoice. See [Prepayment invoices](<#prepayment>).| `"documentSubtype": "PREPAYMENT_INVOICE"` `CORRECTED_INVOICE`| `384`| Corrected invoice (_facture rectificative_) — replaces a prior invoice with corrected content. See [Corrected invoices](<#corrected>).| `"documentSubtype": "CORRECTED_INVOICE"` `SELF_BILLED_INVOICE`| `389`| Self-billed invoice (_autofacturation_) — the customer issues on the supplier's behalf. See [Self-billing](<#self-billing>).| Prefer the `selfBilled: true` flag — it also flips the party roles. Prefer the `selfBilled` flag for `389` Setting `documentSubtype: "SELF_BILLED_INVOICE"` tags the UBL but does _not_ swap Seller and Buyer. The top-level [`selfBilled: true`](<#self-billing>) flag does both — tags `389` _and_ flips the roles — so it is the right choice for real self-billing. ## Credit & debit notes A credit note (`type: "credit-note"`, UNCL1001 `381`) reduces or cancels a prior invoice; a debit note (`type: "debit-note"`, `383`) increases one. Both are first-class documents that flow through the same lifecycle as an invoice — a credit note is _not_ a lifecycle status on the original invoice. Under the French reform, both must reference the invoice they amend via `document.billingReference` (BT-25, the UBL `BillingReference/InvoiceDocumentReference/ID`) and, where known, `document.billingReferenceDate` (BT-26). Omitting the reference on a FR credit/debit note fails validation (`BR-FR-CO-04`/`BR-FR-CO-05`). [code] curl -X POST https://api.flowie.ink/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "credit-note", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "CN-2026-0007", "issueDate": "2026-05-02", "currency": "EUR", "billingReference": "INV-2026-0042", "billingReferenceDate": "2026-04-15", "lines": [ { "description": "Refund — consulting services", "quantity": 2, "unitPrice": 150.00, "vatRate": 21.0 } ] } }' [/code] ## Prepayment invoices (_facture d'acompte_) A **prepayment invoice** — _facture d'acompte_ , or down-payment / advance invoice — bills an amount **before** the goods are delivered or the service is completed. It is a real, VAT-bearing invoice in its own right (with its own number and, where the advance is taxable, VAT due on the advance) — not a proforma or a quote. Tag it with the UNCL1001 subtype `386` via `documentSubtype`: [code] curl -X POST https://api.flowie.ink/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "invoice", "documentSubtype": "PREPAYMENT_INVOICE", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "ACPT-2026-0042", "issueDate": "2026-04-15", "currency": "EUR", "orderReference": "PO-2026-0042", "note": "Acompte 30% — commande PO-2026-0042", "lines": [ { "description": "Advance — 30% of project fee", "quantity": 1, "unitPrice": 3000.00, "vatRate": 21.0 } ] } }' [/code] You can send the numeric code instead of the name (`"documentSubtype": "386"`) — both render the same UBL `InvoiceTypeCode` (BT-3). Like every subtype it is only valid for `type: "invoice"`. **Settling the advance.** When the work is done you issue the _final_ (balance) invoice as an ordinary `type: "invoice"` (subtype `380`) and **deduct the amount already invoiced on the acompte** , so the customer is billed only the remaining balance — carry the deduction as a negative line (or, with `format=ubl-xml`, a document-level allowance) and cite the acompte's number in `document.note` or `document.orderReference` for the audit trail. The acompte and the balance invoice together add up to the full order value. Country specifics In 🇮🇹 Italy the advance is its own _TipoDocumento_ — `TD02` (_acconto/anticipo su fattura_) or `TD03` (_su parcella_) — set through `document.note`; see [Italian document types](<../compliance/it/document-types.html>). Under the 🇫🇷 French reform the acompte follows the standard e-invoice flow carrying `InvoiceTypeCode` `386`. ## Corrected invoice (_facture rectificative_) A **corrected invoice** re-issues an invoice whose content was wrong — a mistyped amount, the wrong line, a bad VAT rate — as a fresh, self-standing invoice that **replaces** the original rather than adjusting it. Tag it with the UNCL1001 subtype `384` via `documentSubtype`, and point it at the invoice it supersedes with `document.billingReference` (BT-25) so the chain stays auditable: [code] curl -X POST https://api.flowie.ink/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "invoice", "documentSubtype": "CORRECTED_INVOICE", "from": "0009:FR86797978996", "to": "0009:BE0123456789", "document": { "number": "INV-2026-0042-R1", "issueDate": "2026-04-20", "currency": "EUR", "billingReference": "INV-2026-0042", "billingReferenceDate": "2026-04-15", "lines": [ { "description": "Consulting services (corrected quantity)", "quantity": 8, "unitPrice": 150.00, "vatRate": 21.0 } ] } }' [/code] Send the numeric code if you prefer (`"documentSubtype": "384"`); like every subtype it is only valid for `type: "invoice"`. **Corrected invoice vs. credit note.** A corrected invoice (`384`) _replaces_ the original with the right figures. An [avoir / credit note](<#credit-debit>) instead _cancels or reduces_ the original and leaves it standing — often followed by a brand-new invoice. Under the French reform the credit-note route is the usual way to correct an already-transmitted invoice; reach for `384` when a single rectifying invoice that references the original is the cleaner record. Either way, carry the link in `document.billingReference`. Correcting before vs. after transmission Nothing sent yet? Just fix and send the invoice normally — there is no correction to model. The `384` subtype (and the `billingReference` link) is for when the original has already reached the buyer and the tax authority and must be superseded on the record. ## Self-billing (_autofacturation_) **Self-billing** is the arrangement where the **customer issues the invoice on the supplier's behalf** — common in agriculture, marketplaces, and royalty settlements, and permitted where the two parties have agreed to it. It is still a two-party sale between a distinct seller and buyer; only the party who _issues_ the document changes. Set it with the top-level `selfBilled: true` flag. Flowie then: * treats the acting organization (`from`) as the **Buyer** / initiator; * treats `to` as the **Seller** (the supplier being billed); * tags the document with UNCL1001 subtype `389` (Self-Billed Invoice). It is a shorthand for `documentSubtype: "SELF_BILLED_INVOICE"` that _also_ flips the roles, and it is only valid for `type: "invoice"` — self-billed credit notes (UNCL1001 `261`) are not yet modelled downstream, so `selfBilled` on any other type is a `400`. [code] curl -X POST https://api.flowie.ink/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "invoice", "selfBilled": true, "from": "0009:FR86797978996", # you — the customer, issuing on the supplier'"'"'s behalf "to": "0009:BE0123456789", # the supplier — becomes the Seller "document": { "number": "SB-2026-0100", "issueDate": "2026-04-15", "currency": "EUR", "lines": [ { "description": "Grain delivery — March", "quantity": 12, "unitPrice": 210.00, "vatRate": 0.0 } ] } }' [/code] Self-billing with a third party When the self-billed document also involves a distinct `payer` or `payee` (e.g. a factoring arrangement), drop the `selfBilled` shorthand and describe every role explicitly with [`document.parties`](): one entry per role, with exactly one carrying `initiator: true` (the org your key acts as). ## Self-invoicing & reverse charge (_autofattura_) "Self-invoice" is also used for a different, VAT-driven case: under a **reverse charge** or on a cross-border purchase, the **buyer issues a document to account for the VAT itself** , because the supplier did not (or could not) charge it. Here the same party is effectively both seller and buyer of record — it is not the two-party self-billing above. How this is expressed depends on the jurisdiction: * **🇮🇹 Italy (SDI).** Self-invoices and integrations carry a dedicated _TipoDocumento_ — `TD16`–`TD19` for reverse charge and foreign purchases, `TD20`/`TD21`/`TD27`/`TD29` for the _autofatture_ where seller = buyer. Set the code via `document.note`; Flowie validates the seller/buyer and country rules before transmission. See the full table on [Italian document types (TD01–TD29)](<../compliance/it/document-types.html>). * **Cross-border / EN 16931.** On the structured invoice, a reverse-charge or exempt supply is carried per line with the right `vatCategory` (`AE` reverse charge, `K` intra-community, `G` export, `E` exempt, `O` out of scope) plus a stated exemption reason — see [Tax exemption & zero rate](). Flowie renders the matching BG-23 VAT breakdown so the zero-VAT category is declared rather than a bogus 0 % standard rate. Two things both called "self-invoice" **Self-billing** (`selfBilled: true`, UNCL1001 `389`) = the customer issues a normal invoice for a real supplier, roles flipped. **Reverse-charge self-invoicing** (Italian _autofattura_ , TD16–TD29) = the buyer issues a document to self-account for VAT. Pick by _why_ you are issuing, not just the word. ## Orders, quotes & requisitions Not every document is an invoice. Flowie also carries the **pre-invoice** documents of the procure-to-pay chain — the ones that lead up to the bill. They flow through the same [`POST /v1/documents/send`]() pipeline; only the `type` changes. * **Purchase request** (`type: "purchase-request"`) — a _purchase requisition_ : the buyer's internal request to authorise a purchase, the first step of the chain. Try the [Purchase request example](<#try-it>). * **Quote** (`type: "quote"`) — a quotation / proposal the seller sends. No fiscal effect. Try the [Quote example](<#try-it>). * **Purchase order** (`type: "purchase-order"`) — the buyer's order to the seller. Try the [Purchase order example](<#try-it>). * **Sales order** (`type: "sales-order"`) — the seller's acknowledgement back to the buyer, pairing with the purchase order. These are order-side documents, not fiscal invoices. Chain the whole thread — _requisition → quote → order → invoice_ — by carrying `document.orderReference` (and `document.buyerReference`) forward from one document to the next, so it stays linkable end to end. About `purchase-request` (the requisition) A **purchase requisition** (PR) is the internal approval a buyer raises before a [purchase order](<#orders>) goes to the supplier. Flowie carries it as the `purchase-request` type, rendered as the transaction-documents `PURCHASE_REQUEST` document. Like the other order-side types it takes a `from`/`to` and a `document` body; put the requisition number in `document.number` and any originating reference in `document.buyerReference`. ## Expenses (employee & card spend) There is **no`expense` document type** — an expense is not a thing you _send_ , it is spend you _account for_ , and it maps onto the model above in one of two ways depending on whether a supplier invoice exists: * **Expense backed by a supplier invoice** (a hotel, a SaaS subscription, a supplier that issues a proper invoice). This is just an ordinary `type: "invoice"` that you _receive_ — your company is the buyer, and it arrives inbound like any other invoice (see [Document · direction]()). Nothing expense-specific: it is captured, matched and booked as a received invoice. This is French reform _cas d'usage_ 5. * **Expense with no invoice** — a restaurant receipt, a toll ticket, a taxi, a lodged-card purchase. There is no structured invoice to exchange over Peppol/PA, so the amount is declared to the tax authority as **e-reporting** (transaction / payment _data_), not sent as an e-invoice. These are French _cas d'usage_ 6 (expenses without an invoice), 27 (toll tickets), 28 (restaurant receipts) and 7 (lodged corporate card). Expenses are received, not a send type Because expenses are the buyer-side view of a supplier's invoice (or a receipt reported as data), they never need a new `type` value. For the invoice-backed case, receive and reconcile the inbound invoice; for the no-invoice case, see the [e-reporting deep dive](<../compliance/fr/use-cases.html#ereporting>) and the full [cas d'usage referential](<../compliance/fr/use-cases.html#all>) (cases 5–7, 27, 28). ## Which one do I use? * Ordinary sale → `type: "invoice"` (subtype defaults to `380`). * Billing an advance before delivery ([acompte](<#prepayment>)) → `type: "invoice"` \+ `documentSubtype: "PREPAYMENT_INVOICE"`. * Replacing an invoice's content → `type: "invoice"` \+ `documentSubtype: "CORRECTED_INVOICE"`. * Reducing / cancelling an invoice → `type: "credit-note"` with `billingReference`. * Charging more after the fact → `type: "debit-note"` with `billingReference`. * You are the customer issuing for the supplier → `type: "invoice"` \+ `selfBilled: true`. * Self-accounting for VAT under reverse charge (IT) → `type: "invoice"` \+ the right `TD` code in `document.note`. * Requisitioning a purchase (internal request) → `type: "purchase-request"`. * Ordering / quoting → `type: "purchase-order"`, `"sales-order"`, or `"quote"`. * Recording an event, no recipient → `type: "event"`. * Booking an [expense](<#expenses>) → not a send type: receive the supplier's `invoice`, or e-report it when there's no invoice. ## References * [Send a document]() — the endpoint, every field including `type`, `documentSubtype` and `selfBilled`. * [Multiple parties]() — explicit role-tagged parties for self-billing with a third party. * [Tax exemption & zero rate]() — VAT categories and exemption reasons for reverse-charge and exempt supplies. * [Data model · Document]() — the three orthogonal status fields on every document. * [Italy · Document types (TD01–TD29)](<../compliance/it/document-types.html>) — the full _TipoDocumento_ referential, including the _autofatture_. ======================================================================== # E-invoicing formats # Source: https://docs.get-flowie.com/reference/formats.html ======================================================================== --- title: "E-invoicing formats" description: "Every e-invoicing format, explained: EN 16931, UBL 2.1, UN/CEFACT CII, Peppol BIS Billing 3.0 and PINT, Factur-X and ZUGFeRD, XRechnung, FatturaPA, Facturae, KSeF FA(3), ZATCA, MyInvois — which ones Flowie sends and receives, and a link to every official specification." canonical: "https://docs.get-flowie.com/reference/formats" source: "https://docs.get-flowie.com/reference/formats.html" --- # E-invoicing formats API Reference # E-invoicing formats An **e-invoicing format** is the machine-readable structure an invoice travels in — not a PDF of an invoice, but the invoice itself as data a buyer's system can book without retyping. There are only **two XML syntaxes** that matter in Europe ([UBL 2.1](<#ubl>) and [UN/CEFACT CII](<#cii>)), one semantic standard on top of them ([EN 16931](<#en16931>)), and then a long tail of **national profiles** — [Factur-X](<#hybrid>), [ZUGFeRD](<#hybrid>), [XRechnung](<#national>), [FatturaPA](<#national>), [Facturae](<#national>), [KSeF FA(3)](<#national>), [ZATCA](<#clearance>), [MyInvois](<#clearance>) — that constrain the same data for one country's tax administration. This page is the complete map, and says exactly which of them Flowie produces, accepts and delivers. You do not have to pick one Send [`POST /v1/documents/send`]() your invoice as JSON and Flowie renders the syntax the destination requires — Peppol BIS Billing 3.0 UBL for the Peppol network, the country-native format where the tax administration mandates one. Already have XML? Deposit [UBL](<#ubl>), [CII](<#cii>) or a [Factur-X PDF](<#hybrid>) and Flowie validates and routes it as-is. The `format` field is documented under [what Flowie handles](<#flowie>). ## The short answer, by country If you only read one section, read this one. In 2026 the format question resolves to four cases: * **You are sending inside the EU over Peppol** (Belgium, the Netherlands, the Nordics, Ireland, most B2G) → [Peppol BIS Billing 3.0](<#peppol>), which is UBL 2.1 constrained to EN 16931. This is the default Flowie emits. * **You are sending to a country with its own clearance platform** (Italy, Poland, Romania, Spain, Saudi Arabia, India, Malaysia, Egypt, Turkey) → the [national format](<#clearance>) that platform accepts, cleared before or as the invoice is delivered. * **You are sending in France or Germany** → a [hybrid Factur-X / ZUGFeRD PDF](<#hybrid>), or plain [CII](<#cii>)/[UBL](<#ubl>), or [XRechnung](<#national>) for German public buyers. All three are legal; the buyer's capability decides. * **You do not know** → send JSON and let Flowie resolve the recipient's capability from the network directory. That is what [`POST /v1/directory/verify`]() answers. Per-country mandates, deadlines and the exact network each one runs on are documented in the [compliance section](<../compliance/index.html>) — 47 jurisdictions, with a [coverage matrix](<../compliance/index.html#matrix>) you can sort by network. ## The four layers: model, syntax, profile, network Almost every argument about e-invoicing formats is two people talking about different layers. An invoice on the wire is four decisions stacked, and each is independent of the others: Layer| What it fixes| Examples ---|---|--- **1\. Semantic model** | Which business terms exist and what they mean — `BT-1` is the invoice number, `BT-9` the due date. No syntax at all. | [EN 16931](<#en16931>), the semantic core of every European format **2\. Syntax** | How those terms are serialised into a file a parser can read. | [UBL 2.1](<#ubl>) (OASIS), [UN/CEFACT CII D16B](<#cii>) **3\. Profile / CIUS** | Which optional terms become mandatory, which code lists are allowed, which national identifiers are required. A _CIUS_ narrows EN 16931; an _extension_ adds to it. | [Peppol BIS Billing 3.0](<#peppol>), [XRechnung](<#national>), [Factur-X EN 16931 profile](<#hybrid>), [PINT](<#peppol>) **4\. Network / transport** | How the file reaches the buyer and the tax administration. | Peppol AS4 4-corner, Italy's SDI, France's PA/PDP, Poland's KSeF, Saudi Fatoora "Is XRechnung a format?" It is a _profile_ (layer 3) that can be carried in either syntax (layer 2) and always satisfies the same semantic model (layer 1). That is why an XRechnung invoice and a Peppol BIS invoice can be byte-different and still carry identical business content — and why converting between them is a mapping exercise, not a re-keying one. Flowie holds the semantic model once, in the [document data model](), and renders the layers below it. ## What Flowie handles Two directions, one endpoint each. On the way **out** , the `format` field on [`POST /v1/documents/send`]() declares what you are handing us: `format`| What you send| What Flowie does ---|---|--- `json` | The canonical [Flowie document model]() in the `document` field. | Renders EN 16931-compliant UBL 2.1 (Peppol BIS Billing 3.0 customisation), then delivers it in the destination's native format. `ubl-xml` | Your own UBL 2.1 `Invoice` or `CreditNote` XML in `xml`. | Validates against the EN 16931 and national schematrons, then routes it. Your bytes stay the emitted original. `cii-xml` | UN/CEFACT `CrossIndustryInvoice` XML — including the XML extracted from a Factur-X or ZUGFeRD PDF. | Same: CII schematrons, then routing. `auto` | A file in `file.content` and no opinion about it. | Sniffs the magic bytes and the XML root element — `CrossIndustryInvoice` → CII, `Invoice`/`CreditNote` → UBL, `%PDF` → PDF — and picks the pipeline. This is the safe default. `raw` | Anything else — a PDF, a scan, a spreadsheet. | Stores it as-is, no network routing, no structured validation. Announce CII as CII A Factur-X deposit is **CII, not UBL**. Declaring `format: "ubl-xml"` for it runs the wrong schematron and reports a perfectly valid invoice as broken. If you are not certain, use `auto` — the root element decides, and it is never wrong. On the way **in** , every document you receive is available in three shapes from [the document endpoints](): the **original** artefact exactly as the sender deposited it (the Factur-X PDF, if that is what they sent), the **structured XML** , and the normalised **JSON** that [webhooks]() carry. You never have to parse a syntax you do not want to support. On the French leg, the AFNOR XP Z12-013 flow declares its syntax explicitly — `CII`, `UBL`, `Factur-X` for invoices, plus `CDAR` for lifecycle statuses and `FRR` for e-reporting. See the [French integration playbook](<../compliance/fr/integration.html>). ## Format catalogue Every e-invoicing format you are likely to meet, what it actually is, where it is required, and whether Flowie handles it. Click a column header to sort. Format | What it is | Where it matters | Flowie | Official reference ---|---|---|---|--- [**EN 16931**](<#en16931>) | Semantic model (not a file format) | EU-wide baseline; every European profile is a CIUS of it | Native | [European Commission]() [**UBL 2.1**](<#ubl>) | XML syntax (OASIS) | Peppol, Denmark, Norway, Netherlands, Saudi Arabia, Malaysia, Turkey | Send & receive | [OASIS UBL 2.1]() [**UN/CEFACT CII**](<#cii>) (D16B) | XML syntax (Cross Industry Invoice) | France, Germany, and the XML inside every Factur-X / ZUGFeRD PDF | Send & receive | [UNECE XML schemas]() [**Peppol BIS Billing 3.0**](<#peppol>) | CIUS of EN 16931 in UBL 2.1 | The Peppol network — 30+ countries, the EU default | Send & receive | [OpenPeppol BIS 3.0]() [**Peppol PINT**](<#peppol>) | Global billing template + per-jurisdiction specialisations | Australia, New Zealand, Japan, Singapore, UAE, and the EU PINT profile | Send & receive | [PINT Billing]() [**Factur-X**](<#hybrid>) | Hybrid PDF/A-3 with embedded CII XML | France — the format most French suppliers will emit | Send & receive | [FNFE-MPE]() [**ZUGFeRD**](<#hybrid>) | The same hybrid standard, German edition | Germany — B2B, interchangeable with Factur-X | Send & receive | [FeRD]() [**XRechnung**](<#national>) | German CIUS of EN 16931 (UBL or CII) | Germany — mandatory for federal B2G, widely used B2B | Send & receive | [KoSIT / XÖV]() [**FatturaPA**](<#national>) | Italian national XML schema (pre-dates EN 16931) | Italy — every B2B, B2C and B2G invoice, cleared through SDI | Send & receive | [Agenzia delle Entrate]() [**Facturae**](<#national>) | Spanish national XML, signed with XAdES | Spain — B2G via FACe, alongside the Crea y Crece B2B rollout | Send & receive | [facturae.gob.es]() [**KSeF FA(3)**](<#national>) | Polish national XML schema | Poland — mandatory B2B clearance through KSeF from 2026 | Send & receive | [Ministerstwo Finansów]() [**ISDOC**](<#national>) | Czech UBL-derived XML, in use since 2009 | Czechia — public sector accepts ISDOC and Peppol BIS | Send & receive | [ISDOC specification]() [**OIOUBL**](<#national>) | Danish UBL profile, pre-dating Peppol | Denmark — NemHandel, legacy public-sector ERPs | Send & receive | [oioubl.info]() [**EHF**](<#national>) | Norwegian profile, now a thin layer over Peppol BIS | Norway — B2G since 2012 | Send & receive | [DFØ / Anskaffelser]() [**Finvoice**](<#national>) | Finnish bank-led XML standard | Finland — bank channels, alongside Peppol BIS | Send & receive | [Finance Finland]() [**ebInterface**](<#national>) | Austrian XML standard | Austria — accepted alongside Peppol BIS on the federal portal | Send & receive | [ebInterface]() [**ZATCA e-invoice**](<#clearance>) | UBL 2.1-based XML, cryptographically stamped | Saudi Arabia — Fatoora clearance and reporting | Send & receive | [ZATCA]() [**MyInvois**](<#clearance>) | UBL 2.1 in XML or JSON | Malaysia — LHDN clearance, phased by turnover | Send & receive | [MyInvois SDK]() [**GST e-invoice (INV-01)**](<#clearance>) | JSON schema registered with an IRP for an IRN | India — B2B above the turnover threshold | Send & receive | [GST e-Invoice portal]() [**ETA e-invoice**](<#clearance>) | JSON/XML submitted to the tax authority | Egypt — universal B2B/B2G clearance | Send & receive | [Egyptian Tax Authority]() [**UBL-TR (e-Fatura)**](<#clearance>) | Turkish UBL 2.1 customisation | Türkiye — e-Fatura and e-Arşiv | Send & receive | [GİB e-Fatura]() [**UN/EDIFACT INVOIC**](<#legacy>) | Pre-XML EDI message | Retail, automotive and logistics supply chains | On request | [UNECE EDIFACT]() [**PDF / scan**](<#legacy>) | Not an e-invoicing format | Nowhere, legally, once a mandate is live | Stored as-is | — **Send & receive** means Flowie renders the format on the way out and normalises it on the way in — you work in JSON and never touch the schema. Country-by-country detail, including which network carries which format, is in the [coverage matrix](<../compliance/index.html#matrix>). ## EN 16931 — the European semantic standard **EN 16931 is not a file format.** It is the semantic data model that says what an invoice contains: 164 business terms (`BT-1`…) grouped into business groups (`BG-1`…), plus roughly 200 business rules that say when each is required and how the totals must add up. Every European e-invoicing format is a constraint on it. It exists because of EU Directive 2014/55/EU, which obliged public buyers across the Union to accept electronic invoices in a common standard. **EN 16931-1:2026 was published in May 2026 and formally withdrew the 2017 edition** , with a migration period while profiles catch up — so a document that validates against a 2017-era schematron will keep validating for now, and the practical change arrives when each national profile republishes against the new edition. The companion **CEN/TS 16931-2** lists the syntaxes that comply with it, and there are exactly two: [UBL 2.1](<#ubl>) and [UN/CEFACT CII](<#cii>). Everything else in Europe is a profile of one of those two. Flowie exposes the model directly: the [business-terms referential](<../compliance/fr/business-terms.html>) lists all 164 BTs, what each maps to in UBL and CII, and which ones France additionally requires. Validation failures come back naming the business term, not the schematron step — see [the error catalog](). ## UBL 2.1 — the XML syntax most networks speak **UBL (Universal Business Language) 2.1** is an OASIS standard defining XML schemas for the whole procurement chain — orders, despatch advices, invoices, credit notes. Its `Invoice` and `CreditNote` documents are one of the two EN 16931-compliant syntaxes, and the one the Peppol network chose. An EN 16931 UBL invoice announces its profile in two elements at the top of the document: [code] urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 [/code] `CustomizationID` is the _profile_ — the CIUS the document claims to satisfy. `ProfileID` is the _business process_ it belongs to. Get either wrong and a compliant access point will reject the document before a human sees it. Flowie writes both for you when you send `format: "json"`, and validates them when you deposit your own XML. UBL is also the base of several national formats that pre-date or extend the European standard — [OIOUBL](<#national>) (Denmark), [ISDOC](<#national>) (Czechia), [UBL-TR](<#clearance>) (Türkiye), [ZATCA](<#clearance>) (Saudi Arabia) and [MyInvois](<#clearance>) (Malaysia). ## UN/CEFACT CII — the other compliant syntax **CII (Cross Industry Invoice)** is UN/CEFACT's XML syntax, standardised in schema release **D16B**. Its root element is `rsm:CrossIndustryInvoice`, and it carries the same EN 16931 business terms as UBL in a different tree — three top-level sections (`ExchangedDocument`, `SupplyChainTradeTransaction` and the header context) instead of UBL's flatter layout. CII matters far more than its market share suggests, because it is the XML embedded inside every [Factur-X and ZUGFeRD](<#hybrid>) PDF — which makes it the dominant syntax in France and Germany, the two largest e-invoicing markets in continental Europe. Telling them apart in one line Read the root element. `` → CII. `` or `` in a UBL namespace → UBL. That is exactly what Flowie's `format: "auto"` does, and why it is a safer choice than declaring the syntax yourself. ## Factur-X & ZUGFeRD — the hybrid PDF formats **Factur-X** (France) and **ZUGFeRD** (Germany) are the same standard published by two bodies — [FNFE-MPE]() and [FeRD]() — under two names. A Factur-X invoice is a **PDF/A-3 file with a CII XML attachment embedded inside it** : a human opens the PDF and reads an invoice; a machine opens the same file, pulls out `factur-x.xml`, and books it. One artefact, both audiences, no reconciliation problem. The standard defines a ladder of profiles, from _MINIMUM_ and _BASIC WL_ (too thin to be a legal invoice on their own) through _BASIC_ and _EN 16931_ (the fully compliant core) to _EXTENDED_ (adds terms beyond EN 16931). France's B2B reform accepts Factur-X at the EN 16931 profile and above. Flowie treats the PDF as the original: deposit a Factur-X and the embedded CII is extracted, mapped and validated, while the **PDF you sent stays the artefact returned as the original document** — which is what AFNOR XP Z12-013 requires of a French emitter, and what an auditor will ask for. See the [France overview](<../compliance/fr/index.html>) and the [Germany page](<../compliance/de.html>) for the two mandates. ## Peppol BIS Billing 3.0 and PINT **Peppol BIS Billing 3.0** is a CIUS of EN 16931 expressed in UBL 2.1, and it is the single most widely deployed e-invoicing profile in Europe. It is what travels the Peppol network's four-corner model: you send to your access point, your access point delivers to the recipient's, and an SMP lookup resolves who that is. Flowie is an access point, so [sending]() is one API call. **PINT (Peppol International)** is the newer, global generalisation: a common billing template that each jurisdiction specialises rather than fork. PINT specialisations are live or landing in [Australia and New Zealand](<../compliance/au.html>) (PINT A-NZ), [Japan](<../compliance/jp.html>) (JP PINT), [Singapore](<../compliance/sg.html>) (InvoiceNow), [the UAE](<../compliance/ae.html>) (PINT AE, on a five-corner model that adds the tax authority as a corner) — and in the EU, as [PINT EU](), the successor profile to BIS Billing 3.0. Reachability is per document type A recipient registered on Peppol advertises which document types it accepts. Before you send, ask [`POST /v1/directory/verify`]() with the _type you will actually send_ — a participant reachable for invoices is not automatically reachable for credit notes or orders. ## National European formats and CIUS Most EU countries either use Peppol BIS as-is or narrow it with a national CIUS. A handful run formats that pre-date the European standard and are still legally required. ### XRechnung (Germany) The German CIUS of EN 16931, maintained by [KoSIT](). Mandatory for invoices to federal public buyers, and the reference profile for the B2B mandate phasing in through 2028. XRechnung can be carried in either UBL or CII, and adds German specifics — _Leitweg-ID_ routing, mandatory buyer contact details. Details on the [Germany page](<../compliance/de.html>). ### FatturaPA (Italy) Italy's national XML schema, cleared through the _Sistema di Interscambio_ (SDI) for every B2B, B2C and B2G invoice. It pre-dates EN 16931 and is not a CIUS of it: it has its own element names, its own _TipoDocumento_ codes (TD01–TD29) and its own outcome messages (_esiti_) that come back asynchronously after submission. Flowie maps the canonical model onto it and surfaces the esiti as [lifecycle events]() — see the [Italy overview](<../compliance/it/index.html>) and the [TD explorer](<../compliance/it/document-types.html>). ### The rest, briefly * **[Facturae]()** (Spain) — national XML with a mandatory XAdES signature, used for B2G through FACe while the Crea y Crece B2B framework rolls out. [Spain →](<../compliance/es.html>) * **[KSeF FA(3)]()** (Poland) — the schema for the national clearance platform; an invoice has no legal existence until KSeF assigns it a number. [Poland →](<../compliance/pl.html>) * **RO e-Factura** (Romania) — a national CIUS of EN 16931 cleared through ANAF. [Romania →](<../compliance/ro.html>) * **[ISDOC]()** (Czechia) — a UBL-derived national standard from 2009; public buyers accept it and Peppol BIS. [Czechia →](<../compliance/cz.html>) * **[OIOUBL]()** (Denmark) — the Danish UBL profile carried over NemHandel, still alive in legacy public-sector ERPs. [Denmark →](<../compliance/dk.html>) * **EHF** (Norway) — now essentially Peppol BIS with Norwegian identifiers. [Norway →](<../compliance/no.html>) * **[Finvoice]()** (Finland) — a bank-led standard delivered through banking channels alongside Peppol. [Finland →](<../compliance/fi.html>) * **[ebInterface]()** (Austria) — accepted on the federal e-invoicing portal next to Peppol BIS. [Austria →](<../compliance/at.html>) * **[UBL.BE]()** (Belgium) — the Belgian Peppol BIS profile, now that the B2B mandate is live and HERMES has been retired. [Belgium →](<../compliance/be.html>) ## Clearance and reporting formats outside the EU Outside Europe the dominant model is **clearance** : the invoice is submitted to the tax administration and only becomes valid once it comes back stamped, numbered or signed. The format is whatever that platform's schema says, and it is rarely EN 16931. * **[Saudi Arabia — ZATCA / Fatoora]()** : UBL 2.1-based XML with a cryptographic stamp, a UUID and a QR code; standard invoices are cleared before issuance, simplified ones reported after. [Saudi Arabia →](<../compliance/sa.html>) * **[Malaysia — MyInvois]()** : UBL 2.1 in XML or JSON, validated by LHDN, which returns a UUID and a QR code. [Malaysia →](<../compliance/my.html>) * **[India — GST e-invoice]()** : the INV-01 JSON schema registered with an Invoice Registration Portal, which returns the IRN and a signed QR code. [India →](<../compliance/in.html>) * **[Egypt — ETA]()** : JSON/XML documents submitted to the tax authority for near-real-time clearance. [Egypt →](<../compliance/eg.html>) * **[Türkiye — e-Fatura / e-Arşiv]()** : UBL-TR, a Turkish customisation of UBL 2.1, through the GİB. [Türkiye →](<../compliance/tr.html>) * **Israel — ITA allocation number** : no new document format; invoices above a threshold need an allocation number requested from the tax authority before they are deductible. [Israel →](<../compliance/il.html>) * **China — fully digital e-fapiao** : issued inside the STA's Golden Tax IV platform rather than exchanged between trading partners. [China →](<../compliance/cn.html>) Flowie's job on these is the same in every case: you send the canonical JSON, and the country connector produces the platform's schema, submits it, and reports the outcome back as lifecycle events you can subscribe to. What differs is _when_ the invoice becomes legally valid — which is why [the lifecycle endpoint](), not the send response, is the thing to watch in a clearance country. ## Legacy EDI, and why a PDF is not an e-invoice **UN/EDIFACT INVOIC** and **ANSI X12 810** are the pre-XML EDI invoice messages, still carrying enormous volume in retail, automotive and logistics. They are structured and machine-readable, so they solve the same problem — but they are not EN 16931 syntaxes, and a mandate that names UBL or CII will not accept them. Bridging is a mapping project; talk to us if you have an EDI backbone to keep. A **PDF invoice, including one sent by email, is not an electronic invoice** under any current mandate — nor is a scan, nor a spreadsheet. The test every regulation applies is whether the invoice can be processed automatically without re-keying, which a flat PDF cannot. This is the single most common misconception in e-invoicing projects, and the reason [Factur-X](<#hybrid>) exists: it keeps the PDF a human wanted _and_ the data the regulation requires in one file. Flowie will happily store a flat PDF with `format: "raw"` — it just will not route it as a compliant invoice. ## Which format should I send? * **Building a new integration** → send `format: "json"`. You describe the invoice once; Flowie renders the right syntax per destination and re-renders it when a country changes its profile. * **Your ERP already emits UBL or CII** → deposit it with `ubl-xml` / `cii-xml`. Your bytes remain the emitted original, which matters for audit. * **Your ERP emits Factur-X or ZUGFeRD PDFs** → send the PDF with `format: "auto"`. The embedded CII is extracted and validated; the PDF stays the original. * **You are selling into Germany** → XRechnung for public buyers, Factur-X/ZUGFeRD or plain CII/UBL for B2B. [Germany →](<../compliance/de.html>) * **You are selling into Italy, Poland, Romania, Spain or a Gulf/Asian clearance country** → send JSON and let the country connector produce the national schema. The format is not really your choice there; the platform's schema is the contract. [Coverage matrix →](<../compliance/index.html#matrix>) * **You do not know what the recipient accepts** → [`POST /v1/directory/verify`]() before you send. ## Frequently asked questions ### What is the difference between UBL and CII? They are two XML syntaxes for the same semantic content. UBL 2.1 is an OASIS standard used by Peppol and most Northern European networks; UN/CEFACT CII is used in France and Germany and is the XML embedded in Factur-X and ZUGFeRD PDFs. Both are listed by CEN/TS 16931-2 as compliant with EN 16931, so an invoice can be converted from one to the other without losing business content. The root element tells them apart: `CrossIndustryInvoice` for CII, `Invoice` or `CreditNote` for UBL. ### Is Factur-X the same as ZUGFeRD? Yes — technically the same hybrid PDF/A-3 standard, published jointly by FNFE-MPE in France and FeRD in Germany under two names. A ZUGFeRD file is a valid Factur-X file and vice versa, at the same profile level. The names differ for governance and market reasons, not technical ones. ### Is XRechnung a Peppol format? No. XRechnung is a German CIUS of EN 16931; Peppol BIS Billing 3.0 is OpenPeppol's CIUS of the same standard. XRechnung documents are commonly _transported_ over the Peppol network, which is why the two are often confused, but they are different profiles with different mandatory fields — notably the German _Leitweg-ID_. ### Is a PDF invoice an electronic invoice? No. Under EU Directive 2014/55/EU and the national mandates that follow it, an electronic invoice must be issued, transmitted and received in a structured format that allows automatic processing. A PDF — or a scan, or an emailed image — does not qualify, however it was produced. A hybrid Factur-X/ZUGFeRD PDF does qualify, because the structured XML travels inside it. ### Do I have to convert my invoices myself? No. Send the canonical JSON model to [`POST /v1/documents/send`]() and Flowie produces whatever the destination requires. Conversion only becomes your problem if you insist on depositing finished XML for a country whose profile you have not implemented. ### Does EN 16931-1:2026 break my integration? Not on its own. The 2026 edition was published in May 2026 and formally withdrew the 2017 edition, but national profiles adopt it on their own timetable and validation keeps accepting the current profile versions during the migration. Anything that does change lands in the [changelog](<../changelog.html>) before it reaches you. ### Which formats does Flowie support? On input: the canonical JSON model, UBL 2.1, UN/CEFACT CII, and Factur-X/ZUGFeRD PDFs (plus any file stored as-is with `format: "raw"`). On output: Peppol BIS Billing 3.0 and PINT for the Peppol network, and the national format required by each of the 47 jurisdictions documented under [compliance](<../compliance/index.html>) — Factur-X and CII for France, XRechnung and ZUGFeRD for Germany, FatturaPA for Italy, KSeF FA(3) for Poland, Facturae for Spain, ZATCA for Saudi Arabia, MyInvois for Malaysia, and the rest of the [catalogue](<#catalogue>) above. ## Official references Primary sources, in the order the layers stack. When a national profile and this page disagree, the national profile wins — tell us and we will fix the page. * [European Commission — compliance with the eInvoicing standard]() (EN 16931, and [how to obtain a copy]()) * [OASIS — Universal Business Language 2.1]() * [UNECE — UN/CEFACT XML schemas (Cross Industry Invoice)]() * [OpenPeppol — BIS Billing 3.0]() · [PINT Billing]() · [PINT EU]() · [Peppol eDelivery (AS4)]() * [FNFE-MPE — Factur-X]() · [FeRD — ZUGFeRD]() * [KoSIT — XRechnung]() * [Agenzia delle Entrate — FatturaPA]() * [DGFiP — spécifications externes B2B]() (France) * [Ministerstwo Finansów — KSeF]() · [Facturae]() · [ISDOC]() · [OIOUBL]() · [Finvoice]() · [ebInterface]() · [UBL.BE]() * [ZATCA]() · [MyInvois SDK]() · [India GST e-invoice]() · [Egyptian Tax Authority]() · [GİB e-Fatura]() And on this site: [the send endpoint](), [the canonical document model](), [document & invoice types](), [47 country guides](<../compliance/index.html>), and [all 164 EN 16931 business terms](<../compliance/fr/business-terms.html>). ======================================================================== # Sandbox guide # Source: https://docs.get-flowie.com/sandbox/index.html ======================================================================== --- title: "Sandbox" description: "Every test scenario for Flowie Exchange sandbox: test VATs, test Peppol IDs, error triggers, lifecycle paths, webhook simulators." canonical: "https://docs.get-flowie.com/sandbox/" source: "https://docs.get-flowie.com/sandbox/index.html" --- # Sandbox Sandbox # Test the entire API without sending a real invoice Every endpoint, every webhook, every regulatory platform has a deterministic sandbox counterpart. Use the rows below to trigger any outcome you need to test — the recipient is unreachable, PPF rejects with code 00058, the rate-limit kicks in, the lifecycle reaches `paid` after a 30-second delay. No real Peppol traffic is generated. Promise The sandbox is API-identical to production. If a request works in sandbox, the only thing that changes in live mode is the network destination. We test this contract on every release. ## Base URLs Environment| Base URL| Key prefix ---|---|--- Sandbox| `https://back.flowie.ink/exchange`| `flw_test_…` · `flw_plat_test_…` · `flw_wl_test_…` Production| `https://back.p2p-flowie.com/exchange`| `flw_live_…` · `flw_plat_live_…` · `flw_wl_live_…` ## Test API keys ⚡ Easiest path — no signup, no JWT Hit the public bootstrap endpoint from your terminal (rate-limited to 120 keys per IP per hour): [code] curl -X POST https://back.flowie.ink/exchange/v1/sandbox/bootstrap \ -H "Content-Type: application/json" \ -d '{"label":"my-laptop"}' [/code] You get back the full `apiKey` (shown _once_), a starter sandbox company, and a 7-day expiry. Or click [**"Get a test API key"**](<../index.html#get-test-key>) on the landing page — same endpoint, the result auto-loads into the [Playground](<../playground/index.html>). ### Key types — personal, platform, white-label By default `/v1/sandbox/bootstrap` mints a **personal** key (prefix `flw_test_`) that behaves like a regular tenant integration. Pass `"keyType": "platform"` or `"white_label"` to mint a multi-tenant key that satisfies the platform-key gate on `/v1/platform/*`: [code] curl -X POST https://back.flowie.ink/exchange/v1/sandbox/bootstrap \ -H "Content-Type: application/json" \ -d '{"label":"my-platform","keyType":"platform"}' [/code] keyType| Token prefix| Unlocks ---|---|--- `personal` (default)| `flw_test_…`| All non-platform endpoints `platform`| `flw_plat_test_…`| \+ `POST /v1/platform/companies` (multi-tenant onboard), `GET /v1/platform/companies`, `GET /v1/platform/events`, `GET /v1/platform/usage`, `PATCH /v1/platform/settings` `white_label`| `flw_wl_test_…`| Same as `platform` \+ branding If you already have a Flowie dashboard JWT, you can also create longer-lived keys explicitly: [code] curl -X POST https://back.flowie.ink/exchange/v1/api-keys \ -H "Authorization: Bearer $FLOWIE_DASHBOARD_JWT" \ -d '{"name":"local-dev","scopes":["*"]}' [/code] Or grab one from the dashboard **Settings → API keys → New key (test mode)**. As with live keys, the full string is shown _once_. ### What sandbox synthesises (vs. live infra) Sandbox keys never reach the live Peppol network, the einvoice-validator, the tag service, the payment service, the request-log store, or org-v2's BOR. Each route either short-circuits to a synthetic response or runs in a memory-only mode so the contract is exercisable without external dependencies. The table below is the canonical list — anything not on it behaves identically to production. Route| Sandbox behaviour ---|--- `POST /v1/documents/send` (any format)| Returns a `doc_sbx_…` id immediately. `format=auto`/`raw` with a `file` payload returns `deliveryStatus="stored"` \+ `fileId` \+ `storedFormat` without uploading — except a Factur-X or CII invoice sent with `format=auto`, which is read into a structured document as in production. `GET /v1/documents/{id}/xml`| Synthesises a minimal valid UBL Invoice XML for any `doc_sbx_…` / `doc_test_…` / `flw_…` id. `GET /v1/documents/{id}/pdf`| Returns a 1-page PDF stub for any `doc_sbx_…` / `doc_test_…` / `flw_…` id. `DELETE /v1/companies/{id}`| Idempotent — never 404s. `GET /v1/directory/{peppol_id}`| Synthesises a participant record (no live Peppol/PPF lookup). Always returns `smpStatus="active"`. `POST /v1/partners` · `GET /v1/partners`| POST returns a synthetic `prt_sbx_…`. GET returns an empty page (sandbox tenants start with no partnerships). `POST /v1/categorization/objects/{id}/tags` · `POST .../auto`| Returns synthetic assignments / AI suggestions. Tag groups are pre-seeded with `grp_sbx_unspsc`, `grp_sbx_accounting`, `grp_sbx_custom`. `POST /v1/events/{id}/ack` · `POST /v1/events/ack` · `POST /v1/events/{id}/replay`| Idempotent — accepts any event id, including ones that were never emitted. Replay returns a synthetic delivery record. `GET /v1/requests/{request_id}`| Synthesises a believable failed-request envelope (502 from a Peppol AP) for any id, so the inspector contract round-trips without first triggering a real failure. `GET /v1/payments/documents/{id}` · `POST .../pay` · `POST /v1/payments/export/iso20022`| Returns synthetic `PaymentInfo` / `PaymentRecord` / pain.001 ISO 20022 stubs. Live `payment-staging` service is bypassed. `POST /v1/platform/companies` (platform key) · `GET /v1/platform/companies` · `PATCH /v1/platform/settings` · `DELETE /v1/platform/api-keys/{key_id}`| Synthesise empty managed-companies pages, echo settings updates, and idempotently revoke arbitrary key ids — no org-v2 children are required. `GET /afnor/directory-service/v1/siret/code-insee:{siret}` · `GET /afnor/.../routing-code/siret:{siret}/code:{routing_identifier}`| Synthesise believable INSEE establishment / routing-code records for any 14-digit SIRET — no live INSEE lookup required. `POST /afnor/flow-service/v1/flows`| Routes through the broadened `POST /v1/documents/send` sandbox synth — a `flw_…` id is returned without requiring a real Peppol registration. `GET /afnor/flow-service/v1/flows/{flow_id}` (any docType)| Resolves any `flw_…` id (including 32-hex / 36-uuid shapes) via the document_service sandbox synth. `POST /document/callback` (cXML PunchOut)| **Not** short-circuited. Authentication is still enforced via `` in the cXML envelope (or supplier-identity fallback) — sandbox keys do not bypass this gate. ## Test VAT numbers Pass any of these to [POST /v1/companies](<../reference/index.html#create-company>) or [/companies/resolve](<../reference/index.html#resolve-company>) to deterministically trigger a behavior. VAT| Country| Outcome ---|---|--- `BE0000000001`| BE| Enriches as _Sandbox Test BVBA_ , status `active`, SMP-registered after ~2s. `BE0000000099`| BE| Returns `422 VAT_INACTIVE`. `BE0000000404`| BE| Returns `422 VAT_NOT_FOUND`. `BE0000000500`| BE| Returns `503 UPSTREAM_UNAVAILABLE` (registry down). `FR12345678901`| FR| Enriches with a public-sector flag → SDI/PPF reporting enabled. `FR99999999999`| FR| `422 VAT_NOT_FOUND`. `IT00000000010`| IT| Enriches Italian; auto-enables SDI reporting. `IT00000000099`| IT| SDI returns `00306` (_Codice Destinatario unknown_). `DE000000001`| DE| Enriches; no auto-compliance (Germany is voluntary). `NL000000001B01`| NL| Enriches; auto-enables NL Peppol routing. `ES00000000C`| ES| Enriches; FACe (Spain public-sector) flag set. Slow enrichment Append `?simulateLatencyMs=2500` to `POST /companies` in sandbox to force a slow enrichment. Useful to test loading states. ## Test Peppol IDs (recipient side) Peppol ID| Behavior ---|--- `0208:TEST_OK`| Delivers in ~1s. Fires `document.sent`, `document.delivered`. `0208:TEST_OK_SLOW`| Delivers in ~30s. Lets you exercise polling UIs. `0208:TEST_AP_FAIL`| Recipient AP rejects with `AP_REJECTED`. Fires `document.failed` after ~2s. `0208:TEST_AP_FLAKY`| First two attempts time out, third succeeds. Tests retry logic in your UI. `0208:TEST_TIMEOUT`| All transport attempts time out → `document.failed` with `TRANSPORT_FAILURE`. `0208:TEST_REJECT_SCHEMA`| Recipient rejects with a UBL schematron failure (BR-CO-15). `0208:TEST_REJECT_BUYER_REF`| Recipient requires `buyerReference` — rejects PPF code `00058`. `0208:TEST_DUPLICATE`| Recipient marks the document as duplicate (`DUP`). `0208:TEST_NOT_REGISTERED`| SMP returns "not found" → `422 RECIPIENT_NOT_FOUND`. `0208:TEST_CANNOT_RECEIVE_INVOICE`| Registered, but doesn't accept `INVOICE` doctype → `422 RECIPIENT_CANNOT_RECEIVE`. ## End-to-end recipient simulators Each test Peppol ID below is a fully simulated recipient. Sending to it triggers a full lifecycle including counterparty acks/rejects. Peppol ID| Persona| Lifecycle path it drives on the receiver side ---|---|--- `0208:SIM_HAPPY`| Happy path| `delivered → under_review → approved → paid` over ~5 min. `0208:SIM_SLOW_PAY`| Late payer| `delivered → approved` immediately, then `paid` 60 days later (use time-travel to skip ahead). `0208:SIM_DISPUTE`| Disputes invoices| `delivered → under_review → disputed` with reason `QUA` (quantity discrepancy). `0208:SIM_REJECT`| Rejects on first review| `delivered → rejected` with reason `PRI` (price disagreement). `0208:SIM_PARTIAL`| Pays in installments| `approved → partially_paid (50%) → partially_paid (75%) → paid` over 3 days. ## Lifecycle simulators For your _own_ sent documents, you can advance the lifecycle on demand: [code] # Force a sandbox document to "paid" right now curl -X POST …/v1/documents/{doc_id}/lifecycle \ -H "Authorization: Bearer $TEST_KEY" \ -d '{ "status": "paid", "paymentDate": "2026-04-25", "paymentAmount": 2359.50, "paymentCurrency": "EUR", "paymentReference":"SBX-PAY-001" }' [/code] The compliance hooks fire normally — see [compliance simulators](<#test-compliance>) below. Reason code (force a rejection)| What gets reported ---|--- `RE`| Generic rejection — PPF/SDI accept silently. `QUA`| Quantity discrepancy. `PRI`| Price disagreement. `TAX`| Tax mismatch — SDI flags for review. `DUP`| Duplicate — PPF returns `00043`. ## Compliance platform simulators (PPF / SDI) To exercise the compliance pipeline, set the company's `metadata.simulateCompliance` field. The next lifecycle update on any of that company's docs uses the simulated response. Belgium has no regulator-side report (HERMES decommissioned 2025-12-31) — BE invoices skip this pipeline entirely. Value| PPF / SDI response ---|--- `"accept"`| 200 OK in < 1s. Fires `compliance.reported`. `"reject_00058"`| PPF returns `00058` (missing Service Exécutant). `compliance.reported.failed`. `"reject_00306"`| SDI returns `00306` (Codice Destinatario unknown). `"timeout_30s"`| Authority times out; circuit breaker behavior visible at [`/health/readiness`](<../reference/index.html#readiness>). `"flaky_50pct"`| 50% probability of acceptance per attempt. [code] # Set the simulator on a sandbox company curl -X PATCH …/v1/companies/{company_id} \ -H "Authorization: Bearer $TEST_KEY" \ -d '{"metadata": {"simulateCompliance": "reject_00058"}}' [/code] ## Triggering each webhook event Each row below is a **copy-pasteable curl** that produces exactly one webhook delivery against your registered sandbox endpoint. Event| How to trigger ---|--- `document.received`| Send to your own sandbox company from `0208:SIM_HAPPY`. `document.sent`| Send anything to `0208:TEST_OK`. `document.delivered`| Send to `0208:TEST_OK`; arrives ~1s later. `document.failed`| Send to `0208:TEST_AP_FAIL`. `document.updated`| `POST /documents/{id}/actions` with `{"action":"tag","tag":"x"}`. `lifecycle.updated`| `POST /documents/{id}/lifecycle` with any allowed status. `company.smp_registered`| Create a company with VAT `BE0000000001`; arrives ~2s later. `compliance.reported`| Mark a French/Italian/Belgian doc as `paid` with `simulateCompliance="accept"`. `compliance.reported.failed`| Same as above with `simulateCompliance="reject_00058"`. To replay any past event byte-identically: [code] curl -X POST …/v1/events/{event_id}/replay \ -H "Authorization: Bearer $TEST_KEY" [/code] Need fixture payloads to seed your tests without hitting the API? See [webhook fixtures](<../fixtures/index.html>). ## Forcing specific errors Pass `X-Sandbox-Force-Error` on any request to make the API return that error code: [code] curl …/v1/companies \ -H "Authorization: Bearer $TEST_KEY" \ -H "X-Sandbox-Force-Error: UPSTREAM_UNAVAILABLE" [/code] Header value| Resulting status / body ---|--- `INVALID_REQUEST`| 400 `EXPIRED_TOKEN`| 401 `INSUFFICIENT_SCOPE`| 403 `RESOURCE_NOT_FOUND`| 404 `IDEMPOTENCY_BODY_MISMATCH`| 409 `VAT_NOT_FOUND`| 422 `RATE_LIMITED`| 429 with `Retry-After: 30` `INTERNAL_ERROR`| 500 `UPSTREAM_UNAVAILABLE`| 503 ## Forcing a rate-limit Sandbox rate-limits are normally generous. To _force_ a 429 right now: [code] curl -X POST …/v1/sandbox/rate-limit/exhaust \ -H "Authorization: Bearer $TEST_KEY" \ -d '{"durationSeconds": 60}' [/code] Every subsequent call returns `429` with a real `Retry-After` header for the next 60 seconds. Useful for testing your backoff implementation under realistic conditions. ## Time-travel For sandbox companies you can advance the clock to verify deferred behaviors (60-day late payments, 12-month deprecation windows, idempotency cache TTL): [code] curl -X POST …/v1/sandbox/clock/advance \ -H "Authorization: Bearer $TEST_KEY" \ -d '{"companyId": "comp_…", "by": "60d"}' [/code] Accepts `by` as `1h`, `3d`, `2w`, `1m`, `1y`. The clock is per-company and never affects another tenant. `POST …/clock/reset` snaps it back. Side-effect ordering Time-travel fires every webhook that _would_ have fired in the skipped interval, in chronological order. Don't skip a year unless you actually want a thousand events on your endpoint. ## Reset & data lifetime Resource| Sandbox lifetime| Reset ---|---|--- Companies, partners, webhooks, API keys| Persistent| Delete via API or dashboard. Documents| 90 days from creation| Auto-purged. Use `POST /v1/sandbox/reset` to wipe all docs immediately. Events| 30 days| Auto-purged. Idempotency cache| 24 hours (same as live)| `POST /v1/sandbox/idempotency/flush` Rate-limit counters| 60s window (same as live)| — [code] # Nuke EVERYTHING in your sandbox tenant curl -X POST …/v1/sandbox/reset \ -H "Authorization: Bearer $TEST_KEY" \ -d '{"confirm": "yes"}' [/code] ## Local webhook tunnels To receive webhooks while running your handler on `localhost`, use any tunnel: [code] ngrok http 3000 # OR cloudflared tunnel --url http://localhost:3000 [/code] Then point a sandbox webhook at `https://.ngrok.io/hooks`. The dashboard's **Resend** button sends a byte-identical retry — perfect for iterating on your signature verifier. ## Copy-paste bootstrap scripts Spin up a complete test scenario (one sender, one recipient simulator, one webhook, three sent invoices) with a single shell script: [code] #!/usr/bin/env bash set -euo pipefail BASE="https://back.flowie.ink/exchange/v1" KEY="$FLOWIE_TEST_KEY" H=(-H "Authorization: Bearer $KEY" -H "Content-Type: application/json") # 1. Create a sandbox sender SEND=$(curl -s -X POST "$BASE/companies" "${H[@]}" \ -d '{"vatNumber":"BE0000000001"}') COMP=$(echo "$SEND" | jq -r .id) echo "→ sender: $COMP" # 2. Register a webhook (replace URL with your tunnel) curl -s -X POST "$BASE/webhooks" "${H[@]}" \ -d '{"url":"'"$WEBHOOK_URL"'","events":["*"]}' > /dev/null # 3. Send 3 invoices to the happy-path simulator for n in 001 002 003; do curl -s -X POST "$BASE/documents/send" "${H[@]}" \ -H "Idempotency-Key: bootstrap-$n" \ -d '{ "type":"invoice", "from":"'"$COMP"'", "to":"0208:SIM_HAPPY", "document":{ "number":"INV-2026-'"$n"'", "issueDate":"2026-04-25", "currency":"EUR", "lines":[{"description":"Test","quantity":1,"unitPrice":100,"vatRate":21}] } }' | jq -r '.id + " → " + .status' done [/code] The same script in [Python · Node · Go on GitHub](). ## Gotchas * **Sandbox keys never reach production.** If you accidentally point a `flw_test_…` key at `https://back.flowie.ink`, you get `401 INVALID_TOKEN`. Production rejects test keys and vice versa. * **Webhook signatures use the webhook's own secret** , not a global sandbox secret. Each webhook you create has its own. * **Time-travel is per-company.** Two parallel test runs on different sandbox companies don't interfere. * **Idempotency cache TTL is the same in sandbox** (24h). If a test reuses the same key within that window, you'll see the cached response, not a fresh send. * **Test data is not anonymized in logs.** Don't paste real customer VATs into sandbox just because "it's only a test." ======================================================================== # API keys # Source: https://docs.get-flowie.com/sandbox/keys.html ======================================================================== --- title: "API Keys" description: "Create, list and revoke Flowie Exchange API keys from your browser. Sign in with your Flowie account — no curl required." canonical: "https://docs.get-flowie.com/sandbox/keys" source: "https://docs.get-flowie.com/sandbox/keys.html" --- # API Keys API Keys # Manage your API keys Create a long-lived API key for your Flowie organization, list the keys that already exist, and revoke any you no longer need — all from this page. Sign in with the same Flowie account you use for the dashboard; the key inherits your organization and tier. Keys minted here are also remembered locally so the [Playground](<../playground/index.html>) and [API reference](<../reference/index.html>) Try-it widgets can pick them from a dropdown. Where the key works A key belongs to the **environment it was created on** — `https://back.flowie.ink/exchange` (staging) or `https://back.p2p-flowie.com/exchange` (production). These are **separate backends with separate keys** : a staging key returns `401` on production and vice-versa. Pick the environment in the form below before creating. Pass the key as `Authorization: Bearer flw_…`. Note the prefix is the _mode_ , not the environment: `flw_live_…` = live mode, `flw_test_…` = sandbox mode — both exist on staging _and_ production, so the prefix alone does **not** tell you which environment a key is for. The full string is shown **once** , right after creation — save it in your secret store before navigating away (we also cache it in this browser's `localStorage` so the Playground can reuse it). ### Sign in to manage your API keys If you're already signed in to Flowie in another tab, we'll detect it automatically. Otherwise, open the dashboard, sign in, then come back here. [Sign in with Flowie ↗](<#>) I just signed in — recheck Or paste a Flowie JWT manually Paste an `access_token` from your Flowie session (DevTools → Application → Local Storage → look for an `@@auth0spajs@@::…` entry on `staging.flowieapp.io`, or grab a `Bearer …` header from a Network request). Stored only in this browser's `localStorage`. Save token No account? [Sign up for free]() — under a minute, then come back here. ## Create a new key Name Environment Staging · back.flowie.ink Production · back.p2p-flowie.com Company (optional) (org-wide — no specific company) Create key **✓ Key created.** Copy it now — you will not see the full value again. Copy ## Your keys Name | Env | Prefix | Company | Created | Expires | ---|---|---|---|---|---|--- No API keys yet. Create one above to get started. ## How it works This page calls the same public endpoints documented in the [API reference](<../reference/index.html#create-api-key>). Nothing happens server-side that you couldn't reproduce with `curl`: * **Create** → `POST /v1/api-keys` with `{"name": "...", "companyId": "..."}`. * **List** → `GET /v1/api-keys` (paginated; this page reads the first 100). * **Revoke** → `DELETE /v1/api-keys/{id}` (204 on success). Revocation is immediate; any in-flight request finishes, but the next one returns `401`. Your Flowie JWT is held in `localStorage` only (key `flowie-playground-state.key`). It never leaves the browser except as an `Authorization: Bearer …` header to the Exchange API. If you belong to multiple organizations, use the organization picker in the topbar to choose which one a new key targets — the picker sets the `X-Flowie-Organization-Id` header on every request. The page detects your existing Flowie session via a hidden iframe (`/__exchange-handshake.html`) hosted on `staging.flowieapp.io` (or `app.flowie.me` in production). The iframe reads the Auth0 SDK's cached access token from the dashboard's `localStorage` and posts it back via `postMessage` — strict origin validation, no servers, no cookies. If you're not signed in there, the page falls back to the dashboard sign-in link or manual JWT paste. ======================================================================== # Live playground # Source: https://docs.get-flowie.com/playground/index.html ======================================================================== --- title: "Playground" description: "Live API playground in your browser. Send any request to the Flowie Exchange API with a sandbox key — no terminal, no setup." canonical: "https://docs.get-flowie.com/playground/" source: "https://docs.get-flowie.com/playground/index.html" --- # Playground GETPOSTPATCHPUTDELETE https://back.flowie.ink Send ⏎ ### Parameters Edit any value below — your changes flow back into the request above. Click **Save** on a row to reuse the value across endpoints. ### Headers ▾ \+ Add header ### Body (JSON) Live request cURL Python JS Copy ▾ [code] curl … [/code] — Press `Send` to run this request. **Token expired.** ↻ Refresh from Flowie Pick another token [code] // Press Send (or ⌘⏎) to fire the request. [/code] [/code] [/code] [code] ======================================================================== # Request inspector # Source: https://docs.get-flowie.com/playground/requests.html ======================================================================== --- title: "Request inspector" description: "Look up any request_id from a Flowie Exchange error response and see exactly what was sent and received." canonical: "https://docs.get-flowie.com/playground/requests" source: "https://docs.get-flowie.com/playground/requests.html" --- # Request inspector API requests # Every request, by API key & user Browse all requests made to the Flowie Exchange API — with your API keys or from the app — and see who made each one. Filter by API key, user, method or status, or look up a single `requestId` below. Secrets are redacted at capture time; logs are kept for 7 days. Any method GETPOSTPUTPATCHDELETE Filter Reset By API keyBy user Show usage summary Set your API key above, then Filter to load activity. Load more * * * ## Inspect one request by id ======================================================================== # Webhook cookbook # Source: https://docs.get-flowie.com/reference/webhooks.html ======================================================================== --- title: "Webhooks" description: "Event catalog, HMAC signing, retry policy, and idempotency patterns for Flowie Exchange webhooks." canonical: "https://docs.get-flowie.com/reference/webhooks" source: "https://docs.get-flowie.com/reference/webhooks.html" --- # Webhooks Webhook Cookbook # Webhooks Webhooks are how your stack learns that something happened on Peppol. Every time a document arrives, a delivery fails, or a lifecycle status changes, Flowie makes an HTTPS POST to each endpoint you've configured — with exponential retries, HMAC signatures, and a durable twin in the [Events API]() for replay. Delivery guarantees **At-least-once.** Your handler must be idempotent. Duplicates are rare but possible after a 2xx response times out on our side. ## Event catalog Event| Fires when| Key fields in `data` ---|---|--- `document.received`| An incoming Peppol document has been persisted.| `documentId`, `type`, `number`, `direction`=`incoming` `document.sent`| An outgoing document has been handed off to the recipient's access point.| `documentId`, `type`, `sentAt` `document.delivered`| The recipient's access point confirmed final delivery.| `documentId`, `deliveredAt` `document.failed`| Delivery permanently failed (recipient unreachable, schema rejection, …).| `documentId`, `errorCode`, `errorMessage` `document.updated`| A document's metadata was updated (e.g. tagged, archived, note added).| `documentId`, `changes` (field diff) `lifecycle.updated`| Lifecycle status transitioned.| `documentId`, `previousStatus`, `currentStatus`, `compliance` `company.smp_registered`| A company's SMP record went live.| `companyId`, `peppolId` `compliance.reported`| A lifecycle change was reported to PPF (FR) or SDI (IT). Belgium has no regulator-side report.| `documentId`, `reportedTo`, `status` `*`| Subscribes to every event.| Use sparingly — prefer explicit lists. ## Payload shape Every delivery is a JSON POST with this envelope: [code] { "id": "evt_01HY3AB9C2DE3FG", "type": "document.received", "livemode": true, "createdAt": "2026-04-25T10:05:08Z", "apiVersion":"2026-04-01", "data": { "documentId": "doc_01HY7AB9C2DE3FG", "type": "invoice", "direction": "incoming", "number": "INV-2026-0417", "sender": { "peppolId": "0208:0123456789", "name": "ACME BVBA" }, "receiver": { "peppolId": "0208:9876543210", "name": "Globex SRL" } } } [/code] Request headers include: [code] POST /hooks/peppol HTTP/1.1 Host: example.com Content-Type: application/json User-Agent: Flowie-Webhooks/3.0 X-Flowie-Signature: t=1714046708,v1=3d9e8b7… X-Flowie-Event: document.received X-Flowie-Event-Id: evt_01HY3AB9C2DE3FG X-Flowie-Delivery: dlv_01HY3AB9C2DE3FG X-Flowie-Attempt: 1 [/code] ## Signing & verification Every request carries `X-Flowie-Signature`. The header is comma-separated key/value pairs: * `t` — Unix timestamp at signing time * `v1` — HMAC-SHA256 of `t + "." + raw_body`, hex-encoded To verify: 1. Split the header by `,` into `t` and `v1`. 2. Reject if `|now - t| > 5 minutes` — that's a replay. 3. Compute `HMAC-SHA256(secret, t + "." + raw_body)`. 4. Constant-time compare against `v1`. Use the raw body Verify _before_ any JSON parsing or transcoding. Even a re-serialized JSON is no longer byte-identical — it will fail the HMAC check. [code] import hmac, hashlib, time from fastapi import Request, HTTPException SECRET = b"whsec_..." # the secret you created with the webhook async def verify(req: Request): raw = await req.body() header = req.headers.get("X-Flowie-Signature", "") parts = dict(p.split("=", 1) for p in header.split(",")) t, sig = parts.get("t"), parts.get("v1") if not t or not sig: raise HTTPException(400, "Missing signature") if abs(time.time() - int(t)) > 300: raise HTTPException(400, "Stale") expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, sig): raise HTTPException(401, "Invalid signature") return raw [/code] [code] import crypto from "node:crypto"; const SECRET = process.env.FLOWIE_WEBHOOK_SECRET; export function verify(req, rawBody) { const header = req.headers["x-flowie-signature"] || ""; const parts = Object.fromEntries(header.split(",").map(p => p.split("="))); const { t, v1 } = parts; if (!t || !v1) throw new Error("Missing signature"); if (Math.abs(Date.now()/1000 - Number(t)) > 300) throw new Error("Stale"); const mac = crypto.createHmac("sha256", SECRET) .update(`${t}.`).update(rawBody).digest("hex"); const ok = crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(v1)); if (!ok) throw new Error("Invalid signature"); } [/code] [code] func verify(r *http.Request, secret []byte) error { raw, _ := io.ReadAll(r.Body); r.Body = io.NopCloser(bytes.NewReader(raw)) parts := map[string]string{} for _, p := range strings.Split(r.Header.Get("X-Flowie-Signature"), ",") { if kv := strings.SplitN(p, "=", 2); len(kv) == 2 { parts[kv[0]] = kv[1] } } t, err := strconv.ParseInt(parts["t"], 10, 64) if err != nil || math.Abs(float64(time.Now().Unix()-t)) > 300 { return errors.New("stale") } h := hmac.New(sha256.New, secret) h.Write([]byte(parts["t"] + ".")); h.Write(raw) if !hmac.Equal([]byte(hex.EncodeToString(h.Sum(nil))), []byte(parts["v1"])) { return errors.New("invalid signature") } return nil } [/code] ## Retries & backoff Flowie retries any non-2xx response (and any timeout > 20s) on this schedule: Attempt| Delay after failure| Cumulative ---|---|--- 1| —| 0m 2| 30s| 30s 3| 2m| 2m 30s 4| 10m| 12m 30s 5| 30m| 42m 30s 6| 2h| ≈ 2h 42m 7| 6h| ≈ 8h 42m 8 (last)| 12h| ≈ 20h 42m After 8 failures, the webhook is auto-**paused**. You'll receive an email and the `status` field on the webhook flips to `paused`. Manually re-activate it with a `PATCH` once the endpoint is healthy. Respond fast, process async Ack within 5 seconds with `200`, then hand the payload to a queue. Long synchronous processing in your handler multiplies tail-latency and increases the odds of a retry storm. ## Idempotency on your side Because retries can overlap with a successful delivery you missed, your handler must treat every event as "at-least-once". Two patterns work well: 1. **Dedupe table.** Use `X-Flowie-Event-Id` as a unique key in a fast KV (Redis, DynamoDB). Ignore duplicates. 2. **Idempotent state transitions.** Upsert by `documentId` — setting `status = paid` again is a no-op. ## Replay & the Events API Every webhook attempt has a matching event in the [Events API](). If your endpoint was down, fetch missed events: [code] curl "https://back.p2p-flowie.com/exchange/v1/events?type=document.received&limit=100" \ -H "Authorization: Bearer $KEY" [/code] Process them, then acknowledge in bulk to clear the queue: [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/events/ack \ -H "Authorization: Bearer $KEY" \ -d '{"eventIds": ["evt_01…", "evt_02…"]}' [/code] ## Testing locally 1. Expose your dev server with `ngrok http 3000` (or your preferred tunnel). 2. Create a test-mode webhook pointing at `https://.ngrok.io/hooks/peppol`. 3. Send a document in sandbox — you'll see `document.received` fire. 4. In the dashboard, open any delivery and click **Resend** to replay the exact byte-identical request. ## Troubleshooting Symptom| Likely cause| Fix ---|---|--- Webhook is `paused` after deploy| Endpoint returned 5xx 8 times in a row| Fix the endpoint, then `PATCH` the webhook back to `active` and replay via Events API. Signature mismatch| You're signing a re-serialized body| Verify on the raw buffer, before JSON parse. Events arrive out of order| Retries of an earlier delivery arrive after a later one| Read `data.updatedAt` — don't rely on receipt order. Store monotonic versions. Duplicate processing| Your handler isn't idempotent| Dedupe on `X-Flowie-Event-Id`. Slow deliveries| Your endpoint takes > 5s| Enqueue fast, process async. ## Interactive signature verifier Paste a webhook secret, the timestamp from `X-Flowie-Signature`, and the raw body. We compute the HMAC in your browser (nothing is sent to a server) and compare against the signature you provide. Webhook secret Timestamp (t=…) Raw body Signature (v1=…) (optional) Compute & verify Load example Clear All computation happens in your browser via [SubtleCrypto](). Your secret never leaves the page. ## Payload fixtures Need realistic JSON to seed your handler tests? [/fixtures](<../fixtures/index.html>) ships one downloadable `.json` per event type, with copy-to-clipboard and a tarball bundle. ======================================================================== # Build with AI # Source: https://docs.get-flowie.com/build-with-ai/index.html ======================================================================== --- title: "Build with AI" description: "Build with AI on Flowie Exchange. Give AI agents native access to Peppol e-invoicing — MCP servers for Claude, Cursor and custom agents, agent-ready docs (llms.txt), and self-service agent onboarding." canonical: "https://docs.get-flowie.com/build-with-ai/" source: "https://docs.get-flowie.com/build-with-ai/index.html" --- # Build with AI AI Agents # Build with AI Flowie Exchange is built to be driven by AI. Point Claude Desktop, Claude Code, Cursor, n8n, or your own custom agent at the API and it can send, receive, and manage Peppol e-invoices as native tool calls — no glue code, no bespoke wrappers. This page is the hub for every AI surface: the [MCP servers](<#mcp>), the [agent-ready docs](<#docs-for-agents>), and [self-service agent onboarding](<#agent-onboarding>). Same auth, same quota, same sandbox Every AI surface is a thin layer over the REST API you already know. MCP tool calls are forwarded to the underlying FastAPI handler with your `Authorization` header preserved — so JWT, `flw_*` keys, tenant scoping, rate limits, and sandbox simulators all work identically. ## AI tools Three ways to put Flowie Exchange in front of an agent. Most integrations start with the MCP server. ### [MCP server → Connect Claude Desktop, Claude Code, Cursor, or a custom Python agent over the Model Context Protocol and call the API as native tools. ](<#mcp>) ### [Docs for agents → Machine-readable docs — `llms.txt` as a fast page index, `llms-full.txt` as the whole corpus, plus one Markdown slice per endpoint. ](<#docs-for-agents>) ### [Agent onboarding → Let an agent self-provision: zero-friction sandbox bootstrap (no human in the loop) or OAuth-style consent with PKCE for production scope grants. ]() ## MCP server The Flowie Exchange API ships **two Model Context Protocol servers** so AI agents — Claude Desktop, Claude Code, Cursor, n8n, custom Python agents — can send, receive, and manage Peppol e-invoices as native tool calls. ### Endpoints Mode| Tools| Production| Sandbox ---|---|---|--- **Curated** _(recommended)_ | 34 | `https://back.p2p-flowie.com/exchange/mcp` | `https://back.flowie.ink/exchange/mcp` **Full** | 94 | `https://back.p2p-flowie.com/exchange/mcp/full` | `https://back.flowie.ink/exchange/mcp/full` The curated server exposes only the eight tags an agent actually needs: `Documents`, `Directory`, `Companies`, `Lifecycle`, `Compliance`, `Partners`, `UBL Generator`, `Portability`. Admin, sandbox control plane, AFNOR certification, and debug routes are hidden — fewer tokens spent on tool discovery, far fewer "wrong tool" misfires. Pick **full** only when the agent genuinely needs platform / white-label / certification surface. Transport is **streamable-HTTP** (the modern MCP transport, MCP spec `2025-06-18`). The legacy SSE transport is no longer mounted. ### Authentication Every request the agent makes is forwarded to the FastAPI handler with the original `Authorization` header preserved, so the same scoping rules apply: tenant isolation, per-key quotas, sandbox vs live partitioning. [code] Authorization: Bearer flw_test_your_key_here [/code] Use a `flw_test_…` key against the sandbox host while you're developing the agent — every test recipient from the [sandbox guide](<../sandbox/index.html>) is reachable through MCP exactly as it is through REST. Need a key? [Bootstrap one in one click](<../index.html#get-test-key>), or — if the agent must **request its own key on behalf of a real user** — see the [OAuth consent flow](). ### Quickstart — Claude Desktop Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows): [code] { "mcpServers": { "flowie-exchange": { "url": "https://back.flowie.ink/exchange/mcp", "transport": "streamable-http", "headers": { "Authorization": "Bearer flw_test_your_key_here" } } } } [/code] Restart Claude Desktop. The hammer icon shows **34 tools loaded**. Try: _"List my last 5 incoming invoices."_ ### Quickstart — Claude Code In the project directory, drop a `.mcp.json` file (Claude Code picks it up automatically per project): [code] { "mcpServers": { "flowie-exchange": { "url": "https://back.flowie.ink/exchange/mcp", "transport": "streamable-http", "headers": { "Authorization": "Bearer flw_test_your_key_here" } } } } [/code] Or register globally so every project sees it: [code] claude mcp add flowie-exchange https://back.flowie.ink/exchange/mcp \ --transport streamable-http \ --header "Authorization: Bearer flw_test_your_key_here" [/code] ### Quickstart — Cursor / VS Code In **Cursor** : _Settings → MCP → Add new server_ , paste the same JSON shape as Claude Desktop. In **VS Code** with the Continue extension: same JSON under `continue.config.mcpServers`. Both speak streamable-HTTP natively. ### Quickstart — Python (mcp SDK) For custom agents, the official `mcp` Python SDK speaks streamable-HTTP directly: [code] # pip install mcp import asyncio, os from mcp.client.streamable_http import streamablehttp_client from mcp import ClientSession URL = "https://back.flowie.ink/exchange/mcp" KEY = os.environ["FLOWIE_KEY"] async def main(): async with streamablehttp_client( URL, headers={"Authorization": f"Bearer {KEY}"} ) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print(f"{len(tools.tools)} tools available") # Call a tool by name with REST-style args result = await session.call_tool( "list_documents", arguments={"direction": "incoming", "status": "unread", "limit": 5}, ) print(result.content[0].text) asyncio.run(main()) [/code] ### Tool catalog (curated) The curated server exposes one MCP tool per FastAPI operation tagged `Documents`, `Directory`, `Companies`, `Lifecycle`, `Compliance`, `Partners`, `UBL Generator`, `Portability`. The high-leverage ones for agents: Tool| What it does ---|--- `send_document`| Send an e-invoice / credit note / order over Peppol. `resolve_portability_taxpayer`| One identifier → the company's identity, its regime and what its platform change requires. `open_portability_request`| Open a platform change: designation agreement, computed clocks, first evidence entry. `record_portability_event`| Record a notice, an objection (classified), an agreement or the annuaire update. `get_portability_request`| State re-derived from the evidence chain, with tacit approval and chain verification. `list_documents`| Filter by direction, status, type, date range. `search_documents`| Full-text + structured search across all documents. `get_document_structured`| Flat, agent-friendly view — every field as a primitive. `validate_document`| Pre-flight a payload through BIS / EN-16931 rules. `update_lifecycle`| Approve, reject, mark as paid, dispute. `search_directory`| Find Peppol participants by name, VAT, or country. `verify_recipient`| Check a Peppol ID can receive a given document type. `resolve_company`| Look up by VAT / SIREN — get Peppol ID + enriched profile. `create_company`| Register a sender, auto-publish to the Peppol SMP. `get_compliance_report`| Latest PPF (FR) or SDI (IT) report status for a document. `list_business_terms`| Every EN 16931 business term with its French obligation — what a BT id means, and whether the reform requires it. Run `tools/list` over MCP to enumerate the full set with input schemas and descriptions. Every tool's input schema mirrors the REST endpoint's request body — see the [API Reference](<../reference/index.html>) for the canonical shape. ### Common workflow — _"What invoices arrived this week?"_ The agent picks the right tools from the prompt; you do nothing. [code] User: "What invoices arrived this week and which ones are still unpaid?" Agent → list_documents({direction: "incoming", since: "2026-04-26"}) → for each: get_document_structured({documentId}) → for each unpaid: get_compliance_report({documentId}) → summarises totals by supplier, flags the ones past dueDate [/code] ### Common workflow — _"Send an invoice to ACME"_ Three tools, one chain. The agent verifies the recipient before sending. [code] User: "Bill ACME BVBA €4,500 + VAT for April consulting, due in 30 days." Agent → search_directory({q: "ACME BVBA"}) # finds peppolId → verify_recipient({peppolId, documentType: "INVOICE"}) → send_document({ type: "invoice", from: "comp_abc123", to: "0208:0123456789", document: { number: "INV-2026-0451", issueDate: "2026-04-30", dueDate: "2026-05-30", currency: "EUR", lines: [{ description: "Consulting — April 2026", quantity: 1, unit: "lot", unitPrice: 4500.00, vatRate: 21 }] } }) [/code] The agent sees the returned `documentId` \+ `deliveryStatus` and reports back. Pass an `Idempotency-Key` at the REST layer if you want retry safety — MCP forwards it as a tool argument. ### Common workflow — _"Mark INV-0417 as paid"_ [code] User: "INV-2026-0417 was paid yesterday — close the loop." Agent → search_documents({number: "INV-2026-0417"}) # → documentId → update_lifecycle({ documentId, status: "paid", note: "Paid 2026-04-29 via SEPA" }) [/code] The lifecycle change automatically triggers PPF (FR) or SDI (IT) reporting where applicable — the agent doesn't need to know about that. Watch `compliance.reported` on your [webhook stream](<../reference/webhooks.html#events>) for confirmation. Belgian invoices skip this step (HERMES was decommissioned 2025-12-31). ### Common workflow — _"Onboard a new supplier"_ [code] User: "Add Globex SRL (VAT IT09876543210) as a partner and check they're on Peppol." Agent → resolve_company({vatNumber: "IT09876543210"}) # enriched profile → verify_recipient({peppolId}) # canReceive: true? → save_partner({...}) # in your CRM/ERP [/code] ### Direct HTTP (no SDK) MCP is just JSON-RPC over an HTTP POST. If you don't want the SDK: [code] # 1. Initialize the session curl -X POST https://back.flowie.ink/exchange/mcp \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "curl", "version": "1.0"} } }' # 2. List tools curl -X POST https://back.flowie.ink/exchange/mcp \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' # 3. Call a tool curl -X POST https://back.flowie.ink/exchange/mcp \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc":"2.0","id":3,"method":"tools/call", "params":{ "name":"list_documents", "arguments":{"direction":"incoming","limit":5} } }' [/code] ### Errors MCP errors mirror the underlying REST errors — same codes, same shape, wrapped in JSON-RPC. A `401` from REST surfaces as MCP error `-32001` with the original Flowie error code in `data.errorCode`. See the [error catalog](<../reference/errors.html>) for everything you might see. The two MCP-specific errors: * **`tool not found`** — agent called a tool that's not in the curated set. Switch to `/mcp/full` or rename. * **`invalid arguments`** — input schema mismatch. Run `tools/list` and follow the `inputSchema` exactly. ### Rate limits & quotas MCP calls inherit your REST quota — there's no separate budget. One MCP `tools/call` = one REST request. Use the same `X-Flowie-RateLimit-Remaining` header logic to back off; the header is surfaced on the JSON-RPC response envelope under `_meta`. ### Sandbox Point the agent at `https://back.flowie.ink/exchange/mcp` with a `flw_test_…` key and every sandbox feature works: forced errors via `X-Sandbox-Force-Error`, simulated recipients (`0208:SIM_HAPPY`, `SIM_DISPUTE`, `TEST_AP_FAIL`), lifecycle simulators, the lot. See the [sandbox guide](<../sandbox/index.html>) for the full menu. Tip — keep a sandbox profile in Claude Desktop Claude Desktop supports multiple `mcpServers` entries. Register both `flowie-exchange-sandbox` (test key, sandbox URL) and `flowie-exchange-prod` (live key, prod URL). Then prompt the agent explicitly: _"Use the sandbox server to dry-run this."_ ### When to use full vs curated * **Curated** — agents that send, receive, search, and reconcile invoices. Default choice for 95% of integrations. * **Full** — IDE integrations, ops scripts, AFNOR-certified flows, white-label admin, request inspector. Larger context cost; only when you genuinely need the extra surface. You can mount both — agents pick the right one based on the host you point them at. There's no auth difference between the two, so the same key works against both URLs. ## Docs for agents The whole documentation site is published in machine-readable form, following the [llms.txt]() convention. Point an agent (or a RAG pipeline) at these instead of scraping HTML — every page carries a `` so tools can discover them automatically. Resource| What it is ---|--- [`llms.txt`](<../llms.txt>)| Page index with titles and one-line descriptions — a fast lookup so an agent can decide what to fetch. [`llms-full.txt`](<../llms-full.txt>)| The entire corpus in one file — every page back-to-back as clean Markdown. Drop it straight into a context window. [`llms/reference/index.md`](<../llms/reference/index.md>)| Directory of per-endpoint Markdown slices — one file per API operation, so an agent can pull just the one endpoint it needs. `openapi.json`| The full OpenAPI 3.1 spec — the same one that generates the MCP tool schemas. Everything under `docs.get-flowie.com` is reachable this way: `https://docs.get-flowie.com/llms.txt`, `https://docs.get-flowie.com/llms-full.txt`, and one Markdown file per endpoint under `https://docs.get-flowie.com/llms/reference/`. ## Agent onboarding An agent doesn't need a human to hand it a key. Two self-service paths let it provision access on its own — see the full [agent onboarding guide]() for both. * **Sandbox bootstrap** — one unauthenticated POST returns a 7-day `flw_test_…` key plus a starter sandbox company. Zero human in the loop; ideal for prototyping, demos, and agent CI. See the [bootstrap flow](). * **OAuth consent (PKCE)** — when the agent must act _on behalf of a real user_ , the OAuth-style consent flow issues a scoped, production-grade key after the user approves. See the [OAuth consent flow](). ======================================================================== # Agent plugin (MCP, skills & CLI) # Source: https://docs.get-flowie.com/build-with-ai/plugin.html ======================================================================== --- title: "Agent plugin" description: "Connect Flowie Exchange to your coding agent in one command: an MCP server with 34 curated e-invoicing tools, 14 hosted agent skills, and the CLI. Public beta." canonical: "https://docs.get-flowie.com/build-with-ai/plugin" source: "https://docs.get-flowie.com/build-with-ai/plugin.html" --- # Agent plugin AI Agents # Agent plugin **Public beta.** Everything on this page works today and is free to use. Tool names, skill contents and the CLI surface can still change between releases — we version them and announce every change in the [changelog](<../changelog.html>), but do not pin a production workflow to an exact tool name yet. See [What beta means](<#beta>). Give your coding agent the Flowie Exchange tools, the know-how to use them correctly, and an account it can open by itself. Three surfaces, one setup: Surface| What it gives the agent| Status ---|---|--- [MCP server](<#mcp>) | **Hands.** 34 curated tools — send and receive invoices, resolve a company on Peppol, track lifecycle, check country compliance. | Beta · live [Agent skills](<#skills>) | **Know-how.** 14 hosted skills that tell an agent which endpoint answers a question, and which mistake not to make. | Beta · live [CLI](<#cli>) | **A terminal.** The same operations from a shell or from CI, with no model in the critical path. | Private beta The tools and the skills are meant to be installed together. An MCP server on its own makes your agent rediscover every trap in cross-border e-invoicing; the skills are where we wrote those down. ## Quickstart Pick your agent. Every command below points at the **sandbox** — no key, no signup, and nothing you run can reach a real tax authority. ### Claude Code [code] claude mcp add flowie-exchange https://back.flowie.ink/exchange/mcp [/code] ### Cursor, VS Code, Codex & anything else that speaks MCP Add the server to your client’s MCP configuration: [code] { "mcpServers": { "flowie-exchange": { "url": "https://back.flowie.ink/exchange/mcp" } } } [/code] ### Get a key without leaving the agent Your agent can open its own sandbox account — no human, no form: [code] curl -X POST https://back.flowie.ink/exchange/v1/sandbox/bootstrap \ -H 'Content-Type: application/json' -d '{}' [/code] That returns an `flw_test_…` key, an organization and a company. Send it as `Authorization: Bearer ` on every subsequent call. For production, or to act on a real user’s account, read [Agent onboarding](). ## MCP server Two endpoints, same authentication: Endpoint| Tools| Use it when ---|---|--- `/exchange/mcp`| ~34, curated| Almost always. Small enough to leave room in the context window for the actual task. `/exchange/mcp/full`| Every documented operation| You need sandbox control, platform/white-label or AFNOR certification routes. Environment| Base ---|--- Sandbox| `https://back.flowie.ink/exchange/mcp` Production| `https://back.p2p-flowie.com/exchange/mcp` Authentication is a bearer token on every call — a user JWT or an `flw_*` API key — and the token’s organization scopes every read and write. **No money moves:** payment fields are invoice metadata only. The server describes itself at `/.well-known/mcp/server-card.json`, and names its authorization server at `/.well-known/oauth-authorization-server`. An agent that follows the discovery chain finds both without being told. Full tool catalogue and worked workflows: [MCP server reference](). ## Agent skills A skill is a Markdown file that teaches an agent how to do one job with this API — which endpoint answers the question, what the fields mean, and the mistake that looks like success. They execute nothing, so they cost nothing at runtime, and they are what stops an agent inventing a field the API does not have. The catalogue is discoverable and content-addressed: [code] curl https://docs.get-flowie.com/.well-known/agent-skills/index.json [/code] Every entry carries a `url` and a `sha256` digest, so a client can cache a skill and tell when it changed. Fourteen are published today: Skill| What it covers ---|--- `send-invoice`| Send a compliant e-invoice to any recipient in 47 countries. `receive-invoices`| Webhooks or polling, and how to choose. `check-reachability`| Is this company reachable, and on which network. `track-lifecycle`| Delivery and approval statuses, including the French 200–213 set. `register-on-peppol`| Register a company and claim its identifier. `country-compliance`| What each jurisdiction requires, and by when. `sandbox-bootstrap`| Open an account with no human in the loop. `search-documents`| The filter language, with worked queries. `handle-webhooks`| Events, retries and signature verification. `record-payment`| Payment metadata on an invoice — and what it is not. `generate-french-invoice`| A compliant sample for every French business case. `change-platform`| Portability: move a company without losing the clock. `debug-failed-request`| Read an error and fix the cause, not the symptom. `connect-mcp`| Wire this server into an agent that has never seen it. Most MCP clients load skills from the index automatically once the server is connected. If yours does not, point it at the raw file — for example `https://docs.get-flowie.com/skills/send-invoice/SKILL.md`. ## CLI **Private beta.** The CLI is not published yet. Ask us at [developers@flowie.fr]() and we will add you. The CLI covers the same ground from a terminal, for the cases where a model in the loop is a liability rather than a help — a CI job, a runbook, a migration you need to be able to re-run and diff. It also runs an MCP server locally over stdio, if you would rather your agent talk to a process you started than to an endpoint we host. We will document install and commands here when it leaves private beta. Until then the hosted MCP server above is the supported path, and everything an agent can do through it is also reachable over plain HTTP: see the [API reference](<../reference/index.html>). ## Check it works One request proves the whole chain — transport, discovery and tool listing: [code] curl -X POST https://back.flowie.ink/exchange/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{ "protocolVersion":"2024-11-05","capabilities":{}, "clientInfo":{"name":"my-agent","version":"0"}}}' [/code] A `result` carrying `serverInfo.name: "flowie-exchange"` means you are connected. A `404` means you reached an older deployment — tell us, because that is a bug on our side, not a configuration problem on yours. ## What beta means Concretely, so you can decide what to build on: * **Stable enough to build on:** the endpoints, the authentication model, the sandbox bootstrap, and the skill index URL. These are covered by our [deprecation policy](<../deprecation-policy.md>) like any other part of the API. * **Still moving:** individual tool names and arguments, the composition of the curated set, and skill contents. A renamed tool breaks a prompt rather than a compiler, so we announce every change in the [changelog](<../changelog.html>) — read it before pinning. * **Not covered yet:** the CLI, which is private beta and carries no compatibility promise at all. * **Breaking changes** to the underlying API still ship only in a new major under a new URL prefix. That does not change in beta. Building something on this? [Tell us]() — during beta that is the fastest way to get a tool renamed back, a skill corrected, or a missing capability added. ======================================================================== # Agent onboarding (sandbox + OAuth) # Source: https://docs.get-flowie.com/build-with-ai/agent-onboarding.html ======================================================================== --- title: "Agent onboarding" description: "How an AI agent autonomously signs up for a Flowie Exchange API key — sandbox bootstrap (zero friction) and OAuth-style consent flow with PKCE for production-grade access." canonical: "https://docs.get-flowie.com/build-with-ai/agent-onboarding" source: "https://docs.get-flowie.com/build-with-ai/agent-onboarding.html" --- # Agent onboarding AI Agents # Agent onboarding — sign up & sign in autonomously **If the URL the agent received contains`?handoff=hand_…`, jump to [Handoff token](<#handoff>) first** — that's the fastest path and the user pre-approved your scopes. Otherwise, three paths exist depending on context: Path| Human in the loop?| Issued key bound to| Best for ---|---|---|--- [Handoff token](<#handoff>)| Pre-approved by the user| **The user's real org** (`flw_test_…` or `flw_live_…`)| The user pasted you a personalized URL; you run as their account. [Sandbox bootstrap](<#sandbox>)| No| Fresh sandbox org · `flw_test_…`| Prototyping, demos, agent CI, MCP playground. [OAuth consent flow (PKCE)](<#oauth>)| Yes — one-time consent| Sandbox org · `flw_test_…` (production rolling out)| Agents that need to act on a specific user's data with explicit scope grants. v1 status The OAuth flow currently issues sandbox `flw_test_…` keys (7-day expiry). Production `flw_live_…` issuance is gated on a dashboard-side consent UI; we'll announce in the [changelog](<../changelog.html>) when it ships. Until then: use OAuth for the consent ceremony but expect a sandbox- scoped key on the other end. ## Handoff token — pre-approved personalized link The fastest, most useful path. The user generates a single-use URL on the [home page](<../index.html#agent-handoff>) (or via `POST /v1/oauth/handoff` from any client they're already authenticated to) and pastes the URL to you. The URL embeds a token bound to their organization with a pre-approved scope set. Why this is the right default The minted key is bound to the **user's real organization** — not a fresh sandbox. So when you call `POST /v1/companies`, `POST /v1/documents`, etc., they land in their actual account. No consent UI, no PKCE round-trips: the human did the consent up front when they generated the link. **Step 1 — Detect the token.** If your URL contains `?handoff=hand_…`, extract it. [code] from urllib.parse import urlparse, parse_qs url = "https://back.flowie.ink/exchange/docs-public/agent-onboarding.html?handoff=hand_AbC..." token = parse_qs(urlparse(url).query).get("handoff", [None])[0] [/code] **Step 2 — Redeem it.** Single POST. No other auth required; the token is the credential. [code] curl -X POST https://back.flowie.ink/exchange/v1/oauth/handoff/exchange \ -H "Content-Type: application/json" \ -d '{"handoff_token":"hand_AbC..."}' [/code] Response (same shape as the OAuth `/token` endpoint): [code] { "access_token": "flw_test_…", "token_type": "Bearer", "scopes": ["send","receive","documents.read","companies.read","stats"], "expires_in": 604800, "company_id": "comp_…", "organization_id":"org_…" } [/code] Use `access_token` as your `Authorization: Bearer …` for every subsequent call. **Constraints & security model:** * **Single-use:** a second exchange returns `400 Handoff token has already been used.` * **Short TTL:** default 10 min, max 60 min — the user controls this when generating the link. * **Scope-bounded:** the user can only pre-approve scopes their own token already holds. You can't escalate. * **Org-bound:** the issued key inherits the user's organization, company, and tier — it can't be used to access any other tenant. * **Default scopes** (when generated from the home page): `send`, `receive`, `documents.read`, `companies.read`, `stats`. The user can override via the API to grant fewer or more. **If redemption fails** with `400 Invalid or expired handoff token` the URL was either reused, expired, or never valid. Ask the user to generate a fresh link from [the home page](<../index.html#agent-handoff>) — or, if they prefer, fall back to the [OAuth consent flow](<#oauth>) below. ⚠ Always send a JSON body, even if empty The Flowie LB (Google Cloud HTTPS LB) returns `411 Length Required` on POSTs without a body. Browser `fetch(url, {method:"POST"})` with no body, Python `requests.post(url)` without `json=`, and `curl -X POST` without `-d` all hit this. Always include `-d '{}'` (or the language equivalent) when calling `/v1/oauth/handoff/exchange` or `/v1/oauth/handoff/sandbox`. The 411 is rejected at the LB before reaching the FastAPI app, so you won't see it in our logs. ## Sandbox bootstrap — zero-friction path The agent calls a public, rate-limited endpoint and gets a fresh test key plus a starter sandbox company. No auth, no consent, no human: [code] curl -X POST https://back.flowie.ink/exchange/v1/sandbox/bootstrap \ -H "Content-Type: application/json" \ -d '{"label":"my-agent"}' [/code] Response: [code] { "organizationId": "org_sbx_…", "apiKey": "flw_test_…", "keyPrefix": "flw_test_abc1", "keyType": "personal", "company": { "id": "comp_sbx_…", "peppolId": "0208:0000000001", "vatNumber": "BE0000000001", "name": "Sandbox Test BVBA", "country": "BE" }, "expiresAt": "2026-05-12T…" } [/code] Constraints: * **Rate limit:** 120 calls per IP per hour. * **Key TTL:** 7 days. * **Test mode only:** the key talks to the sandbox host `back.flowie.ink`; using it against production `back.p2p-flowie.com` returns `401 INVALID_TOKEN`. * **Documents are not delivered** over real Peppol — they route to an internal echo recipient. See [Sandbox shortcuts](<../sandbox/index.html#sandbox-shortcuts>). For more advanced sandbox shapes — platform / white-label keys, simulated errors, time-travel — see the [Sandbox guide](<../sandbox/index.html>). ## OAuth consent flow — agent acts on behalf of a user When an agent needs to operate on a real user's account, the user must explicitly approve the scope list before the agent gets a key. Flowie implements a deliberately minimal slice of OAuth 2.1 for this: * **Public clients only.** Agents can't reliably keep secrets, so there's no `client_secret`. * **PKCE mandatory.** The agent generates a one-time `code_verifier`, hashes it with SHA-256, and sends only the hash up. The server checks the verifier against the hash on the token exchange. Protects the auth code in transit. * **One-time auth codes.** 5-minute TTL, single-use. * **OOB by default.** Agents that can't host a redirect URI use `urn:ietf:wg:oauth:2.0:oob` — the consent page shows the auth code on screen for the user to copy back. ### The four-step dance [code] ┌──────┐ ┌──────────────────┐ │agent │ │ Flowie Exchange │ └───┬──┘ └─────────┬────────┘ │ │ │ 1. POST /v1/oauth/authorize │ │ {client_name, scopes, │ │ code_challenge=SHA256(verifier)} │ ├──────────────────────────────────────────►│ │ ◄──── 200 {consent_url} │ │ │ │ 2. Show consent_url to user │ │ │ │ User clicks link, lands on consent │ page, reviews scopes, clicks Approve │ │ │ 3. ◄── auth_code shown on screen │ │ (or redirected to your URI) │ │ │ │ 4. POST /v1/oauth/token │ │ {grant_type, code, code_verifier} │ ├──────────────────────────────────────────►│ │ ◄──── 200 {access_token: flw_test_…} │ │ │ [/code] ## Scope catalogue Fetch the live catalogue at [`GET /v1/oauth/scopes`]() — public, no auth. The minimum bar: Scope| What it grants ---|--- `send`| Issue invoices, credit notes, orders over Peppol. `receive`| Configure inbound delivery + webhooks + SMP registration. `documents.read`| List, search, download XML / PDF / structured views. `documents.search`| Filtered search across the corpus. `documents.write`| Mark read / archive / tag / add notes. `companies.read`| Read sender / partner companies + Peppol registrations. `companies.write`| Update companies, register on the SMP. `directory`| Search the Peppol directory, verify reachability. `partners`| Manage trading partners and routing settings. `payments`| Record payments, manage terms, ISO 20022 / SEPA export. `lifecycle`| Approve / reject / mark as paid — drives PPF/SDI compliance reporting. `compliance`| Read compliance dashboard + report records. `stats`| Per-period sent / received / delivered / failed counters. Ask for less, not more Agents that ask for `send` alone get approved more often than agents that demand the full scope list up-front. If you need extra access later, trigger a new consent flow with the additional scopes — the user knows what they're agreeing to. ## PKCE walkthrough RFC 7636. The agent generates two values once per authorization: 1. `code_verifier` — a random 43-128 character string, base64url-safe. _This is the agent's secret. Never sends it until step 4._ 2. `code_challenge` = `BASE64URL(SHA256(code_verifier))` with no padding. The challenge goes up in the `POST /v1/oauth/authorize` request. The verifier goes up in the `POST /v1/oauth/token` request. Server compares — if they don't match, the exchange fails. ## Claude Desktop recipe An MCP-connected Claude Desktop agent that sets itself up. Prompt the user with the consent URL, accept the OOB code back, swap for an API key, then add it to the MCP config: [code] User: "Set up a Flowie sandbox account for me." Agent (internal, hidden): 1. POST /v1/sandbox/bootstrap → flw_test_… key + sandbox company 2. Update ~/Library/.../claude_desktop_config.json: { "mcpServers": { "flowie-exchange": { "url": "https://back.flowie.ink/exchange/mcp", "transport": "streamable-http", "headers": {"Authorization": "Bearer flw_test_…"} } } } 3. Tell user to restart Claude Desktop. Agent (visible): "Done. I provisioned a sandbox account at organization org_sbx_…. After you restart Claude, you'll have access to 34 Peppol tools (send_document, list_documents, …). Try: 'List my last 5 invoices.'" [/code] This is the all-autonomous path — perfect for demoing or developing. For real production access (touching a user's actual Peppol traffic), use the OAuth flow below. ## Python recipe [code] """Self-onboarding Flowie agent — OAuth-style consent flow with PKCE.""" import base64, hashlib, secrets, webbrowser import httpx BASE = "https://back.flowie.ink/exchange" def pkce_pair(): verifier = secrets.token_urlsafe(48).rstrip("=")[:64] digest = hashlib.sha256(verifier.encode()).digest() challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode() return verifier, challenge # 1. Register intent + grab the consent URL. verifier, challenge = pkce_pair() r = httpx.post(f"{BASE}/v1/oauth/authorize", json={ "client_name": "My Local Python Agent", "scopes": ["send", "documents.read", "documents.search"], "code_challenge": challenge, "code_challenge_method": "S256", }) r.raise_for_status() auth = r.json() print(f"Open in your browser:\n {auth['consent_url']}\n") webbrowser.open(auth["consent_url"]) # 2. Wait for the user to paste the OOB code back. auth_code = input("Paste the auth code from the browser: ").strip() # 3. Exchange code + verifier for an API key. r = httpx.post(f"{BASE}/v1/oauth/token", json={ "grant_type": "authorization_code", "code": auth_code, "code_verifier": verifier, }) r.raise_for_status() token = r.json() print(f"Got key prefix {token['access_token'][:16]}…") print(f"Scopes: {', '.join(token['scopes'])}") print(f"Expires in {token['expires_in'] // 3600}h") # 4. Use it. api = httpx.Client( base_url=f"{BASE}/v1", headers={"Authorization": f"Bearer {token['access_token']}"}, ) print(api.get("/documents", params={"limit": 5}).json()) [/code] ## curl recipe For the absolute lowest-level diagnostic. Generate verifier + challenge in any language; here we use OpenSSL: [code] # 1. PKCE pair VERIFIER=$(openssl rand -base64 48 | tr -d '+/=' | head -c 64) CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr -d '+/=' | tr 'a-z' 'a-z') # 2. Authorize RESP=$(curl -s -X POST https://back.flowie.ink/exchange/v1/oauth/authorize \ -H "Content-Type: application/json" \ -d "{\"client_name\":\"curl agent\", \"scopes\":[\"send\"], \"code_challenge\":\"$CHALLENGE\", \"code_challenge_method\":\"S256\"}") CONSENT_URL=$(echo "$RESP" | jq -r .consent_url) echo "Open: $CONSENT_URL" # 3. After clicking Approve, paste the code: read -p "Auth code: " CODE # 4. Exchange curl -s -X POST https://back.flowie.ink/exchange/v1/oauth/token \ -H "Content-Type: application/json" \ -d "{\"grant_type\":\"authorization_code\", \"code\":\"$CODE\", \"code_verifier\":\"$VERIFIER\"}" | jq . [/code] ## Step-up — when an agent needs more scope mid-session The flow above is whole-cycle: agent gets a fresh key with N scopes. If the agent later needs an additional scope (e.g. it has `documents.read` but discovers it needs `payments` to mark an invoice paid), the recommended pattern is to **start a fresh consent cycle** with the additional scope, present the user the new consent URL, and replace the existing key. There is no append-scope-to-existing-key endpoint by design — keeping every issued key tied to exactly one explicit consent record makes audit trails clean. ## FAQ ### Why not just use the sandbox bootstrap for everything? Sandbox bootstrap is anonymous. It works for prototyping, but the issued key is bound to a fresh empty sandbox org — not to the user's real Flowie account. The OAuth flow ties the key to a real user's consent, which is what you need for any agent that will touch production data. ### Why PKCE? My agent runs on a server, I can keep a secret. If your agent is server-side and confidential, you'll be migrated to the production OAuth flow when it ships (with `client_secret` support). For the v1 sandbox-issuing flow, every client is treated as public to keep the surface honest and the rollout simple. ### What happens if the user closes the consent page before clicking Approve? The consent request expires after 10 minutes (no auth code is ever issued). The agent gets a clean 400 on token exchange. Ask the user to retry. ### Can I get a key that lasts more than 7 days? Not via the OAuth flow yet. Production OAuth (coming separately) will mint `flw_live_…` keys with the same TTL semantics as keys created through the dashboard (90 days default, configurable per org). Until then, the OAuth-issued sandbox keys auto-rotate every 7 days. ### How do I revoke a key the agent issued itself? The user revokes from their dashboard; or the agent calls [`DELETE /v1/api-keys/{id}`](<../reference/index.html#revoke-api-key>) with its own key. Revocation is immediate. ### Does the OAuth flow ever return an existing key, or always a new one? Always a new one. Each consent flow mints a new key + new sandbox org, deliberately — preserves the one-key-per-consent-record audit invariant. ======================================================================== # Error catalog # Source: https://docs.get-flowie.com/reference/errors.html ======================================================================== --- title: "Errors" description: "Every error code returned by the Flowie Exchange API, with the cause and the remediation." canonical: "https://docs.get-flowie.com/reference/errors" source: "https://docs.get-flowie.com/reference/errors.html" --- # Errors Error Catalog # Every error, with a fix If you see one of these codes, jump to the row. Every entry includes the typical cause and the exact remediation. ## The error envelope All errors — whether from Flowie itself or relayed from an upstream (Peppol SMP, PPF, SDI) — share this shape: [code] { "error": { "type": "validation_error", // coarse category "code": "INVALID_REQUEST", // stable machine code "message": "Request validation failed", "details": [ // optional, field-level { "field": "document.lines[0].vatRate", "rule": "range", "message":"Must be between 0 and 100" } ], "requestId": "req_01HXYZ2K3M4N5P6Q7R", "docUrl": "https://docs.get-flowie.com/errors#INVALID_REQUEST" } } [/code] `requestId` is always present — include it in every support ticket. ## 400 · Validation errors Code| Cause| Fix ---|---|--- `INVALID_REQUEST`| One or more fields failed schema validation.| Inspect `details[]`; each entry names the offending `field` and `rule`. `MISSING_FIELD`| A required field is absent.| Supply the field. Required fields are marked in the [API reference](). `INVALID_ENUM`| Value isn't in the allowed set.| Use one of the listed enum values — don't assume case-insensitivity. `INVALID_VAT_FORMAT`| Pattern `^[A-Z]{2}[A-Z0-9]+$` failed.| Strip spaces, uppercase, include country prefix. `INVALID_IBAN`| IBAN checksum failed.| Re-check the IBAN against `mod-97`. `INVALID_CURRENCY`| Not an ISO 4217 code.| Use 3-letter uppercase codes (`EUR`, `USD`…). `INVALID_DATE`| Not ISO 8601.| Format as `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SSZ`. ## 401 / 403 · Authentication & authorization Code| Cause| Fix ---|---|--- `MISSING_AUTH`| No `Authorization` header.| Add `Authorization: Bearer …`. `INVALID_TOKEN`| JWT couldn't be verified against our JWKS.| Fetch a fresh token. Check the audience claim matches `https://api.flowie.ink/`. `EXPIRED_TOKEN`| JWT past its `exp`.| Refresh and retry. `REVOKED_KEY`| API key was deleted.| Issue a new key via [POST /v1/api-keys](). `INSUFFICIENT_SCOPE`| Key is valid but lacks the required scope.| Re-issue the key with the missing scope or switch to a broader key. `COMPANY_FORBIDDEN`| The key is scoped to a different company.| Use the right key, or add `X-Flowie-Company` on a platform key. ## 404 · Not found Code| Cause| Fix ---|---|--- `RESOURCE_NOT_FOUND`| Generic 404.| Check the ID — IDs are case-sensitive. `COMPANY_NOT_FOUND`| No company with this id / VAT / Peppol ID in your org.| If you used `vat:` or `peppol:` prefix, double-check the scheme. `DOCUMENT_NOT_FOUND`| Document doesn't exist or is outside your visibility.| Platform keys see tenant docs only when acting on behalf of that tenant (`X-Flowie-Company`). ## 409 · Conflict Code| Cause| Fix ---|---|--- `COMPANY_EXISTS`| VAT already registered by your organization.| Treat as an idempotent upsert — use `existingId` from the error payload. `IDEMPOTENCY_IN_PROGRESS`| A request with the same key is still processing.| Wait a moment and retry. `IDEMPOTENCY_BODY_MISMATCH`| Same key, different body.| Either reuse the exact original body or use a new key. `INVALID_TRANSITION`| Lifecycle status change isn't allowed from the current state.| See `allowedTransitions` returned by [GET lifecycle](). ## 422 · Semantic errors Code| Cause| Fix ---|---|--- `VAT_NOT_FOUND`| VAT doesn't exist in the national registry.| Double-check the VAT; registries lag by a few days for new entities. `VAT_INACTIVE`| VAT is flagged inactive (ceased activity).| Confirm with the customer. `RECIPIENT_NOT_FOUND`| Peppol ID isn't registered anywhere.| Ask the customer for a valid Peppol ID or use [directory search](). `RECIPIENT_CANNOT_RECEIVE`| Peppol ID exists but doesn't accept this document type.| Check `documentTypes` on the directory record. Ask the recipient's AP to extend SMP. `UBL_VALIDATION_FAILED`| Rendered UBL failed Peppol BIS schematron.| See Peppol BIS rule codes below (`BR-*`, `BR-CO-*`). ## 429 · Rate limit Code| Cause| Fix ---|---|--- `RATE_LIMITED`| You exceeded req/min.| Sleep `Retry-After` seconds, then retry. Parallelize fewer calls, or upgrade plan. `QUOTA_EXCEEDED`| Monthly document quota is used up.| Upgrade plan, or wait for the monthly reset. ## 5xx · Server errors Code| Cause| Fix ---|---|--- `INTERNAL_ERROR`| Unexpected server error.| Retry with exponential backoff. Persist → report `requestId` to support. `UPSTREAM_UNAVAILABLE`| A dependency (SMP, PPF…) is down. Circuit breaker is open.| Retry after `Retry-After`. Check [status page](). `UPSTREAM_TIMEOUT`| Dependency took too long.| Safe to retry — request is idempotent when you pass `Idempotency-Key`. ## Delivery failures These come _after_ a `document.sent` event, as a `document.failed` webhook. The document stays sendable — fix and re-send with a new number. Code| Cause| Fix ---|---|--- `AP_REJECTED`| Recipient's access point rejected the payload.| Read `errorMessage` — often a schema or buyer-reference issue. `TRANSPORT_FAILURE`| Temporary AS4 transport failure.| Retry automatically — Flowie re-sends up to 5 times. `SBDH_ERROR`| Standard Business Document Header malformed.| Internal; should not surface. Contact support. ## Compliance failures Relayed from PPF (FR) or SDI (IT). Surfaced via `compliance.reported` webhook with `status: "failed"`. Belgium has no regulator-side report; BE-CIUS validation errors surface as `BR-BE-*` on the synchronous send response — see [Belgium · error codes](<../compliance/be.html#error-codes>). Platform| Code| Meaning ---|---|--- PPF (FR)| `00025`| Invoice number doesn't match PPF format. PPF (FR)| `00058`| Service Executant missing for public buyer. SDI (IT)| `00200`| Schema validation error. SDI (IT)| `00306`| Codice Destinatario unknown. ## Peppol BIS rule codes (selected) Rule| Summary ---|--- `BR-01`| An Invoice shall have a Specification identifier. `BR-02`| An Invoice shall have an Invoice number. `BR-16`| An Invoice shall have at least one Invoice line. `BR-CL-04`| Invoice currency code shall be from ISO 4217. `BR-CO-10`| Sum of line net amounts equals net amount. `BR-CO-15`| Invoice total with VAT = net + VAT. `BR-DEC-12`| Decimals limited to 2 on monetary totals. Full list: [Peppol BIS 3.0 rules](). ======================================================================== # Integration guides # Source: https://docs.get-flowie.com/guides/index.html ======================================================================== --- title: "Integration Guides" description: "Step-by-step playbooks for sending, receiving, going live, and building white-label products on Flowie Exchange." canonical: "https://docs.get-flowie.com/guides/" source: "https://docs.get-flowie.com/guides/index.html" --- # Integration Guides Guides # Integration playbooks Short, opinionated, end-to-end recipes for the six tasks most teams do in their first month. ## Sending invoices over Peppol Register the sender, verify the recipient, `POST /v1/documents/send`, watch the delivery webhook. **[Read the full guide → Send an invoice over Peppol]()** ## Receiving invoices Incoming documents land as `document.received` webhooks: subscribe once, verify the HMAC, fetch the structured view, advance the lifecycle — or poll `GET /v1/documents` if you cannot expose an endpoint. **[Read the full guide → Receive invoices]()** ## Changing platform — portability 🇫🇷 France only A French taxpayer may change _Plateforme Agréée_ at any time and **keeps its SIREN/SIRET addressing** , so nothing downstream has to be re-addressed. Import the company from its SIRET — one call, or a whole client book with `POST /v1/companies/import/batch` — then let Flowie build and parse the normalised inter-PA message. The clocks are the hard part: **24 h to acknowledge** , **5 _jours ouvrés_ to decide**, and past that delay _le silence vaut accord_. This flow is PPF-specific: outside France there is no PA to leave. **[Read the full guide → Portability (change of PA)]()** Leaving a provider **outside** France is a different job: a registry edit, an authorisation to re-grant at the tax authority, and an archive to get back. What that takes per country — and a request form that needs no API key — is on its own page. **[Read the full guide → Changing platform in Europe]()** ## Inbound: ERP webhooks → `/v1/documents/send` `POST /v1/documents/send` doubles as Flowie's **single inbound integration point**. If your ERP, accounting platform, or homegrown system can fire an outbound webhook (every modern one can), point it at `/v1/documents/send` directly — or wire one Logic App / Power Automate flow / Lambda in between to translate the event payload. No "inbound webhook receiver" abstraction; the same endpoint that lets you send invoices over Peppol also accepts whatever your ERP fires at it. Why one endpoint instead of a separate "inbound" route? * **One mental model** — your team learns "Flowie ingests at `/documents/send`" and that's it. * **Same idempotency, same auth, same lifecycle** — whatever you push in flows through the regular pipeline (validation, Peppol routing where applicable, lifecycle state machine, webhooks back out to subscribers). * **Format flexibility** — structured JSON, raw UBL XML, or a base64'd file (PDF / Factur-X / ZIP / image / proprietary). Sniff routes UBL through the validated path and reads a Factur-X or CII invoice into a structured document; everything else gets stored on the documents service with `deliveryStatus="stored"`. ### The pattern [code] ┌────────────────┐ webhook fires ┌─────────────────────┐ HTTPS POST ┌──────────────────┐ │ Your ERP / │ on invoice posted / │ Glue (Logic App, │ /v1/documents/send │ Flowie Exchange │ │ accounting SaaS│ ─────────────────────▶│ Power Automate, λ) │ ─────────────────▶│ (this API) │ └────────────────┘ PO confirmed, etc. └─────────────────────┘ bearer + idem └──────────────────┘ [/code] The glue layer is optional — many ERPs let you POST directly to a custom URL with a custom header. Use it when you need to map fields, transform payloads, or pull in attachments. ### Pick a payload shape Choose| When| Body ---|---|--- `format=json` | You can map ERP fields (number, dates, lines, totals) to the Flowie schema. | `{ type, format:"json", from, to, document:{...} }` `format=ubl-xml` | Your ERP already renders Peppol BIS 3.0 / EN 16931 XML. | `{ type, format:"ubl-xml", from, to, xml:"..." }` `format=auto` with `file` | You have the rendered document (PDF, Factur-X PDF/A-3, attachment) and want Flowie to **sniff** the bytes — UBL XML routes through the validated pipeline, a Factur-X PDF/A-3 or a CII invoice is read into a structured document; other PDFs and images get stored as-is. | `{ type, format:"auto", from, to, file:{ content:, contentType, filename } }` `format=raw` with `file` | Audit-trail / archive / proprietary format you don't want Flowie to interpret. | Same as above; response carries `deliveryStatus="stored"` \+ `fileId` \+ `storedFormat`. URL query params + raw body | ERP webhooks where you want a **fixed URL** and the source system POSTs its native event JSON verbatim — no wrapping, no base64, no Power Automate transformation. The wrapper constants travel as query params. | `POST /v1/documents/send?type=event&from=vat:…` \+ `Content-Type` header + body = the ERP payload byte-for-byte. See [Shape E](<#ingest-shape-rawbody>). ### Every combination — copy-paste recipes Same endpoint, six document types, four payload shapes. The matrix below is exhaustive; pick the row that matches what your source system can produce. type| format| Body field| Response `deliveryStatus`| Sniff? ---|---|---|---|--- `invoice` · `credit-note` · `debit-note` · `purchase-order` · `sales-order` · `quote` | `json`| `document`| `pending` (Peppol-routed)| — same six| `ubl-xml`| `xml`| `pending` (validated then routed)| — same six| `auto`| `file` = UBL XML| `pending` (sniff → ubl-xml path)| UBL/CII detected invoice / credit-note / debit-note| `auto`| `file` = Factur-X or CII| `pending` (or `awaiting_registration`) + the CII `number`| `%PDF-` with an embedded CII, or a `CrossIndustryInvoice` root same six| `auto`| `file` = other PDF| `stored` \+ `fileId` \+ `storedFormat:"pdf"`| `%PDF-` magic same six| `auto`| `file` = PNG / JPEG| `stored` \+ `storedFormat:"png"|"jpeg"`| image magic bytes same six| `auto`| `file` = ZIP| `stored` \+ `storedFormat:"zip"`| `PK\x03\x04` magic same six| `auto`| `file` = JSON / unknown| `stored` \+ `storedFormat:"json"|"binary"`| fall-through same six| `raw`| `file` = anything| `stored` \+ `storedFormat` reflects bytes| none — never sniffed #### Shape A — structured JSON You have the field-level data and want Flowie to render the UBL for you. Works for every `type`; only the type literal and a couple of cross-references change. [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Idempotency-Key: invoice-row-12345" \ -H "Content-Type: application/json" \ -d '{ "type": "invoice", /* or credit-note | debit-note | purchase-order | sales-order | quote */ "format": "json", "from": "vat:BE0123456789", "to": "0208:0123456789", "document": { "number": "INV-2026-0042", "issueDate": "2026-04-30", "dueDate": "2026-05-30", "currency": "EUR", "buyerReference": "PO-9988", /* aka Service Exécutant for FR PPF */ "orderReference": "QUO-1234", /* link to a quote / PO */ "seller": { "name": "ACME BVBA", "vatNumber": "BE0123456789" }, "buyer": { "name": "Globex SRL", "vatNumber": "IT12345678901" }, "payment": { "means": "credit_transfer", "iban": "BE68539007547034", "bic": "GKCCBEBB", "reference": "INV-2026-0042" }, "lines": [ { "description": "Consulting", "quantity": 10, "unit": "HUR", "unitPrice": 150.00, "vatRate": 21 }, { "description": "Travel", "quantity": 1, "unit": "C62", "unitPrice": 320.00, "vatRate": 21 } ], "allowances": [{ "amount": 50, "reason": "Loyalty discount" }], "totals": { "netAmount": 1770.00, "vatAmount": 371.70, "grossAmount": 2141.70 } } }' [/code] #### Shape B — pre-rendered UBL / CII XML Your ERP already emits Peppol BIS 3.0 / EN 16931 XML. Send the bytes inline; Flowie validates against the BIS schematron before delivery. [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Idempotency-Key: invoice-row-12345" \ -d '{ "type": "invoice", "format": "ubl-xml", "from": "vat:BE0123456789", "to": "0208:0123456789", "xml": "\n..." }' [/code] CII (Cross-Industry Invoice) XML is also accepted — Flowie detects the namespace automatically. Validation errors come back as `422` with a `schematronViolations` list. #### Shape C — file with `format=auto` (recommended) The forgiving option. Encode any file as base64; Flowie sniffs the first 64 bytes for magic bytes and routes accordingly. UBL/CII XML auto-promotes to the validated pipeline; PDFs and images persist as-is. **This is the right choice for ERP webhooks where you don't fully control what the source emits.** [code] # PDF (typical AP/AR invoice scan or a Factur-X PDF/A-3) curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Idempotency-Key: D365-{BusinessEventId}" \ -d '{ "type": "invoice", "format": "auto", "from": "vat:BE0123456789", "to": "0208:0123456789", "file": { "content": "JVBERi0xLjQKJe...", // base64 PDF "contentType": "application/pdf", "filename": "INV-2026-0042.pdf" } }' # Response (201): # { # "id": "doc_abc123", # "status": "stored", # "type": "invoice", # "deliveryStatus": "stored", # "fileId": "file_xyz", # "storedFormat": "pdf", # ... # } [/code] Same shape works for every supported file format — the sniffer outputs `pdf`, `png`, `jpeg`, `gif`, `zip`, `ubl-xml`, `xml`, `json`, or `binary`. When sniff returns `ubl-xml` the request transparently re-enters the UBL pipeline (validated, Peppol-routed) and the response is `deliveryStatus="pending"` instead. #### Shape D — file with `format=raw` (archive only) Skip the sniffer entirely — store the bytes verbatim. Useful for audit-trail copies, legacy formats Flowie shouldn't try to interpret, or when you simply want to _park_ a document on the file API and retrieve it later via `GET /v1/documents/{id}/pdf` or `/xml`. [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -d '{ "type": "invoice", "format": "raw", "from": "vat:BE0123456789", "to": "0208:0123456789", "file": { "content": "AQIDBAUG...", // base64 of anything "contentType": "application/x-acme-format", "filename": "legacy-export.acme" } }' [/code] #### Shape E — URL query params + raw body (recommended for ERP webhooks) The most permissive option for ERP integrations that emit native event JSON and want a fixed webhook URL with zero body wrapping. The constants (`type`, `from`, optional `contentType`, `filename`) travel as URL query parameters; the request body is the native ERP payload, byte-for-byte. The server reads `request.body()`, wraps internally, and routes through the same pipeline as Shapes A–D. **This is what you want when D365 / SAP / NetSuite Business Events should POST their payload verbatim without a Power Automate / iPaaS transformation step.** [code] curl -X POST "https://back.p2p-flowie.com/exchange/v1/documents/send?type=event&from=vat:FR53309136540" \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Idempotency-Key: D365-SalesOrderConfirmed-{eventId}" \ -H "Content-Type: application/json" \ -d '{ "BusinessEventId": "SalesOrderConfirmed", "SalesOrderId": "SO-2026-0042", "CustomerAccount": "RETAIL-PV-75008", "TotalAmount": 6480.00, "Currency": "EUR" }' [/code] Supported query parameters: * `type` (required to trigger raw-body mode) — same enum as the JSON body field (`invoice`, `credit-note`, …, `event`). For non-Peppol audit/observability events, use `type=event`. * `from` (required when type ≠ event) — sender identifier (`vat:…`, `0009:…`, `peppol:…`, or `comp_…`). For `type=event`, defaults to `org:{actingOrgId}` if omitted. * `contentType` (optional) — overrides the request `Content-Type` header for the stored file. Useful when the payload's true media type doesn't match the wire `Content-Type`. * `filename` (optional) — explicit stored filename. Defaults to `{Idempotency-Key}.bin` or a random UUID-based name. Detection: the server activates raw-body mode **iff** the URL contains `?type=…`. When no query params are present, the existing JSON body schema applies (Shapes A–D) — zero regression. Same `SendDocumentResponse` shape comes back regardless of which mode you used. D365-specific recipe: configure the Business Event HTTPS endpoint with the URL above, set `{{EventPayload}}` as the request body, leave OAuth2 auth at the header level. Nothing else to map — no Power Automate flow, no body template, no base64. #### Six document types, one endpoint Every shape above accepts any of the six document types. The `type` literal is the only thing that changes between them; lifecycle states differ accordingly ([Quote → SO → PO → Invoice flow](<#order-flow>)). type| Typical sender| Lifecycle entry| Cross-references ---|---|---|--- `invoice` | Seller| `issued`| `orderReference` → PO `credit-note` | Seller| `issued`| `originalInvoiceId` → invoice `debit-note` | Seller| `issued`| `originalInvoiceId` → invoice `purchase-order` | Buyer | `issued`| `orderReference` → quote `sales-order` | Seller| `issued`| `orderReference` → PO `quote` | Seller| `issued`| — #### Batch — many docs in one call The same endpoint exposes a batch sibling at [`POST /v1/documents/send/batch`](<../reference/index.html#send-batch>). Wrap up to 100 documents in a single request; each item gets its own `idempotencyKey`. The response carries per-item results so partial failures don't poison the whole batch. [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send/batch \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -d '{ "documents": [ { "idempotencyKey": "row-101", "type": "invoice", "format": "auto", "from": "vat:BE0123456789", "to": "0208:0123456789", "file": { "content": "JVBERi0xLjQK...", "contentType": "application/pdf", "filename": "INV-101.pdf" } }, { "idempotencyKey": "row-102", "type": "credit-note", "format": "json", "from": "vat:BE0123456789", "to": "0208:0123456789", "document": { /* ... */ } }, { "idempotencyKey": "row-103", "type": "purchase-order", "format": "ubl-xml", "from": "0208:0123456789", "to": "vat:FR12345678901", "xml": ") with the wrapper constants in the query string. The Business Event's native `EventPayload` goes verbatim as the request body — no Power Automate flow, no template, no base64. [code] POST https://back.p2p-flowie.com/exchange/v1/documents/send?type=event&from=vat:FR12345678901 Authorization: Bearer flw_live_… Idempotency-Key: D365-{BusinessEventId}-{EventId} Content-Type: application/json {{EventPayload}} // ← native D365 event JSON, no transformation [/code] ### SAP S/4HANA & Event Mesh SAP Event Mesh emits topics like `sap/s4/Invoice/Created/v1`. Subscribe an HTTPS webhook target (or run a small consumer) and translate to `/v1/documents/send`. The structured JSON path is usually the right choice — S/4HANA's invoice payload maps cleanly to Flowie's `document.lines`. [code] @app.post("/sap/webhook") async def sap_inbound(req: Request, x_event_type: str = Header()): event = await req.json() if x_event_type != "sap/s4/Invoice/Created/v1": return Response(204) payload = { "type": "invoice", "format": "json", "from": f"vat:{event['SellingCompany']['VATId']}", "to": f"vat:{event['BuyingCompany']['VATId']}", "document": map_sap_to_flowie(event), } httpx.post( "https://back.p2p-flowie.com/exchange/v1/documents/send", json=payload, headers={ "Authorization": f"Bearer {FLOWIE_API_KEY}", "Idempotency-Key": f"SAP-{event['MessageId']}", }, ) return Response(204) [/code] ### NetSuite, Sage Intacct, custom * **NetSuite** — User Event Script triggers on Record Type = Invoice. POST to Flowie from inside the SuiteScript using N/https. Use the NetSuite internal id as the idempotency key. * **Sage Intacct** — Smart Events on Record Type = Invoice. Same shape. * **QuickBooks Online** — webhook subscription on entity = Invoice. Pull the invoice via QBO API, then POST to Flowie. * **Custom / homegrown ERP** — fire any HTTPS POST that ends up at `/v1/documents/send`. As long as the bearer is valid and the body parses, Flowie ingests it. ### Idempotency Always set the `Idempotency-Key` header to a value derived from the source system — typically `{system}-{eventId}` (e.g. `D365-{BusinessEventId}`, `SAP-{MessageId}`, `QBO-{webhookEventId}`). Flowie caches the response for 24 hours, so a webhook retry with the same key returns the cached doc without duplicating it. [Reference → Idempotency](<../reference/index.html#idempotency>). ### Retries & failures Most ERPs retry on 5xx and stop on 4xx. Flowie returns: * **201** — accepted; you have a doc id. Always idempotent on retry with the same `Idempotency-Key`. * **400 / 422** — payload-level rejection (bad enum, missing required field, invalid base64, invalid date). Fix the mapping; retrying won't help. * **413** — file exceeds 5 MiB. Strip the attachment or split. * **429** — rate-limited. Honour `Retry-After`. * **5xx** — Flowie or downstream is degraded; safe to retry with backoff. Every captured failure has a `requestId` in the body and is queryable at [`GET /v1/requests/{requestId}`](<../reference/index.html#request-inspector>) for the next 7 days — paste the id into a Slack thread and your teammate sees the same redacted envelope. ### Test in sandbox Bootstrap a sandbox key (`POST /v1/sandbox/bootstrap`) and point your ERP's webhook target at `https://back.flowie.ink/exchange` with the test bearer. Sandbox accepts every payload shape the production endpoint does and synthesises plausible doc ids without touching the live Peppol network — see [sandbox shortcuts](<../sandbox/index.html#sandbox-shortcuts>) for the full list. Runnable demos Three copy-pasteable end-to-end examples live in [`examples/`]() in the API repo. All bootstrap a sandbox key on the fly (or fall back to a long-lived sandbox key if the per-IP rate-limit kicks in), so they run zero-config: File| Scenarios| What it covers ---|---|--- [`erp_inbound.py`]() | 4 | Generic ERP inbound — one scenario per payload shape, mapped to D365 / SAP / NetSuite / QuickBooks. [`erp_inbound_pmu.py`]() | 5 | PMU-specific — Hippodrome de Vincennes, Atos, Publicis, retail-point PO, audit event. Pinned to the PMU production org id. [`d365_event_inbound.py`]() | 6 | D365 events that aren't invoices — SalesOrderConfirmed, PurchaseOrderApprovalDone, VendorPaymentJournalPosted, WorkflowCompletedV3, BetVolumeReported, BettingAgentRegistered. Uses `format=raw` to archive the JSON event payload. Run any of them with `python examples/{file}.py`; output shows the resulting `doc_sbx_…` ids and which shape was sniffed. Set `FLOWIE_BASE` \+ `FLOWIE_API_KEY` to point at production. **For PMU specifically** : the step-by-step D365 admin guide at [`examples/d365-pmu-setup.md`]() walks through which Business Events to activate, how to wire the HTTPS endpoint with OAuth, the Power Automate flows per event type, and the sandbox→prod cutover. ## Tracking lifecycle, end to end Status transitions are enforced — you can't skip from `issued` to `paid`. The happy path: [code] issued → under_review → approved → partially_paid? → paid [/code] Side branches: [code] any-non-terminal → rejected (with reasonCode) any-non-terminal → disputed (with reasonCode) [/code] Keep your side in sync by acting on `lifecycle.updated`: [code] @app.post("/hooks/peppol") async def hook(req: Request): raw = await verify(req) event = json.loads(raw) if event["type"] == "lifecycle.updated": d = event["data"] db.execute( "UPDATE invoices SET status=%s, updated_at=%s WHERE flowie_id=%s", (d["currentStatus"], d["at"], d["documentId"]), ) return Response(status_code=204) [/code] ## Order integrations — Quote → SO → PO → Invoice Flowie Exchange covers the full order-to-cash and source-to-pay chain. The same `POST /v1/documents/send` endpoint and lifecycle machinery handles every document type — only `type` and a couple of cross-references change. Six types are first-class: type| Direction| Lifecycle| Peppol BIS profile ---|---|---|--- `quote`| Seller → Buyer| `issued → accepted | rejected`| — `purchase-order`| Buyer → Seller| `issued → confirmed → fulfilled`| `urn:fdc:peppol.eu:poacc:trns:order:3` `sales-order`| Seller → Buyer| `issued → confirmed → fulfilled`| `urn:fdc:peppol.eu:poacc:trns:order_response:3` `invoice`| Seller → Buyer| `issued → under_review → approved → paid`| `urn:cen.eu:en16931:2017` (BIS 3.0) `credit-note`| Seller → Buyer| same as invoice| BIS 3.0 Credit Note `debit-note`| Seller → Buyer| same as invoice| BIS 3.0 Debit Note ### The chain Each document references the previous one via `orderReference` (links a document to a PO/SO) or `quoteReference` (links a PO to its originating quote). Flowie carries those references through the whole chain so you can render an invoice and trace it back to the original quote in one query. Typical S2P (source-to-pay) for a buyer: [code] quote (received) ↓ accepted → purchase-order (sent, orderReference="QUO-1234") ↓ confirmed → sales-order (received, orderReference="PO-5678") ↓ fulfilled → invoice (received, orderReference="PO-5678") ↓ approved → paid [/code] Typical O2C (order-to-cash) for a seller: [code] quote (sent) ↓ accepted → purchase-order (received, quoteReference="QUO-1234") ↓ confirmed → sales-order (sent, orderReference="PO-5678") ↓ fulfilled → invoice (sent, orderReference="PO-5678") [/code] ### Send a quote, then a PO [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "quote", "from": "0009:FR12345678901", "to": "0208:0123456789", "document": { "number": "QUO-2026-0042", "issueDate": "2026-04-30", "currency": "EUR", "lines": [{ "description": "Consulting", "quantity": 10, "unitPrice": 150, "vatRate": 20 }] } }' [/code] Once the buyer accepts and emits the PO, send it referencing the quote: [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "purchase-order", "from": "0208:0123456789", "to": "0009:FR12345678901", "document": { "number": "PO-2026-9988", "issueDate": "2026-04-30", "currency": "EUR", "orderReference": "QUO-2026-0042", "lines": [{ "description": "Consulting", "quantity": 10, "unitPrice": 150, "vatRate": 20 }] } }' [/code] ### Three-way matching (PO ↔ SO ↔ Invoice) When an invoice arrives that references a known PO, Flowie auto-matches lines by `itemCode` \+ `quantity` \+ `unitPrice` within a tolerance (configurable per organization). The result is exposed via the underlying tx-docs service: [code] curl https://back.p2p-flowie.com/exchange/v1/documents/{invoiceId}/structured \ -H "Authorization: Bearer $FLOWIE_API_KEY" [/code] The response carries a `matching` object with per-line `matchedQuantity` / `variance`. Variance over the tolerance flips the invoice lifecycle to `disputed` with reasonCode `QUA` (quantity) or `PRI` (price). Approve or override via [`POST /v1/documents/{id}/lifecycle`](<../reference/index.html#update-lifecycle>). ### Webhook events Every order document fires the same envelope as invoices, qualified by `data.type`: [code] document.received { data: { type: "purchase-order", ... } } document.delivered { data: { type: "sales-order", ... } } lifecycle.updated.confirmed { data: { documentType: "PURCHASE_ORDER", currentStatus: "confirmed" } } lifecycle.updated.fulfilled { data: { documentType: "SALES_ORDER", currentStatus: "fulfilled" } } [/code] If you only care about orders (not invoices), filter at subscription time: [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/webhooks \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -d '{ "url": "https://yourapp.example.com/hooks/orders", "events": ["document.received", "lifecycle.updated.confirmed", "lifecycle.updated.fulfilled"], "filter": { "documentType": ["PURCHASE_ORDER", "SALES_ORDER", "QUOTE"] } }' [/code] ### Test in sandbox The sandbox simulators (`0208:SIM_HAPPY`, `SIM_DISPUTE`, `SIM_PARTIAL`) drive the full chain — sending a PO to `SIM_HAPPY` auto-emits the matching `sales-order` from the simulated counterparty 5–10 seconds later, then the invoice 30 seconds after. Use [`POST /v1/sandbox/clock/advance`](<../sandbox/index.html#test-clock>) to skip the wait. See [sandbox shortcuts](<../sandbox/index.html#sandbox-shortcuts>) for the full list of synthesized behaviours. ## Building a white-label platform If you're an ERP, an accounting SaaS, or a public-sector aggregator, you'll run Flowie under your own brand. The model is "Stripe Connect for Peppol": * You hold one **platform key** (`flw_plat_live_…` or `flw_wl_live_…`). * For each tenant customer, you [onboard](<../reference/index.html#platform-onboard>) a managed company. * You can either keep acting on their behalf (`X-Flowie-Company`) or issue a tenant-scoped key they use directly. ### Onboarding in one shot [code] curl -X POST …/v1/platform/companies \ -H "Authorization: Bearer flw_plat_live_xyz" \ -d '{ "vatNumber":"FR86797978996", "receiveDocuments":true, "webhook":{"url":"https://erp.acme.fr/hooks/flowie","events":["*"]}, "apiKey":{"name":"tenant-acme","scopes":["send","documents.read","lifecycle"]} }' [/code] The response gives you the tenant's company object, a freshly minted API key (once-shown), and the configured webhook. Save the key in your tenant's secret store. ### Scoping requests to a tenant Two ways. Pick based on your threat model: Pattern| When| Pros / Cons ---|---|--- **Platform key +`X-Flowie-Company`** | You hold one key in your own vault, act on each tenant. | Fewer secrets to manage · but one key compromise = all tenants. **Per-tenant key** | Tenants directly hit the API from their stack. | Blast radius limited to one tenant · but you must manage rotation. ### Branding & custom domain [`PATCH /v1/platform/settings`](<../reference/index.html#platform-settings>) lets you set a logo, primary color, and a `customDomain` (`peppol.yourbrand.com`). TLS is provisioned automatically. ## Compliance — 47 countries across Europe, MENA, and Asia-Pacific Flowie covers **47 jurisdictions** on four continents — every EU member plus Norway, Iceland, Liechtenstein, the UK, and Switzerland in Europe; Saudi Arabia, the UAE, Israel, Egypt, and Türkiye in the Middle East; India, Singapore, Malaysia, Thailand, and Vietnam in South / SE Asia; Japan, South Korea, and China in East Asia; Australia and New Zealand in the Pacific. Each country has its own dedicated page with mandate timeline, format profile, required fields, error codes, primary government sources, and sandbox shortcuts: [**📋 Compliance overview — coverage map across all 47 countries (Europe, MENA, APAC) →**](<../compliance/index.html>) Coverage model: Flowie operates a registered Peppol Access Point directly where we hold national accreditation, and integrates via a vetted local partner registered with the in-country regulator (KSeF, SDI _intermediario_ , ZATCA service-provider, ASP, etc.) for jurisdictions that require an in-country provider. Either way, you call the same `POST /v1/documents/send`. ### Quick highlights — the regimes you're most likely to encounter #### 🇫🇷 France — PPF + PA / PDP Mandatory for domestic B2B from **September 2026** (receive) and **September 2027** (send). Flowie is a registered **Plateforme Agréée (PA)** — number `0064`. _The DGFiP renamed PDP → PA in 2025; both labels refer to the same accreditation._ Lifecycle transitions (`approved`, `rejected`, `paid`) are auto-reported to PPF within 2 minutes. Public-sector recipients require a `buyerReference` (Service Exécutant) — without it, PPF rejects with code `00058`. [Full deep-dive →](<../compliance/fr/index.html>) #### 🇮🇹 Italy — SDI (Sistema di Interscambio) Mandatory since 2019 for B2B, B2C, and B2G. Flowie routes through its own SDI adapter; you never talk to SDI directly. A SDI rejection surfaces as a `document.failed` webhook with the native SDI error code. [Full deep-dive →](<../compliance/it/index.html>) #### 🇧🇪 Belgium — Pure Peppol since 2026-01-01 Belgium decommissioned HERMES on 2025-12-31; the B2B mandate (Loi du 6 février 2024) is delivered exclusively over **Peppol BIS Billing 3.0** with the BE-CIUS profile — exactly the network Flowie already routes on. [Full deep-dive →](<../compliance/be.html>) #### 🇩🇪 Germany — Wachstumschancengesetz (B2B phasing 2025–2028) Receive obligation universal since **1 January 2025** ; send obligation phases by company size — large from 2027, all from 2028. XRechnung (XML, federal-favoured) and ZUGFeRD/Factur-X (PDF/A-3 hybrid, B2B-favoured). [Full deep-dive →](<../compliance/de.html>) #### 🇪🇸 Spain — Veri*Factu + Crea y Crece + FACe Veri*Factu corporate live since July 2025; Crea y Crece B2B mandate phasing 2026–2028. FACe handles B2G. Three obligations layered, all handled from the same JSON. [Full deep-dive →](<../compliance/es.html>) #### 🇵🇱 Poland — KSeF mandatory clearance Large taxpayers from **1 February 2026** ; all VAT taxpayers from **1 April 2026**. Clearance regime — invoices not legally valid until KSeF returns a number. FA(2) format mandatory. [Full deep-dive →](<../compliance/pl.html>) #### 🇷🇴 Romania — RO e-Factura Universal B2B clearance since July 2024 — the most aggressive timeline in the EU. ANAF returns a signed XML before legal delivery. [Full deep-dive →](<../compliance/ro.html>) #### 🇸🇦 Saudi Arabia — ZATCA Fatoora Real-time clearance through the Fatoora portal. Phase 1 (Generation) universal since December 2021; Phase 2 (Integration) ramps by wave through **30 June 2026** (Wave 24 captures every taxpayer with revenue > SAR 375,000). UBL 2.1 with KSA-specific extensions (TLV QR code, cryptographic stamp, hash chain). [Full deep-dive →](<../compliance/sa.html>) #### 🇦🇪 UAE — Peppol 5-corner with FTA First MENA country to adopt the Peppol 5-corner model — sender AP, receiver AP, plus a real-time copy to the FTA's Data Reporting Platform. Phase 1 (revenue > AED 50m + government) live **1 July 2026** ; full rollout by July 2027. PINT AE format. [Full deep-dive →](<../compliance/ae.html>) #### 🇮🇱 Israel — ITA allocation-number clearance SHAAM clearance returns an allocation number; without it, the buyer cannot deduct input VAT. Threshold tightens fast: NIS 10,000 from January 2026, **NIS 5,000 from June 2026** — effectively all VAT B2B. [Full deep-dive →](<../compliance/il.html>) #### 🇮🇳 India — GST IRP & IRN Every B2B invoice from a taxpayer above ₹5 cr turnover must be cleared by an IRP (Invoice Registration Portal); response carries an IRN + signed QR code. Taxpayers ≥ ₹10 cr have a **30-day reporting deadline** from issue. Multiple IRPs in operation; Flowie load-balances. [Full deep-dive →](<../compliance/in.html>) #### 🇸🇬 Singapore — Peppol InvoiceNow + GST 5-corner Newly incorporated GST registrants must comply from **1 April 2026** ; existing businesses absorbed in waves through April 2031. PINT-SG format. IMDA = Peppol Authority; IRAS receives the 5th-corner copy. [Full deep-dive →](<../compliance/sg.html>) #### 🇲🇾 Malaysia — LHDN MyInvois Real-time clearance via MyInvois — UUID + QR code returned for embedding. Final wave **1 January 2026** covers RM 1m–5m taxpayers; SMEs below RM 1m are exempt (cabinet raised the floor in December 2025). [Full deep-dive →](<../compliance/my.html>) #### 🇦🇺 Australia + 🇳🇿 New Zealand — Peppol PINT A-NZ Joint trans-Tasman CIUS. Australia's ATO is the Peppol Authority (federal NCEs Peppol-default by Dec 2026, no B2B mandate). New Zealand's MBIE makes large suppliers (revenue > NZ$33m) Peppol-mandatory from **1 January 2027**. Mandated NZ agencies pay 95% of Peppol invoices in 5 business days. [AU →](<../compliance/au.html>) [NZ →](<../compliance/nz.html>) #### 🇯🇵 Japan — JP PINT & Qualified Invoice Qualified Invoice System mandatory since October 2023 (T-prefixed registration numbers). Peppol JP PINT recommended but voluntary. The lever Japan uses is tax economics: input-tax credit on non-qualified invoices drops to 50% in Oct 2026, 0% in Oct 2029. [Full deep-dive →](<../compliance/jp.html>) #### 🇨🇳 China — Fully Digital e-fapiao + Golden Tax IV Fully digital e-fapiao universal since 2024–2025; new VAT Law supporting regulations effective **1 January 2026**. Every fapiao is issued _through_ the STA platform — there is no off-platform legal invoice. [Full deep-dive →](<../compliance/cn.html>) ### Real-time reporting regimes Greece ([myDATA](<../compliance/gr.html>)), Hungary ([NAV Online Számla](<../compliance/hu.html>)), Spain ([Veri*Factu](<../compliance/es.html>)), Korea ([NTS HomeTax](<../compliance/kr.html>)), and Türkiye ([e-Arşiv](<../compliance/tr.html>)) all require near-real-time invoice reporting. Flowie ships the reporting envelope on every send. The remaining 30+ countries — Austria, Bulgaria, Croatia, Cyprus, Czechia, Denmark, Estonia, Finland, Greece, Hungary, Iceland, Ireland, Latvia, Liechtenstein, Lithuania, Luxembourg, Malta, Netherlands, Norway, Portugal, Slovakia, Slovenia, Sweden, Switzerland, UK, Egypt, Vietnam, Thailand, Türkiye — are documented in full in the [coverage map](<../compliance/index.html>). ### Pure-Peppol countries The Netherlands, Sweden, Norway, Austria, Ireland, Cyprus, Malta, Luxembourg, Latvia, Belgium and others run no central hub — Peppol AP-to-AP delivery is the entire mandate. From a caller perspective, just `POST /v1/documents/send`; nothing extra to configure. See the [overview map](<../compliance/index.html>) for which countries fall into this bucket. Reporting is automatic — but your data must be clean If `paymentDate` is later than `issueDate` by > 90 days, SDI flags it as late-payment. If your `currency` differs from the original invoice, PPF rejects the report. Validate before calling `/lifecycle`. ## Sandbox testing Everything behaves identically to production — except no real Peppol delivery happens. Base URL: `https://back.flowie.ink/exchange`, keys start with `flw_test_`. ### Test VAT numbers VAT| Behavior ---|--- `BE0000000001`| Always enriches successfully. `BE0000000099`| Returns `VAT_INACTIVE`. `BE0000000404`| Returns `VAT_NOT_FOUND`. ### Test Peppol IDs Peppol ID| Behavior ---|--- `0208:TEST_OK`| Delivers successfully after ~1s. `0208:TEST_AP_FAIL`| Fires `document.failed` after ~2s (simulated AP rejection). `0208:TEST_TIMEOUT`| Simulates a transport timeout; retries then fails. ### Triggering webhook replays Any event delivered to a sandbox webhook has a **Resend** button in the dashboard. The replayed request is byte-identical to the original — perfect for testing signature verification. ## Going-live checklist ✓| Item| Why it matters ---|---|--- ☐| Switch base URL to `https://back.p2p-flowie.com/exchange`| You'd be surprised. ☐| Swap test key for live key| `flw_test_…` → `flw_live_…`. ☐| Register live webhooks with fresh secrets| Don't reuse sandbox secrets in production. ☐| Run a canary invoice to your own Peppol ID| End-to-end smoke test on real infrastructure. ☐| Set up monitoring on `document.failed` \+ `compliance.reported.failed`| You want to hear about delivery issues before your customer does. ☐| Implement `Retry-After` backoff| Graceful behavior under rate-limits. ☐| Persist `Idempotency-Key` per outgoing row| Safe retries across deploys. ☐| Store `requestId` in your application logs| First thing support asks for. ☐| Subscribe to [status.flowie.ink]()| Catch upstream (SMP, PPF, SDI) incidents. ☐| Document your error → UI message mapping| Surface user-facing errors cleanly. ☐| Plan for v1 deprecation (12-month horizon)| Watch the [changelog](<../changelog.html>). ## Migrating from v2 If you were on the legacy `/api/…` surface, here's the mapping for the 5 biggest changes in v3: v2| v3| Note ---|---|--- `POST /api/send`| `POST /v1/documents/send`| Body shape unchanged; add `type: "invoice"`. `GET /api/invoices`| `GET /v1/documents?type=invoice`| Unified list across document types. `POST /api/invoices/{id}/paid`| `POST /v1/documents/{id}/lifecycle` with `status:"paid"`| State machine replaces ad-hoc endpoints. `GET /api/peppol/search`| `GET /v1/directory/search`| Identical params. `POST /api/webhooks`| `POST /v1/webhooks`| Event names normalized; see [catalog](<../reference/webhooks.html#events>). v2 stays online until **2027-04-01**. After that, requests to `/api/…` return `410 Gone`. ======================================================================== # Send an invoice over Peppol # Source: https://docs.get-flowie.com/guides/send-invoice.html ======================================================================== --- title: "Send an invoice over Peppol" description: "Send an invoice over Peppol with one REST call: register the sender, verify the recipient, POST /v1/documents/send with an idempotency key, then watch the delivery webhooks." canonical: "https://docs.get-flowie.com/guides/send-invoice" source: "https://docs.get-flowie.com/guides/send-invoice.html" --- # Send an invoice over Peppol Guides # Send an invoice over Peppol One REST call delivers a compliant e-invoice to any Peppol participant. This is the happy path in four steps, from a cold start to a `document.delivered` webhook. ## 1 · Register the sender One-time per company. Creates the company, enriches it from the VAT number, and publishes it to the Peppol SMP so it can both send and receive. [code] curl -X POST …/v1/companies \ -H "Authorization: Bearer $FLOWIE_KEY" \ -d '{"vatNumber":"BE0123456789"}' [/code] If the company already exists on another platform, use [`POST /v1/companies/import`](<../reference/index.html#import-company>) instead, then [`POST /v1/companies/{id}/register`](<../reference/index.html#register-company>) to activate it on Peppol. ## 2 · Verify the recipient Always verify before sending. A recipient that is not registered for your document type will bounce, and the bounce arrives asynchronously — minutes after you thought the invoice was gone. [code] curl -X POST …/v1/directory/verify \ -H "Authorization: Bearer $FLOWIE_KEY" \ -d '{"peppolId":"0208:9876543210","documentType":"INVOICE"}' [/code] Check `canReceive` in the response before continuing. ## 3 · Send the invoice Describe the invoice as JSON and we render, sign and route the UBL for you. Pass a persistent `Idempotency-Key` — generate it before the first attempt, from your own database row id, so a crash between generation and the HTTP call is still recoverable. [code] curl -X POST …/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Idempotency-Key: inv-2026-001" \ -d '{ "type": "invoice", "from": "comp_abc123", "to": "0208:9876543210", "document": { "number": "INV-2026-001", "issueDate": "2026-04-25", "dueDate": "2026-05-25", "currency": "EUR", "lines": [{ "description": "Consulting, April 2026", "quantity": 10, "unit": "hours", "unitPrice": 150.00, "vatRate": 21 }] } }' [/code] Already have UBL XML or a Factur-X PDF? Send it as-is with the `xml` or `file` field instead of `document` — see [the endpoint reference](<../reference/index.html#send-document>) for the payload matrix. ## 4 · Watch the delivery The response returns immediately with `status: "sent"`; delivery is asynchronous. A [subscribed webhook]() receives `document.delivered` once the recipient's access point confirms, or `document.failed` with an error code if it does not. Pre-flight checks before switching a customer live Run the payload through [`POST /v1/documents/validate`](<../reference/index.html#validate-document>) in CI. It catches BIS rule violations (BR-*), unreachable recipients and currency mismatches without touching Peppol. ## Next * [Receive invoices]() — the other half of the exchange. * [ERP webhooks → send]() — wire D365, SAP or NetSuite as the inbound source. * [Go-live checklist]() — prove you are production-ready. * [Compliance](<../compliance/index.html>) — what changes per country. ======================================================================== # Receive invoices # Source: https://docs.get-flowie.com/guides/receive-invoices.html ======================================================================== --- title: "Receive invoices" description: "Receive invoices from Peppol: subscribe a webhook, verify the HMAC signature, fetch the structured view and advance the lifecycle — plus the polling fallback and event replay." canonical: "https://docs.get-flowie.com/guides/receive-invoices" source: "https://docs.get-flowie.com/guides/receive-invoices.html" --- # Receive invoices Guides # Receive invoices Inbound documents arrive as `document.received` webhooks. Webhooks are the recommended path; polling is the fallback when you cannot expose an HTTPS endpoint. ## 1 · Subscribe once [code] curl -X POST …/v1/webhooks \ -H "Authorization: Bearer $FLOWIE_KEY" \ -d '{ "url":"https://example.com/hooks/peppol", "events":["document.received","document.updated","lifecycle.updated"] }' [/code] The full event catalogue is in the [webhook reference](<../reference/webhooks.html#events>). ## 2 · Verify the HMAC on delivery Every delivery carries `X-Flowie-Signature: t=,v1=` over `t + "." + raw_body`. Compare in constant time, against the _raw_ body — a re-serialised JSON body will not match — and reject anything older than five minutes. See [signing & verification](<../reference/webhooks.html#signing>). ## 3 · Fetch the structured view [code] curl …/v1/documents/{id}/structured \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] A flat, agent-friendly projection of the document — push it into your ERP, AP automation or warehouse. You can also pull the canonical [UBL XML](<../reference/index.html#document-xml>) or a [PDF rendering](<../reference/index.html#document-pdf>). ## 4 · Move the lifecycle along Call [`POST /v1/documents/{id}/lifecycle`](<../reference/index.html#update-lifecycle>) as the invoice is reviewed, approved, disputed and paid. We report the transitions to the local regime (France PPF, Italy SDI) for you. On the French side, mind the difference between [refusal (210) and technical rejection (213)](<../compliance/fr/refusal-rejection.html>) — one is terminal. ## Polling instead of webhooks No public endpoint? Poll [`GET /v1/documents`](<../reference/index.html#list-documents>) with `direction=incoming`. It is cursor-paginated: keep passing the returned `cursor` until `hasMore` is `false`, and never hard-code an offset. [code] curl "…/v1/documents?direction=incoming&limit=100" \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] ## If you miss an event Failed deliveries retry eight times over roughly 20 hours, then the webhook auto-pauses. You can replay any single event with [`POST /v1/events/{id}/replay`](<../reference/index.html#replay-event>), or acknowledge a backlog with [`POST /v1/events/ack`](<../reference/index.html#ack-batch>). The [retry schedule](<../reference/webhooks.html#retries>) is in the webhook reference. ## Next * [Send an invoice]() — the outbound half. * [Webhook cookbook](<../reference/webhooks.html>) — events, payloads, signing, retries. * [Webhook fixtures](<../fixtures/>) — signed sample payloads to develop against. ======================================================================== # Portability — change of Plateforme Agreee # Source: https://docs.get-flowie.com/guides/portability.html ======================================================================== --- title: "Portability (change of PA)" description: "Move a taxpayer between Plateformes Agreees: import the company from its SIRET, build and parse the normalised inter-PA message (subject + 18-field CSV), and track the request through its states and legal clocks." canonical: "https://docs.get-flowie.com/guides/portability" source: "https://docs.get-flowie.com/guides/portability.html" --- # Portability (change of PA) Guides # Portability (change of PA) A taxpayer may change _Plateforme Agréée_ at any time, and keeps its SIREN/SIRET-based addressing when it does — the PPF annuaire guarantees identifier portability, so nothing downstream has to be re-addressed. What has to happen instead is a hand-over between the two platforms: the **gaining PA** asks, the **losing PA** answers, and the annuaire is flipped on an agreed effective date. This guide covers both directions, because Flowie plays both roles: incoming (a taxpayer picked Flowie, we issue the request) and outgoing (another PA is porting a taxpayer away, we must answer inside the legal delay). Four endpoints cover the exchange — two to onboard the company, two to speak the inter-PA wire format. Outside France? There is no _Plateforme Agréée_ to leave, and no regulated hand-over. What a switch takes in every other European country — the registry edit, the authorisation to re-grant, who keeps the archive — is covered in [Changing platform in Europe](), which also carries a migration request form that needs no API key. ## The clocks you must beat Portability is a deadline problem before it is an integration problem. Three rules drive everything below: * **Acknowledge within 24 hours** of receiving a request. * **Decide within 5 business days** (_jours ouvrés_ , so weekends and _jours fériés_ do not count). * **Silence is agreement** (_le silence vaut accord_): past the delay, the port proceeds without the losing platform's approval. A missed acknowledgement is a compliance failure on its own, independently of whether you would have accepted the port. Timestamp every message you send and receive — the `request_datetime` column of the CSV below exists for exactly that proof. ## 1 · Import the taxpayer from its SIRET The portal flow gives you one input: the taxpayer's **SIRET**. Everything else is derived. Flowie takes the SIREN from the first nine digits, implies country `FR`, builds the Peppol id `0009:`, and resolves the legal name — and the current PA — from the PPF annuaire. [code] curl -X POST …/v1/companies/import \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{ "siret": "92137626500017", "mode": "portability" }' [/code] Three outcomes, in priority order: * **Import** — you passed a `sovosCompanyId`: the existing company is pulled and its tax id, name and capabilities are taken as authoritative. * **Provision** — no company id but an organization is known (request field or the configured default): a managed connection is provisioned from the SIRET. * **Local-only** — neither: the company is registered as `pending_verification` and a `company.import.pending` event is written so ops can link the backend later. Onboarding never hard-fails for want of a backend id, which matters when a port request arrives before the commercial paperwork is done. The call is **idempotent on the SIRET** : re-running it re-syncs the existing registration rather than creating a second one, so a retry after a timeout is safe. You get back the organization with its `id` and `peppolId`. Supply `companyName` or `countryCode` only to override what the annuaire resolves — omit them and the annuaire wins. ## 2 · Import in bulk A platform migration moves hundreds of companies at once. `POST /v1/companies/import/batch` takes a list of the very same objects and runs them concurrently, five at a time. [code] curl -X POST …/v1/companies/import/batch \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{ "items": [ {"siret": "92137626500017"}, {"siret": "55208131766522"} ] }' [/code] The response is one result row per input item, in input order: [code] { "results": [ {"index": 0, "status": "imported", "companyId": "org_…", "peppolId": "0009:921376265"}, {"index": 1, "status": "failed", "error": "siret must be 14 digits"} ] } [/code] **A failed item does not sink the batch.** The call still returns `200` with a partial result set, so check every row rather than the status code — `index` points back at the position in your request. Because each item goes through the same idempotent path, re-sending the whole batch to retry the failures will not duplicate the ones that already succeeded. ## 3 · Send the inter-PA message The channel the AIFE imposes between platforms is **email** , with a normalised subject, a codified status and an 18-field CSV. `POST /v1/portability/messages` assembles that message, **emails it to the counterparty platform** and records it — so you never hand-format a subject line, never look up where to send it, and never lose the proof that you sent it. [code] curl -X POST …/v1/portability/messages \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{ "messageType": "REQUEST", "state": "received", "requestRef": "POR-2026-000123", "directionRole": "GAINING_PA", "taxpayerSiren": "921376265", "taxpayerSiret": "92137626500017", "losingPaName": "ESKER", "effectiveDate": "2026-09-01", "mandateRef": "MDT-2026-8891" }' [/code] You get back the log id, the subject, the CSV as header plus row and its hash, who it was addressed to and how that address was found, and whether it actually left: [code] { "id": "pmsg_9f2c7a1d4b8e4c0f9a6d3e2b1c7f5a80", "subject": "[PORTABILITE][REQUEST][REQ][SIREN:921376265][REF:POR-2026-000123]", "messageType": "REQUEST", "statusCode": "REQ", "state": "received", "filename": "POR-2026-000123-REQ.csv", "csvHeader": "request_ref;message_type;…", "csvRow": "POR-2026-000123;REQUEST;REQ;…", "csvSha256": "6b1f…", "to": "contact-pdp@esker.com", "recipientSource": "registry:ESKER", "dispatched": true, "reason": "sent", "smtpMessageId": "<176…@flowie.fr>", "createdAt": "2026-09-01T08:14:02.114000+00:00" } [/code] You do not need to know the other platform’s email address Name the counterparty — `losingPaName` when you are the gaining platform, `gainingPaName` when you are the losing one — and it is resolved against the [registry of registered Plateformes Agréées](<#platforms>): the dedicated portability inbox the platform published if it has one, its DGFiP _courriel de contact_ otherwise. `recipientSource` tells you which happened (`registry:`, `explicit` when you passed `to` yourself, or `unresolved` when the name matched nothing). Passing `to` always wins. The message is recorded whether or not it is emailed Sending is gated by a kill-switch and by the SMTP configuration, and a sandbox key never reaches a real platform. When the mail does not go out, `dispatched` is `false` and `reason` says why — `dispatch_disabled`, `not_configured` (no relay or no sender), `no_recipient` (nothing to address it to), `sandbox`, or the SMTP error itself. The row in the message log is written either way, so a dry environment produces the same audit trail minus the email, and a relay outage leaves you with the exact message to re-send rather than a gap. A non-production deployment can also set a recipient override: the message still resolves the real counterparty and records it, but it is delivered to the override address, so testing never emails a real platform. How it leaves The message goes out through Flowie’s own mail path, so it carries the platform’s sender identity and delivery handling rather than a relay only this service knows about; the counterparty replies to the address in `PORTABILITY_CHANNEL_FROM`, which the body states. That path attaches files by reference, so the CSV travels **inline in the body** — byte-identical to the `csvRow` you get back and to what was hashed. If you need the CSV as a real `.csv` file, pin the direct-relay transport (`PORTABILITY_TRANSPORT=smtp`) and it is attached instead. `transport` on the response and in the log says which one carried it. The four `messageType` values map to the steps of the exchange — `REQUEST`, `ACK`, `DECISION`, `COMPLETION` — while `state` is your internal state and is translated to the wire status code for you (see [Request states](<#states>)). `directionRole` says who is speaking: `GAINING_PA` or `LOSING_PA`. The subject grammar is strict and positional: [code] [PORTABILITE][][][SIREN:<9 digits>][REF:] [/code] A SIREN that is not exactly nine digits, an unknown message type, or a `requestRef` containing `]` is rejected with `400` before anything is built. ## 4 · Parse an inbound message The other half: turn a message you received back into structured fields. Pass the subject, and the CSV row when you have it. [code] curl -X POST …/v1/portability/messages/parse \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{ "subject": "[PORTABILITE][DECISION][ACC][SIREN:921376265][REF:POR-2026-000123]", "csvRow": "POR-2026-000123;DECISION;ACC;LOSING_PA;921376265;…" }' [/code] The response gives you the message type, the wire status code, the internal `state` it maps back to, the SIREN, the request reference, and — when a row was supplied — the 18 parsed columns as `fields`. **A subject that does not match the grammar returns`400`: dead-letter it, do not open a request from it.** That is the whole point of a normalised subject — anything that fails to parse is not a portability message, and guessing at its intent is how you end up porting the wrong taxpayer. A CSV row with anything other than 18 columns is rejected the same way. ## 5 · Switch the routing at the date d'effet Agreeing a port changes nothing by itself. What decides where an invoice goes is the taxpayer’s e-invoicing address on the compliance backend — that is what the directory ends up routing on — and `POST /v1/portability/routing` is what moves it: [code] curl -X POST …/v1/portability/routing \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -d '{ "organizationId": "019c76b2-9c94-7000-8cb6-ef104afb6093", "siren": "921376265", "siret": "92137626500018", "effectiveDate": "2026-10-01", "role": "GAINING_PA" }' [/code] [code] { "organizationId": "019c76b2-9c94-7000-8cb6-ef104afb6093", "connectionId": "conn_7Yb3…", "role": "GAINING_PA", "siren": "921376265", "effectiveDate": "2026-10-01", "serviceUntil": null, "created": true, "address": { "id": "addr_2Kd9…", "siren": "921376265", "active": true } } [/code] The date is the point. As the **gaining** platform you declare the address with `validFrom` = the date d'effet, so a port agreed in August for 1 October does not start pulling invoices in August. As the **losing** platform (`role: "LOSING_PA"`) nothing is deleted: emission stops on the date d'effet while reception stays open until `effectiveDate + 12 months` — the minimal service LFI 2026 requires of the outgoing platform, so flows already in flight still resolve. Override the window with `minimalServiceMonths` when a contract promises longer. It is idempotent on the SIREN: an address already declared for it is updated, never duplicated. If the organization has more than one connection you must name it with `connectionId` — choosing one for you is how a port lands on the wrong company. And when you are onboarding the taxpayer in the same move, `POST /v1/companies/import` now takes the same `effectiveDate` and carries it onto the address it creates. ## The PA address book A port request is only as sendable as your knowledge of where the other platform reads its mail. `GET /v1/portability/platforms` is that address book — every operator the DGFiP has registered, with the address a portability message should actually go to: [code] curl "…/v1/portability/platforms?q=esker" \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] [code] { "data": [ { "name": "ESKER", "website": "https://www.esker.fr/", "email": "info@esker.fr", "portabilityEmail": "contact-pdp@esker.com", "contactEmail": "contact-pdp@esker.com", "registeredOn": "2025-10-14", "status": "registered" } ], "total": 1, "source": "https://www.impots.gouv.fr/je-consulte-la-liste-des-plateformes-agreees", "snapshotDate": "2026-08-20" } [/code] It merges the two official DGFiP lists — operators meeting every condition (`status: "registered"`) and those still awaiting the interoperability tests (`"pending_interop"`) — with the dedicated portability inboxes platforms circulated among themselves. `contactEmail` is the one that matters: the dedicated address when there is one, the generic DGFiP contact otherwise. Filter with `q` (name, email or website) and `status`. The list moves every week as platforms are registered, so the payload carries its own `snapshotDate` and the `source` it was built from. If you need the authoritative list at this instant, that source is it. ## Request states A portability request moves through eight states. Each maps to a short wire code carried in the subject and in the CSV, so a counterparty can machine-route on it: State| Wire code| What it means ---|---|--- `received`| `REQ`| Request created or inbound message parsed. Starts the 24 h and 5-day clocks. `acknowledged`| `ACK`| Receipt confirmed inside 24 h. The first SLA is met. `accepted`| `ACC`| Decided in favour of the port, inside the 5-day window. `rejected`| `REJ`| Refused. A reason is expected — fill `reasonCode` and `reasonText`. `auto_accepted`| `TAC`| Tacit agreement: the deadline passed with no decision, so _silence vaut accord_. `executing`| `MIG`| Effective date reached; the annuaire switch is in progress. `completed`| `CMP`| The annuaire confirms the new platform is active. `failed`| `ERR`| Transport or annuaire error. Send the internal state in `state`; you never write the wire code yourself. On the way back in, `parse` resolves the code to the state for you. An unknown state is a `400`, not a silent pass-through. The two decision states differ in who fires them. `accepted` and `rejected` are a deliberate act inside the window; `auto_accepted` is what happens _to_ the silent party when the window closes. If you are the losing platform, `TAC` arriving on your request is the signal that you missed the deadline. ## Tracking a request Company onboarding writes to the event log, which is where you follow a migration today: [code] curl "…/v1/events?type=company.imported&limit=100" \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] Two event types are written by the import path: `company.imported` when the registration is active, and `company.import.pending` when the company landed local-only and still needs an ops link. Each carries the company id, the Peppol id, the SIRET, the SIREN and the country, which is enough to reconcile a bulk migration row by row. The listing is cursor-paginated — keep passing the returned `cursor` until `hasMore` is `false`. These two are event-log entries, not webhook events They are readable through [`GET /v1/events`](<../reference/index.html#list-events>) but they are not in the webhook catalogue, so subscribing a webhook to `company.imported` will not deliver anything. Poll the event log for now; the [webhook reference](<../reference/webhooks.html#events>) lists what does get pushed. Every message you send is logged, and the log is queryable: [code] curl "…/v1/portability/messages?requestRef=POR-2026-000123" \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] `GET /v1/portability/messages` lists them newest first, scoped to your organization, filterable by `requestRef`, `siren`, `state`, `messageType` and `dispatched` — so one reference replays a whole exchange, and `dispatched=false` finds the messages that never left and need re-sending. It is cursor-paginated like the rest of the API and returns a real `total`. `GET /v1/portability/messages/{id}` returns the proof bundle for one message: the exact CSV row that left with its `csvSha256`, the recipient and how it was resolved, the SMTP message id, and `annuaire` — what the PPF _annuaire_ answered for that taxpayer at the moment we sent. The annuaire itself is read-only to us: a port is not written into it, it is written into the routing address ([step 5](<#execute>)) and propagates from there. The snapshot is what lets you prove the before and check the after — compare it with what the annuaire says once propagation has happened, which is a call of its own: [code] curl "…/v1/portability/annuaire/921376265" -H "Authorization: Bearer $FLOWIE_KEY" [/code] `GET /v1/portability/annuaire/{siren}` answers with the line that decides where this taxpayer's invoices go: `currentPaMatricule` (the platform routing it — `9998` is the PPF default, meaning nobody has been declared and there may be nothing to port), `effectiveFrom`, an `effectiveTo` when a departure is already scheduled, and `isFlowie` once the switch has propagated to us. Read it before a port to know who you are porting away from, and after to know whether it landed. There is still no server-side SLA timer Nothing fires an acknowledgement for you and nothing flips a request to _silence vaut accord_ when the fifth business day passes. The message log gives you the timestamps to prove the delays; the clocks themselves are still yours to run. ## The 18-field CSV One header row and one data row, semicolon-delimited, in this exact order: #| Column| Notes ---|---|--- 1| `request_ref`| Your stable reference for the request. 2| `message_type`| REQUEST / ACK / DECISION / COMPLETION. 3| `status_code`| The wire code from the table above. 4| `direction_role`| GAINING_PA or LOSING_PA. 5| `taxpayer_siren`| Nine digits. 6| `taxpayer_siret`| Fourteen digits. 7| `taxpayer_name`| Legal name. 8| `gaining_pa_id`| Operator code or SIREN. 9| `gaining_pa_name`| 10| `losing_pa_id`| 11| `losing_pa_name`| 12| `effective_date`| _Date d'effet_ , ISO-8601. 13| `transferred_addresses`| Routing ids, pipe-joined. Send a list, get a list back. 14| `mandate_ref`| The _mandat de désignation_. 15| `mandate_signatory`| Legal representative. 16| `request_datetime`| ISO-8601 with an explicit timezone — the proof of delay. Defaults to now (UTC). 17| `reason_code`| Expected on a rejection. 18| `reason_text`| Only `transferred_addresses` repeats, and it uses `|` inside the cell so it never collides with the delimiter. Missing values are written as empty strings, never omitted — the column count is what the parser validates. ## What is still provisional The wire format is a working model, not a certified one The subject grammar, the status codes and the CSV column set are Flowie's reading of the process pending publication of the AIFE annex of 15/07, which is not yet publicly indexed. They are centralised in a single module precisely so that reconciliation is one well-tested edit rather than a scattered migration. Build against them — that is what they are for — but treat the exact strings as subject to change, keep your own `requestRef` as the key you join on, and re-read the [changelog](<../changelog.html>) before you go live. What will not change is the shape: a normalised subject, a codified status, eighteen columns, and clocks that start the moment a request lands. ## Next * [Import a company](<../reference/index.html#import-company>) — full parameter list and responses. * [Portability endpoints](<../reference/index.html#portability>) — send, follow and parse, in the API reference. * [France compliance](<../compliance/fr/index.html>) — the wider PPF and PA picture. * [Events](<../reference/index.html#list-events>) — the log you poll to follow a migration. ======================================================================== # Changing platform in Europe — country by country # Source: https://docs.get-flowie.com/guides/portability-europe.html ======================================================================== --- title: "Changing platform in Europe" description: "What changing e-invoicing platform takes in every European country: whether your routing address changes, what you must re-grant at the tax authority, who holds the archive, and how to request a migration without touching the API." canonical: "https://docs.get-flowie.com/guides/portability-europe" source: "https://docs.get-flowie.com/guides/portability-europe.html" --- # Changing platform in Europe Guides # Changing platform in Europe Whether you can leave your current e-invoicing provider, what it takes, and who has to do what — country by country. **[France has a regulated hand-over]()** ; everywhere else, switching is three unrelated jobs that happen to fall in the same week. You do not need the API to start Use the [migration request form](<#request>) below. It runs in your browser, needs no account and no API key, and hands us a complete request — identifiers, current platform, target date — by e-mail or clipboard. The endpoints on this page are what _we_ then run on your behalf. ## The five layers a switch touches “Changing provider” sounds like one operation. It is five, owned by five different parties, and they fail independently. Layer| What it is| Who changes it| Portable? ---|---|---|--- **Identifier**| VAT number, SIREN/SIRET, NIP, Peppol participant ID| Nobody — it is yours| Always. Identifiers never change when you switch. **Routing address**| Peppol SMP entry, the French annuaire, the Italian _codice destinatario_| Your platform or access point| Usually — but the _value_ can change (Italy) **Authorisation**| _delega_ , technical user, KSeF certificate, SPV authorised user| You, at the tax authority| Never transferred — always re-granted **Data & archives**| Original XML, lifecycle statuses, the legal archive| Your outgoing provider| The real fight. Regulated only in France **Contract**| Notice period, exit fees, export format| Both parties| Capped by the [EU Data Act](<#eu-right>) ## What it takes, per country The pattern that decides everything: **where the archive lives**. In centralised-clearance countries the state holds your invoices, so leaving is cheap. In decentralised countries your outgoing provider holds them, so leaving is expensive. Country| Does your address change?| What you must re-grant| Who holds the archive| Effort ---|---|---|---|--- 🇫🇷 **France** · PPF| No — SIREN/SIRET addressing is kept; the annuaire is re-pointed| A signed designation agreement (_accord formel_)| You / your platform — with a 1-year continuity duty| Regulated 🇮🇹 **Italy** · SDI| **Yes** — the _codice destinatario_ belongs to the intermediary| _Delega_ to the new intermediario; ideally register your _indirizzo telematico_| You (_conservazione_ , 10 years, signed packages)| Hard 🇵🇱 **Poland** · KSeF| No address exists — buyers pull from KSeF| A KSeF certificate for the new provider; revoke the old| The state, 10 years| Easy 🇷🇴 **Romania** · e-Factura| No — everything goes through the SPV| An authorised user holding a qualified certificate| ANAF holds the cleared invoices| Easy 🇭🇺 **Hungary** · NAV| No — reporting only, no routing| A technical user for the new software| You (reporting regime)| Easy 🇬🇷 **Greece** · myDATA| No — you declare a transmission channel| The channel declaration + provider credentials| You, with myDATA as the reported record| Medium 🇪🇸 **Spain** · Crea y Crece| No — private platforms must interoperate| Provider onboarding (rules land with the mandate)| You + the _copia fiel_ at AEAT| Medium 🇵🇹 **Portugal**| n/a — but **document series are bound to certified software**| New series registered under the new software's certificate| You (SAF-T PT is your export)| Hard 🇭🇷 **Croatia** · Fiskalizacija 2.0| Yes — the state directory names your provider (“AMS”)| The directory entry| You + the reported record| Medium 🇹🇷 **Türkiye** · GİB| Yes — via your _özel entegratör_| An activation form signed with your e-seal| Your integrator| Ask us 🇧🇪 🇳🇱 🇩🇰 🇸🇪 🇳🇴 🇫🇮 and the rest of **Peppol Europe**| No — the participant ID stays; the SMP entry moves| Nothing at a tax authority| You| Easy 🇩🇪 **Germany**| No platform layer to leave (Factur-X by e-mail or Peppol)| Nothing| You (GoBD, 8 years)| Easy **Effort** is about the switch, not about us: “Hard” means there are counterparties to notify or an archive to move, not that we cannot do it. ## Ask us to migrate you One field. Type the identifier you already know — a SIRET, a VAT number, or just the company name — and everything else is resolved from our records and the registries we already query: legal name, country, SIREN/SIRET, Peppol id, the annuaire addressing line, and what your country requires. You correct anything that is wrong; you type nothing that we can look up. Migration request Your SIRET, VAT number, or company name Your e-mail Fill this in for me No account and no API key: the page mints a throwaway sandbox key for the lookup and forgets it when you close the tab. Resolution runs against `POST /v1/portability/resolve`. **Correct something, or fill it in by hand** — only if the lookup got it wrong or found nothing. Country 🇫🇷 France — PPF 🇮🇹 Italy — SDI 🇵🇱 Poland — KSeF 🇷🇴 Romania — e-Factura 🇭🇺 Hungary — NAV 🇬🇷 Greece — myDATA 🇪🇸 Spain — Crea y Crece 🇵🇹 Portugal — AT 🇭🇷 Croatia — Fiskalizacija 2.0 🇹🇷 Türkiye — GİB Peppol country (BE, NL, DK, SE, NO, FI, DE…) Legal name Platform you are leaving Who signs, for the company Effective date [Send this request]() Copy as text The lookup is the only thing that leaves your browser, and only when you ask for it. The button opens your own mail client with the request filled in. We reply within one business day, and run the technical part for you. **Already a customer?** The same resolution runs against your own records rather than the sandbox, so a signed-in company confirms one screen and types nothing at all. ### What we do once you send it 1. **We check who holds you today** in the relevant registry — the PPF annuaire, the Peppol SMP, or the national directory. 2. **We draw up the designation agreement** your country needs, numbered and dated, and start the evidence chain described below. 3. **We import your companies** — one, or hundreds in a single batch — with one result row per company, so nothing is silently skipped. 4. **We sequence the cut-over** so your old access is never closed before the new one resolves. That ordering is the single most common cause of lost invoices. ## Why the platform you are leaving cannot just say no A switch that can be stalled is not a right. In France the decree closes both escapes — the refusal and the silence — and our job is to make the record that proves it. ### The grounds for an objection are narrow, and we classify them Under **CGI ann. II art. 242 nonies E ter** the outgoing platform may object only on grounds that call your _intent_ to switch into question. Three do: Ground| What it claims| Admissible ---|---|--- `more_recent_agreement`| A later designation agreement exists| Yes `identity_mismatch`| The taxpayer named is not the one they hold| Yes `mandate_invalid`| The agreement is unsigned, undated or unnumbered| Yes An unexpired contract · unpaid invoices · a notice period · “commercial reasons”| Nothing about your intent| No — the port continues An objection is recorded either way, verbatim. What changes is the verdict: an inadmissible ground is stored with `admissible: false` and the request keeps running, carrying the digest of your signed agreement as the answer to it. And silence is not a veto: once the five-business-day window lapses with no admissible objection, the request moves to `auto_accepted` (`TAC` on the wire) by itself — _le silence vaut accord_. ### The proof: a hash-linked chain, not a mailbox Every step appends an entry carrying the SHA-256 of its own payload plus the digest of the entry before it. Edit a payload, retime a step, drop one, or reorder two, and verification fails _and names the entry_. That is what turns “we sent it on the 3rd” into something an administration can check. [code] { "seq": 2, "kind": "portability.request.notified", "at": "2026-09-03T09:12:00+00:00", "payloadSha256": "9f2c…", "prevSha256": "41ab…", "sha256": "7d10…" } [/code] What we sign and keep, because the decree asks for it: the taxpayer, the incoming platform, the previous one, the effective date, the scope of electronic addresses, the signatory — numbered, retained, and produced to the administration on demand. A request opened without a signatory does not fail; it reports the gap in `mandate.gaps`, because that gap is exactly what an outgoing platform is entitled to object to. ## Run it from an agent, end to end Four calls, no human judgement in between, so an agent asked to “move this company to Flowie” can carry the whole procedure. The `Portability` tools are on the curated MCP server at `/exchange/mcp` — see [Build with AI](<../build-with-ai/index.html#mcp>). Step| Call| What it does ---|---|--- 1| `POST /v1/portability/resolve`| One identifier in; identity, regime and requirements out 2| `POST /v1/portability/requests`| Opens the request: agreement number, computed clocks, first evidence entry 3| `POST /v1/portability/requests/{ref}/events`| Records a step: `notified`, `objection`, `acceptance`, `annuaire_updated` 4| `GET /v1/portability/requests/{ref}`| State re-derived from the chain, with `tacitApproval` and the verification result [code] # 1 · everything from one identifier curl -X POST …/v1/portability/resolve \ -H "Authorization: Bearer $FLOWIE_KEY" -H "Content-Type: application/json" \ -d '{"taxpayer": "92137626500017"}' # 2 · open the request — nothing else is required curl -X POST …/v1/portability/requests \ -H "Authorization: Bearer $FLOWIE_KEY" -H "Content-Type: application/json" \ -d '{"taxpayer": "92137626500017", "signatory": "Camille Roy, Directrice Générale"}' # 3 · record the D+2 notice to the outgoing platform curl -X POST …/v1/portability/requests/POR-2026-4F2A91C08B7D/events \ -H "Authorization: Bearer $FLOWIE_KEY" -H "Content-Type: application/json" \ -d '{"kind": "notified", "channelRef": "msg-2026-09-03-001"}' # 4 · where does it stand, and is the proof intact? curl …/v1/portability/requests/POR-2026-4F2A91C08B7D \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] State is never stored, always folded from the evidence chain, so step 4 is the single source of truth — and an agent that was not running when the request was opened reaches the same answer as one that was. The whole history is also readable as ordinary events (`portability.request.*`) over `GET /v1/events`. Outside France the same four calls apply, with less law behind them The clocks and the objection rules are French. Elsewhere the chain still gives you a timestamped record of what you asked for and when — which is what the [Data Act](<#eu-right>) switching right is argued with. ## Peppol countries: the participant ID stays, the SMP entry moves In every Peppol country the switch is a registry edit, not a re-addressing. Your participant ID does not change, document-type and process registrations are re-created identically, and only the endpoint and its transport certificate point somewhere new. The specification provides a **migration key** for this: your outgoing provider generates it, the new provider presents the same key to the SML, and the registration moves. In practice many providers never expose that key, so the real-world sequence degrades to _deregister, then re-register_ — which opens a window where invoices can be misrouted, delivered twice, or lost. Ask for the migration key in writing before you sign anything And never terminate the old access point before a lookup shows the new one resolving. Propagation is usually hours, but it can take days with a complex integration. ## France: the only regulated hand-over France is the exception. A change of _plateforme agréée_ follows a procedure fixed by decree, with deadlines on both platforms and a continuity obligation on the one you leave. The taxpayer keeps its SIREN/SIRET addressing throughout; what changes is the annuaire entry, and only a platform can write to it. **[Read the France portability guide →]()** for the designation agreement, the day-by-day timetable, the eight request states and the inter-PA message format. ## Clearance and intermediary countries, one by one ### 🇮🇹 Italy — the address changes, and that is the problem Your _codice destinatario_ is the intermediary's channel code, so switching changes it and every supplier holding the old one must be told. The mitigation is to register your _indirizzo telematico_ in _Fatture e Corrispettivi_ , so SdI routes to the registered channel. The _delega_ to an _intermediario_ runs for four years unless you set a shorter term, does not auto-renew, and is revocable at any time in the same form it was granted. Your _conservazione sostitutiva_ obligation is ten years, with signed and time-stamped packages, and it applies independently to what you issue and what you receive. ### 🇵🇱 Poland — the state is the archive, so switching is cheap KSeF stores every invoice for ten years from the end of its year of issue and is the official record, so there is no archive to move and no address to propagate: your buyers pull from KSeF. What you port is credentials. KSeF certificates are live from February 2026; tokens work until the end of 2026 and are replaced by certificate-only access from 1 January 2027. Grant the new provider its own credential, then revoke the old one. ### 🇷🇴 Romania — authorised users, not addresses e-Factura runs through the SPV, and a provider acts under an authorised user holding a qualified certificate. Switching means registering the new provider's certificate holder and revoking the outgoing one's rights. ANAF holds the cleared invoices. ### 🇭🇺 Hungary — a technical user per software NAV Online Számla is a reporting regime: no routing to move, no counterparties to notify. Your primary user creates a technical user for the new software and you delete the old keys. ### 🇬🇷 Greece — the transmission channel is a declared choice myDATA accepts a direct ERP integration, an accredited provider, or the free _Timologio_ app. Switching provider means re-declaring the channel and re-issuing credentials. The B2B mandate lands on 2 March 2026 for turnover above €1M and 1 October 2026 for everyone else, each with a transition period. ### 🇪🇸 Spain — interoperability is mandated, a switch procedure is not _Crea y Crece_ is a four-corner model: invoices travel through compliant private platforms or the public AEAT solution, and private platforms also submit a _copia fiel_. Mandated interoperability is a strong indirect guarantee — your counterparties stay reachable whoever you pick — but there is no regulated hand-over. Large companies are in scope from 1 October 2027, everyone else from 1 October 2028. ### 🇵🇹 Portugal — the lock-in is the software certificate Invoices must come from AT-certified software and carry its certification number, the ATCUD and a QR code. A document series is registered under the software that created it, so switching means opening **new series** under the new software's certificate — a series does not follow you. SAF-T (PT) is your export on the way out. ### 🇭🇷 Croatia — a state directory of approved providers Fiskalizacija 2.0 went live on 1 January 2026 with a state directory of taxpayers and approved providers (“AMS”) alongside the FiskApplication portal. Switching is a directory re-pointing — the closest structural analogue to the French annuaire outside France. ### 🇹🇷 Türkiye — the biggest market, the least public procedure Around 1.49 million taxpayers file through a private integrator (_özel entegratör_) against roughly 76,000 on the GİB portal, so switching integrator is a mass-market event — but no integrator-to-integrator procedure is published. Talk to us before you give notice. ## Your EU right to switch Outside France there is no tax rule that governs leaving a provider. There _is_ a horizontal one, and it is stronger than most contracts: the **EU Data Act** , whose switching provisions have applied since 12 September 2025 to data-processing services — e-invoicing platforms included. * Providers must remove commercial, technical, contractual and organisational **obstacles to switching**. * **Two months' notice** is the maximum they can require of you. * The switch must complete within **30 calendar days** , extendable only where it is technically unfeasible. * Absent an applicable standard, you must be able to export **all your data in a structured, commonly used, machine-readable format**. * It binds **existing contracts** , fixed-term ones included, and reaches non-EU providers serving EU customers. Looking further out, **ViDA** makes EN 16931 mandatory for intra-EU invoicing from 1 July 2030 and aligns domestic reporting by 1 January 2035. That shrinks the format half of switching cost — but a common format is not portability: it says nothing about who holds your archive or who is registered as your platform. ## Who holds the archive holds the customer Country| Retention| What it means when you leave ---|---|--- 🇵🇱 Poland| 10 years, in KSeF| Nothing to move — the state is the record 🇮🇹 Italy| 10 years, _conservazione_| Signed, time-stamped packages have to be handed over 🇩🇪 Germany| 8 years (§14b UStG)| GoBD adds original format, immutability, machine-readability 🇫🇷 France| 10 years for commercial records| Plus a 1-year continuity duty on the platform you leave 🇨🇭 Switzerland · 🇦🇹 Austria · 🇬🇧 UK| 10 · 7 · 6 years| Contractual export, no regulated hand-over ## What to demand from the provider you are leaving Ask before you sign the exit, not after. In France most of this is owed to you; elsewhere the Data Act is your lever. 1. **Original XML** invoices, probative value intact. 2. **Human-readable renditions** (PDF or Factur-X). 3. **Lifecycle statuses** — the full history, in CSV, JSON or XML. 4. **Counterparty lists** with their addresses and routing codes. 5. **Attachments** in their original formats. 6. **Accounting entries** (in France, the FEC). 7. **Technical logs** , or a signed attestation covering them. 8. The **Peppol migration key** , in writing, where Peppol applies. 9. **Registry evidence** that the old entry is deactivated and the new one resolves. 10. A **credential revocation plan** sequenced _after_ the new provider is authorised. 11. **In-flight reconciliation** : documents submitted but not yet acknowledged at cut-over. 12. The **conservation packages** with their signature and timestamp metadata, where the archive stays behind. ## What we could not confirm Said plainly, because a switch planned on a guess is a switch that slips: * The **Türkiye** integrator-to-integrator procedure is not published anywhere we could find. * Whether **Greece** restricts myDATA channel changes _within_ a tax year — the one rule that would block a mid-year switch. * Whether the registered Italian _indirizzo telematico_ overrides an invoice-level _codice destinatario_ unconditionally. * The **Croatian** AMS directory re-pointing procedure and its deadlines. * Peppol **in-flight document** handling and rollback during a migration. Where a country appears above, we verify it live with the registry before we quote you a date. ======================================================================== # Platform onboarding kit # Source: https://docs.get-flowie.com/guides/onboarding-kit.html ======================================================================== --- title: "Platform Onboarding Kit" description: "A complete walkthrough for platform builders: onboard one tenant, then a hundred. Scoped keys, branding, webhooks, billing." canonical: "https://docs.get-flowie.com/guides/onboarding-kit" source: "https://docs.get-flowie.com/guides/onboarding-kit.html" --- # Platform Onboarding Kit Platform Onboarding Kit # Build your own e-invoicing product on top of Flowie This is the playbook accounting SaaS, ERPs, and public-sector aggregators follow when they integrate Flowie under their own brand. By the end you'll have a working tenant onboarding flow, scoped credentials, branded UX, signed webhooks, and a path to support thousands of customers. Mental model in one sentence You hold a **platform key**. For each customer of yours (a "tenant"), you onboard one **managed company**. You then either keep acting on their behalf with `X-Flowie-Company`, or hand them a **scoped tenant key** for direct integration. ## Mental model Concept| What it means ---|--- **Platform organization**| Your Flowie account. Holds platform keys, branding, webhook fan-out config. **Managed company**| One Peppol-registered legal entity belonging to a tenant. _One per tenant per VAT_. **Platform key**| `flw_plat_live_…` or `flw_wl_live_…` — your master credential. Never expose to tenants. **Tenant key**| Per-managed-company personal key (`flw_live_…`). Optional — only issue if the tenant integrates Flowie directly. **X-Flowie-Company**| Header you set with a platform key to act on a specific tenant. ## Prerequisites * A Flowie organization with **Platform** or **White-label** entitlement (request via [sales]()). * Test API credentials. The dashboard's **Settings → API keys → Platform key** screen issues them. * An HTTPS endpoint that can receive webhooks (your dev tunnel is fine for now). 1. ### Get a platform key [code] curl -X POST https://back.flowie.ink/exchange/v1/api-keys \ -H "Authorization: Bearer $FLOWIE_DASHBOARD_JWT" \ -d '{"name":"my-platform","scopes":["platform","*"],"keyType":"platform"}' [/code] You'll get back something like: [code] { "id": "key_01HXY", "key": "flw_plat_test_AbC123…", "keyPrefix": "flw_plat_test_AbC", "scopes": ["*"], "createdAt": "2026-04-25T10:00:00Z" } [/code] Persist the `key` string in your secret manager. You won't see it again. 2. ### Onboard your first tenant One call does everything atomically: registers the company, publishes it to Peppol SMP, opens a tenant-scoped webhook, and (optionally) mints a tenant key. [code] curl -X POST https://back.flowie.ink/exchange/v1/platform/companies \ -H "Authorization: Bearer $PLATFORM_KEY" \ -H "Idempotency-Key: tenant-acme-init" \ -H "Content-Type: application/json" \ -d '{ "vatNumber": "FR86797978996", "name": "ACME France SARL", "metadata": { "tenantId": "t_acme", "tier": "premium" }, "webhook": { "url": "https://yourplatform.com/hooks/flowie?tenant=t_acme", "events": ["document.received","document.delivered","document.failed", "lifecycle.updated","compliance.reported.failed"] }, "apiKey": { "name": "tenant-acme", "scopes": ["send","receive","documents.read","documents.write","lifecycle"] } }' [/code] Response: [code] { "company": { "id": "comp_01HY7…", "peppolId": "0009:FR86797978996", "vatNumber": "FR86797978996", "name": "ACME France SARL", "country": "FR", "status": "active", "smpRegistered": false, "metadata": { "tenantId": "t_acme", "tier": "premium" }, "createdAt": "2026-04-25T10:00:00Z" }, "apiKey": { "id": "key_01HY7…", "key": "flw_test_tacme_xyz123…", "keyPrefix": "flw_test_tacme", "name": "tenant-acme" }, "webhook": { "id": "wh_01HY7…", "url": "https://yourplatform.com/hooks/flowie?tenant=t_acme", "status": "active" } } [/code] Idempotent by design Reusing `Idempotency-Key` within 24h returns the same response. If your retry is from a different deploy and the original key has expired, you'll get a `409 COMPANY_EXISTS` with the existing `companyId` — treat it as success. SMP registration is async. Listen for the `company.smp_registered` event on the platform-level webhook (or poll `GET /companies/{id}`) to know when the tenant can send/receive. 3. ### Choose a key strategy Pattern| When| Trade-off ---|---|--- **Platform-only** (`X-Flowie-Company`) | Your stack does everything; tenants never touch the API. | One secret to manage · platform key compromise = all tenants. **Per-tenant key** | Tenants integrate directly (e.g. via your SDK) or you want hard isolation. | Blast radius limited to one tenant · you must manage rotation & storage. **Hybrid** | Most platforms. Use the platform key from your backend; issue tenant keys only on request. | Best of both, slightly more code. Acting on behalf of a tenant from your backend looks like this: [code] curl -X POST https://back.flowie.ink/exchange/v1/documents/send \ -H "Authorization: Bearer $PLATFORM_KEY" \ -H "X-Flowie-Company: comp_01HY7…" \ -H "Idempotency-Key: t_acme-inv-001" \ -H "Content-Type: application/json" \ -d @invoice.json [/code] Without the header, the call would error `403 COMPANY_REQUIRED` — platform keys must always specify whom they're acting for. 4. ### Wire up webhooks You have two options. Pick the one that matches how you want to fan out events: 1. **Platform-level webhook.** One endpoint receives events from all tenants. Each event includes `data.company.id` and the tenant's `metadata` so you can route. Easier to operate. 2. **Per-tenant webhook** (created during onboarding above). One endpoint per tenant. Heavier, but gives you per-tenant retry isolation. Either way, the receiver pattern is the same — verify HMAC, ack fast, queue work: [code] @app.post("/hooks/flowie") async def flowie_hook(req: Request, tenant: str | None = None): raw = await req.body() verify_hmac(req.headers["X-Flowie-Signature"], raw, secret=lookup_webhook_secret(tenant)) event = json.loads(raw) queue.enqueue("process_flowie_event", tenant=tenant, event=event) return Response(status_code=204) [/code] Full verification recipe in the [webhook cookbook](<../reference/webhooks.html#signing>); payload fixtures in [/fixtures](<../fixtures/index.html>). 5. ### Brand the experience (white-label) If you have a white-label entitlement, you can replace Flowie's branding everywhere your tenants see it: [code] curl -X PATCH https://back.flowie.ink/exchange/v1/platform/settings \ -H "Authorization: Bearer $PLATFORM_KEY" \ -d '{ "branding": { "displayName": "ACME e-Invoice", "logoUrl": "https://acme.com/logo.svg", "primaryColor":"#0F62FE", "supportEmail":"support@acme.com" }, "customDomain": "peppol.acme.com", "defaults": { "preferredFormat": "ubl-xml", "autoCompliance": { "FR": true, "IT": true, "BE": true } } }' [/code] The `customDomain` field provisions TLS automatically (Let's Encrypt). DNS records to point at us are returned in the response. 6. ### Send on behalf of a tenant Same call as a single-tenant integration, plus the `X-Flowie-Company` header. Pull the company id from your tenant table by tenantId: [code] def send_invoice(tenant_id: str, invoice: dict) -> dict: company_id = db.get("flowie_company_id", tenant_id=tenant_id) return platform_api.post( "/documents/send", headers={ "X-Flowie-Company": company_id, "Idempotency-Key": f"{tenant_id}-{invoice['id']}", }, json={ "type": "invoice", "from": company_id, "to": invoice["recipientPeppolId"], "document": invoice["body"], }, ).json() [/code] ## Scale to 100+ tenants The onboarding API is designed for batch use. Common patterns: * **Backfill from your existing customer table.** Iterate, call `POST /platform/companies` with an idempotency key per customer. Failures are isolated; safe to retry. * **Just-in-time onboarding.** Onboard the first time a tenant tries to send. Hide the latency behind a "preparing your workspace" loading state — typically < 5 seconds. * **Bulk send.** Use [`POST /v1/documents/send/batch`](<../reference/index.html#send-batch>) for nightly jobs. Up to 100 documents per call, all atomic per item. Concrete script for backfill: [code] for tenant in db.tenants(active=True): try: api.post("/platform/companies", headers={"Idempotency-Key": f"backfill-{tenant.id}"}, json={ "vatNumber": tenant.vat, "name": tenant.name, "metadata": {"tenantId": tenant.id}, }, timeout=30, ) except httpx.HTTPStatusError as e: log.error("onboard_failed", tenant=tenant.id, status=e.response.status_code, body=e.response.text) continue [/code] ## Billing & chargebacks Flowie bills the platform organization monthly, by document volume. Use [`GET /v1/platform/usage`](<../reference/index.html#platform-usage>) to break down per tenant for chargebacks: [code] curl "https://back.p2p-flowie.com/exchange/v1/platform/usage?period=month&groupBy=company" \ -H "Authorization: Bearer $PLATFORM_KEY" [/code] [code] { "period": { "start":"2026-04-01", "end":"2026-04-30" }, "total": { "documentsSent": 18420, "documentsReceived": 22100 }, "byCompany": [ { "companyId":"comp_…", "tenantId":"t_acme", "sent": 4203, "received": 5012, "complianceReports": 3801 }, … ] } [/code] ## Observability Metric| How to read it ---|--- Tenant health| Per-company `document.failed` rate over rolling 24h. Compliance health| `compliance.reported.failed` count by country. Webhook delivery| Webhook record's `failureCount` field; monitor for > 0. Quota burn| `GET /v1/stats?period=month` per tenant; alert at 80%. Upstream health| `GET /health/readiness` on Flowie's side; see circuit-breaker state. ## Offboarding a tenant Three steps, in order: 1. Revoke the tenant key: `DELETE /v1/platform/api-keys/{key_id}`. 2. Disable the per-tenant webhook (don't delete — keep the audit trail): `PATCH /v1/webhooks/{id}` with `{"status":"disabled"}`. 3. Deregister the company: `DELETE /v1/companies/{id}`. Historical documents remain queryable for the legally-mandated retention period (10y in IT, 6y in FR). ## Production go-live checklist ✓| Item| Why ---|---|--- ☐| Platform key stored only in secret manager (Vault / AWS SM / GSM)| Compromise = blast radius across all tenants. ☐| Tenant keys (if used) stored encrypted at rest, scoped by tenant| Reduces blast radius if one is leaked. ☐| All `POST` calls send an `Idempotency-Key` derived from your DB row id| Safe retries across deploys. ☐| Webhook handler verifies HMAC on the raw body, before parsing| Forgery resistance. ☐| Webhook handler dedupes on `X-Flowie-Event-Id`| At-least-once delivery. ☐| Webhook handler queues work, doesn't process inline| Stay under the 5s ack window. ☐| Per-tenant alerting on `document.failed` and `compliance.reported.failed`| Fail fast, fix fast. ☐| Monthly chargeback job hits `/platform/usage`| Don't eat your tenants' cost. ☐| Custom domain DNS verified; TLS auto-renewing| Brand integrity. ☐| Sandbox-mode integration tests in CI before any prod deploy| Catch regressions. ======================================================================== # European compliance overview # Source: https://docs.get-flowie.com/compliance/index.html ======================================================================== --- title: "Compliance · all 47 countries" description: "Flowie Exchange compliance coverage across 47 countries on four continents — EU-27 plus EEA, UK and Switzerland, the Middle East (KSA, UAE, Israel, Egypt, Türkiye), South Asia and SE Asia (India, Singapore, Malaysia, Thailand, Vietnam), East Asia (Japan, South Korea, China), and the Pacific (Australia, New Zealand). Mandate status, network, format, and Flowie support at a glance." canonical: "https://docs.get-flowie.com/compliance/" source: "https://docs.get-flowie.com/compliance/index.html" --- # Compliance · all 47 countries Compliance · all 47 countries # E-invoicing coverage map — 47 countries, four continents Flowie covers **47 jurisdictions** across Europe, MENA, and Asia-Pacific — operating Peppol Access Points directly where we hold national accreditation, and integrating via vetted local partners where in-country presence is required by the regulator. The table below gives you the mandate status, network, and at-a-glance summary for each. Click any country for the full deep-dive. _Last refreshed: 2026-07-13._ ## Coverage matrix Click any column header to sort. Click a second time for descending, a third to restore the default order. Country | Status | Network | Tagline | Where things stand ---|---|---|---|--- [🇦🇺 **Australia**]()| Phased rollout| Peppol BIS 3.0 + PINT A-NZ| Peppol PINT A-NZ · federal default by Dec 2026 · ATO Peppol Authority| Federal B2G default by end-2026; no B2B mandate yet — Peppol-led adoption only. [🇦🇹 **Austria**]()| Live mandate| Peppol BIS 3.0| Peppol BIS B2G mandate live since 2014 · No B2B mandate yet| Federal B2G live; B2B will follow the EU ViDA timeline. [🇧🇪 **Belgium**]()| Live mandate| Peppol BIS 3.0| Peppol BIS B2B mandate live since 1 January 2026 (HERMES dropped)| Pure Peppol; no central hub. [🇧🇬 **Bulgaria**]()| Phased rollout| Peppol BIS 3.0 + national SAF-T| SAF-T phase-in 2026–2028 · No domestic B2B mandate yet| SAF-T being introduced for large taxpayers; full e-invoicing TBD. [🇨🇳 **China**]()| Live mandate| STA Golden Tax IV| Fully digital e-fapiao · Golden Tax IV nationwide · new VAT Law 2026| Fully digital e-fapiao universal nationwide; VAT Law 2026 cements the regime. [🇭🇷 **Croatia**]()| Live mandate| National Fiscalisation portal + Peppol BIS 3.0| Fiscalisation 2.0 B2B mandate live since 1 January 2026| B2B mandate ramping; full VAT-taxpayer scope reached during 2026. [🇨🇾 **Cyprus**]()| Live mandate| Peppol BIS 3.0| Peppol BIS B2G live · No B2B mandate yet| B2G mandate stable; B2B awaiting EU ViDA framework. [🇨🇿 **Czechia**]()| Live mandate| ISDOC 6.0 (national) + Peppol BIS 3.0| B2G mandate live · ISDOC + Peppol BIS · No B2B mandate yet| Public sector accepts both ISDOC and Peppol BIS; no B2B mandate. [🇩🇰 **Denmark**]()| Live mandate| Peppol BIS 3.0 + OIOUBL via NemHandel| OIOUBL/Peppol BIS · B2G live since 2005 · Bookkeeping Act phasing 2024–2026| B2G universal since 2005; new Bookkeeping Act introduces digital record-keeping with embedded e-invoicing requirements. [🇪🇬 **Egypt**]()| Live mandate| ETA portal (national clearance, JSON/XML)| ETA clearance live for B2B/B2G · e-receipt expanding for B2C| Universal B2B/B2G clearance since 2023; B2C e-receipt expanding; threshold lowered for 2026. [🇪🇪 **Estonia**]()| Phased rollout| Peppol BIS 3.0 + Estonian e-invoicing register| B2B-on-request live since July 2025 · B2G universal · Peppol BIS| B2B-on-request live; full B2B mandate expected ahead of ViDA. [🇫🇮 **Finland**]()| Live mandate| Peppol BIS 3.0 + Finvoice 3.0 (national)| B2B-on-request since 2020 · B2G universal · Finvoice + Peppol| B2B-on-request universal in practice; B2G universal. [🇫🇷 **France**]()| Phased rollout| PPF + Peppol BIS 3.0| PPF mandate · receive Sept 2026 · send Sept 2027| PPF receive obligation imminent; full send rollout 2027. [🇩🇪 **Germany**]()| Phased rollout| Peppol BIS 3.0 + XRechnung CIUS + ZUGFeRD/Factur-X| B2B mandate phasing 2025–2028 · XRechnung B2G · ZUGFeRD/Factur-X B2B| Receive obligation universal since Jan 2025; send phasing through 2028 by company size. [🇬🇷 **Greece**]()| Live mandate| myDATA (AADE) + Peppol BIS 3.0| myDATA real-time reporting universal · Peppol BIS for cross-border| myDATA universal; B2B e-invoicing extension via approved providers expected to expand. [🇭🇺 **Hungary**]()| Live mandate| NAV Online Számla + Peppol BIS 3.0| NAV Online Számla 3.0 reporting universal since 2021| Real-time invoice reporting universal; structured-invoice send mandate not yet legislated. [🇮🇸 **Iceland**]()| Phased rollout| Peppol BIS 3.0| Peppol BIS B2G adoption · No B2B mandate yet| B2G voluntary today; e-invoicing adoption rising via EEA alignment. [🇮🇳 **India**]()| Live mandate| GST IRP (Invoice Registration Portal) + e-Way Bill| Mandatory IRN issuance via GST IRP · ₹5 crore threshold| B2B IRN clearance universal above ₹5 cr turnover; 30-day reporting cap above ₹10 cr. [🇮🇪 **Ireland**]()| Live mandate| Peppol BIS 3.0| Peppol BIS B2G live since 2019 · No B2B mandate yet| B2G stable; B2B consultation results expected 2026. [🇮🇱 **Israel**]()| Live mandate| ITA SHAAM (national clearance, JSON)| ITA allocation-number clearance · accelerated 2026 thresholds| CTC clearance live since May 2024; thresholds tightening rapidly through 2026. [🇮🇹 **Italy**]()| Live mandate| SDI + Peppol BIS 3.0| SDI mandatory clearance since 2019 — universal B2B + B2G + B2C| Most mature CTC regime in the EU. [🇯🇵 **Japan**]()| Voluntary| Peppol BIS 3.0 + JP PINT| Peppol JP PINT · voluntary network on top of Qualified Invoice System| Voluntary Peppol layer on top of the mandatory Qualified Invoice System (since Oct 2023). [🇱🇻 **Latvia**]()| Live mandate| Peppol BIS 3.0| B2B mandate live since 1 January 2026 · G2B universal| B2B mandate now live; reporting model rather than CTC. [🇱🇮 **Liechtenstein**]()| Voluntary| Peppol BIS 3.0| Peppol BIS available · No mandate · Small market| Voluntary; small market typically routed via Swiss/Austrian APs. [🇱🇹 **Lithuania**]()| Live mandate| Peppol BIS 3.0 + E.sąskaita + i.MAS| E.sąskaita B2G universal · i.MAS reporting universal · No B2B mandate yet| Reporting universal via i.MAS / i.SAF-T; e-invoicing send obligation B2G only. [🇱🇺 **Luxembourg**]()| Live mandate| Peppol BIS 3.0| Peppol BIS B2G universal · No B2B mandate yet| Phased B2G complete; B2B awaits EU ViDA framework. [🇲🇾 **Malaysia**]()| Live mandate| LHDN MyInvois portal + UBL 2.1 (MY CIUS)| MyInvois clearance · phased rollout completing Jan 2026 (RM 1m floor)| Phased clearance live; final wave Jan 2026; SMEs < RM 1m exempt. [🇲🇹 **Malta**]()| Live mandate| Peppol BIS 3.0| Peppol BIS B2G live since 2019 · No B2B mandate yet| B2G stable; B2B awaiting EU ViDA framework. [🇳🇱 **Netherlands**]()| Live mandate| Peppol BIS 3.0 + NLCIUS| Peppol-by-default · B2G universal · NLCIUS profile · No B2B mandate yet| B2G universal; very high voluntary B2B adoption via SimplerInvoicing community. [🇳🇿 **New Zealand**]()| Phased rollout| Peppol BIS 3.0 + PINT A-NZ| Peppol PINT A-NZ · MBIE Peppol Authority · NZ$33m supplier mandate Jan 2027| B2G receive mandate live since 2022; ramping to send obligation in 2026 and supplier obligation in 2027. [🇳🇴 **Norway**]()| Live mandate| Peppol BIS 3.0 + EHF + SAF-T| EHF/Peppol BIS B2G universal since 2012 · SAF-T universal| EHF/Peppol B2G universal; SAF-T universal; B2B consultation in progress. [🇵🇱 **Poland**]()| Phased rollout| KSeF (national clearance) + Peppol BIS for cross-border| KSeF mandatory clearance · large taxpayers Feb 2026 · all April 2026| KSeF 2.0 + FA(3) mandatory for large taxpayers; full universal scope April 2026. [🇵🇹 **Portugal**]()| Live mandate| FE-AP (national B2G) + Peppol BIS 3.0 + SAF-T| ATCUD + SAF-T universal · B2G via FE-AP · No B2B mandate yet| ATCUD + SAF-T universal; B2G universal; B2B mandate proposed for 2027. [🇷🇴 **Romania**]()| Live mandate| RO e-Factura (ANAF clearance) + Peppol BIS for cross-border| RO e-Factura mandatory clearance universal since July 2024| Universal B2B clearance + SAF-T reporting; one of the most aggressive regimes in the EU. [🇸🇦 **Saudi Arabia**]()| Live mandate| ZATCA Fatoora + UBL 2.1 (KSA CIUS)| Mandatory clearance via Fatoora portal · live since 2021| Most mature CTC regime in MENA — universal B2B + B2G live; Phase 2 integration ramping by wave through 2026. [🇸🇬 **Singapore**]()| Phased rollout| Peppol BIS 3.0 + PINT-SG (5-corner)| Peppol InvoiceNow + GST 5-corner reporting · phased through 2031| Voluntary Peppol since 2019; GST InvoiceNow mandatory rollout 2025-2031. [🇸🇰 **Slovakia**]()| Phased rollout| IS EFA + Peppol BIS 3.0| IS EFA phased B2G · No B2B mandate yet| IS EFA B2G phasing; full B2B not yet legislated. [🇸🇮 **Slovenia**]()| Live mandate| UJP + Peppol BIS 3.0| UJP B2G universal since 2015 · No B2B mandate yet| B2G universal via UJP; B2B consultation in progress. [🇰🇷 **South Korea**]()| Live mandate| NTS HomeTax (national clearance, XML)| NTS e-Tax invoice · universal corporate clearance since 2011| World-leading CTC: every corporation, plus sole proprietors above KRW 80m, must issue e-Tax invoices. [🇪🇸 **Spain**]()| Phased rollout| Veri*Factu (AEAT) + FACe (B2G) + Peppol BIS 3.0| Veri*Factu reporting · Crea y Crece B2B mandate · FACe B2G| Veri*Factu postponed to 2027; Crea y Crece B2B phasing 2026–2028. [🇸🇪 **Sweden**]()| Live mandate| Peppol BIS 3.0 + SFTI| Peppol BIS B2G universal since 2019 · SFTI · No B2B mandate yet| B2G universal; B2B awaiting EU ViDA framework. [🇨🇭 **Switzerland**]()| Phased rollout| Peppol BIS 3.0| Federal B2G ramping · No B2B mandate · Peppol BIS| Federal B2G adoption rising; no federal B2B mandate. [🇹🇭 **Thailand**]()| Voluntary| RD e-Tax Invoice & e-Receipt portal (XML)| Voluntary e-Tax invoice/e-Receipt · ETDA-aligned XML · no mandate yet| Voluntary regime; the Revenue Department is encouraging adoption but no mandate is in force. [🇹🇷 **Türkiye**]()| Live mandate| GİB / Hazine clearance + UBL-TR| GİB e-Fatura since 2014 · e-Arşiv universal from 2026| Mature CTC regime; e-Arşiv universal from January 2026. [🇦🇪 **United Arab Emirates**]()| Phased rollout| Peppol BIS 3.0 + PINT AE (5-corner)| Peppol 5-corner model · voluntary pilot 1 July 2026 · first mandate 1 January 2027| Peppol-based CTC — voluntary pilot mid-2026, first mandate January 2027. [🇬🇧 **United Kingdom**]()| Phased rollout| Peppol BIS 3.0 + MTD reporting| MTD VAT reporting universal · NHS Peppol B2G · No general B2B mandate| MTD VAT universal; e-invoicing consultation results expected 2026. [🇻🇳 **Vietnam**]()| Live mandate| GDT national e-invoice platform (XML)| Universal e-invoice since 2022 · Decree 70 expansion 2025-2026| Universal mandatory e-invoice; Decree 70 expanded scope to POS retail and foreign suppliers in 2025-2026. ## Mandate timeline Every country's key e-invoicing dates on a single 2014 → 2030 axis. Each dot is a deadline; **green** = already in force, **amber** = phasing in this year, **blue** = scheduled. Hover or focus a dot for the full description, or click to jump to that country's deadlines section. Below the chart is a sortable "what's coming next" table. Past — already in force This year — phasing in Future — scheduled Today Country 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 [🇦🇺 Australia · Peppol via ATO]() []( "**2019-10-31** · ATO becomes Australian Peppol Authority — Joins OpenPeppol.") []( "**2022-07-01** · All federal NCEs — Mandatory Peppol receipt capability for B2G.") []( "**2025-05-15** · All Peppol senders — Migration to PINT A-NZ; legacy A-NZ BIS deprecated.") []( "**2026-07-01** · Federal NCEs — 30% of received invoices via Peppol target.")[]( "**2026-12-31** · Federal NCEs — Automated Peppol send + receive default.") [🇦🇹 Austria · Peppol B2G]() []( "2014-01-01 · Federal contracting authorities — B2G e-invoicing mandatory \(BGBl. I Nr. 32/2014\).") []( "≥ 2030 · All B2B taxable supplies \(expected\) — Aligned with EU ViDA — not yet legislated; planning baseline only.") [🇧🇪 Belgium · Peppol BIS]() [🇧🇬 Bulgaria · NRA SAF-T phase-in]() []( "2026-01-01 · Largest taxpayers \(turnover > BGN 300M\) — SAF-T monthly reporting begins.") []( "2027-01-01 · Mid-size taxpayers — SAF-T reporting onboarded.") []( "2028-01-01 · All VAT-registered businesses — SAF-T reporting universal.") [🇨🇳 China · Fully digital e-fapiao]() []( "**2021-12-01** · Pilot — 5 provinces — Fully digital e-fapiao introduced.") []( "**2022-2024** · Geographical rollout — Pilot extends across all provinces.") []( "**2024-12-01** · All taxpayers \(general + small-scale\) — Permitted nationwide; paper and earlier electronic formats progressively phased out.") []( "**2026-01-01** · All VAT-registered — New VAT Law supporting regulations in force; e-fapiao codified.") [🇭🇷 Croatia · Fiscalisation 2.0]() []( "**2026-01-01** · All VAT-registered B2B — Structured e-invoice + real-time fiscalisation report.") []( "2027-01-01 · Non-VAT businesses \(planned\) — Smaller taxpayers absorbed; legislation pending.") [🇨🇾 Cyprus · Peppol BIS B2G]() []( "2019-04-18 · Central government — B2G mandate \(EU directive transposition\).")[]( "2019-04-18 · Sub-central public authorities — Same date — Cyprus did not stagger central vs. sub-central.") []( "≥ 2030 · B2B \(expected\) — EU ViDA alignment; not yet legislated.") [🇨🇿 Czechia · ISDOC + Peppol]() []( "2019-04-18 · Central government — Must accept e-invoices \(EU 2014/55/EU\).") []( "2020-04-18 · Sub-central public authorities — Mandate extended.") []( "≥ 2030 · B2B \(expected\) — EU ViDA timeline; no national legislation yet.") [🇩🇰 Denmark · OIOUBL & Peppol]() []( "2024-07-01 · Class B/C/D companies — Bookkeeping Act: must use a registered digital bookkeeping system.") []( "**2026-01-01** · Class A companies — Same Bookkeeping Act obligation extended to smaller companies.") [🇪🇬 Egypt · ETA e-invoicing]() []( "**2020-11-15** · Pilot — 134 large taxpayers — Phase 0 e-invoicing live.") []( "**2021-2023** · Waves 1–9 — All VAT-registered companies onboarded by April 2023.") []( "**2022-2024** · B2C e-receipt waves — Mandatory B2C e-receipt rolled out by sector and turnover.") []( "**2026-03-31** · All taxpayers ≥ EGP 250k revenue — Resolution 281 of 2025: registration deadline at the lowered threshold.")[]( "**2026** · All B2C — Every printed e-receipt must display an ETA-validated QR code.") [🇪🇪 Estonia · B2B-on-request 2025]() []( "2017-03-01 · Central government — B2G receive obligation.") []( "2019-07-01 · All public authorities — B2G send obligation.") []( "**2025-07-01** · Domestic B2B \(on-request\) — Sellers must issue a structured e-invoice when the buyer is a registered e-invoice recipient.") []( "≥ 2027 · Universal B2B \(expected\) — Pending legislation; would convert on-request to mandatory.") [🇫🇮 Finland · Finvoice + Peppol]() []( "**2020-04-01** · Domestic B2B — Buyer's right to request a structured invoice — de facto universal.") []( "2027-03-01 · Possible full B2B mandate — EU ViDA-aligned; Finnish Tax Administration consultation underway.") [🇫🇷 France · PPF]() [🇩🇪 Germany · Wachstumschancengesetz]() []( "2017-04-18 · Federal contracting authorities — B2G mandate live \(XRechnung over Peppol\).") []( "**2025-01-01** · All German B2B buyers — Must be able to receive structured e-invoices.") []( "2026-12-31 · Transition period ends — Paper invoices for B2B no longer accepted by default.") []( "**2027-01-01** · Sellers with turnover > €800k — Must send structured e-invoices.") []( "**2028-01-01** · All B2B sellers — Universal send obligation.") [🇬🇷 Greece · myDATA]() []( "**2021-10-01** · All Greek VAT-registered businesses — myDATA real-time reporting mandatory.") []( "2024-04-01 · Public-sector contracting — B2G via Peppol BIS for state suppliers.") []( "≥ 2026 · Universal B2B e-invoicing \(expected\) — AADE consultation underway; would convert myDATA reporting into full e-invoicing.") [🇭🇺 Hungary · NAV Online Számla]() []( "2018-07-01 · B2B invoices > HUF 100k VAT — Real-time reporting introduced.") []( "2020-07-01 · All B2B invoices — Threshold removed; universal B2B reporting.") []( "**2021-01-04** · B2C invoices — Reporting extended to B2C — universal scope.") []( "≥ 2027 · Structured-invoice send mandate \(expected\) — Legislation in consultation; ViDA-aligned.") [🇮🇸 Iceland · Peppol-aligning]() []( "≥ 2027 · B2G mandate \(planned\) — Government has signalled alignment with the EU directive.") [🇮🇳 India · GST IRP]() []( "**2020-10-01** · Turnover > ₹500 cr — Phase 1 — IRN mandatory.") []( "**2021–2022** · ₹100 cr → ₹50 cr → ₹20 cr — Phased threshold reductions.") []( "**2023-08-01** · Turnover > ₹5 cr — Current universal threshold.") []( "**2025-04-01** · Turnover ≥ ₹10 cr — 30-day reporting deadline enforced — late submissions rejected by IRP.") [🇮🇪 Ireland · Peppol BIS B2G]() []( "2019-04-18 · Central government — B2G mandate live.") []( "2020-04-18 · Sub-central public authorities — Mandate extended.") []( "≥ 2027 · B2B \(consultation\) — Revenue Commissioners running stakeholder consultation; legislation TBD.") [🇮🇱 Israel · ITA clearance]() []( "**2024-05-05** · Invoices ≥ NIS 25,000 — Clearance live — voluntary trial period ended.") []( "**2025-01-01** · Invoices ≥ NIS 20,000 — Threshold tightened.") []( "**2026-01-01** · Invoices ≥ NIS 10,000 — Accelerated by ITA in December 2025.")[]( "**2026-06-01** · Invoices ≥ NIS 5,000 — Final threshold — originally planned for 2028, brought forward.") [🇮🇹 Italy · SDI]() [🇯🇵 Japan · Peppol JP PINT]() []( "**2022-09** · Digital Agency joins OpenPeppol — Japan Peppol Authority established.") []( "**2023-10-01** · All taxable persons — Qualified Invoice System mandatory; T-prefixed registration numbers required.") []( "**2026-10-01** · All taxable persons — Transition: input-tax credit on non-qualified invoices drops to 50%.") []( "**2029-10-01** · All taxable persons — Final transition: input-tax credit on non-qualified invoices drops to 0%.") [🇱🇻 Latvia · B2B mandate 2026]() []( "2025-01-01 · G2B \(government-to-business\) — Public authorities must issue e-invoices to businesses.") []( "**2026-01-01** · All B2B taxable transactions — Universal mandate. Mandatory issue + receive.") [🇱🇮 Liechtenstein · Peppol via CH]() [🇱🇹 Lithuania · E.sąskaita + i.MAS]() []( "2017-07-01 · Public-sector contracting — E.sąskaita mandatory for B2G.") []( "2019-01-01 · Large taxpayers — i.SAF-T reporting \(annual\).") []( "2020-01-01 · All taxpayers — i.SAF-T extended; periodic cadence by company size.") []( "≥ 2027 · B2B mandate \(expected\) — VMI consultation in progress.") [🇱🇺 Luxembourg · Peppol B2G phased]() []( "2022-05-18 · Large companies \(B2G\) — Send mandate.")[]( "2022-10-18 · Mid-size companies \(B2G\) — Send mandate.") []( "2023-03-18 · Small / micro companies \(B2G\) — Send mandate.") []( "≥ 2030 · B2B \(expected\) — EU ViDA framework.") [🇲🇾 Malaysia · MyInvois]() []( "**2024-08-01** · Turnover > RM 100 m — Wave 1 mandatory.") []( "**2025-01-01** · Turnover RM 25–100 m — Wave 2 mandatory.")[]( "**2025-07-01** · Turnover RM 5–25 m — Wave 3 mandatory.") []( "**2026-01-01** · Turnover RM 1–5 m — Wave 4 mandatory — final wave.") [🇲🇹 Malta · Peppol B2G]() []( "2019-04-18 · Central government — B2G mandate live.") []( "2020-04-18 · Sub-central public authorities — Mandate extended.") []( "≥ 2030 · B2B \(expected\) — EU ViDA.") [🇳🇱 Netherlands · Peppol-by-default]() []( "2017-01-01 · Central government — B2G mandate live.") []( "2019-04-18 · All public authorities — EU directive transposition.") []( "≥ 2030 · B2B mandate \(expected\) — EU ViDA framework; Belastingdienst has indicated alignment without national front-running.") [🇳🇿 New Zealand · Peppol via MBIE]() []( "**2022-03-31** · Central government agencies — Mandatory to receive Peppol e-invoices.") []( "**2025-05-15** · All Peppol senders — Migration to PINT A-NZ; legacy A-NZ BIS deprecated.") []( "**2026-01-01** · Agencies handling > 2,000 invoices/yr — Must also send Peppol e-invoices; pay 95% within 5 business days.") []( "**2027-01-01** · Suppliers with revenue > NZ$33 m \(last 2 yrs\) — Must invoice government via Peppol.") [🇳🇴 Norway · EHF & Peppol]() []( "2019-04-01 · All public authorities — EHF/Peppol BIS universal.") []( "2020-01-01 · All taxpayers — SAF-T NO on-demand obligation.") []( "≥ 2027 · B2B mandate \(consultation\) — Skatteetaten reviewing options.") [🇵🇱 Poland · KSeF]() []( "2022-01-01 · Voluntary KSeF — Available for early adopters.") []( "**2026-02-01** · Large taxpayers \(sales > PLN 200M\) — KSeF 2.0 mandatory; FA\(3\) replaces FA\(2\) for everyone on this date.")[]( "**2026-04-01** · All other VAT taxpayers — KSeF mandatory.") []( "2027-01-01 · Cash register integration — POS systems must connect to KSeF for B2C documents.") [🇵🇹 Portugal · ATCUD + SAF-T]() []( "2021-01-01 · Public-sector contracting \(B2G\) — FE-AP universal.") []( "2023-01-01 · All invoices — ATCUD mandatory on every invoice.") []( "**≥ 2027** · B2B \(proposed\) — Universal e-invoicing mandate; AT consultation underway.") [🇷🇴 Romania · RO e-Factura]() []( "2022-07-01 · High-fiscal-risk products \(B2B\) — RO e-Factura mandatory for selected sectors.") []( "2024-01-01 · All B2B reporting \(5-day window\) — Reporting obligation universal.")[]( "**2024-07-01** · All B2B clearance — Full clearance — invoices invalid without ANAF acceptance.") []( "2025-01-01 · B2C extension — RO e-Factura extended to B2C invoices.") []( "2026-01-01 · All taxpayers SAF-T — D406 monthly reporting universal.") [🇸🇦 Saudi Arabia · ZATCA Fatoora]() []( "**2021-12-04** · All VAT taxpayers — Phase 1 \(Generation\) — invoices must be issued in structured format with QR code.") []( "**2023-01-01** · Wave 1 \(turnover > SAR 3 bn in 2021\) — Phase 2 integration with Fatoora live.") []( "**2024-2025** · Waves 2–22 — Phase 2 integration rolled out by descending turnover bands.") []( "**2026-03-31** · Wave 23 \(turnover > SAR 750k\) — Phase 2 integration deadline.")[]( "**2026-06-30** · Wave 24 \(turnover > SAR 375k\) — Phase 2 integration deadline — captures essentially the full VAT register.") [🇸🇬 Singapore · InvoiceNow]() []( "**2019-01-09** · All businesses \(voluntary\) — InvoiceNow Peppol network launched by IMDA.") []( "**2025-05-01** · GST-registered \(voluntary\) — Soft launch of GST InvoiceNow.")[]( "**2025-11-01** · Newly incorporated companies registering for GST voluntarily — GST InvoiceNow mandatory.") []( "**2026-04-01** · All new voluntary GST registrants — GST InvoiceNow mandatory.") []( "2028-04-01 · Existing GST-registered, supplies ≤ S$200k — Mandatory.") []( "2029-04-01 · Existing GST-registered, supplies ≤ S$1m — Mandatory.") []( "2030-04-01 · Existing GST-registered, supplies ≤ S$4m — Mandatory.") [🇸🇰 Slovakia · IS EFA]() []( "2022-04-01 · Pilot — Voluntary IS EFA participation.") []( "2025-01-01 · Central government — IS EFA mandatory for receive.") []( "2027-01-01 · All public authorities \(planned\) — IS EFA universal B2G.") [🇸🇮 Slovenia · UJP]() []( "2015-01-01 · Public-sector contracting — UJP mandatory for all suppliers to public buyers.") []( "≥ 2027 · B2B mandate \(consultation\) — FURS reviewing options.") [🇰🇷 South Korea · NTS e-Tax]() []( "**2014-07-01** · Sole proprietors > KRW 1 bn turnover — Threshold rolled out.") []( "**2019-2023** · Threshold steps down: KRW 300m → 100m — Sole proprietors absorbed.") []( "**2024-07-01** · Sole proprietors > KRW 80m turnover — Current threshold — unchanged for 2026.") [🇪🇸 Spain · Veri*Factu + FACe]() []( "2015-01-15 · Public-sector contracting \(B2G\) — FACe mandatory.") []( "**≥ 2026-Q4** · Large taxpayers \(Crea y Crece\) — B2B e-invoicing mandate \(date pending royal decree\).") []( "**2027-01-01** · Corporate income tax payers — Veri*Factu obligation begins \(postponed from 2025-07-01, then 2026-01-01, by RDL 15/2025\).")[]( "**2027-07-01** · All remaining taxpayers — Veri*Factu obligation extended \(postponed from 2026-07-01 by RDL 15/2025\).") []( "**≥ 2028** · All taxpayers \(Crea y Crece\) — Universal B2B mandate.") [🇸🇪 Sweden · Peppol-by-default]() []( "2019-04-01 · All public buyers — B2G mandate live \(DIGG\).") []( "≥ 2030 · B2B \(expected\) — EU ViDA.") [🇨🇭 Switzerland · Peppol B2G ramp]() []( "2016-01-01 · Federal contracting > CHF 5k — B2G e-invoicing accepted \(not yet mandatory\).") []( "2024-01-01 · Federal contracting universal receipt — All federal departments accept Peppol BIS.") [🇹🇭 Thailand · RD e-Tax]() []( "**2017-2019** · Email path — e-Tax Invoice by Email available for SME \(≤ THB 30 m\).") [🇹🇷 Türkiye · GİB e-Fatura]() []( "**2014-04-01** · Large taxpayers — e-Fatura mandatory.") []( "**2017** · B2C reporting — e-Arşiv introduced.") []( "**2020-2024** · Phased threshold reductions — e-Fatura threshold steps down through TRY 5m / 3m by sector.") []( "**2026-01-01** · All taxpayers — TRY 3,000 e-Arşiv threshold removed — universal e-invoice obligation.")[]( "**2026-02-02** · All taxpayers — Updated UBL-TR technical standards in effect.") [🇦🇪 UAE · FTA e-invoicing]() []( "2026-02-23 · All taxpayers — MoF publishes UAE Electronic Invoicing Guidelines v1.0 + PINT AE technical spec.")[]( "**2026-07-01** · Voluntary participants — Pilot phase opens — voluntary adoption only, no obligation attaches on this date.")[]( "**2026-10-30** · Phase 1 taxpayers \(revenue ≥ AED 50 m\) — Deadline to appoint an Accredited Service Provider \(extended from 2026-07-31\).") []( "**2027-01-01** · Revenue ≥ AED 50 m — Phase 1: PINT AE issuance + DRP reporting mandatory.")[]( "2027-03-31 · All other VAT-registered + government entities — Deadline to appoint an Accredited Service Provider.")[]( "2027-07-01 · All other VAT-registered — Phase 2 — mandatory, including most free zone entities.")[]( "2027-10-01 · Government entities — Phase 3 — mandatory.") [🇬🇧 UK · MTD + Peppol NHS]() []( "2019-04-01 · All VAT-registered UK businesses — MTD for VAT launched.") []( "2021-04-01 · NHS suppliers — Peppol BIS mandatory for NHS England trading.") []( "≥ 2027 · General B2B mandate \(under consultation\) — HMRC reviewing — Italy-style or France-style framework not yet selected.") [🇻🇳 Vietnam · GDT e-invoice]() []( "**2022-07-01** · All organisations and businesses — Mandatory e-invoice — universal scope.") []( "**2025-06-01** · All taxpayers — Decree 70/2025 in force — POS, foreign suppliers, tighter timing.") []( "**2026-01-16** · All taxpayers — Decree 310/2025 restructures penalty framework for invoice violations.") ### What's coming next (sortable) Date| Country| What ships ---|---|--- **2025-01-01**| [🇩🇪 Germany]()| Must be able to receive structured e-invoices. **2025-01-01**| [🇮🇱 Israel]()| Threshold tightened. **2025-01-01**| [🇲🇾 Malaysia]()| Wave 2 mandatory. **2025-04-01**| [🇮🇳 India]()| 30-day reporting deadline enforced — late submissions rejected by IRP. **2025-05-01**| [🇸🇬 Singapore]()| Soft launch of GST InvoiceNow. **2025-05-15**| [🇦🇺 Australia]()| Migration to PINT A-NZ; legacy A-NZ BIS deprecated. **2025-05-15**| [🇳🇿 New Zealand]()| Migration to PINT A-NZ; legacy A-NZ BIS deprecated. **2025-06-01**| [🇻🇳 Vietnam]()| Decree 70/2025 in force — POS, foreign suppliers, tighter timing. **2025-07-01**| [🇪🇪 Estonia]()| Sellers must issue a structured e-invoice when the buyer is a registered e-invoice recipient. **2025-07-01**| [🇲🇾 Malaysia]()| Wave 3 mandatory. **2025-11-01**| [🇸🇬 Singapore]()| GST InvoiceNow mandatory. **2026**| [🇪🇬 Egypt]()| Every printed e-receipt must display an ETA-validated QR code. **2026-01-01**| [🇨🇳 China]()| New VAT Law supporting regulations in force; e-fapiao codified. **2026-01-01**| [🇭🇷 Croatia]()| Structured e-invoice + real-time fiscalisation report. **2026-01-01**| [🇩🇰 Denmark]()| Same Bookkeeping Act obligation extended to smaller companies. **2026-01-01**| [🇮🇱 Israel]()| Accelerated by ITA in December 2025. **2026-01-01**| [🇱🇻 Latvia]()| Universal mandate. Mandatory issue + receive. **2026-01-01**| [🇲🇾 Malaysia]()| Wave 4 mandatory — final wave. **2026-01-01**| [🇳🇿 New Zealand]()| Must also send Peppol e-invoices; pay 95% within 5 business days. **2026-01-01**| [🇹🇷 Türkiye]()| TRY 3,000 e-Arşiv threshold removed — universal e-invoice obligation. **2026-01-16**| [🇻🇳 Vietnam]()| Decree 310/2025 restructures penalty framework for invoice violations. **2026-02-01**| [🇵🇱 Poland]()| KSeF 2.0 mandatory; FA(3) replaces FA(2) for everyone on this date. **2026-02-02**| [🇹🇷 Türkiye]()| Updated UBL-TR technical standards in effect. **2026-03-31**| [🇪🇬 Egypt]()| Resolution 281 of 2025: registration deadline at the lowered threshold. **2026-03-31**| [🇸🇦 Saudi Arabia]()| Phase 2 integration deadline. **2026-04-01**| [🇵🇱 Poland]()| KSeF mandatory. **2026-04-01**| [🇸🇬 Singapore]()| GST InvoiceNow mandatory. **2026-06-01**| [🇮🇱 Israel]()| Final threshold — originally planned for 2028, brought forward. **2026-06-30**| [🇸🇦 Saudi Arabia]()| Phase 2 integration deadline — captures essentially the full VAT register. **2026-07-01**| [🇦🇺 Australia]()| 30% of received invoices via Peppol target. **2026-07-01**| [🇦🇪 United Arab Emirates]()| Pilot phase opens — voluntary adoption only, no obligation attaches on this date. **2026-10-01**| [🇯🇵 Japan]()| Transition: input-tax credit on non-qualified invoices drops to 50%. **2026-10-30**| [🇦🇪 United Arab Emirates]()| Deadline to appoint an Accredited Service Provider (extended from 2026-07-31). **2026-12-31**| [🇦🇺 Australia]()| Automated Peppol send + receive default. **2027-01-01**| [🇩🇪 Germany]()| Must send structured e-invoices. **2027-01-01**| [🇳🇿 New Zealand]()| Must invoice government via Peppol. **2027-01-01**| [🇪🇸 Spain]()| Veri*Factu obligation begins (postponed from 2025-07-01, then 2026-01-01, by RDL 15/2025). **2027-01-01**| [🇦🇪 United Arab Emirates]()| Phase 1: PINT AE issuance + DRP reporting mandatory. **2027-07-01**| [🇪🇸 Spain]()| Veri*Factu obligation extended (postponed from 2026-07-01 by RDL 15/2025). **2028-01-01**| [🇩🇪 Germany]()| Universal send obligation. ## How Flowie handles each regime From the API caller's perspective, every country is the same call: `POST /v1/documents/send`. Flowie figures out the rest: * **Pure Peppol 4-corner** (BE, NL, SE, NO, IS, AT, CY, IE, MT, LU, AU, NZ, JP, …) — Flowie's AP delivers; that's it. * **Peppol 5-corner** (UAE PINT AE, Singapore InvoiceNow + IRAS) — Peppol delivery _plus_ a real-time copy to the national tax authority. * **Hard clearance** (IT SDI, PL KSeF, RO e-Factura, KSA Fatoora, IL ITA, EG ETA, IN GST IRP, MY MyInvois, KR NTS, CN Golden Tax IV, VN GDT, TR GİB) — Flowie submits to the central platform first, captures the clearance number / UUID / IRN / allocation number, then delivers. * **Reporting regimes** (HU NAV, GR myDATA, ES Veri*Factu, BG/SK/SI SAF-T) — Flowie ships the reporting envelope on every send, in addition to delivery. * **Hybrid PA / PDP** (FR PPF) — Flowie is a registered _Plateforme Agréée_ (PA, formerly PDP); lifecycle transitions auto-report. * **National-format wrappers** (DE XRechnung, DK OIOUBL, NL NLCIUS, ES Facturae for FACe, CZ ISDOC, FI Finvoice, NO EHF, JP PINT, PINT-SG, PINT A-NZ) — Flowie auto-renders the right format from your JSON based on the recipient. * **Voluntary regimes** (CH, IS, LI, JP qualified-invoice, TH e-Tax) — Peppol BIS or national format on opt-in basis; tax authority does not gate validity. ## Regime types — what they mean Regime| What it means| EU examples ---|---|--- **Clearance**| Invoice not legally valid until the central platform accepts it. Synchronous.| IT, PL, RO **PDP / decentralised**| Multiple accredited platforms; invoices flow peer-to-peer with parallel reporting to the central authority.| FR **Real-time reporting**| Invoice exists immediately; metadata reported in near-real-time.| HU, GR, ES (Veri*Factu) **SAF-T**| Periodic structured accounting export. Not real-time.| BG, LT, NO, PT, SK **Pure Peppol**| 4-corner model; AP-to-AP routing, no central hub.| BE, NL, SE, NO, IE, AT, … **Voluntary / no mandate**| E-invoicing accepted but not required.| CH, IS, LI ======================================================================== # France · PPF compliance # Source: https://docs.get-flowie.com/compliance/fr/index.html ======================================================================== --- title: "France · PPF compliance" description: "Sending invoices to French buyers under the e-invoicing reform: PPF, PA / PDP status, Service Exécutant, deadlines, and how Flowie reports on your behalf. Flowie is registered Plateforme Agréée (PA, formerly PDP) number 0064." canonical: "https://docs.get-flowie.com/compliance/fr/" source: "https://docs.get-flowie.com/compliance/fr/index.html" --- # France · PPF compliance Compliance · 🇫🇷 France # France — Portail Public de Facturation (PPF) ## TL;DR * From **1 September 2026** , every French business _must be able to receive_ e-invoices — and large & mid-sized (ETI) businesses _must send_ them. * From **1 September 2027** , SMEs and micro-enterprises _must send_ e-invoices too. * Flowie is a registered **Plateforme Agréée (PA)** — number `0064`. _The DGFiP renamed PDP → PA in 2025; most market actors and existing contracts still use "PDP" interchangeably._ * You don't talk to PPF directly. Send via [`POST /v1/documents/send`](<../../reference/index.html#send-document>) as usual; we route through the right PA and report status to PPF. * Lifecycle changes (`approved`, `rejected`, `paid`) are auto-reported within ~2 minutes. Already with another PA? Porting to Flowie is a documented, four-call path A taxpayer may change _Plateforme Agréée_ at any time and keeps its SIREN/SIRET addressing, so nothing downstream is re-addressed. The [**portability guide**](<../../guides/portability.html>) walks the whole hand-over: import from a SIRET (single or in bulk), build and parse the normalised inter-PA message, and beat the three legal clocks — 24 h to acknowledge, 5 _jours ouvrés_ to decide, _le silence vaut accord_ past the delay. ## Deadlines Date| Who| What ---|---|--- **2026-09-01**| All FR businesses (any size)| **Receive** e-invoices in Factur-X, UBL, or CII. **2026-09-01**| Large & mid-sized (GE / ETI) businesses| **Send** e-invoices; e-reporting starts. **2027-09-01**| SMEs & micro-entrepreneurs| **Send** e-invoices. Continuous| All| Lifecycle status reporting (e-reporting) within 24h. DGFiP can — and does — shift these dates The reform was already postponed once (from 2024 to 2026). Watch the [changelog](<../../changelog.html>); we update this page within 24h of any official communication. ## Background — Y- and 4-corner models France adopted the **Y-model** (also called the "5-corner model"): every invoice flows through a registered platform — the public PPF or a private **PA** (Plateforme Agréée, formerly PDP) — which forwards it to the recipient's platform _and_ sends a copy of the headers to PPF for tax-administration. [code] Sender → PA / PDP (Flowie) → [PPF receives extract for e-reporting] → Recipient PA / PDP → Recipient ERP [/code] PA vs PDP — what changed in 2025 The original 2024 ordonnance used the term **PDP** (_Plateforme de Dématérialisation Partenaire_). A 2025 DGFiP rename to **PA** (_Plateforme Agréée_) modernised the label, but the legal regime, accreditation process, and our number (`0064`) are unchanged. Most existing contracts, market communication, and even the [official DGFiP list]() URL still say "PDP". This page leads with PA but keeps PDP visible — both refer to the same thing. The "4-corner" model (used by Italy, Belgium, and the rest of Peppol) is corner1→corner2→corner3→corner4 without the central tax-administration leg. France's 5th corner is the leg to PPF. ## Flowie's PA (Plateforme Agréée) status Field| Value ---|--- PA / PDP number| `0064` Legal entity| Flowie SAS SIREN| `987 654 321` Authorized formats| Factur-X 1.0.07, UBL 2.1 (Peppol BIS 3), UN/CEFACT CII D16B Authorized flows| B2B, B2G, B2C-receipt Audit certificate| [PDP-0064-2026.pdf]() DGFiP listing| [impots.gouv.fr/pdp]() ## Required fields for French invoices The Peppol BIS 3.0 schema is mandatory; PPF adds a CIUS-FR profile on top. The most common gotchas: * document.buyerReferencestringrequired for B2G **"Service Exécutant"** code given to you by the public buyer. Without it, PPF rejects with `00058`. * document.orderReferencestringrequired for B2G **"Engagement Juridique"** — public-procurement commitment number. * seller.additionalIdentifiers[siret]14 digitsrequired SIRET (the SIREN + 5-digit establishment code). Flowie populates this from the registry on company creation. * document.note (Cadre de facturation)enumrequired A1 (basic), A2 (deposit), … A24 (auto-billing). Defaults to A1; pass another only if you know what you're doing. * document.payment.ibanFR-IBANoptional Required for credit-transfer payments. PPF doesn't enforce, but most public buyers do. ## Routing & the Annuaire PPF maintains the **Annuaire** — the official directory of every French business and which PDP it uses. Flowie syncs nightly. To look up a French recipient's preferred PDP: [code] curl …/afnor/directory-service/v1/siret/code-insee:12345678900012 \ -H "Authorization: Bearer $KEY" [/code] The response includes the recipient's PDP code. We use this automatically on every send to a French Peppol ID — you never need to look it up yourself. ## Lifecycle statuses (statuts du cycle de vie) New — the interactive lifecycle reference This section is the summary. The full referential — animated state diagram, mandatory / recommended / libre filters, per-status API code, CDAR field guide — lives on the [**Lifecycle explorer**](); the end-to-end implementation path (webhook handler, responsibility matrix, go-live checklist) is the [**Integration playbook**](). The B2B reform mandates _both_ e-invoicing and _e-reporting_ — transmission of each invoice's lifecycle status to the DGFiP via your PA (Plateforme Agréée). The status set is fixed by AFNOR **XP Z12-012** (CDAR field `MDT-105`): **14 codes, 200–213** , in three tiers — **4 obligatoires** (200, 210, 212, 213 — always produced, always reach the DGFiP concentrator), **5 recommandés** (203, 204, 205, 206, 211) and **5 libres** coded statuses (201, 202, 207, 208, 209 — optional between platforms). Anything outside 200–213 is a custom status that carries _no_ official code and is _not_ transmitted to the PPF. Flowie emits these automatically when you call [`POST /documents/{id}/lifecycle`](<../../reference/index.html#update-lifecycle>). The 4 **mandatory** statuses (the ones the PPF/DGFiP require): Code| Statut (FR)| Meaning| Flowie lifecycle ---|---|---|--- `200`| Déposée| The sending platform attests the invoice is received, checked & compliant — start of the lifecycle.| `submitted` `210`| Refusée| The buyer refuses the invoice in full — a deliberate **business / commercial** refusal (see [below](<#refus-rejet>)). Auto-cancels the invoice.| `rejected` (buyer) `212`| Encaissée| The supplier confirms payment received (partial or full). Feeds the VAT (CA3) pre-fill.| `paid` `213`| Rejetée| A functional control at the sending or receiving platform detected an anomaly — a **technical / format** rejection (see [below](<#refus-rejet>)). Auto-cancels the invoice.| `failed` The 10 **optional** statuses — 5 _recommandés_ and 5 _libres_ — emit them when the corresponding business event happens; they give your counterparty visibility but are not strictly required (and a platform must never fail an invoice because one didn't arrive): Code| Statut (FR)| Tier| Meaning ---|---|---|--- `201`| Émise par la plateforme| Libre| The sender's PA confirms it transmitted the invoice to the recipient's PA. `202`| Reçue par la plateforme| Libre| The recipient's PA confirms receipt from the sender's PA. `203`| Mise à disposition| Recommandé| The recipient's PA has made the invoice available to the recipient. `204`| Prise en charge| Recommandé| The recipient acknowledges receipt of the invoice. `205`| Approuvée| Recommandé| The recipient accepts the invoice in full. `206`| Approuvée partiellement| Recommandé| The recipient accepts the invoice only partially. `207`| En litige| Libre*| The recipient disputes all or part of the invoice _without_ a full refusal. `208`| Suspendue| Libre| The recipient requests supporting documents; processing is suspended. `209`| Complétée| Libre| The supplier has supplied the awaited documents (resolves `208`). `211`| Paiement transmis| Recommandé| The recipient confirms the invoice was paid (or the supplier confirms a refund). * `207 En litige` reads _libre_ in the v2.3 transmission table; some industry readings class it _recommandé_. Optional either way — see the [tier guide](). **Chorus Pro statuses are not B2B codes.** _"Mise en paiement"_ and _"Mandatée"_ belong to the legacy Chorus Pro (B2G public-sector) flow, not the B2B 200–213 set. Their closest B2B equivalent is `211 Paiement transmis`; in a PPF/B2B context treat them as _libre_. Likewise the code ranges `250/251/282` (données réglementaires), `300/301/303/304` (e-reporting), `400/401` (annuaire) and `500/501` (flux) are **not** invoice business statuses — don't map a lifecycle to them. Listen for `compliance.reported` webhooks to know when each report lands. `compliance.reported.failed` means the PPF rejected the submission itself — see codes below. ## Refus (210) vs rejet (213) — and their motifs **Dedicated reference:** for the full treatment of the negative statuses — `210 Refusée`, `213 Rejetée` and `208 Suspendue` (on hold), with the global-process diagram, per-status API calls and the cross-country mapping — see [Refusal, rejection & on-hold](). The summary below covers refus vs rejet. Both statuses are **mandatory** and both **auto-cancel** the invoice (VAT cancelled; the supplier must issue a corrected invoice, and an _avoir_ /credit note if the original was already accepted). They differ in _who_ says no and _why_ : | `213 — Rejetée` (rejet)| `210 — Refusée` (refus) ---|---|--- **Who**| A platform (sending PA, receiving PA, or the PPF) — _automatic_.| The buyer / recipient — a _human, deliberate_ decision. **Why**| Non-conformity: technical / syntactic / semantic / regulatory.| A business disagreement about a _valid, well-formed_ invoice. **When**| _Before_ the buyer validly processes the invoice. The norm splits it into **« Rejetée à l'émission »** (sender-side — never validly issued) and **« Rejetée en réception »** (caught receiving-side).| _After_ a valid invoice has been delivered to and seen by the buyer. ### Typical rejet (213) triggers * Invalid or corrupt format (Factur-X / UBL / CII not EN 16931-compliant) * Failed syntactic or semantic / coherence control (BR-FR-CTC schematron) * Antivirus failure, or an attachment over the size limit * Invoice-number uniqueness breach (duplicate for this seller) * Invalid SIREN / SIRET against the PPF annuaire * Addressing / routing error — _destinataire introuvable_ * A mandatory data element is missing ### Typical refus (210) reasons * Amount inconsistent with the order / quote / contract * Contested unit price or quantity * Goods not delivered / service not rendered * Double-billing / duplicate of an invoice already received * A mandatory legal mention is missing (CGI art. 242 nonies A) * Payment terms that don't match the contract **The motif is coded — and the codes circulating online are fake.** For _both_ `210` and `213` (and « Rejetée à l'émission »/« en réception ») the CDAR status message carries the reason in **two fields** : `MDT-113` (_ReasonCode_ , a coded value drawn from a restricted controlled vocabulary — rule `BR-FR-CDV-CL-09`) plus an optional free-text `MDT-114` (_Reason_). The literal motif strings widely circulated by vendor blogs and AI summaries — `TX_TVA_ERR`, `REJ_UNI`, `REJ_COH`, `REJ_ADR`, `CMD_ERR`, `DOUBLE_FACT`, `ROUTAGE_ERR`, `CALCUL_ERR` ("~45 codes / 6 families") — **do not appear anywhere in the official AFNOR XP Z12-012** ; they are fabricated. The authoritative `Code motif → Libellé` list lives _only_ in the **« Tableau des motifs de STATUTS »** sheet of the XP Z12-012 Excel annex inside the _Spécifications externes B2B_ ZIP (current v3.2). Flowie surfaces whatever `MDT-113`/`MDT-114` the platform returned verbatim on the `document.lifecycle` webhook and on `GET /v1/documents/{id}` — we do **not** invent a code. _(International mapping note:`BR-FR-CDV-CL-05` maps refus to UNTDID-1373 status `50 — Rejected`, distinct from the French 210/213.)_ ## French use cases & frameworks Two different vocabularies get conflated here. The **B2B reform** uses the **cas d'usage of AFNOR XP Z12-014**. The older **cadres de facturation** (`A1`, `A2`…) are a **legacy Chorus Pro (B2G public-sector)** construct that the B2B reform does _not_ reuse. And `A / B1 / B2 / C` are the **circuits of the schéma en Y** (which party uses the PPF vs a PA) — _not_ invoice types. ### B2B vs e-reporting — the big split * **B2B domestic** invoices flow as structured e-invoices through PA platforms — this is _e-invoicing_. * **B2C, international and intra-community** transactions are covered by _e-reporting_ — you transmit transaction / payment _data_ to the DGFiP, you do **not** exchange a structured invoice through the PPF/PA network. ### Cas d'usage B2B (AFNOR XP Z12-014) XP Z12-014 enumerates the B2B business scenarios in three families (data-implementation, third-party, lifecycle-impacting). The count grows by version: **v1.2 = 42 cas** (Oct 2025), **v1.3 = 44 cas** (Feb 2026, adds case 43 international B2B e-reporting + case 44 DROM/COM/TAAF), and **v1.4 = 45 cas** (30 June 2026, adds case 45 _auto-facture bidirectionnelle_). This is the summary; the full referential — all 45 numbered, a deep dive on every theme, and how to model each with Flowie — is on the dedicated [**Use cases (XP Z12-014)**]() page. The scenarios most relevant to an integration: Theme| Cas d'usage| Summary ---|---|--- **Acompte** (advance / deposit)| 20–21, 32, 24, 34| A deposit invoice (_facture d'acompte_), then a final invoice referencing it. 32 = monthly payments; 24 = arrhes; 34 = partial collection / cancellation. **Avoir / facture rectificative** (credit note)| _no standalone case_| A credit note is a first-class _document type_ that must reference the original invoice and travel the same circuit — it is **not** its own numbered case. (18 = notes de débit; 22a/22b = escompte.) **Autofacturation** (self-billing)| 19b, 23, 19a, 17b| The buyer or a third party issues the invoice for the seller (e.g. a marketplace). 19a = tiers facturant sous mandat; 23 = particulier ↔ pro. **Autoliquidation** (reverse charge)| _no dedicated case_| Handled as a **VAT mention / attribute** on the invoice, not a numbered case. (13 = sous-traitance paiement direct; 14 = co-traitance B2B.) **Tiers payeur / mandats / débours**| 2–12, 15, 16, 17a, 39| Third-party payers, payment intermediaries, transparent intermediaries (débours), subrogation. **TVA — régimes particuliers**| 25, 29, 33, 42, 30| Gift vouchers/cards (25); assujetti unique / VAT group (29); TVA sur marge (33); détaxe (42); TVA déjà collectée B2C↔B2B bridge (30). **B2C → e-reporting**| 27, 28, 30| Toll (27), restaurant receipts (28), B2C e-reporting bridge (30) — these are _e-reporting_ , not invoice exchange. **International / intracommunautaire → e-reporting**| 43 (43a/43b), 44| Foreign-party and intra-community operations reported as _data_ ; 44 = DROM/COM/TAAF. **Edge cases**| 1, 31, 35, 36, 37, 40, 41| Multi-order/multi-delivery (1), mixed (31), notes d'auteur (35), secret professionnel (36), SEP (37), netting/compensation (40), barter (41). ### Cadres de facturation (legacy Chorus Pro / B2G) If you see `A1`…`A25` in a Flowie flow, that is the **Chorus Pro (public-sector)** mapping — _what_ document is deposited and by _whom_ — carried over for B2G, not part of the new B2B reform. The most common: Cadre| Meaning ---|--- `A1`| Dépôt par un fournisseur d'une facture (à régler ou avoir) — the standard case, the vast majority. `A2`| Dépôt d'une facture déjà payée (e.g. carte d'achat). `A3`| Dépôt d'un mémoire de frais de justice. `A4` / `A5` / `A7` / `A8`| Works contracts: projet de décompte mensuel (A4), état d'acompte (A5), projet de décompte final (A7), décompte général & définitif signé (A8). `A9` / `A10`| Demande de paiement d'un sous-traitant (A10 = marchés de travaux). `A12`| Facture / demande de paiement d'un cotraitant, validée par le mandataire. `A13`–`A25`| Further works décomptes by cotraitant, MOE (maîtrise d'œuvre) or MOA (maîtrise d'ouvrage). _(No`A11` or `A21` exist in the transmission table.)_ ## PPF error codes Code| Meaning| Fix ---|---|--- `00025`| Invoice number doesn't follow PPF pattern.| Use alphanumeric only; max 20 chars; no special characters except `-` and `/`. `00043`| Duplicate invoice number for this seller.| Increment your numbering. PPF tracks (sellerSiret, number) tuples. `00058`| Missing Service Exécutant for public buyer.| Set `document.buyerReference`. `00104`| SIRET unknown in Annuaire.| Buyer hasn't registered yet — they must onboard before you can invoice them. `00200`| Schema validation error.| Inspect `error.details[]` — usually a missing required field. `00306`| Recipient PDP rejected.| Read the recipient PDP's reason; often Cadre de facturation mismatch. `00500`| PPF temporarily unavailable.| We retry automatically; you'll see `compliance.reported` when it recovers. ## Testing your French integration Use these sandbox primitives: What you want to test| How ---|--- Happy-path PPF acceptance| Company VAT `FR12345678901`; `simulateCompliance: "accept"`. Service-Exécutant rejection| `simulateCompliance: "reject_00058"`; send without `buyerReference`. Annuaire miss| Send to `0009:00000000000000` → PPF returns `00104`. PPF outage| `simulateCompliance: "timeout_30s"` — exercise circuit breaker. 10-minute paid batching| Mark as `paid`; use [time-travel](<../../sandbox/index.html#test-clock>) to skip 10 min and watch the report fire. ## FAQ ### Why does this page mix "PA" and "PDP"? PDP (_Plateforme de Dématérialisation Partenaire_) was the original name used by the 2021 ordonnance and the 2024 reform documentation. The DGFiP renamed it to **PA** (_Plateforme Agréée_) in 2025. The legal regime, the accreditation criteria, and our number (`0064`) are unchanged — only the label moved. Both terms appear in market communication; we lead with PA but keep PDP visible because every existing contract, every backup of the DGFiP list, and most ERP integrations still use PDP. ### Do I need a separate contract with the DGFiP? No. Your contract with Flowie covers PA / PDP services. We handle the DGFiP relationship. ### What happens if Flowie loses PA / PDP status? PA authorization is renewed every 3 years. If for any reason ours lapses, we have a contractual fallback to route through PPF directly — your integration doesn't change. Discounted period guaranteed for any disruption. ### Can I use my own PA / PDP for some invoices? Yes — set `settings.preferredPDP` on the company. We fall back to your choice when the recipient's PA allows it. ### Does Factur-X count as e-invoice or PDF? Both. Factur-X is a hybrid — a human-readable PDF/A with a structured XML embedded. PPF accepts it as e-invoice; recipients can render the PDF if they don't process the XML. Flowie generates Factur-X by default for FR domestic invoices. ## References **Primary sources** (French government & EU regulator): * [impots.gouv.fr · Facturation électronique]() — DGFiP's official taxpayer portal; mandate scope, calendar, FAQ. * [impots.gouv.fr · Liste officielle des PDP]() — Authoritative list of registered _Plateformes de Dématérialisation Partenaires_. * [CEDEF (Bercy) · Facturation électronique]() — Ministry of Economy explainer; legal-text references. * [Ordonnance n° 2021-1190 du 15 septembre 2021]() — Foundational legal text creating the e-invoicing obligation (Légifrance). * [Loi de finances 2024 · Article 91]() — Article that re-set the calendar to September 2026 / 2027. * [EU Commission · eInvoicing in France]() — Pan-EU reference factsheet. * [OpenPeppol · France profile]() — Authoritative Peppol facts (FR is a Peppol Authority since 2025). * [Flowie · PDP authorization (number 0064)]() — Our DGFiP-issued PDP certificate. **Industry analyses** (independent confirmation of the timeline): * [PwC France · Réforme de la facturation électronique]() — Big-4 implementation analysis. * [FNFE-MPE · Forum national de la facture électronique]() — Industry consortium tracking the reform. ======================================================================== # France · Invoice lifecycle referential (statuses 200–213) # Source: https://docs.get-flowie.com/compliance/fr/lifecycle.html ======================================================================== --- title: "France · Invoice lifecycle explorer (statuts 200–213)" description: "Interactive reference for the French e-invoicing lifecycle: all 14 AFNOR XP Z12-012 statuses (200–213), mandatory vs recommended vs free, who emits each one, the allowed transitions, and the exact Flowie API call that emits or observes every status." canonical: "https://docs.get-flowie.com/compliance/fr/lifecycle" source: "https://docs.get-flowie.com/compliance/fr/lifecycle.html" --- # France · Invoice lifecycle explorer (statuts 200–213) Compliance · 🇫🇷 France # The French e-invoicing lifecycle — interactive reference Every invoice exchanged under the French reform carries a **cycle de vie** — a sequence of statuses fixed by AFNOR **XP Z12-012** (CDAR data element `MDT-105`): **14 codes, 200–213**. Four are **obligatoires** (200, 210, 212, 213 — always transmitted to the DGFiP concentrator), five are **recommandés** (203, 204, 205, 206, 211), and the remaining five coded statuses are **libres** (201, 202, 207, 208, 209 — optional between platforms). Anything outside the referential is a custom status that never leaves your own tooling. This page is the full referential: who emits each status, in which phase, the allowed transitions — and the exact API call that emits or observes it with Flowie (Plateforme Agréée n° `0064`). 200 Déposée 201 Émise 203 Mise à disposition 205 Approuvée 211 Paiement transmis 212 Encaissée ## Interactive explorer Filter by tier, click any status for its full definition and the code to add, or press play to watch an invoice travel the network — each step logs the webhook Flowie fires. Show All 14 Mandatory 4 Recommended 5 Free / libre 5 Play ▶ Happy path ▶ Dispute resolved ▶ Suspension ▶ Refusal (210) ▶ Platform reject (213) Mandatory (obligatoire) Recommended (recommandé) Free (libre, coded) Happy terminal Transition Possible when intermediate statuses are skipped Click a status in the diagram — or play a scenario — to see its definition, its transitions, and the exact API call that emits it. ## The three tiers — obligatoire, recommandé, libre The official referential (the « Transmission » column of the DGFiP external specifications, kept by AFNOR XP Z12-012) classifies the 14 coded statuses in three tiers — and everything outside the referential forms a fourth, uncoded family: Tier| Statuses| What it means| Transmitted? ---|---|---|--- **Obligatoire** (mandatory) | `200` Déposée, `210` Refusée, `212` Encaissée, `213` Rejetée | Must be emitted whenever the corresponding event occurs. `200` opens every lifecycle; `212` carries the collected amount (`MEN`) that feeds the VAT-on-collection (CA3) pre-fill; `210`/`213` cancel the invoice and require a coded motif. | Yes — always, including to the **PPF concentrator** (within the 24-hour reporting window). **Recommandé** | `203` Mise à disposition, `204` Prise en charge, `205` Approuvée, `206` Approuvée partiellement, `211` Paiement transmis | Officially recommended _“pour assurer le bon déroulé des échanges”_. Optional — but they are what gives your counterparty (and your own AR/AP team) real-time visibility. A serious integration emits them. | Between platforms, when emitted. **Libre** (coded) | `201` Émise, `202` Reçue, `207` En litige†, `208` Suspendue, `209` Complétée | Part of the referential — coded, interoperable, defined semantics — but entirely at each platform’s / party’s discretion. Not every platform supports receiving them. | Between platforms, when emitted & supported. **Custom** (uncoded) | unbounded | Internal workflow states like _bon à payer_ or _exportée en compta_. No `MDT-105` code, no CDAR — see [modelling them with tags](<#libres>). | Never. † `207 En litige` is the one contested cell: the v2.3 dossier’s transmission table reads _libre_ , while several industry readings (and some trainings) class it _recommandé_. Treat it as optional either way; we track every annex revision and will update this page if the classification moves. The 2024 PPF pivot changed what “mandatory” binds Since the October 2024 pivot (PPF reduced to _annuaire_ \+ data concentrator), the operative rule is: the **4 obligatoires are always produced and always reach the DGFiP** ; the other 10 coded statuses are **optional between platforms** — a platform must not fail an invoice because a recommended status never arrived. The three-tier vocabulary survives in the AFNOR annexes and in practice; both framings are shown here. Mandatory ≠ emitted by you “Mandatory” binds the _platform_ (the PA), not your integration, for `200` and `213` — Flowie emits those automatically. Your integration is on the hook for the business decisions only: `210 Refusée` when the buyer refuses, `212 Encaissée` when the supplier is paid. The [cheat-sheet below](<#cheatsheet>) says exactly which side emits what. ## The 14 statuses of the referential (MDT-105) The referential splits in two phases, carried in the CDAR’s `MDT-77` type code: **Transmission** statuses (`305` — produced automatically by the platforms as the invoice moves) and **Traitement** statuses (`23` — business decisions produced by the buyer or the supplier). Code| Statut (FR)| English| Tier| Phase| Emitted by| Meaning ---|---|---|---|---|---|--- `200`| Déposée| Deposited| Mandatory| Transmission| Seller’s PA| The sending platform attests the invoice is received, checked & compliant — start of every lifecycle. Invoice data reaches the PPF within 24 h of this timestamp. `201`| Émise par la plateforme| Issued by platform| Libre| Transmission| Seller’s PA| The seller’s PA confirms it transmitted the invoice to the recipient’s PA. `202`| Reçue par la plateforme| Received by platform| Libre| Transmission| Buyer’s PA| The recipient’s PA confirms receipt from the sender’s PA (not yet visible to the buyer). `203`| Mise à disposition| Made available| Recommended| Transmission| Buyer’s PA| The invoice is available to the buyer on their platform. `204`| Prise en charge| Acknowledged| Recommended| Traitement| Buyer| The buyer acknowledges the invoice and starts processing it. `205`| Approuvée| Approved| Recommended| Traitement| Buyer| The buyer accepts the invoice in full. `206`| Approuvée partiellement| Partially approved| Recommended| Traitement| Buyer| The buyer accepts the invoice only in part — carries the approved / non-approved amount blocks (`MAP`/`MNA`); usually followed by a credit note. `207`| En litige| In dispute| Libre†| Traitement| Buyer| The buyer disputes all or part of the invoice _without_ refusing it outright. Motif required. Resolves to approval or refusal. `208`| Suspendue| Suspended| Libre| Traitement| Buyer| Processing is suspended pending supporting documents from the supplier. Motif required. `209`| Complétée| Completed| Libre| Traitement| Supplier| The supplier delivered the awaited material — resolves `208`. Complementary data travels in the status message (`MDG-43`, code `MAJ`); the invoice itself is _not_ re-sent. `210`| Refusée| Refused| Mandatory| Traitement| Buyer| Deliberate **business** refusal of a valid invoice. Terminal — cancels the invoice; coded motif from the restricted list required. `211`| Paiement transmis| Payment sent| Recommended| Traitement| Buyer| The buyer confirms the payment was sent (or the supplier confirms a refund). Amount blocks `MPA` (paid) / `RAP` (remainder). `212`| Encaissée| Collected| Mandatory| Traitement| Supplier| The supplier confirms funds received (partial or full). Must carry the collected amount (`MDT-207 = MEN`, rule `BR-FR-CDV-14`) — this is the e-reporting payment-data vehicle behind the VAT (CA3) pre-fill for services. Terminal. `213`| Rejetée| Rejected| Mandatory| Transmission| Either PA| **Technical** rejection by a platform control (format, SIRET, duplicate…). Terminal — the invoice was never validly exchanged. The norm splits it into _rejetée à l’émission_ and _rejetée en réception_ ; coded motif required. Two frequent third-party errors **“The buyer emits Encaissée”** — no: `211` is the buyer saying _payment sent_ ; `212 Encaissée` is emitted by the **supplier** (both map to UNTDID 1373 code `47 Paid`, which is why they get conflated). And **“there is a code 214 Visée”** — _Visée_ / _Mise en paiement_ belong to the legacy Chorus Pro **B2G** flow; the B2B referential stops at `213`. For the crucial difference between `210 Refusée` (business refusal by the buyer) and `213 Rejetée` (technical rejection by a platform), plus `208 Suspendue` (on hold) and their coded motifs (`MDT-113`/`MDT-114`) — see the dedicated [Refusal, rejection & on-hold]() reference (or the [summary on the France overview]()). ## Status → Flowie API cheat-sheet One table to bookmark. _Automatic_ means Flowie emits the status for you — you only observe it (webhook `lifecycle.updated`, or [`GET /v1/documents/{id}/lifecycle`](<../../reference/index.html#get-lifecycle>)). Everything else is one call to [`POST /v1/documents/{id}/lifecycle`](<../../reference/index.html#update-lifecycle>). Code| Statut| Your side| What you do ---|---|---|--- `200`| Déposée| Supplier| **Automatic** — emitted when your `POST /v1/documents/send` passes controls. `201`| Émise par la plateforme| Supplier| **Automatic** — observe via webhook `document.sent`. `202`| Reçue par la plateforme| Supplier| **Automatic** — observe via webhook `document.delivered`. `203`| Mise à disposition| Buyer| **Automatic** — your inbound webhook `document.received` fires; the invoice is in your queue. `204`| Prise en charge| Buyer| `POST …/lifecycle {"status":"under_review"}` `205`| Approuvée| Buyer| `POST …/lifecycle {"status":"approved"}` `206`| Approuvée partiellement| Buyer| `POST …/lifecycle {"status":"approved", "remainingAmount": …}` — the remaining amount signals a partial approval. `207`| En litige| Buyer| `POST …/lifecycle {"status":"disputed", "reason": "…"}` `208`| Suspendue| Buyer| `POST …/lifecycle {"status":"disputed", "reasonCode":"suspended", "reason":"…"}` — the `suspended` reason code makes Flowie emit `208` instead of `207`. `209`| Complétée| Supplier| Attach the requested material: `POST …/actions {"action":"link","relatedDocumentId":"…"}` (or `add-note`) on a suspended invoice — Flowie emits `209`. `210`| Refusée| Buyer| `POST …/lifecycle {"status":"rejected", "reasonCode":"…", "reason":"…"}` — reason is forwarded verbatim as `MDT-113`/`MDT-114`. `211`| Paiement transmis| Buyer| `POST …/lifecycle {"status":"paid", "paymentDate":"…"}` — from the _buyer_ org, this emits `211`. `212`| Encaissée| Supplier| `POST …/lifecycle {"status":"paid", "paymentDate":"…", "paymentAmount": …}` — from the _supplier_ org, this emits `212`. Partial collection: use `"partially_paid"` \+ `remainingAmount`. `213`| Rejetée| Supplier| **Automatic** — a platform control failed. Observe webhook `document.failed` (or `compliance.reported.failed`), fix, and re-send. Same call, two codes — 211 vs 212 The norm distinguishes _who states_ that money moved: the buyer saying “payment sent” is `211`; the supplier saying “funds received” is `212` (the mandatory one, since it drives VAT on encaissements). With Flowie you make the same `{"status":"paid"}` call from either side — the party role on the document decides which code is transmitted. ## Skipping statuses — what a minimal legal flow looks like Because only 4 of the 14 statuses are mandatory, a perfectly legal lifecycle can be as short as `200 → 212` (deposited, then collected) — or `200 → 210` / `200 → 213` when things go wrong. The recommended statuses are not checkpoints: an invoice does _not_ have to pass through `204` to be approved, and a buyer may refuse (`210`) without ever emitting `207 En litige` first. Use the **Mandatory** filter in the explorer above to see the minimal graph. Two consequences for your integration: * **Never assume ordering.** Your webhook consumer must accept `lifecycle.updated` events that jump tiers (e.g. straight from `submitted` to `paid`). Idempotent, out-of-order-tolerant handlers are the norm — see the [playbook’s reference handler](). * **Emit generously, consume defensively.** Emitting the recommended (and even the libre) statuses costs one API call each and materially improves your counterparty’s (and your own) visibility — but never _require_ them from the other side: a platform is not allowed to fail an invoice because an optional status never arrived. ## Canonical scenarios The five playable scenarios in the explorer, in prose — these are the flows to test before go-live: Scenario| Status sequence| Outcome ---|---|--- **Happy path**| `200 → 201 → 202 → 203 → 204 → 205 → 211 → 212`| Invoice approved and paid; VAT pre-fill fed by `212`. **Dispute resolved**| `200 … 204 → 207 → 205 → 211 → 212`| Buyer contests (`207`), parties settle, approval and payment proceed. **Suspension**| `200 … 204 → 208 → 209 → 205 → 211 → 212`| Buyer requests supporting documents (`208`); supplier completes (`209`); flow resumes. **Refusal**| `200 … 203 → 204 → 210`| Business refusal by the buyer. Invoice cancelled; supplier must issue a corrective invoice (and an _avoir_ if it was already accepted). **Platform reject**| `200 → 201 → 213`| Technical rejection (format, SIRET, duplicate…). The invoice never legally existed on the network; fix and re-send. ## How statuses travel — the CDAR message Between platforms, a lifecycle status is not a bare number: it travels as a **CDAR** (_Cross Domain Acknowledgement and Response_) message — the UN/CEFACT `CrossDomainAcknowledgementAndResponse` document, which France pins to the **D22B** XSD and constrains with the `BR-FR-CDV` Schematron rules. It is the **only** lifecycle syntax in the French _socle minimal_ — the UBL `ApplicationResponse` familiar from Peppol is _not_ an accepted syntax for the French CDV flux. The fields that matter: Field| Content| Example ---|---|--- `MDT-77`| _TypeCode_ — the phase: `305` = transmission (platform-generated), `23` = traitement (business decision)| `23` `MDT-105`| _ProcessConditionCode_ — the status code from the 200–213 referential (+ its label in `MDT-106`)| `210` `MDT-88`| _StatusCode_ — optional generic UNTDID 1373 equivalent, for international coherence (see mapping below)| `50` `MDT-113`| _ReasonCode_ — coded motif from the restricted vocabulary of the XP Z12-012 annex (rule `BR-FR-CDV-CL-09`). Required for `210`/`213` (and expected for `206`/`207`/`208`).| `(see the official « Tableau des motifs de STATUTS » annex)` `MDT-114`| _Reason_ — optional free text| `"Prix unitaire ligne 3 non conforme au devis"` `MDG-43` / `MDT-207`| Characteristic blocks qualifying amounts & data: `MEN` collected (mandatory on `212`), `MPA`/`RAP` paid & remainder (`211`), `MAP`/`MNA` approved & non-approved (`206`), `MAJ` replacement data (`209`)| `MEN = 2359.50` `MDT-87` \+ `MDG-35` \+ `MDG-40`| Invoice identification: number + issue date + seller party (SIREN) — one CDAR references one invoice, one status| `FA-2027-0042` Flowie builds, signs and routes CDAR messages for you in both directions: your `POST …/lifecycle` becomes an outbound CDAR; inbound CDARs from the buyer’s platform become `lifecycle.updated` webhooks with `reasonCode`/`reason` passed through **verbatim** — we never invent or re-map a motif (the motif code lists circulating on vendor blogs are [largely fabricated](); trust only the AFNOR annex). ### International mapping — UNTDID 1373 & Peppol Each French code has a generic UNTDID 1373 equivalent (`BR-FR-CDV-CL-05`), carried in `MDT-88`: FR| → UNTDID 1373| FR| → UNTDID 1373 ---|---|---|--- `200` Déposée| `10`| In preparation| `207` En litige| `46`| Litigious `201` Émise| `51`| Issued| `208` Suspendue| `39`| Suspended `202` Reçue| `43`| Received| `209` Complétée| `37`| Complete `203` Mise à disposition| `48`| Available| `210` Refusée| `50`| Rejected `204` Prise en charge| `45`| In process| `211` Paiement transmis| `47`| Paid `205` Approuvée| `1`| Accepted| `212` Encaissée| `47`| Paid `206` Approuvée part.| `49`| Cond. accepted| `213` Rejetée| `8`| Rejected (tech.) **Peppol is a different layer.** Peppol status flows use UBL `ApplicationResponse` twice — the _Message Level Response_ (validation outcome) and the _Invoice Response_ (buyer decision, 7-code UNCL4343 subset: `AB IP UQ CA RE AP PD`). There is **no official normative table** mapping the French 2xx codes to UNCL4343 as of mid-2026; the informal overlaps (`205≈AP`, `206≈CA`, `210≈RE`, `211/212≈PD`…) break down for the transmission phase, where `200`/`213` correspond to Peppol’s MLR / transport-receipt layer rather than to an Invoice Response. When you exchange cross-border through Flowie we translate at the edge and always keep the French codes authoritative for the DGFiP leg. ## Custom statuses — your workflow, off the wire Beyond the five _coded_ libre statuses (201, 202, 207, 208, 209 — which do travel between platforms when supported), anything outside 200–213 is a _custom_ status: useful, unregulated, and strictly local. Typical examples and how to model them with Flowie without polluting the regulated lifecycle: Libre status| Typical meaning| Model it as ---|---|--- _En validation interne_| Waiting on an internal approver| A tag: `POST …/actions {"action":"tag","tag":"workflow/validation-interne"}` _Bon à payer_| Cleared for payment by AP| A tag + optionally `assign` to the payer _Exportée en comptabilité_| Pushed to the ledger| A tag, set by your ERP sync after `GET …/structured` _Relance envoyée_| Dunning reminder sent| An `add-note` action with the reminder reference _Mise en paiement / Mandatée_| Legacy **Chorus Pro B2G** statuses| Treat as libre in a B2B context — their closest B2B code is `211`; see [the overview’s warning](). Tags and notes never generate a CDAR and are never reported to the DGFiP — which is exactly the point. If a state should be visible to your counterparty, use the regulated status; if it’s internal process, keep it libre. ## Machine-readable referential (JSON Schema) Everything on this page is also published as data, so your integration (or your AI agent) can consume the referential instead of scraping it: Artifact| URL| What it is ---|---|--- **Dataset** | [`/schemas/fr-lifecycle-statuses.json`](<../../schemas/fr-lifecycle-statuses.json>) | All 14 statuses — tier, phase, emitter, terminality, motif & amount-block rules (`MEN`/`MPA`/`MAP`…), UNTDID 1373 mapping, canonical transitions, and the exact Flowie call or webhook per status. **JSON Schema** | [`/schemas/fr-lifecycle-status.schema.json`](<../../schemas/fr-lifecycle-status.schema.json>) | Draft 2020-12 schema the dataset validates against — use it to type your own copy, generate models, or validate a vendored snapshot in CI. [code] const { statuses } = await (await fetch( "https://docs.get-flowie.com/schemas/fr-lifecycle-statuses.json")).json(); const mandatory = statuses.filter(s => s.tier === "mandatory"); // → 200 Déposée, 210 Refusée, 212 Encaissée, 213 Rejetée const next = Object.fromEntries(statuses.map(s => [s.code, s.transitionsTo])); // next[204] → [205, 206, 207, 208, 210] [/code] The dataset carries a semantic `version` plus the DGFiP / AFNOR spec versions it was verified against (`specVersions`); we bump it with every annex revision. The tables above and the interactive diagram are generated from the same facts — if you spot a divergence, that's a bug: [tell us](). ## Test the full lifecycle in the sandbox The [sandbox](<../../sandbox/index.html>) ships a working replica of this state machine. A five-minute session that exercises every mandatory status: 1. Send an invoice with a `flw_test_` key — status `200` is synthesized immediately. 2. Drive the buyer side: `POST …/lifecycle {"status":"under_review"}` then `{"status":"approved"}` (codes `204`, `205`). 3. Mark it paid from the supplier org (`212`) — with `simulateCompliance: "accept"` on the org, a synthetic `compliance.reported` webhook fires, exactly like the real PPF acknowledgement. 4. Now break things: `simulateCompliance: "reject_00058"` replays a PPF rejection end-to-end (`compliance.reported.failed`), and an illegal transition (e.g. `received → paid`) returns `409 invalid_transition` with the allowed next states. Full test matrix — including timeout and flaky simulators for your retry logic — in the [integration playbook](). ## References * [impots.gouv.fr · Facturation électronique]() — DGFiP official portal (mandate scope, calendar, external specifications ZIP with the XP Z12-012 annexes). * [AFNOR XP Z12-012]() — the lifecycle referential itself (statuses, CDAR profile, motif annex). The motif list lives _only_ in the « Tableau des motifs de STATUTS » sheet of the official annex. * [EU Commission · eInvoicing in France]() — pan-EU factsheet. * [FNFE-MPE]() — Forum national de la facture électronique. * [France overview]() — deadlines, required fields, PPF error codes, refus/rejet motifs. * [Integration playbook]() — ship a French-compliant integration end to end. ======================================================================== # France · Refusal (210), rejection (213) & on-hold (208) statuses # Source: https://docs.get-flowie.com/compliance/fr/refusal-rejection.html ======================================================================== --- title: "Refusal, rejection & suspension — the negative lifecycle statuses" description: "The three ways an e-invoice stops, stalls or dies: 210 Refusée (business refusal), 213 Rejetée (technical rejection) and 208 Suspendue (on hold). Codes, descriptions, who emits them and when, the exact Flowie API call, coded motifs (MDT-113/114) — for France, generalized to Italy SDI, Peppol and UNTDID 1373. Plus the supplier" canonical: "https://docs.get-flowie.com/compliance/fr/refusal-rejection" source: "https://docs.get-flowie.com/compliance/fr/refusal-rejection.html" --- # Refusal, rejection & suspension — the negative lifecycle statuses Compliance · 🇫🇷 France # Refusal, rejection & on hold — when an invoice stops, stalls or dies Most of the French lifecycle is about an invoice moving _forward_. This page is about the three statuses where it doesn't: **210 Refusée** (the buyer refuses a valid invoice), **213 Rejetée** (a platform rejects a non-conform one) and **208 Suspendue** (processing is paused pending documents). Two of them are **mandatory** and **terminal** — they cancel the invoice for VAT and force a corrective — so getting them right matters more than any happy-path status. The third is the reversible "on hold". This is the full reference: the code and label, who emits it and when, the exact Flowie call, the coded motif — and how the same three ideas surface outside France. The one distinction that trips everyone up **Rejet (213)** is a _platform_ saying an invoice is malformed _before_ it validly exists — technical, automatic. **Refus (210)** is a _buyer_ saying no to a valid invoice _after_ receiving it — a business decision. They map to _different_ AFNOR codes, carry different motifs, and put the responsibility on different parties. Conflating them is the single most common integration bug in this area. ## The three at a glance 210 · MDT-105 ### Refusée Refused — business refusal The buyer deliberately refuses a valid, well-formed invoice. Cancels it; the supplier must issue a corrective (and an _avoir_ if it was already accepted). ObligatoireTerminal · échecBuyerMotif required 213 · MDT-105 ### Rejetée Rejected — technical rejection A platform control (format, SIRET, duplicate, antivirus…) rejects the invoice. It never validly entered the network. Fix the payload and send a _new_ invoice. ObligatoireTerminal · échecPlatformMotif required 208 · MDT-105 ### Suspendue Suspended — on hold The buyer pauses processing pending supporting documents. **Reversible** : the supplier answers with `209 Complétée` and the invoice re-enters processing. LibreNon-terminalBuyerMotif expected Code| Label| Tier| Emitted by| Phase| Terminal?| Effect| UNTDID 1373 ---|---|---|---|---|---|---|--- `210`| Refusée| Obligatoire| Buyer| Traitement | Yes — **failure**| Invoice cancelled for VAT; corrective required| `50` Rejected `213`| Rejetée| Obligatoire| Any platform| Transmission | Yes — **failure**| Invoice never validly existed; re-send a new one| `8` Rejected (technical) `208`| Suspendue| Libre| Buyer| Traitement | No — reversible| Processing paused; resolves via `209 Complétée`| `39` Suspended ## Where they sit in the global process The happy path runs `200 → … → 212`. These three are the exits and the pause off that rail. `213` can fire _before_ the invoice validly enters (at emission or at reception); `210` fires _after_ the buyer has the invoice; `208` is a loop _inside_ processing that `209` unwinds. Transmission Traitement Settlement 200Déposée 204Prise en charge 205Approuvée 212Encaissée 213 Rejetée 208 209 Suspendue → Complétée 210 Refusée Happy terminal (212) Terminal failure (210, 213) Reversible hold (208 → 209) See the full 14-status graph, animated, on the [lifecycle explorer](). The rule that makes these exits legal without every intermediate status: [optional statuses may be skipped](), so a lifecycle can jump straight from `200` to `210` or `213`. ## 210 · Refusée — business refusal **What it is.** A deliberate, human decision by the **buyer** to refuse a _valid, well-formed_ invoice that was correctly delivered. It is not about format — the invoice passed every technical control — it is about the _content_ of the deal. `210` is **mandatory** , **terminal** , and **cancels the invoice for VAT** : the supplier must issue a corrective invoice, plus an _avoir_ (credit note) if the original had already been accepted. **When to use it.** After the invoice is delivered and visible to the buyer (typically after `203/204`), when a business reason makes it unacceptable: * Amount inconsistent with the order, quote or contract * Contested unit price or quantity * Goods not delivered / service not rendered * Duplicate of an invoice already received * A mandatory legal mention is missing (CGI art. 242 nonies A) * Payment terms that don't match the contract Refuse, or dispute first? `210` is final. If the disagreement might still be resolved, prefer `207 En litige` (dispute) or `208 Suspendue` (ask for documents) first — both are reversible and keep the invoice alive. Reach for `210` only when you are certain the invoice must be cancelled and re-issued. The list above is the easy half. The refusals people actually argue about are the ones where the invoice is _wrong about the buyer_ — a stale postal address, a SIRET belonging to the wrong establishment, a name that no longer matches the register. Those have their own section: [Can I refuse for that?](<#cases>) **How to emit it with Flowie.** One call, from the **buyer** org: [code] curl -X POST https://api.flowie.ink/v1/documents/{document_id}/lifecycle \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "rejected", "reasonCode": "", "reason": "Prix unitaire ligne 3 non conforme au devis" }' [/code] Flowie builds the CDAR, transmits `210` to the DGFiP (it is one of the four mandatory statuses), and fires `lifecycle.updated`. The `reasonCode`/`reason` travel **verbatim** as `MDT-113`/`MDT-114` — see [the motif section](<#motifs>). ## 213 · Rejetée — technical rejection **What it is.** An _automatic_ rejection by a **platform** control — the sending PA, the receiving PA, or the PPF concentrator — because the invoice is non-conform. The invoice **never validly entered the network**. It is **mandatory** and **terminal**. The norm splits it into **« Rejetée à l'émission »** (caught sender-side, before it ever leaves) and **« Rejetée en réception »** (caught at the recipient's platform). **What triggers it.** * Invalid or corrupt format (Factur-X / UBL / CII not EN 16931-compliant) * Failed syntactic or semantic / coherence control (`BR-FR-CTC` Schematron) * Antivirus failure, or an attachment over the size limit * Invoice-number uniqueness breach (duplicate for this seller) * Invalid SIREN / SIRET against the PPF annuaire * Addressing / routing error — _destinataire introuvable_ * A mandatory data element is missing Never mutate a rejected invoice `213` is terminal because the invoice legally never existed. You do **not** "resubmit" or patch it — you fix the payload and send a _new_ invoice (which starts its own lifecycle at `200`). Re-using the number of a `213`'d invoice is fine; re-using the number of a `210`'d one needs a corrective, because that invoice _did_ exist. **How you see it with Flowie.** `213` is **automatic** — you don't emit it, you observe it. When a control fails, Flowie fires `document.failed` (and, for the reporting leg, `compliance.reported.failed`) with the platform's motif attached. Then: * Read `reasonCode`/`reason` off the webhook (or [`GET /v1/documents/{id}`](<../../reference/index.html#get-document>)). * Fix the payload and call [`POST /v1/documents/send`](<../../reference/index.html#send-document>) again. ## 208 · Suspendue — on hold **What it is.** The **buyer** pauses processing because something is missing — a delivery note, a PO reference, a supporting document. Unlike `210`/`213`, `208` is **not terminal** : the invoice is alive, just parked. It is a _libre_ (optional) status, but a very useful one — it tells the supplier exactly what's blocking payment. **The resolution loop.** Suspension is one half of a pair: 1. Buyer emits `208 Suspendue` with a motif describing what's needed. 2. Supplier supplies the material and emits `209 Complétée` — the complementary data travels in the status message (`MDG-43`, code `MAJ`); the invoice itself is **not** re-sent. 3. The invoice returns to `204` processing, and can then be approved (`205`), partially approved (`206`), or ultimately refused (`210`). **How to emit it with Flowie.** Suspension rides the same `disputed` call as `207 En litige`; the `reasonCode` `"suspended"` is the discriminator that makes Flowie transmit `208` rather than `207`: [code] curl -X POST https://api.flowie.ink/v1/documents/{document_id}/lifecycle \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "disputed", "reasonCode": "suspended", "reason": "Bon de livraison manquant pour les lignes 4–7" }' [/code] The supplier then resolves it by attaching the requested material (which emits `209`): [code] curl -X POST https://api.flowie.ink/v1/documents/{document_id}/actions \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "link", "relatedDocumentId": "doc_..." }' [/code] ## Refus vs rejet vs litige vs suspension Four statuses say some version of "not yet / not this". They are genuinely different — here is the whole quartet side by side so you never pick the wrong one: Status| Who| About| Terminal?| Resolves to ---|---|---|---|--- `213` Rejetée| Platform (auto)| Non-conformity (technical / format / regulatory)| **Yes**| — (send a new invoice) `210` Refusée| Buyer| Business refusal of a valid invoice| **Yes**| — (corrective + avoir) `207` En litige| Buyer| Contests all/part, without refusing outright| No| `205` / `206` / `210` `208` Suspendue| Buyer| Pauses pending supporting documents| No| `209` → back to `204` Not a status: code 214 "Visée" There is no `214` in the B2B referential — it stops at `213`. _Visée_ / _Mise en paiement_ / _Mandatée_ belong to the legacy **Chorus Pro B2G** flow; their closest B2B equivalent is `211 Paiement transmis`. Treat them as custom/local statuses in a B2B context. ## Can I refuse for that? — the identity & address cases Nearly every real refusal question is a variant of one question: _the invoice is wrong about who I am or where I am — is that a refusal?_ Two answers are needed, and integrations routinely confuse them. 1. **May you?** Technically, always. **No platform adjudicates a refusal.** Nothing in XP Z12-012 restricts which motifs a buyer may invoke; your PA transmits the `210` and its `MDT-113` whatever you put in them. There is no referee who will tell you your reason was not good enough. 2. **Should you?** That is the real question, and it never depends on how wrong the data looks. It depends on **which of three layers** the error sits in — and all three get called "the customer's address" in everyday speech, which is why this is the hardest corner of the negative path. Before you copy a `reasonCode`: it has two vocabularies One field, two allow-lists, decided by the leg the status travels on. On the **French DGFiP leg** — every domestic B2B invoice through the PPF/PA network — `reasonCode` must be an **AFNOR XP Z12-012 motif** from that status's `BR-FR-CDV-CL-09` allow-list. Flowie forwards what you send **verbatim** ; it does _not_ translate between vocabularies. And the motif is **FATAL** on `206`, `207`, `208`, `210` and `213`, so a wrong one fails the CDAR schematron rather than degrading quietly. The **14 Peppol`OPStatusReason` codes** named throughout this section (`REC`, `REF`, `LEG`…) are the vocabulary for _Peppol and non-FR_ flows — read them here as the _reason family_ , and send the matching XP Z12-012 motif on a French invoice. **Simplest safe route:** omit `reasonCode` and Flowie fills the valid per-status default (`NON_CONFORME` on a `210`), then put the specifics in the free-text `reason`. See [the motif section](<#motifs>). ### Three layers that all look like "the address" Layer| Carried in| What it decides| Who catches an error ---|---|---|--- 1 · Identity _who the counterparty legally is_ | Buyer **SIREN** `BT-47` (scheme `0002`, mandatory) · buyer **SIRET** `BT-46` (scheme `0009`) · `BT-48` VAT · seller side `BT-30` / `BT-29` | The legal debtor, the taxable person, and who deducts the VAT. | Partly the platform — coherence rules (`BR-FR-09`) and the annuaire (`BR-FR-11`) → `213` 2 · Addressing _where it physically goes_ | Buyer electronic address `BT-49` — the _ligne annuaire_ reception point, addressed as `{siren}_{siret}[_{suffix}]` | Which mailbox the invoice is delivered to. Nothing else. | The platform, always → `213` _destinataire introuvable_ 3 · Description _what the invoice says about you_ | Postal address `BG-8` (`BT-50`, `BT-52`, `BT-53` — mandatory in France) · delivery address `BG-15` · company name · contacts | Nothing technical. It is the paper trail: what the invoice _states_ about the parties. | **Nobody.** Schematron checks presence and shape, never truth. Why a layer-3 error is always yours to decide A control can see that `BT-52` is _present_ and that `BT-53` _looks like_ a French post code. It cannot see that you moved out of that building in 2019. **Every wrong-but-well-formed value passes every`213` control and lands in your lap.** So "shouldn't the platform have caught this?" — no. And "can I `213` it myself?" — also no: `213` belongs to platforms. Once the invoice is in your hands, every "no" available to you is a `210`, a `207` or a `208`. ### The test: substantive, or merely formal? For layer-3 errors the whole question collapses onto one distinction. An error is **substantive** when it changes what the invoice _means_ — a different legal person, a different VAT treatment, a different amount owed. It is **formal** when the invoice still identifies both parties and the operation unambiguously, and is merely inaccurate while doing so. * **Substantive →`210` is the right call.** The invoice has to be cancelled and re-issued; there is nothing to regularize. * **Formal →`210` is available, but disproportionate.** The settled EU line is that substantive conditions prevail over formal ones, so a regularizable mention defect does not by itself cost you the deduction (CJEU _Barlis 06_ C-516/14 and _Senatex_ C-518/14). Refusing anyway is legal and it costs you: it cancels the invoice for VAT, obliges the supplier to re-issue, restarts the payment clock in practice (the corrective carries its own date), and publishes a terminal failure into the DGFiP's lifecycle feed under your name. `209 Complétée` cannot fix a wrong mention — a common wrong turn Suspending with `208` and having the supplier answer `209` is the natural reflex, and it is the wrong tool for a bad _mention_. The complementary data on `209` travels in the **status message** (`MDG-43`, code `MAJ`) — [the invoice itself is never re-sent](<#suspension>). A mandatory mention lives _inside_ the invoice, so correcting one always needs a new document: a **facture rectificative**. Use `208` when something is _missing around_ the invoice (a delivery note, a PO reference); use `207` or `210` when something is _wrong inside_ it. ### Can I refuse an invoice if the postal address is wrong but the SIRET and the addressing are correct? Layers 1 and 2 are correct: the invoice reached your real reception point and names your real SIREN and SIRET. Only `BG-8` is stale — an old street, a moved office, a merged site. Don't refuse This is the textbook **formal** defect. You are unambiguously identified by `BT-47` and `BT-46` — the identifiers the reform made authoritative — and the postal address is descriptive of that identification, not constitutive of it. Your deduction is not at risk, and `210` would cancel an invoice whose amounts are right. **What to do instead.** Two defensible playbooks, in order of preference: 1. **Tolerate and regularize** — the default. Approve (`205`), pay, and ask the supplier for a _facture rectificative_ for your file. Nothing is at risk and the payment clock never stops. 2. **Hold the line without killing the invoice** — `207 En litige` with a `LEG`-family motif (the [XP Z12-012 code](<#motifs>) on a French invoice) and free text naming the field and the correct value. Reversible: it keeps the invoice alive and the pressure on. If the supplier issues the corrective, process the new one. Go straight to `210` only where your own policy is that a defective mandatory mention is never paid. …unless the wrong address is not a typo An address error stops being formal the moment it moves the **VAT treatment**. If the address is not a stale street but a _different establishment under a different regime_ — a **DROM/COM** address on what you booked as a metropolitan supply ([cas d'usage 44]()), or a foreign branch, which pushes the operation out of domestic B2B e-invoicing and into [e-reporting]() altogether — then it is substantive. Refuse: `210` with `LEG`. The same applies to the **delivery address** `BG-15` on a goods invoice: `BR-FR-14` makes it mandatory precisely because it feeds the place of supply, so a wrong one there is usually substantive, not cosmetic. ### Can I refuse an invoice if the SIRET is wrong but the SIREN is correct? Start from the structure, because it settles most of the case on its own: **a SIRET contains its SIREN** — nine digits of SIREN plus a five-digit NIC. So a SIRET error that leaves the SIREN correct can only ever point at _another establishment of the same company_. It can never change the legal person, the taxable person, or who has the right to deduct. `BR-FR-09` encodes exactly that invariant on the seller side: the first nine digits of `BT-29` must equal `BT-30`. There is therefore no version of this case where your counterparty is wrong. There are only four versions of _how far the invoice gets_ : What the wrong SIRET actually is| Who catches it| Outcome| Your move ---|---|---|--- Its first nine digits **don't match** the stated SIREN | Schematron coherence control, sender-side | `213` **à l'émission** — it never leaves | Nothing. The supplier fixes the payload and sends a _new_ invoice — and may reuse the number, since a `213`'d invoice never existed. Well-formed, prefix matches, but the SIRET is **closed or absent from the annuaire** | Annuaire lookup at the sending or receiving PA (`BR-FR-11`) | `213` **en réception** — _destinataire introuvable_ | Nothing — you never see the invoice. A **real, active establishment of your company** — just not the one that ordered | **Nobody.** It routes and delivers normally. | Delivered — `202` / `203` / `204` | Yours to decide — see the verdict below. Correct on your side; it is the **seller's own** SIRET (`BT-29`) that is wrong, its SIREN right | Nobody, once `BR-FR-09` passes | Delivered normally | Your deduction is safe — the taxable person is the SIREN. Ask for a rectificative: it matters for the supplier's e-reporting attribution, not for your VAT. Don't refuse — and above all, not on `REC` In the third row you _are_ the counterparty. Refusing with `REC` (_Receiver unknown — the invoice is not addressed to this party_) would be factually false: with the SIREN right, the invoice **is** addressed to your legal person, and a `REC` on the record invites exactly the dispute you were trying to avoid. If you do refuse, the honest families are `REF` (references incorrect) or `LEG` — sent as the matching [XP Z12-012 motif](<#motifs>) on the French leg. `REC` belongs to the case one row up in identity: **a wrong SIREN** — and there you should refuse, because paying and deducting on an invoice addressed to another legal person is not an option. **What to do with a right-company, wrong-establishment invoice.** * **Default: route it internally and approve.** The wrong SIRET changes your cost-centre allocation, not your VAT. * **Ask for a rectificative when the establishment genuinely matters** — separately managed sites with their own accounting, or an establishment whose VAT regime differs (again **DROM/COM**). A regime difference is substantive: then `210` with `LEG`. * **If it recurs, fix it upstream, not per invoice.** The supplier is addressing the wrong _ligne annuaire_. Point them at the composed reception-point form `{siren}_{siret}[_{suffix}]` — and if you expose several reception points on one SIRET, tell them which `suffixeAdressage` to use. See [Reception-point addressing](<../../reference/index.html#reception-point-addressing>), and hand them [the supplier-side section on a wrong maille](<#wrong-maille>). ### The other cases, answered Same test throughout, applied to the errors that actually turn up. "Refuse?" answers _should you_ , not _may you_. The invoice is wrong about…| Layer| Substantive?| Refuse?| Do this (codes are Peppol families — [FR motif](<#motifs>) on a French invoice) ---|---|---|---|--- Your **SIREN** `BT-47` — a different legal person | 1| **Yes**| **Yes** | `210` · `REC`. Never pay or deduct on another entity's invoice. Your **SIRET** `BT-46`, SIREN right | 1| No| No | Approve; ask for a rectificative. `REF` if you refuse anyway — [not `REC`](<#case-siret>). Your **postal address** `BG-8` | 3| No| No | Approve; ask for a rectificative. [Full case ↑](<#case-address>) Your **VAT number** `BT-48` on a reverse-charge or intra-community invoice | 1| **Yes**| **Yes** | `210` · `LEG`. `BR-AE-03` / `BR-IC-03` make it the basis of the VAT treatment, not a decoration. The **delivery address** `BG-15` on a goods invoice | 3| Usually **yes** — it feeds place of supply| Usually | `210` · `LEG` if the VAT treatment moves; otherwise a rectificative. Your **company name** only — typo, or an old trade name | 3| No| No | Approve. Ask for a rectificative if the name is legally wrong rather than misspelled. A **missing legal mention** — the €40 recovery indemnity, late-payment penalties, the discount statement (`BR-FR-05`) | 3| No| No | `207` · `LEG` and ask. Refuse only on policy. A missing **PO / contract reference** | —| No| No | `208` · `REF` — the supplier can supply it and `209` lifts the hold. This is what suspension is for. A missing **delivery note or supporting document** | —| No| No | `208` · `REF`, then `209`. The **IBAN** differs from the one you hold on file | —| Treat as fraud until proven otherwise| Not yet | `208` · `PAY` and verify **out of band** , on a number you already had. Do not pay, and do not refuse on the assumption it is a mistake — supplier-impersonation fraud looks exactly like this. The **amount, price, quantity or items** | —| **Yes**| If unresolved | `207` · `PRI` / `QTY` / `ITM` first; `210` when the disagreement holds. Nothing — it is a **duplicate** you already received | —| **Yes**| **Yes** | `210` · `OTH` with free text naming the original invoice number. Codes are the 14 official Peppol `OPStatusReason` values — the full table is in the [API reference · status reason codes](<../../reference/index.html#reason-codes>). On the French DGFiP leg the coded motif is drawn from the XP Z12-012 annex instead; see [the motif section](<#motifs>). ### Is it still refusable? — what the transition graph allows Everything above asks _which error_ justifies a refusal. The other half of the question is _when_ : a refusal that was available yesterday may not be available today, because the lifecycle is a state machine and some states have no route to `210` left. Flowie enforces that machine — a transition that isn't in it comes back `400` `invalid_transition` rather than being silently accepted — so this is a hard answer, not a matter of judgement. Where the invoice is now| Still refusable?| The route ---|---|--- `received` · `under_review` 203 / 204 | **Yes, directly** | `rejected` is a legal target from both. You do _not_ have to dispute first — [optional statuses may be skipped](). `disputed` 207 / 208 | **Yes** | `disputed → rejected`. This is what makes `207` the safe first move: it keeps both exits open. `approved` 205 | **Not directly** | `approved → rejected` is not in the machine. Go `approved → disputed → rejected`. [Full case ↓](<#case-approved>) `partially_paid` | **Yes, via dispute** | `partially_paid → disputed → rejected`. A part-paid invoice is not yet closed. `paid` 212 | **No** | Terminal — no transitions out at all. [Full case ↓](<#case-paid>) `rejected` · `failed` 210 / 213 | **No — already refused** | Both terminal. Nothing to do but wait for the replacement invoice, which starts its own lifecycle. [Can I undo it? ↓](<#case-undo>) This is the API's finite-state machine, which is what returns `400`. It is deliberately narrower than what the AFNOR norm leaves theoretically expressible — where the two differ, the machine is the contract you integrate against. The full transition list per status is in the [machine-readable referential](<../../schemas/fr-lifecycle-statuses.json>) and on [the lifecycle explorer](). ### Can I refuse an invoice I have already approved? **Not in one call — but the route is not closed.** `approved → rejected` is not a legal transition, so a direct attempt returns `400 invalid_transition`. Dispute first, then refuse: [code] # 1 — reopen the approved invoice curl -X POST https://api.flowie.ink/v1/documents/{document_id}/lifecycle \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "disputed", "reasonCode": "", "reason": "Prix unitaire ligne 3 contesté après contrôle" }' # 2 — then refuse curl -X POST https://api.flowie.ink/v1/documents/{document_id}/lifecycle \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "rejected", "reasonCode": "", "reason": "Écart non résolu avec le devis" }' [/code] But it costs the supplier more than a normal refusal Refusing an invoice you had accepted is not the same event as refusing one you never accepted. The invoice _was_ valid and accepted, so cancelling it now requires an **_avoir_ (credit note)** in addition to the corrective invoice — the acceptance has to be unwound, not just the invoice replaced. That is why approving is the decision to be careful about: `205` is cheap to emit and expensive to reverse. If you are not sure, `207` or `208` keeps the invoice open at no cost. ### Can I refuse an invoice I have already paid? No — and there is no transition to try `paid` is **terminal** : the machine defines no transitions out of it, so there is no `disputed` back door the way there is from `approved`. A payment closes the lifecycle. Recovering money on a paid invoice is a **credit note** and, if it comes to it, a commercial or legal matter — not a lifecycle status. One distinction worth knowing before you post a payment: `partially_paid` is **not** terminal. It transitions to `paid` or `disputed`, so a part-paid invoice can still be disputed and then refused. If you are paying against an invoice you have doubts about, a partial payment leaves you an exit that a full payment does not. ### Can I undo a 210 I sent by mistake? No. `210` is terminal and there is no reverse transition `rejected` has an **empty** transition list — you cannot walk it back to `approved`, and no API call, support ticket or platform intervention re-opens it. The invoice is cancelled for VAT and the refusal has already travelled to the supplier and to the DGFiP. This is the single strongest argument for reaching for `207` instead whenever you are less than certain: `207` is free to change your mind about, `210` is not. **What actually happens next.** The supplier issues a fresh invoice, which starts its own lifecycle at `200`. Note the asymmetry with a technical rejection, which the page covers [above](<#rejection>): because a `210`'d invoice _did_ validly exist, its number cannot simply be reused — the replacement is a corrective referencing it. A `213`'d invoice never existed, so its number is free. If the refusal was your error, say so quickly and in writing: nothing in the protocol distinguishes a mistaken refusal from a deliberate one, so only the commercial conversation can. ### Can I refuse part of an invoice? Refusal is all-or-nothing — but partial _approval_ is not There is no partial `210`: a refusal cancels the whole invoice, because VAT and the invoice number attach to the document, not to its lines. What you _can_ do is accept it only in part. `206 Approuvée partiellement` is emitted by approving with a `remainingAmount` — there is no separate `partially_approved` status to set, the amount is the discriminator, exactly as `reasonCode: "suspended"` is what makes an approval-side `disputed` travel as [`208` rather than `207`](<#suspension>). [code] # 206 — accept part of the invoice, and say how much is left unapproved curl -X POST https://api.flowie.ink/v1/documents/{document_id}/lifecycle \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "approved", "remainingAmount": 240.00, "reasonCode": "", "reason": "Lignes 4-7 non livrées — 240,00 EUR non approuvés" }' [/code] A motif is **required** on `206`, and the CDAR carries the approved and non-approved amounts in their own blocks (`MAP`, `MAPTTC`, `MNA`, `MNATTC`). It is _recommandé_ , not transmitted to the DGFiP, and leads to `211` — so it settles the invoice rather than reopening it. Expect a corrective or a credit note for the difference. **So which do you reach for?** Use `206` when you know exactly what you owe and want to pay that much now. Use `207` when the disagreement is still open: name the lines with the code for it (`PRI`, `QTY`, `ITM`) plus free text, which leaves the supplier three ways out — credit the disputed lines, correct and re-issue, or convince you — and leaves you both exits (`205` or `210`). Use `210` only when the whole document has to die. And if the good lines are urgent while the bad ones are not, the cleanest commercial answer is often neither: ask the supplier to split the invoice into two documents, one payable now. ### The five-step rule of thumb 1. **Did a platform already say no?** Then it is a `213` and there is nothing for you to do but wait for the new invoice. 2. **Is a refusal still available at all?** — `paid` and an existing `210` / `213` are terminal; `approved` needs `207` first. [The transition table](<#timing>) settles it. 3. **Is the legal person wrong?** — a wrong SIREN, not a wrong SIRET. → `210` · `REC`. 4. **Does the error move the VAT treatment or the amount?** → `210` (after `207`, if it might still be settled). 5. **Otherwise it is formal.** → Approve and ask for a _facture rectificative_ , or `207` / `208` to hold the line. Not `210`. The motif is never adjudicated, but it is permanently on the record Because no one vets your reason, the discipline has to be yours. A `210` and its `MDT-113` travel verbatim to the supplier _and_ to the DGFiP, and they are terminal — so a habit of refusing formal defects becomes a visible pattern in your own lifecycle data and in your suppliers' DSO, with no offsetting benefit. Reach for `207` or `208` whenever the invoice can still be saved; keep `210` for the invoices that genuinely must die. ## I sent it to the wrong _maille_ — the supplier's side Everything above answers the buyer's question. This section answers the supplier's, and it is the most common real-world failure of the French model: the invoice is perfect and it went to the wrong **maille** — the wrong level of granularity in the recipient's addressing. France nests three of them, and only one is the one your customer actually declared. Maille| What you send| Scheme| What it means ---|---|---|--- Legal unit | **SIREN** — 9 digits| `0002` | The company. One routing declaration covering everything it receives. Establishment | **SIRET** — 14 digits| `0009` | One site of that company. A SIRET-level declaration beats the SIREN-level one. Reception point _ligne annuaire_ | `{siren}_{siret}[_{suffix}]` | — business routing, [never folded into the Peppol id](<../../reference/index.html#reception-point-addressing>) | One mailbox inside that establishment — the `suffixeAdressage` picks a service, a cost centre, an ERP queue. Getting the maille wrong has exactly **two** outcomes, and which one you got decides everything that follows: whether the invoice fiscally exists, whether you keep its number, and whether you owe an _avoir_. Read the status before you do anything else. Which of the two happened to me? **The invoice is`213 Rejetée`** — nobody received it, it never existed → [fix and re-send, same number](<#maille-rejected>). **The invoice reached`202` / `203` / `204` or beyond** — someone received it, so it exists → [avoir + facture rectificative, new number](<#maille-delivered>). There is no third case and no in-between: _delivery_ is what makes the invoice real. ### My invoice was rejected before delivery — what do I do? This is the ordinary outcome of a wrong maille, and the cheap one. A `213` is emitted by a _platform_ , automatically, before any human reads the invoice — [the 213 section](<#rejection>) lists the controls that fire it, of which _destinataire introuvable_ and an annuaire miss are the two addressing ones. The invoice never validly entered the network. No avoir, no corrective, same invoice number There is nothing to cancel, because nothing exists to cancel. Fix the recipient and send a **new document with the same number** — the per-seller uniqueness control has nothing to collide with, since the rejected one never validly existed. An _avoir_ here would be a fiscal mistake: you would be crediting an invoice that has no existence. 1. **Read the actual motif — do not guess it.** The platform's `reasonCode` and `reason` arrive on `document.failed` and are readable on [`GET /v1/documents/{id}`](<../../reference/index.html#get-document>). Be sceptical of any code you did not read off your own API response: the strings that circulate for this exact case — `ADR_ERR`, `ROUTAGE_ERR`, `DEST_INC` — [appear nowhere in the AFNOR referential](<#motifs>). 2. **Re-check the recipient in the annuaire, at the right maille.** Not the identifier you hold on file — the one they declared. [How, below.](<#maille-lookup>) 3. **Re-send with[`POST /v1/documents/send`](<../../reference/index.html#send-document>).** Never patch or "resubmit" the rejected document: `213` is terminal, and the corrected invoice is a new document that starts its own lifecycle at `200`. 4. **Do it the same day.** Nothing legally protects you here, but nothing works against you either — an invoice the buyer never received cannot have started their payment clock, and the replacement carries its own issue date. A slow fix costs you DSO, not a dispute. ### The invoice was delivered, but to the wrong establishment or service — what now? Here the maille was **technically valid** — a real SIRET, a live _ligne annuaire_ — just not the right one. Nothing failed, so nothing was rejected: the invoice routed, delivered, and is sitting in somebody's queue. That is the expensive case, and the reason is structural. **No control can tell a valid address from the intended one** ; this is [layer 2 behaving exactly as designed](<#cases-layers>). You cannot recall it There is no un-send, no re-route, and no supplier-side status that withdraws a delivered invoice. `213` belongs to platforms and the pre-delivery window has closed. The invoice exists, it has consumed its number, and it is on the lifecycle feed. From here the only exits are the buyer's (`210` / `207`) or a document of your own that cancels it. **Pick up the phone before you pick up the avoir.** The cheapest resolution involves no new document at all — and from the buyer's side it is also the _recommended_ one: an invoice that reaches the right legal person at the wrong establishment is a [formal defect they are advised not to refuse](<#case-siret>). If they will route it internally and approve it, you are done, and the original date stands. Ask before you cancel; a cancellation you initiate cannot be undone either. **If they will not — or if the establishment genuinely matters** (separately managed sites with their own accounting, or an establishment whose VAT regime differs, DROM/COM above all) — the regularisation is the standard two-document cycle: 1. An **_avoir_** (credit note) cancelling the original and referencing its number. 2. A **facture rectificative** with a **new number** , addressed to the correct maille. Both are real documents with lifecycles of their own. The original number is spent — unlike the `213` case, you do not get it back. Check the SIREN before you decide how bad this is A wrong **SIRET** with the right SIREN still reached your customer's legal person: the taxable person is right, their deduction is safe, and the fix is administrative. A wrong **SIREN** reached a _different legal person_ altogether — that buyer should refuse with `REC`, you must cancel, and none of it is negotiable. The invoice also went to a company that has no business holding it, which is a data-protection problem on top of an invoicing one. The full asymmetry is in [the buyer-side case](<#case-siret>). ### Can I re-use the invoice number after a wrong-maille send? After a `213`, yes. After delivery, no. Number continuity follows _existence_ , not effort. A `213`'d invoice never validly entered the network, so its number was never consumed and the uniqueness control has nothing to collide with — re-use it. A delivered invoice consumed its number the moment it existed, whatever happened next: the _avoir_ and the _facture rectificative_ each take a **new** number in your sequence, and the rectificative references the original. The same rule settles the refusal case from the other direction. An invoice the buyer _refused_ did validly exist, so its number is spent too — a rejection frees a number, a refusal does not. [More on that asymmetry ↑](<#case-undo>) ### How do I find the right reception point before re-sending? Two questions hide in this one, and people answer the wrong one. _Does this address exist?_ is a directory lookup. _Is it the one this customer wants invoices on?_ is a question for the customer. The annuaire answers the first authoritatively and the second only partly. By hand, the public annuaire is searchable on the Chorus Pro portal by SIREN, SIRET or company name. Programmatically, from Flowie: [code] # 1 — which maille did this taxpayer actually declare? # `level` answers it in one word: "siren" or "siret". curl https://api.flowie.ink/v1/portability/annuaire/{siren} \ -H "Authorization: Bearer $FLOWIE_API_KEY" # 2 — pre-flight the recipient you are about to send to curl -X POST https://api.flowie.ink/v1/directory/verify \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "peppolId": "0009:75297877500027", "documentType": "INVOICE" }' [/code] * [`GET /v1/portability/annuaire/{siren}`](<../../reference/index.html#portability>) returns `level` — literally _which maille_ the deciding declaration sits at — plus `addressingId`, the routing platform's matricule, and every raw _ligne d'adressage_ as evidence. `declared: false` means nobody declared anything and the PPF default applies. * [`POST /v1/directory/verify`](<../../reference/index.html#verify-recipient>) is the recommended pre-flight before every send: it says whether the recipient exists and can accept the document type. * [`GET /v1/directory/search`](<../../reference/index.html#search-directory>) turns a company name or a VAT number into candidates when you have no identifier at all. The annuaire tells you what exists, not which one they want A company with fifteen establishments can have a live _ligne annuaire_ on all fifteen — all correct, all deliverable, fourteen of them wrong for your invoice. Nothing in the directory says which one should receive _yours_ : that is a commercial fact, and the only reliable source for it is the customer. When they run several reception points on one SIRET, ask which `suffixeAdressage` to use and store it **against the customer record, not against the invoice**. A recurring wrong maille is an onboarding bug, not a per-invoice one. ### Does a wrong maille restart the payment deadline? The two cases diverge here as well, and only one of them is clean. * **Rejected before delivery.** The buyer received nothing, so there is no received invoice for a payment term to run from. The replacement carries its own issue date and the clock starts there. Fixing it the same day costs a day of DSO and nothing else. * **Delivered to the wrong establishment.** Murkier, and not something the protocol settles. The invoice _was_ received by the buyer's legal person — the argument that no term ever started is weak — but the service that has to approve it never saw it. If it is regularised by an avoir and a rectificative, the rectificative carries its own date in practice; if the buyer simply routes it internally and approves, the original date stands and you have lost only the days it sat in the wrong queue. This is a commercial argument, and the lifecycle is your evidence No status, motif or timestamp in the CDV decides who bears the delay — the lifecycle records _when_ each thing happened and stops there. That is still the thing worth having: [`GET /v1/documents/{id}/lifecycle`](<../../reference/index.html#get-lifecycle>) returns the full event log, so you can say exactly when the invoice was rejected or delivered, and to which reception point. Pull it before you argue about late-payment penalties, not after. ### How do I catch a wrong maille before my customer does? Three signals, in the order they can reach you. Two are events you subscribe to; the third is a _silence_ you have to go looking for, and it is the one that hides a wrong-but-valid maille. Signal| What it means| Wrong maille? ---|---|--- `document.failed` | A platform control rejected it — `213`. The motif is on the payload. | **Often.** _Destinataire introuvable_ and an annuaire miss both land here. [Handle it ↑](<#maille-rejected>) `document.delivered` | It reached a reception point; some mailbox accepted it. | **Cannot tell you.** Delivery proves the address was _live_ , never that it was _right_. `lifecycle.updated` that never arrives | Delivered, then nothing — no `203`, no `204`, no approval, for weeks. | **The classic symptom.** An invoice nobody is processing is usually an invoice in a queue nobody reads. So the real answer is upstream: after-the-fact detection only ever finds the first case, and the second is invisible by construction. Three habits remove most of both. * **Pre-flight every send** with [`POST /v1/directory/verify`](<../../reference/index.html#verify-recipient>). One call, and it turns a `213` two days later into an answer before you have committed the invoice number. * **Store the maille on the customer, not on the invoice.** Capture the reception point — `{siren}_{siret}[_{suffix}]`, suffix included — at onboarding, from the customer, and re-check it against [the annuaire](<../../reference/index.html#portability>) whenever a send fails. * **Alert on delivered-and-silent.** Anything past `document.delivered` with no `lifecycle.updated` beyond your customer's usual approval window deserves a phone call. It is the only detector that catches a valid wrong address. ## The coded motif — MDT-113 / MDT-114 For `210` and `213` a motif is **required** ; for `207` and `208` it is **expected**. The CDAR carries it in two fields: Field| Name| Content ---|---|--- `MDT-113`| ReasonCode| A **coded** value from the restricted controlled vocabulary of the XP Z12-012 annex (rule `BR-FR-CDV-CL-09`). `MDT-114`| Reason| Optional free text — e.g. `"Prix unitaire ligne 3 non conforme au devis"`. Outside the French DGFiP leg (Peppol / non-FR flows), `reasonCode` uses the **14 official Peppol status reason codes** (OPStatusReason) instead — the full table lives in the [API reference · status reason codes](<../../reference/index.html#reason-codes>). The motif codes circulating online are fabricated The literal strings widely repeated by vendor blogs and AI summaries — `TX_TVA_ERR`, `REJ_UNI`, `REJ_COH`, `REJ_ADR`, `CMD_ERR`, `DOUBLE_FACT`, `ROUTAGE_ERR`, `CALCUL_ERR` ("~45 codes / 6 families") — **do not appear anywhere in the official AFNOR XP Z12-012**. The authoritative `Code motif → Libellé` list lives _only_ in the **« Tableau des motifs de STATUTS »** sheet of the XP Z12-012 Excel annex, inside the _Spécifications externes B2B_ ZIP (current v3.2). Flowie forwards whatever `MDT-113`/`MDT-114` the platform returned **verbatim** — we never invent or re-map a motif. ## API cheat-sheet Code| Your side| What you do ---|---|--- `210` Refusée| Buyer| `POST …/lifecycle {"status":"rejected","reasonCode":"…","reason":"…"}` `213` Rejetée| Supplier| **Automatic** — observe `document.failed` / `compliance.reported.failed`, fix, re-send. `208` Suspendue| Buyer| `POST …/lifecycle {"status":"disputed","reasonCode":"suspended","reason":"…"}` `209` Complétée| Supplier| `POST …/actions {"action":"link","relatedDocumentId":"…"}` (or `add-note`) — lifts the suspension. Every status here surfaces on the `lifecycle.updated` webhook and on [`GET /v1/documents/{id}/lifecycle`](<../../reference/index.html#get-lifecycle>). Build your consumer to be idempotent and out-of-order-tolerant — see the [playbook's reference handler](). ## Beyond France — the same idea elsewhere "Technical rejection", "business refusal" and "on hold" are not French inventions — every clearance or four-corner model has some notion of them, even when the codes and the bindingness differ. The generic equivalents: Concept| 🇫🇷 France (CDV)| 🇮🇹 Italy · SDI| Peppol · Invoice Response (UNCL4343)| UNTDID 1373 ---|---|---|---|--- **Technical rejection** | `213` Rejetée | _Notifica di scarto_ (NS) — SDI rejects the file | Negative _Message Level Response_ (transport / validation layer) | `8` / `27` **Business refusal** | `210` Refusée | _Esito committente — rifiuto_ (mainly B2G/PA; no formal B2B refusal channel) | `RE` Rejected | `50` **On hold / query** | `208` Suspendue | No native SDI code — handled commercially, off-platform | `UQ` Under Query | `39` Suspended **Dispute (soft)** | `207` En litige | No native SDI code | `UQ` Under Query | `46` Litigious These mappings are informal — the French codes stay authoritative There is **no official normative table** mapping the French 2xx codes to Peppol UNCL4343 as of mid-2026, and the semantics genuinely differ: Italy's _esito committente_ is largely a B2G construct and does _not_ invalidate a cleared B2B invoice the way `210` does; Peppol's `UQ` covers both "dispute" and "on hold". When you exchange cross-border through Flowie we translate at the edge and always keep the **French codes authoritative for the DGFiP leg**. Use this table to reason about equivalence, not as a wire-format spec. ## References * [Lifecycle explorer]() — all 14 statuses (200–213), interactive, with per-status API code and the CDAR field guide. * [Business terms · the four mentions the reform added]() — the SIREN, SIRET, delivery-address and nature-of-operation fields the [refusal cases](<#cases>) turn on, with their France requirement level. * [Reference · reception-point addressing](<../../reference/index.html#reception-point-addressing>) — the `{siren}_{siret}[_{suffix}]` form to hand a supplier that keeps reaching the wrong establishment. * [France overview · refus & rejet]() — the summary table and the fabricated-motif warning in context. * [Integration playbook · webhook handler]() — idempotent, out-of-order-safe consumer for `lifecycle.updated`. * [Machine-readable referential (JSON)](<../../schemas/fr-lifecycle-statuses.json>) — `terminal`, `reasonRequired`, `untdid1373` and transitions for every code. * [FNFE-MPE]() — AFNOR XP Z12-012 annexes (including the « Tableau des motifs de STATUTS ») and Schematrons. ======================================================================== # France · B2B use cases (cas d'usage, XP Z12-014) — all 45 # Source: https://docs.get-flowie.com/compliance/fr/use-cases.html ======================================================================== --- title: "France · The 45 B2B use cases (cas d'usage · XP Z12-014)" description: "Every AFNOR XP Z12-014 B2B use case (cas d" canonical: "https://docs.get-flowie.com/compliance/fr/use-cases" source: "https://docs.get-flowie.com/compliance/fr/use-cases.html" --- # France · The 45 B2B use cases (cas d'usage · XP Z12-014) Compliance · 🇫🇷 France # The B2B use cases of the French reform — _cas d'usage_ XP Z12-014 The reform doesn't just say "send a structured invoice". It enumerates the concrete **business scenarios** a French e-invoice can encode — advance payments, self-billing, factoring, reverse charge, margin VAT, e-reporting, and so on — as the **cas d'usage** of AFNOR **XP Z12-014**. This page is the complete referential: **all 45 cases** as of v1.4 (published 2026-06-30), a plain-English deep dive on every theme, and exactly how to model each one with Flowie — cross-checked against the public DGFiP and FNFE-MPE sources listed at the [bottom](<#references>). **v1.0** · 2025-06-13 · 36 cas **v1.2** · 2025-10-31 · 42 (adds 37–42) **v1.3** · 2026-02-26 · 44 (adds 43, 44) **v1.4** · 2026-06-30 · 45 (adds 45) The referential grows with each revision; we track every version and update this page. The authoritative `Cas d'usage → titre` list is **Annexe A** of XP Z12-014 (public via FNFE-MPE / the DGFiP _Spécifications externes B2B_) — the numbering and titles below follow it. ## What a _cas d'usage_ actually is A cas d'usage is a named, numbered **business scenario** plus the rules that make it work on the network: which document(s) are exchanged, which fields or attributes carry the specifics, which party emits what, and how the [200–213 lifecycle]() is affected. It is _not_ a new invoice format — every case still travels as Factur-X / UBL / CII (EN 16931) and its lifecycle still uses the same 14 statuses. The case tells you _how to fill and route_ the invoice for that situation. You do **not** pass a "use-case number" to Flowie. You send the invoice with the right structured data (a deposit amount, a self-billing mandate reference, a reverse-charge VAT category, a link to the original invoice…) and Flowie produces a compliant flow. The case list is the map of _what data a given situation needs_ — read it as requirements, not as an API parameter. ## What it is _not_ — three vocabularies people conflate Vocabulary| What it is| Belongs to ---|---|--- **Cas d'usage** (1–45)| B2B business scenarios of the reform| AFNOR XP Z12-014 — _this page_ **Cadres de facturation** (`A1`–`A25`)| _What_ document is deposited and by _whom_| Legacy **Chorus Pro** (B2G public sector) — [below](<#cadres>) **Circuits** (`A`, `B1`, `B2`, `C`)| Who uses the PPF vs a PA — the _schéma en Y_| DGFiP transmission architecture — _not_ invoice types The big split: e-invoicing vs e-reporting Domestic **B2B** invoices flow as structured e-invoices through PA platforms (_e-invoicing_). **B2C, international and intra-community** operations are covered by _e-reporting_ — you transmit transaction / payment **data** to the DGFiP, you do not exchange a structured invoice through the PPF/PA network. Several cases below (marked e-reporting) live on the e-reporting side. ## The three families XP Z12-014 sorts the cases into three families: Family 1 ### Facturation « data » Cases needing extra data or a rule tweak on the invoice itself — multi-order/multi-delivery, advance invoices, discounts & escompte, margin VAT, sub-lines and groupings. Family 2 ### Intervention d'un tiers Cases where a third party is in the loop — factoring, distributor/depositary, marketplaces, payment mandates, self-billing, débours — with document- and lifecycle-sharing mechanics. Family 3 ### Impact sur le cycle de vie Cases that change the lifecycle — partial collection, monthly payments, restaurant & toll receipts, operations under professional secrecy — often flowing from a third party or a special VAT regime. ## All 45 cases, numbered The complete referential (XP Z12-014 v1.4, Annexe A), grouped by practical theme so you can find the one you need. Cases on the e-reporting side are tagged e-reporting; the v1.4 addition is tagged v1.4. #| Cas d'usage (FR)| What it covers & how Flowie models it ---|---|--- Acompte & paiement échelonné 20| Facture d'acompte| Advance / deposit invoice. Send it as its own invoice; the final invoice (21) references it. 21| Facture définitive après acompte| Final invoice that nets out the deposit — `link` it to invoice 20 so the deducted amount is traceable. 24| Gestion des arrhes| _Arrhes_ (forfeitable earnest) vs acompte — different legal effect on cancellation; carried as the deposit's nature. 32| Paiements mensuels| Recurring monthly instalments against one engagement; each collection is a `212` (partial). 34| Encaissement partiel et annulation| Partial collection then cancellation — drives `partially_paid` then a corrective / avoir. Autofacturation & mandats de facturation 19a| Facture émise par un tiers facturant avec mandat| A mandated third party issues on the seller's behalf — carry the mandate reference. 19b| Auto-facturation| The buyer issues the invoice for the seller (self-billing) under agreement. 23| Auto-facturation entre particulier et professionnel| Self-billing where one side is a private individual (e.g. producer buy-back). 45| Auto-facture bidirectionnelle v1.4| Both parties self-bill each other — the v1.4 addition. Affacturage, tiers payeurs & intermédiaires 2| Facture déjà payée par l'acheteur ou un tiers payeur| Already-settled invoice (e.g. lodged card) — emitted _paid_. 3| Facture à payer par un tiers payeur connu| A known third party settles for the buyer. 4| Facture à payer par l'acheteur avec prise en charge partielle| Buyer pays part; a third party covers the rest. 8| Facture à payer à un tiers déterminé à la facturation| Payee resolved at invoicing time (assignment of receivable). 9| Facture à payer à un distributeur / dépositaire| Payment routed to a distributor or depositary. 10| Facture à payer à un tiers bénéficiaire inconnu (affactureur)| **Factoring** : the factor is the beneficiary; the buyer pays them. 11| Facture reçue et traitée par un tiers pour l'acheteur| A third party receives/processes on the buyer's behalf. 12| Intermédiaire transparent, gestionnaire de facture| Transparent intermediary manages the invoice without being a party to the sale. 15| Facture de vente suite à commande d'un tiers| Sale invoiced after a third party placed the order. 17a| Facture à payer à un tiers, intermédiaire de paiement| Payment intermediary in the settlement path. 17b| Facture à payer à un tiers avec mandat de facturation| Third-party payee combined with a billing mandate. 39| Intermédiaire transparent (multi-vendeurs)| Marketplace-style transparent intermediary across several sellers. Frais des collaborateurs & cartes 5| Frais payés par des collaborateurs avec facture| Employee expenses backed by an invoice. 6| Frais payés par des collaborateurs sans facture| Employee expenses without a supplier invoice → e-reporting / receipt path. e-reporting 7| Facture suite à un achat payé avec carte logée| Purchase settled via a lodged corporate card. Sous-traitance, co-traitance & débours 13| Facture de sous-traitance avec paiement direct| Direct-payment subcontracting (public works style). 14| Facture de co-traitance B2B| Joint contractors invoicing together. 16| Facture de débours| _Débours_ : costs advanced in the client's name, re-billed at cost, outside the VAT base. Avoir, notes & escompte 18| Gestion des notes de débit| Debit notes alongside the invoice flow. 22a| Facture payée avec escompte (TVA à l'encaissement)| Early-payment discount, services / VAT-on-collection. 22b| Facture payée avec escompte (livraisons de biens)| Early-payment discount, goods. —| Avoir / facture rectificative| _Not its own numbered case_ : a credit note is a first-class document type that must reference the original and travel the same circuit. Régimes de TVA particuliers 25| Gestion des bons et cartes cadeaux| Single- vs multi-purpose vouchers and gift cards. 29| Assujetti unique| VAT group / single taxable person. 30| TVA déjà collectée (bridge e-reporting B2C)| VAT already collected on a B2C leg feeding into B2B. e-reporting 33| Régime de TVA sur la marge bénéficiaire| Margin-scheme VAT (used goods, travel, art…). 42| Gestion de la détaxe| Tax-free / détaxe handling. —| Autoliquidation (reverse charge)| _Not a dedicated case_ : modelled as a VAT category / mention on the invoice (e.g. subcontracting 13, co-contracting 14). E-reporting — B2C & international 27| Gestion des tickets de péage| Toll tickets reported as data. e-reporting 28| Gestion des notes de restaurant| Restaurant receipts reported as data. e-reporting 43| E-reporting B2B international| Cross-border B2B reported as data. e-reporting 43a| Opérations triangulaires| Triangular international operations. e-reporting 43b| Transferts de stocks| Cross-border stock transfers. e-reporting 44| Transactions avec DROM / COM / TAAF| French overseas territories. e-reporting Contractual, special & edge cases 1| Multi-commande / multi-livraison| One invoice spanning several orders / deliveries. 26| Factures avec clause de réserve contractuelle| Retention-of-title / contractual reserve clause. 31| Factures « mixtes »| Mixed invoices (e.g. goods + services, or B2B + e-reporting lines). 35| Notes d'auteur| Author's fee notes (specific professions). 36| Opérations soumises au secret professionnel| Professional-secrecy operations — restricted line detail. 37| Sociétés en participation (SEP)| Joint-venture (SEP) invoicing. 38| Factures avec sous-lignes et regroupements| Sub-lines and line groupings on the invoice. 40| Paiements groupés / compensation| Netting / set-off across invoices. 41| Pratiques du « barter »| Barter / exchange of goods or services. Titles follow XP Z12-014 Annexe A as published; where a concept is handled as an attribute rather than a numbered case (avoir, autoliquidation) the row is marked `—`. The three-family split is AFNOR's; the theme grouping above is ours, for navigation. ## Deep dive · acompte & paiement échelonné ### 20 · 21Deposit then final invoice The most common "two-document" pattern. The supplier issues a _facture d'acompte_ (20) when a deposit is agreed, then a _facture définitive_ (21) on completion that restates the full amount and **deducts the deposit already invoiced**. The final invoice must reference the deposit invoice so the deducted amount and its VAT are traceable. With Flowie: send both as normal invoices and use a [`link` action](<../../reference/index.html#document-actions>) from the final to the deposit; the deducted line carries the reference. ### 24Arrhes vs acompte Legally distinct from an acompte: _arrhes_ can be forfeited (buyer walks away, loses them) or doubled (seller cancels, repays double), whereas an acompte firmly commits both sides. The distinction changes the VAT and cancellation treatment, so it is carried explicitly as the nature of the down-payment rather than left implicit. ### 32 · 34Instalments & partial collection Case 32 covers recurring monthly payments against a single engagement; case 34 covers a partial collection followed by cancellation. Both are **lifecycle-impacting** : each collection is an [`212 Encaissée`]() (use `partially_paid` \+ `remainingAmount` for partials, repeatable), and a cancellation resolves via a corrective invoice and, if already accepted, an avoir. ## Deep dive · autofacturation & mandats Self-billing inverts the usual emitter: the **buyer** (or a mandated third party) issues the invoice on the supplier's behalf, under a prior agreement. The reform keeps this legal but demands the arrangement be explicit on the flow. * **19b Auto-facturation** — buyer issues for the seller. The buyer's platform is the emitter; the seller must be able to contest. * **19a / 17b Mandat de facturation** — a third party issues under an explicit mandate reference; 17b combines this with a third-party payee. * **23** — self-billing where one party is a private individual (classic in agriculture / producer buy-back). * **45 Auto-facture bidirectionnelle** v1.4 — the newest case: both parties self-bill each other, which needs careful de-duplication so a single economic operation isn't reported twice. ## Deep dive · affacturage & tiers payeurs This is the largest family — anything where **someone other than the buyer** pays, receives, or manages the invoice. The mechanics hinge on _who the payee is_ and _who sees the lifecycle_. * **10 Affacturage (factoring)** — the receivable is assigned to a factor; the buyer pays the factor, not the supplier. The invoice names the factor as beneficiary, and the supplier's collection status reflects the factor's receipt. * **3 · 8 · 9 · 17a Tiers payeur / payee** — a known third party, a payee fixed at invoicing, a distributor/depositary, or a payment intermediary settles the invoice. * **2 · 4** — already-paid invoices, and split payment where the buyer covers part and a third party the rest. * **11 · 12 · 39 Intermédiaires** — a third party receives/processes for the buyer (11), a transparent intermediary manages the invoice (12), or a multi-vendor transparent intermediary (39) — the marketplace pattern. Débours ≠ tiers payeur Don't confuse the payee patterns with [débours (16)](<#credit>): a débours is a cost advanced _in the client's name_ and re-billed at cost, sitting **outside** the VAT base — a data concern on the invoice, not a routing concern. ## Deep dive · sous-traitance, co-traitance & débours * **13 Sous-traitance avec paiement direct** — the subcontractor is paid directly (public-works pattern); typically carries **autoliquidation** (reverse charge) as a VAT mention, since reverse charge is not a numbered case of its own. * **14 Co-traitance B2B** — joint contractors invoice together, each for their share, coordinated by the lead. * **16 Débours** — costs advanced in the client's name and re-billed at cost, excluded from the VAT base; modelled as dedicated lines flagged as débours. ## Deep dive · avoir, notes de débit & escompte **Avoir / facture rectificative (credit note)** is deliberately _not_ a numbered case: it is a first-class **document type**. It must reference the original invoice, travel the same circuit, and — if the original was already accepted — accompany the corrective. Send it through the normal document pipeline with the credit-note type and the link to the original. **18 Notes de débit** handles debit notes alongside the invoice. **22a / 22b Escompte** cover early-payment discounts, split by VAT treatment: 22a for services (VAT on collection), 22b for goods (VAT on delivery) — the split matters because the discount changes the taxable base differently in each regime. ## Deep dive · régimes de TVA particuliers * **33 TVA sur la marge** — VAT charged only on the margin (used goods, art, antiques, travel agencies). The taxable base is the margin, not the sale price; carried as a margin-scheme VAT category. * **42 Détaxe** — tax-free sales / refund handling. * **29 Assujetti unique** — the VAT-group "single taxable person": intra-group flows are outside VAT, which the invoice must signal. * **25 Bons & cartes cadeaux** — single-purpose vouchers (VAT at issue) vs multi-purpose (VAT at redemption). * **30 TVA déjà collectée** e-reporting — bridges VAT already collected on a B2C leg into a B2B flow. * **Autoliquidation (reverse charge)** — again, an attribute/mention, not a case; the buyer self-assesses the VAT. ## Deep dive · e-reporting (B2C & international) These cases are **not invoice exchange** — they transmit transaction / payment **data** to the DGFiP. An integration must not try to route them as structured invoices through the PA network: * **27 Péage · 28 Restaurant · 6 Frais sans facture** — B2C-style receipts reported as data. * **30 TVA déjà collectée** — the B2C→B2B VAT bridge. * **43 (43a/43b) International** — cross-border B2B, triangular operations, and stock transfers reported as data. * **44 DROM / COM / TAAF** — French overseas territories, whose VAT territoriality differs from the metropole. See the [France overview]() for how e-reporting timing (24 h for payment data) ties into the mandatory lifecycle statuses. ## Deep dive · special & edge cases * **1 Multi-commande / multi-livraison** — one invoice covering several orders or deliveries; needs the order/delivery references per line. * **31 Factures mixtes** — an invoice mixing regimes (e.g. B2B lines + e-reporting lines, or goods + services). * **38 Sous-lignes & regroupements** — hierarchical line structure. * **26 Clause de réserve** — retention-of-title / contractual reserve. * **40 Compensation** — netting / set-off across invoices. * **41 Barter** — exchange of goods/services with reciprocal invoices. * **35 Notes d'auteur · 36 Secret professionnel · 37 SEP** — profession-specific and structure-specific cases; 36 restricts line-level detail for confidentiality. ## Legacy — cadres de facturation (Chorus Pro / B2G) If you see `A1`…`A25` in a flow, that is the **Chorus Pro (public-sector)** mapping — _what_ document is deposited and by _whom_ — carried over for B2G, **not** part of the B2B reform's cas d'usage. The most common: Cadre| Meaning ---|--- `A1`| Dépôt par un fournisseur d'une facture (à régler ou avoir) — the standard case, the vast majority. `A2`| Dépôt d'une facture déjà payée (e.g. carte d'achat). `A3`| Dépôt d'un mémoire de frais de justice. `A4` / `A5` / `A7` / `A8`| Works contracts: projet de décompte mensuel (A4), état d'acompte (A5), projet de décompte final (A7), décompte général & définitif signé (A8). `A9` / `A10`| Demande de paiement d'un sous-traitant (A10 = marchés de travaux). `A12`| Facture / demande de paiement d'un cotraitant, validée par le mandataire. `A13`–`A25`| Further works décomptes by cotraitant, MOE (maîtrise d'œuvre) or MOA (maîtrise d'ouvrage). _(No`A11` or `A21` exist in the transmission table.)_ ## How Flowie models them The through-line: **you never send a case number.** You send well-formed structured data and Flowie produces the compliant flow. The building blocks that cover the 45 cases: Mechanism| Covers| API ---|---|--- Document type (invoice / credit note)| Avoir, notes de débit (18)| [`POST /v1/documents/send`](<../../reference/index.html#send-document>) Structured fields & VAT categories| Reverse charge, margin VAT (33), détaxe (42), acompte (20)| Invoice body on send Document links| Deposit↔final (20/21), corrective↔original, factoring (10)| [`POST …/actions {"action":"link"}`](<../../reference/index.html#document-actions>) Party roles & payee| Tiers payeurs (2–17), self-billing (19), marketplaces (39)| Parties on the invoice body Lifecycle statuses| Partial collection (34), instalments (32)| [Lifecycle cheat-sheet]() E-reporting path| B2C (27/28), international (43/44)| Reported as data — not the PA invoice flow Accuracy & version note The normative source is **AFNOR XP Z12-014, Annexe A** (the norm text is on the AFNOR boutique; the annexes are public via FNFE-MPE and referenced from the DGFiP _Spécifications externes B2B_). This page reflects **v1.4 (2026-06-30, 45 cases)**. The count and titles evolve between versions — always confirm against the current annex for a specific case before building to it, and treat the theme grouping here as navigational, not normative. ## References — public sources * [DGFiP · Spécifications externes B2B]() — the official hub linking XP Z12-012 / 013 / 014 and their annexes. * [DGFiP actualité]() — official publication of the dossier des cas d'usage (AFNOR commission). * [AFNOR · XP Z12-014]() — the normative standard, "B2B use cases applicable within the framework of the electronic invoice reform". * [FNFE-MPE]() — publishes the XP Z12-014 _Annexe A_ (cas d'usage) and the Z12-012/013 annexes and Schematrons. * [France overview · use cases summary]() · [Lifecycle explorer]() · [Integration playbook](). ======================================================================== # France · UBL generator — a compliant e-invoice for every business case # Source: https://docs.get-flowie.com/compliance/fr/ubl-generator.html ======================================================================== --- title: "France · UBL generator for every e-invoice business case" description: "Generate a compliant French e-invoice for every business case of the reform — all 45 cas d" canonical: "https://docs.get-flowie.com/compliance/fr/ubl-generator" source: "https://docs.get-flowie.com/compliance/fr/ubl-generator.html" --- # France · UBL generator for every e-invoice business case Compliance · 🇫🇷 France # UBL generator — every French business case, as a document you can run The reform gives you a list of business situations an invoice can encode — the [**cas d'usage** of XP Z12-014]() — and a spreadsheet of fields. What it does not give you is _a compliant invoice for each one_. So the first time you build a deposit invoice, a factored invoice or a self-billed invoice, you find out whether you got it right by having it rejected. This page closes that gap. Pick a business situation, read what is actually happening in it and why the reform treats it as its own case, and take away the **EN 16931 UBL 2.1 XML** and the exact [`POST /v1/documents/send`](<../../reference/index.html#send-document>) call that produces it. **59 scenarios** , covering **all 45 numbered cas d'usage** plus the nine foundations every French integration needs on day one. **59** scenarios **45** cas d'usage covered **UBL 2.1** EN 16931 syntax **FNFE v1.3.0** schematrons validated ## Why a generator and not just a field list A French e-invoice is not one document with optional extras. The same €10,000 of work produces a materially different document depending on whether it is a deposit, a final invoice netting that deposit, a self-billed invoice, a factored one or a subcontractor's reverse-charged one — different type code, different _cadre de facturation_ , different parties, different VAT category, different lifecycle. Each of those choices has a legal consequence, and each has a rule that rejects you for getting it wrong. Three things go wrong most often, and all three are business decisions before they are technical ones: Wrong document, right data A deposit sent as an ordinary invoice (type `380` instead of `386`) reports the VAT in the wrong period and double-counts the revenue when the final invoice lands. [See case 20](<#generator>). Right parties, wrong roles When a factor, a payer or a billing agent is involved, moving them into the seller or buyer block moves the VAT liability with them. The payee is its own party for a reason. [See case 10](<#generator>). Missing the French mentions [BR-FR-05](<#mentions>) requires three legal mentions on _every_ French invoice. Most ERPs built for EN 16931 elsewhere omit all three, and every invoice fails. [See the baseline](<#generator>). ## How to use it 1. **Find your situation.** Filter by theme, or search for what you are actually doing — "deposit", "factoring", "self-billing", "reverse charge", "Réunion". 2. **Read the business description.** What happens, why the reform cares, what goes wrong, and which fields carry it. If that section doesn't match your situation, you have the wrong case — keep looking before you write code. 3. **Take the artefacts.** The UBL XML to validate against, and the send request to fire. Both are already compliant, so a diff against your own output is a list of exactly what you are missing. These samples are checked, not asserted Every scenario on this page was run through the official FNFE **XP Z12-012 v1.3.0** schematrons — XSD, then the EN 16931 profile rules, then the French `BR-FR` rules — plus the complementary CIUS-FR field checks. All of them pass, except one that [deliberately does not](<#validate>), because a B2C receipt is not an e-invoice. ## The generator Simple All business cases59 Your parties and your lines — the reform's part is worked out for you and **explained** : the type code, the _cadre de facturation_ , the tax point, the VAT category with its exemption reason, and the three legal mentions French law requires. Nine out of ten French invoices are this, not one of the 45 numbered cases. Seller — you Legal name BT-27 SIRET BT-34 / BT-30 VAT number BT-31 Legal form & share capital BT-33 Street BT-35 Postal code BT-38 City IBAN BT-84 Buyer — your customer Legal name BT-44 SIRET BT-49 / BT-47 VAT number BT-48 Street BT-38 Postal code BT-40 City What you sold Description| Qty| Unit price | VAT %| Kind| ---|---|---|---|---|--- \+ Add a line The invoice Invoice number BT-1 Document BT-3 Invoice Credit note VAT situation BT-151 Standard French VAT Reverse charge — construction subcontracting Franchise en base Intra-community supply Export outside the EU Issue date BT-2 Due date BT-9 Deliver-to country BT-80 Already paid TVA sur les débits Advanced fields Advanced Currency BT-5 Buyer reference BT-10 Purchase order BT-13 Already paid amount BT-113 BIC BT-86 Payment terms BT-20 Corrects invoice BT-25 …issued on BT-26 Note BT-22 Everything here is optional. The three legal mentions, the cadre, the tax point and the VAT reason are still worked out for you — these only add what the reform lets you state on top. Generate the invoice Loading the referential… Pick a business case on the left. ## The _cadre de facturation_ — the most business-shaped field on the invoice Every French e-invoice declares what kind of transaction it is, in one code (`BT-23`, carried in `cbc:ProfileID`). The first letter says **B** iens (goods), **S** ervices or **M** ixte; the digit says what kind of situation. It is the reform's own answer to "what am I looking at?", and it drives the rules that apply — which is why the generator sets it per scenario rather than defaulting everything to `S1`. Code| What it means in business terms ---|--- Loading… Source: `BR-FR-08`. Sending an invoice with no cadre, or with a cadre that contradicts the tax point (`BT-8`), is the quiet mismatch that puts the VAT in the wrong CA3 period on both sides. ## Invoice types — and why "just send 380" breaks France accepts a closed list of document types (`BT-3`, `BR-FR-04`). Anything else in UNTDID 1001 is rejected. The list is short but load-bearing: it is what tells everyone downstream that a document is a deposit, a self-billed invoice, a factored one or a correction — before anybody reads a single amount. Code| What it is ---|--- Loading… ## The three mentions every French invoice must carry `BR-FR-05` requires at least three notes, each tagged with a subject code (`BT-21`), on _every_ French invoice. They are legal mentions from the Code de commerce, not formatting — and they are the single most common reason an otherwise-correct EN 16931 invoice fails in France, because ERPs built for other markets have no field for them. [code] #PMT#Indemnité forfaitaire pour frais de recouvrement en cas de retard de paiement : 40 EUR (art. L441-10 du Code de commerce). #PMD#Pénalités de retard : trois fois le taux d'intérêt légal, exigibles le jour suivant la date d'échéance. #AAB#Escompte pour paiement anticipé : néant. #BAR#B2B [/code] In UBL the subject code and the text share one `cbc:Note`, with the code carried as a `#CODE#` prefix. The fourth note above is not required by `BR-FR-05` but matters just as much: `BAR` declares which side of the reform the document belongs to (`BR-FR-20`). Subject code| What it carries ---|--- Loading… ## VAT — the categories France accepts, and when VAT becomes due France narrows EN 16931's VAT category list (`BR-FR-15`) and fixes the accepted rates (`BR-FR-16`). Every category other than `S` and `Z` needs a reason and a `VATEX` code — an invoice that charges no VAT without saying why is rejected, and rightly so: the reason is what makes the exemption defensible in an audit. Category| When you use it| Typical VATEX code ---|---|--- `S`| Standard taxable supply — 20%, 10%, 5.5% or 2.1%.| — `AE`| Autoliquidation — the buyer accounts for the VAT. Construction subcontracting, and most B2B services from abroad.| `VATEX-FR-AE` `E`| Exempt — franchise en base, medical and training exemptions, margin scheme, disbursements.| `VATEX-FR-FRANCHISE`, `VATEX-EU-79-C` `K`| Intra-community supply — goods leaving France for another member state.| `VATEX-EU-IC` `G`| Export outside the EU — including the DROM, which are outside the EU VAT territory.| `VATEX-EU-G` `O`| Outside the scope of VAT. Boxed in hard: it cannot share an invoice with any other category (`BR-O-11`) and forbids the seller's VAT number (`BR-O-02`).| `VATEX-EU-O` ### When the VAT becomes chargeable (`BT-8`) This one code decides which VAT return period the invoice lands in, for both parties. Goods are taxable on delivery; services on collection, unless the seller has opted for _TVA sur les débits_. Getting it wrong doesn't fail validation — it just puts the VAT in the wrong month. Code| Meaning ---|--- Loading… ## Use it from the API The same catalogue is served by the API, so you can wire it into your own test suite rather than copying XML out of a browser. The catalogue and the generator need **no API key** — they are pure functions with no access to anyone's data. ### List every business case [code] curl https://back.flowie.ink/exchange/v1/tools/fr/ubl/scenarios [/code] Filter with `?theme=`, `?family=`, `?channel=e-reporting`, `?case=20`, or search the business descriptions with `?q=factoring`. ### Generate a case with your own parties [code] curl -X POST https://back.flowie.ink/exchange/v1/tools/fr/ubl/generate \ -H "Content-Type: application/json" \ -d '{ "scenarioId": "uc-21-final-after-deposit", "number": "FA-2027-0042", "issueDate": "2027-03-01", "seller": { "name": "Ma Société SAS", "siret": "55210055400013" }, "buyer": { "name": "Mon Client SA", "siret": "39158000400021" } }' [/code] The response carries the business description, the invoice model, the UBL XML, the computed totals, and the `POST /v1/documents/send` body that sends it. Identifier _schemes_ stay fixed, so an override can never produce a party whose SIRET and SIREN disagree (`BR-FR-09`). ### Generate and validate in one call [code] curl -X POST https://back.flowie.ink/exchange/v1/tools/fr/ubl/generate-and-validate \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "scenarioId": "uc-10-factoring" }' [/code] This one needs a key, because it calls the schematron service on your behalf. Use it as the reference answer when your own output for the same case is rejected: generate the scenario, validate both, and diff. To validate an invoice you built yourself, use [`POST /v1/documents/validate`](<../../reference/index.html#validate-document>). ## What "valid" means here Each generated document goes through the same four gates a real invoice does: 1. **XSD** — is it structurally a UBL 2.1 invoice? UBL is sequence-typed, so an element in the wrong order fails here, before a single business rule runs. 2. **EN 16931 profile schematron** — do the totals add up (`BR-CO-*`), does every exempt line give a reason, is each VAT breakdown consistent? 3. **BR-FR rules** — the French layer: the legal mentions, the closed type-code and cadre lists, SIRET/SIREN consistency, the accepted VAT rates. 4. **CIUS-FR field checks** — the fields France makes legally mandatory that EN 16931 leaves optional: the tax point code, the due date, the seller's legal form and share capital, the buyer's SIREN and routing address. One scenario fails on purpose The B2C restaurant receipt (`uc-28-restaurant-receipts`) does not pass e-invoice validation, and cannot: a consumer has no SIREN and no routing address, both of which France makes legally mandatory on an e-invoice. A B2C sale belongs to **e-reporting** — you transmit transaction data to the DGFiP, you do not exchange a structured invoice. The generator still renders the document, because that record is what feeds your e-reporting; it just marks `validatesAsEInvoice: false` rather than pretending. Confusing the two channels is the most common structural mistake in French implementations. ## What the generator does not do * **It does not check your data against the annuaire.** `BR-FR-10` and `BR-FR-11` require the seller's and buyer's SIREN to be present and active in the PPF directory. That is a live lookup — see [routing & the annuaire](). * **It does not emit lifecycle statuses.** Each scenario lists the 200–213 statuses it drives, but emitting them is your integration's job — see the [lifecycle explorer]() and the [integration playbook](). * **It does not produce Factur-X.** The samples are UBL. Flowie generates Factur-X from the same data on send; if you need the CII syntax, [send with `format: "cii-xml"`](<../../reference/index.html#send-document>). * **The multi-seller cases are a starting shape, not a finished answer.** Cadres `S8`/`B8`/`M8` turn on a whole extra rule family (`BR-FR-MV-01` to `BR-FR-MV-12`) with per-line seller identifiers and grouping lines. Validate before you build on them. ## References * [The 45 cas d'usage, in full]() — the referential this generator implements, with the deep dives per theme. * [France · PPF & PA overview]() — deadlines, required fields, routing, e-reporting. * [Lifecycle explorer]() — statuses 200–213, who owes which. * [Integration playbook]() — onboard, send, e-report, go live. * [Document types](<../../reference/document-types.html>) — every type Flowie sends, plus self-billing. * [FNFE-MPE]() — the association that publishes the French CIUS and the schematrons. * [impots.gouv.fr · facturation électronique]() — the DGFiP's own reference pages and the _Spécifications externes B2B_. ======================================================================== # France · Business terms — every BT of EN 16931 and what France requires of it # Source: https://docs.get-flowie.com/compliance/fr/business-terms.html ======================================================================== --- title: "France · Every EN 16931 business term (BT)" description: "The complete EN 16931 semantic model for French e-invoicing: all 30 business groups and 164 business terms (BT-1 to BT-165), each with its UBL 2.1 path, whether the French reform makes it mandatory and under which BR-FR rule, and the POST /v1/documents/send field that carries it." canonical: "https://docs.get-flowie.com/compliance/fr/business-terms" source: "https://docs.get-flowie.com/compliance/fr/business-terms.html" --- # France · Every EN 16931 business term (BT) Compliance · 🇫🇷 France # Business terms — the whole EN 16931 model, and what France does with each one An e-invoice is not a picture of an invoice. It is a list of named values the recipient's software reads, and the European standard **EN 16931** gives every one of those values a number: `BT-1` is the invoice number, `BT-9` the due date, `BT-121` the code that says why a line carries no VAT. Your accountant knows them as the _mentions obligatoires_ ; your customer's system knows them as the fields it pays on; a validator quotes them at you when it rejects a document. Every other page here names them one at a time — [this cas d'usage turns on BT-25](), [this API field is BT-9](<../../reference/index.html#send-document>). **This page is the whole list** : all 30 business groups and all 164 business terms, each with the UBL path it lives at, whether France makes it mandatory and under which rule, and the field of `POST /v1/documents/send` that carries it. **32** business groups **164** business terms **37** mandatory in France **84** with an API field ## How to read a row Two columns carry the judgement, and both mean something narrower than they look. ### France — what the reform requires mandatory Required on every French e-invoice. conditional Required when the stated condition holds — the row names it. restricted Allowed, but France narrows the values EN 16931 permits. optional EN 16931 optional, and France adds nothing. ### Flowie — how it reaches the wire sent You state it in the JSON body and it is rendered. derived Computed for you — the totals, the line numbering, the type code. accepted The field exists and is stored, but is not rendered yet. xml-only No JSON field — carry it with `format=ubl-xml`. **Mandatory does not mean you must type it.** Several terms France requires are ones Flowie completes: the document totals, the line numbering, the VAT breakdown. The _Flowie_ column is what tells you which ones you actually have to state — anything marked sent is yours to say, and anything xml-only is a gap you have to fill yourself today. The cardinality column is **EN 16931's** , not France's. The two disagree on purpose: the standard leaves the seller's street optional, France makes it a mention obligatoire. Read the cardinality for the shape of the syntax and the France column for the obligation. ## Every business term Grouped the way the standard groups them — the invoice header first, then each `BG`, then the line. Search matches the id, either name, the UBL path, the French note and the API field, so `autoliquidation`, `TaxSubtotal` and `BT-121` all find the same row. Mandatory in France Conditional Restricted values Document level Line level Has an API field XML only Loading the referential… Term | Name & UBL path | Card. | France | Flowie ---|---|---|---|--- Nothing matches that filter. ## The four mentions the reform added France already required 24 _mentions obligatoires_ on a paper invoice, and EN 16931 carries all of them. The reform added four more, and they are the four an ERP built for e-invoicing elsewhere is most likely to be missing: 1. **The seller's SIREN** — `BT-30`, scheme `0002`. `BR-FR-09` then requires the first nine digits of the SIRET in `BT-29` to equal it. 2. **The buyer's SIREN** — `BT-47`. `BR-FR-11` wants it present and active in the annuaire. 3. **The delivery address** when it differs from the billing address — `BG-15`, mandatory on a goods invoice under `BR-FR-14`. 4. **The nature of the operation** — goods, services or both. This is the _cadre de facturation_ in `BT-23` (`BR-FR-08`): `B1` goods, `S1` services, `M1` mixed, and the numbered variants for deposits, already-paid invoices and subcontracting. Two more get missed almost as often, and neither is new — only newly enforced. `BR-FR-05` makes **three legal mentions** compulsory on every invoice (the €40 recovery indemnity, the late-payment penalties, and the early-settlement discount or a statement that there is none), carried as `BT-21`/`BT-22` notes. And `BR-FR-20` requires a `BAR` note declaring which regime the document belongs to. See [the three legal mentions]() for the exact wording. ## What this list is not * **It is the invoice, not the flows around it.** The France column is the obligation on _flux 1_ — the invoice a seller files. E-reporting (_flux 10_) and the [lifecycle statuses 200–213]() carry their own data with their own rules. * **The French extensions are not BTs.** AFNOR adds fields beyond EN 16931 — the multi-seller line sub-type `EXT-FR-FE-163` and its family — numbered in their own space. They are named in the [cas d'usage]() that need them. * **A BT only renders if your template declares a slot for it.** The mapping fills the slots your template has; it does not create new ones. If a value you send is not appearing, ask which `BT-*` fields your template declares. * **There is no BT-4.** The published model does not assign it. The list is complete without it — you have not lost a row. ## Use it from the API The same referential, from the same source, with no API key — it is a catalogue, not your data: [code] # the whole model curl https://api.get-flowie.com/v1/tools/fr/ubl/business-terms # just what France makes mandatory, at line level curl "https://api.get-flowie.com/v1/tools/fr/ubl/business-terms?fr=mandatory&scope=line" # one term curl https://api.get-flowie.com/v1/tools/fr/ubl/business-terms/BT-121 [/code] Each entry carries `id`, `name`, `nameFr`, `group`, `scope`, `cardinality`, `ubl`, `fr` \+ `frNote`, and `api` \+ `apiState`. Filters: `group`, `scope`, `fr`, `mapped`, `q`. The table above reads the same list as a static asset — [`assets/fr-business-terms.json`](<../../assets/fr-business-terms.json>) — which is the one to fetch if you want the referential without calling anything. ## References * **EN 16931-1:2017/A1:2019** — the semantic data model these ids come from. * **FNFE schematrons v1.3.0 (XP Z12-012)** — the `BR-FR-*` rules the France column names. The [UBL generator]() validates every sample against them. * **AFNOR XP Z12-014 v1.4** — the 45 [cas d'usage](), which decide _which_ of these terms a given invoice needs. * **[POST /v1/documents/send](<../../reference/index.html#send-document>)** — the field-by-field mapping, in the other direction. ======================================================================== # Who routes this company — resolving the platform behind a company in every country # Source: https://docs.get-flowie.com/compliance/routing.html ======================================================================== --- title: "Who routes this company · every country" description: "Resolve which platform routes a company" canonical: "https://docs.get-flowie.com/compliance/routing" source: "https://docs.get-flowie.com/compliance/routing.html" --- # Who routes this company · every country Compliance · All countries # Who routes this company — every country Before you can send an invoice you have to know where it goes, and "where" is never the company — it is the platform that receives on its behalf. Every country answers that question, but not in the same way, and the difference decides whether the answer is a lookup or a list. **France answers from a closed registry.** The DGFiP accredits a fixed set of [Plateformes Agréées]() and the PPF annuaire routes on a four-digit `matriculePlateforme`. The number means nothing on its own, so the answer is a join — and nobody published it until [we did](). **Peppol answers live.** There is no registry of accredited platforms to consult: the participant identifier itself resolves, through DNS and then HTTP, to the access point that serves it — and the access point's certificate names the company operating it. No list to maintain, and never stale. **—** countries covered **—** Peppol participants, measured **—** with a provider registry **—** resolvable live ## Resolve one now Give a Peppol participant identifier — `scheme:value`, like `0208:0848934496` (Belgium) or `0192:991825827` (Norway) — and this asks the network who routes it, right now. ### Who routes this participant? Calls `GET /v1/portability/access-point/{participantId}`. Your key stays in this browser and is never sent anywhere but the API. Resolve ## Every country Participant counts and identifier schemes are **measured, not asserted** : each row comes from querying the Peppol Directory for that country and tallying the schemes real registrations actually use. A country showing no participants has none — it is not a country we failed to look up. Loading the directory… Country | Participants | Identifier schemes in use | Provider registry ---|---|---|--- Nothing matches that filter. ## How the live answer is resolved Three hops, and the last one is the one that actually names a company: 1. **SML — DNS.** The identifier is lowercased, hashed with SHA-256, Base32-encoded, and asked for a `NAPTR` record. The record's regexp field names the SMP that serves the participant: `"!.*!https://smp.example.net!"`. 2. **SMP — HTTP.** `GET /iso6523-actorid-upis::` lists every document type the participant accepts, each linking its own service metadata. 3. **The endpoint certificate.** The service metadata carries the access point's X.509 certificate, and its subject _is_ the answer: `CN=POP000016, OU=PEPPOL PRODUCTION AP, O=B2Brouter Global SL, C=ES`. `O` is the legal name, `CN` the Peppol access point id. **The zone moved, and stale documentation will send you nowhere.** OpenPeppol retired `edelivery.tech.ec.europa.eu` together with the old CNAME/MD5 scheme; production has been `participant.sml.prod.tech.peppol.org` with NAPTR/SHA-256 since **19 March 2026**. Querying the retired zone returns `NXDOMAIN` for every participant on the network — which looks exactly like "nobody routes them". Both schemes are attempted here, and the answer reports which one replied. One consequence worth internalising: **the access point's country is frequently not the company's**. A Belgian company routed by a Spanish provider, a Swedish one by a Finn — normal, and a good reminder that the platform market is European rather than national. ## Where a registry exists instead A handful of countries accredit providers and publish the list. There the France pattern applies — a name, a status, and usually a code — and the answer can be a page rather than a lookup: Country| What the authority registers ---|--- [France is the one built out in full]() — all 166 Plateformes Agréées joined to the matricule each routes on, with the evidence for every number. The others are listed so you know a registry exists to ask for; we have not ingested them. ## Use it from the API [code] # who routes this Belgian company? curl -H "X-API-Key: $FLOWIE_API_KEY" \ https://api.get-flowie.com/v1/portability/access-point/0208:0848934496 # France, from the registry instead curl -H "X-API-Key: $FLOWIE_API_KEY" \ https://api.get-flowie.com/v1/portability/annuaire/350422622 # and who is behind a French matricule curl -H "X-API-Key: $FLOWIE_API_KEY" \ "https://api.get-flowie.com/v1/portability/platforms?matricule=0003" [/code] The access point answer carries `apName`, `apPeppolId`, `apCountry`, `smpUrl`, `endpointUrl`, `transportProfile`, `documentTypeCount`, `certificateExpiresOn`, and `resolvedVia`. A participant nobody routes answers `200` with `error` set rather than `404` — "not registered" is an answer, not a failure. The table above reads a static asset — [`assets/routing-directory.json`](<../assets/routing-directory.json>) — if you want the per-country map without calling anything. ## References * **Peppol Policy for use of Identifiers** — the participant identifier schemes counted in the table above. * **Peppol CNAME to NAPTR Migration (v1.0.0, April 2025)** — the zone and hashing change that took effect on 19 March 2026. * **[France · Plateformes Agréées]()** — the registry answer, in full. * **[Compliance overview]()** — mandates and timelines for all countries. ======================================================================== # France · Plateformes Agréées — every PA and the matricule the PPF annuaire routes on # Source: https://docs.get-flowie.com/compliance/fr/platforms.html ======================================================================== --- title: "Plateformes Agréées · matricule directory" description: "Every French Plateforme Agréée (PA, ex-PDP) with the four-digit matriculePlateforme the PPF annuaire routes on — 14 confirmed by the operator itself, 15 published, 63 proven from the annuaire, and the ones still unknown, named as unknown. Flowie is PA number 0064." canonical: "https://docs.get-flowie.com/compliance/fr/platforms" source: "https://docs.get-flowie.com/compliance/fr/platforms.html" --- # Plateformes Agréées · matricule directory Compliance · 🇫🇷 France # Plateformes Agréées — who is behind each matricule The PPF annuaire routes on a **four-digit number**. Ask it where a French company receives its invoices and it answers `matriculePlateforme: "0064"` — never a name. The DGFiP, for its part, publishes the [official list of Plateformes Agréées]() with commercial name, address, website and immatriculation date, and _no numbers at all_. Nothing published joins the two. This page is that join, with its evidence attached. It matters the moment you read a routing answer: `0064` on its own tells you nothing, and guessing wrong means addressing invoices to the wrong operator. **—** Plateformes Agréées ## How to read a row A matricule is only as good as how it was established, so every row carries that, and the levels are not interchangeable. The API returns the level beside the number; the number itself is shown only to a signed-in reader. ### Confidence confirmed The operator told us the number directly, answering the round of verification mails we sent on 17 September 2026. The strongest source there is. published The operator states the number itself on its own site. Treat as fact. inferred Nobody published it, but the operator's _own_ SIREN routes its own lignes d'adressage on exactly one matricule in the annuaire, and it is the earliest-registered platform claiming it. Strong — not proof. unknown We could not establish it. Published as unknown rather than guessed. ### DGFiP status registered Meets every condition, interoperability tests included. pending interop Immatriculated, still awaiting the interoperability tests with the PPF. `9998` and `9999` are not platforms and never appear here: `9998` is the PPF's own default routing — the taxpayer has declared nobody — and `9999` is a generic, non-nominative code. If a routing answer carries either, there is no PA to name. ## Every Plateforme Agréée Every row carries the operator's website, contact address and establishment, straight from the DGFiP list. Search matches all of it — the number, the name, the domain, the address and the city — so `0064`, `flowie`, `get-flowie.com` and `Paris` all find their row. Confirmed Published Inferred Unknown Registered Pending interop Loading the directory… Matricule | Confidence | Platform | Registered | Status ---|---|---|---|--- Nothing matches that filter. ## How each number was established Three sources, in this order of authority: 1. **The operator telling us.** We wrote to every Plateforme Agréée on 17 September 2026 asking whether the number we held for it was right. Ten answered with the number themselves, one of which nothing public had ever revealed. Nothing beats that, and it is why we asked. 2. **The operator's own words.** A platform that has just been immatriculated tends to say so on its own site, number included. Where we found such a statement, the row records it and the question is closed. 3. **The annuaire, read backwards.** A platform is itself a French company with its own lignes d'adressage, and it routes them on its own matricule. Looking up the operator's SIREN in the PPF annuaire therefore returns its number — a method we checked against every case an operator had already published before trusting it anywhere else. **Reading the annuaire backwards is a strong signal, not a proof, which is why every row carries how it was established.** An operator is free to route its own invoices through a competitor, and several of them do: a platform that publishes one number can perfectly well appear in the annuaire on another's. Read a lone annuaire line as "who routes this company", never as "who this company is". Where several platforms sit on one matricule, the operator is the earliest-registered of them — the others are its customers. Two platforms a fortnight apart on the same number are not a contradiction: one of them is the other's client. The rule fails only when the immatriculation dates tie, and those rows say so. ## The ones we cannot prove Most of the gaps are foreign operators — Italian, German, Danish, Irish — with no French SIREN whose lines could carry a number, and no French-language announcement quoting one. A handful are French platforms that simply never published theirs. They are listed all the same, with their DGFiP `courriel de contact`, and their matricule shown as unknown. That is deliberate: a directory that quietly dropped the rows it could not complete would stop being the list of Plateformes Agréées, and a directory that guessed would be worse than useless — a wrong matricule routes invoices to the wrong platform. **If your platform is on this list and the number is blank or wrong, write to[contact.pdp@flowie.fr]() and it will be corrected.** ## Use it from the API The same directory, from the same source: [code] # every platform, with its matricule and how it is known curl -H "X-API-Key: $FLOWIE_API_KEY" \ https://api.get-flowie.com/v1/portability/platforms # who is 0064? curl -H "X-API-Key: $FLOWIE_API_KEY" \ "https://api.get-flowie.com/v1/portability/platforms?matricule=0064" # and the other direction — who routes this taxpayer today curl -H "X-API-Key: $FLOWIE_API_KEY" \ https://api.get-flowie.com/v1/portability/annuaire/350422622 [/code] Each platform carries `name`, `website`, `email`, `portabilityEmail`, `contactEmail`, `registeredOn`, `status`, and the matricule block: `matricule`, `matriculeConfidence`, `matriculeEvidence`, `matriculeQuote`, `siren`. The annuaire endpoint now returns `currentPaName` and `currentPaConfidence` next to `currentPaMatricule`, so a routing answer reads as a name. The table above reads the same list as a static asset — [`assets/pa-directory.json`](<../../assets/pa-directory.json>) — which is the one to fetch if you want the directory without calling anything. ## References * **[Liste des plateformes agréées]()** — the DGFiP's own list, and the only authority on _who_ is a PA. It moves weekly, and carries no numbers. * **[France · PPF overview]()** — what a PA is, what the PPF does, and the deadlines. * **[Portability](<../../guides/portability.html>)** — changing platform: the inter-PA message, the register, and the routing switch that changes which matricule the annuaire returns. * **[Portability API reference](<../../reference/index.html#portability>)** — every endpoint, including the two above. ======================================================================== # France · E-invoicing integration playbook # Source: https://docs.get-flowie.com/compliance/fr/integration.html ======================================================================== --- title: "France · E-invoicing integration playbook" description: "Ship a French-compliant e-invoicing integration end to end: onboarding & annuaire, receiving, sending Factur-X, emitting the 200–213 lifecycle statuses, e-reporting, sandbox test matrix and go-live checklist — with copy-paste code for every step." canonical: "https://docs.get-flowie.com/compliance/fr/integration" source: "https://docs.get-flowie.com/compliance/fr/integration.html" --- # France · E-invoicing integration playbook Compliance · 🇫🇷 France # The French e-invoicing integration playbook ## Why this playbook From **1 September 2026** every French VAT-liable business must be able to _receive_ e-invoices — and large & mid-sized companies must also _send_ them. SMEs follow on **1 September 2027**. There is no free state exchange platform anymore (the PPF was reduced to the directory + tax concentrator in October 2024), so every invoice flows through a **Plateforme Agréée**. Flowie is PA n° `0064`: this page is the complete, code-first path from zero to a compliant integration — sending, receiving, and the [full 200–213 lifecycle](). Seven steps, each with copy-paste code. A focused team ships this in days, not months. ## Architecture in 60 seconds [code] Your ERP / app │ REST + webhooks (one API for every country) ▼ Flowie (PA n° 0064) ── annuaire lookup ──► recipient's PA ──► buyer │ └── e-reporting & mandatory statuses ──► PPF concentrator ──► DGFiP (≤ 24 h) [/code] * **You never talk to the PPF.** Flowie routes invoices PA-to-PA (resolved via the annuaire) and reports the mandatory data and statuses to the concentrator within the 24-hour window. * **One integration, both directions.** The same document API sends and receives; direction is just a field. * **Statuses are first-class.** Lifecycle statuses travel as CDAR messages between platforms; you emit and observe them through the lifecycle API and webhooks — never by parsing XML. ## Onboard your companies & the annuaire Create each French legal entity with its SIREN/SIRET, then register it. Registration provisions the receiving flow and (for entities you manage) the **annuaire** entry that tells every other PA to route invoices for this SIREN to Flowie. [code] curl -X POST https://back.flowie.ink/exchange/v1/companies \ -H "Authorization: Bearer $KEY" \ -d '{ "name": "Maison Lumière SAS", "country": "FR", "vatNumber": "FR26921376265", "additionalIdentifiers": { "siret": "92137626500017" } }' curl -X POST https://back.flowie.ink/exchange/v1/companies/comp_01H…/register \ -H "Authorization: Bearer $KEY" [/code] Before invoicing a French counterparty, check how they’re reachable (their PA, their identifiers) with a [directory search](<../../reference/index.html#search-directory>) — a bare SIREN/SIRET is routed to an exact lookup: [code] curl "https://back.flowie.ink/exchange/v1/directory/search?q=552100554" \ -H "Authorization: Bearer $KEY" [/code] When a counterparty exposes several **reception points** (_lignes annuaire_) on the same SIRET, address the exact one by passing the composed identifier `{siren}_{siret}[_{suffix}]` (e.g. `75297877500027_001`) as the send `to` — Flowie resolves the participant from the SIRET and carries the `suffixeAdressage` through as routing metadata. See [Reception-point addressing](<../../reference/index.html#reception-point-addressing>). ## Receive — you have to be ready first Reception is the first legal deadline (all companies, September 2026) and the easy half. Register a webhook endpoint, and every inbound invoice — Factur-X, UBL or CII, from any PA — lands as a normalized document: [code] curl -X POST https://back.flowie.ink/exchange/v1/webhooks \ -H "Authorization: Bearer $KEY" \ -d '{ "url": "https://erp.example.fr/hooks/flowie", "events": ["document.received", "lifecycle.updated", "compliance.reported", "compliance.reported.failed", "document.failed"] }' [/code] On `document.received`, pull whichever view your system prefers — the structured JSON, the original XML, or the rendered PDF: [code] curl https://back.flowie.ink/exchange/v1/documents/doc_inb1/structured \ -H "Authorization: Bearer $KEY" # flat JSON for your ERP # also available: …/xml (signed original) and …/pdf (human view) [/code] Receiving is also when your lifecycle duties start The moment an invoice is made available to you ([status 203]()), you are the buyer in the state machine: acknowledging, approving, disputing or refusing are _your_ calls to make — see step 4. ## Send — Factur-X by default The standard [send call](<../../reference/index.html#send-document>) works unchanged; for French domestic B2B, Flowie generates **Factur-X** (EN 16931, CIUS-FR) and routes via the annuaire. Mind the French required fields — SIRET, and since 2026 the buyer’s SIREN, delivery address when it differs, and the nature of the operation (goods / services / mixed): [code] curl -X POST https://back.flowie.ink/exchange/v1/documents/send \ -H "Authorization: Bearer $KEY" \ -H "Idempotency-Key: fa-2027-0042" \ -d '{ "type": "invoice", "from": "comp_01H…", "to": "0009:55210055400013", "document": { "number": "FA-2027-0042", "issueDate": "2027-09-01", "dueDate": "2027-10-01", "currency": "EUR", "buyer": { "name": "Grand Client SA", "vatNumber": "FR40552100554", "additionalIdentifiers": { "siren": "552100554" } }, "lines": [ { "description": "Prestation de conseil — août 2027", "quantity": 8, "unit": "days", "unitPrice": 950.00, "vatRate": 20, "vatCategory": "S" } ] } }' [/code] A `201` response means the invoice passed Flowie’s controls — [status `200 Déposée`]() is emitted and its data will reach the PPF within 24 h. A platform rejection later (unknown SIRET, duplicate number…) surfaces as `document.failed` with the motif — that is [status `213 Rejetée`](): fix and send a _new_ invoice, never a mutation of the old one. B2G invoices (Chorus Pro) additionally need `buyerReference` (Service Exécutant) and `orderReference` — see [required fields](). ## Emit the lifecycle — who owes which status The [explorer]() covers every status in depth; here is the split of responsibilities your integration must implement. Everything is one endpoint: [`POST /v1/documents/{id}/lifecycle`](<../../reference/index.html#update-lifecycle>). When…| You call| FR status emitted| Tier ---|---|---|--- **As buyer (inbound invoices)** AP takes the invoice into processing| `{"status":"under_review"}`| `204` Prise en charge| Recommended You approve it in full| `{"status":"approved"}`| `205` Approuvée| Recommended You approve it in part| `{"status":"approved","remainingAmount":…}`| `206` Approuvée partiellement| Recommended You contest it (without refusing)| `{"status":"disputed","reason":"…"}`| `207` En litige| Libre You need supporting documents| `{"status":"disputed","reasonCode":"suspended","reason":"…"}`| `208` Suspendue| Libre You refuse it (business decision)| `{"status":"rejected","reasonCode":"…","reason":"…"}`| `210` Refusée| **Mandatory** Your payment went out| `{"status":"paid","paymentDate":"…"}`| `211` Paiement transmis| Recommended **As supplier (outbound invoices)** Buyer asked for documents (you received `208`)| `POST …/actions {"action":"link","relatedDocumentId":"…"}`| `209` Complétée| Libre The money arrived on your account| `{"status":"paid","paymentDate":"…","paymentAmount":…}`| `212` Encaissée| **Mandatory** Partial collection| `{"status":"partially_paid","paymentAmount":…,"remainingAmount":…}`| `212` Encaissée (partial)| **Mandatory** The two calls you cannot skip `210` when you refuse as a buyer, `212` when you’re paid as a supplier. Both are legally mandatory, both are auto-reported by Flowie to the DGFiP, and `212` is what pre-fills the CA3 VAT return for services. Everything else improves visibility; these two keep you compliant. Statuses `200`, `201`, `202`, `203` and `213` are Flowie’s job — never emit them yourself. ## The webhook handler — your single integration point One endpoint, four event families, out-of-order-safe. This is the reference shape (Node; the logic transposes 1-to-1 to any stack): [code] import express from "express"; import crypto from "node:crypto"; const app = express(); app.use(express.raw({ type: "application/json" })); // keep the raw body for HMAC // French statuses can be skipped (only 4 of 14 are mandatory) — never // assume ordering. Rank them and ignore stale updates. const RANK = { submitted: 0, received: 1, under_review: 2, disputed: 3, approved: 4, partially_paid: 5, paid: 6, rejected: 9 }; app.post("/hooks/flowie", async (req, res) => { const sig = crypto.createHmac("sha256", process.env.FLOWIE_WEBHOOK_SECRET) .update(req.body).digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(req.headers["x-flowie-signature"] ?? ""))) { return res.status(401).end(); } const event = JSON.parse(req.body); res.status(200).end(); // ack fast, process async switch (event.type) { case "document.received": // FR code 203 — you are the buyer await queue.push("import-invoice", event.data.documentId); break; case "lifecycle.updated": { // any 2xx status, either direction const { documentId, currentStatus, previousStatus, reasonCode, reason } = event.data; if (RANK[currentStatus] <= RANK[previousStatus]) break; // stale / dup await erp.setInvoiceStatus(documentId, currentStatus, { reasonCode, reason }); break; } case "document.failed": // FR code 213 — technical reject await alerting.page("invoice-rejected", event.data); // fix + re-send break; case "compliance.reported": // DGFiP leg confirmed case "compliance.reported.failed": // DGFiP leg rejected — investigate await audit.log(event.type, event.data); break; } }); [/code] Three properties make this production-grade, and all three matter in France specifically: * **Idempotent + monotonic** — statuses may arrive twice (retries) or out of order (skipped optional statuses); the rank check handles both. * **Fast ack** — status bursts happen (a buyer platform replaying a backlog); never do ERP writes before responding. * **Compliance events audited** — `compliance.reported` is your proof the mandatory statuses reached the DGFiP; keep the trail (FEC audits ask for it). ## E-reporting — mostly Flowie’s job, two things are yours E-reporting covers what e-invoicing doesn’t: **B2C** and **international / intra-community** transactions (transmitted as data, not exchanged invoices), plus **payment data** for services. What that means for you: * **Domestic B2B** — nothing to do. The mandatory statuses you emit (esp. `212` with its collected amount) _are_ the payment e-reporting; Flowie forwards them on schedule. * **B2C & cross-border** — send those transactions through the same API (the recipient just isn’t on the French network); Flowie derives and files the transaction data at your VAT regime’s frequency. * **Watch the failures.** `compliance.reported.failed` means the DGFiP leg bounced; Flowie retries with backoff, but repeated failures (bad SIREN, closed period) need your action. ## Prove it in the sandbox — the French test matrix Every scenario above is reproducible with a `flw_test_` key, deterministic [test identifiers](<../../sandbox/index.html>) and the `simulateCompliance` org flag: What you’re proving| How| Expect ---|---|--- Happy path 200→212| Send, then walk `under_review → approved → paid`| `lifecycle.updated` ×3, then `compliance.reported` (with `simulateCompliance:"accept"`) Refusal (210) with motif| `{"status":"rejected","reasonCode":"…"}` as buyer| Terminal state; motif echoed verbatim in the event Platform reject (213)| `simulateCompliance: "reject_00058"`| `compliance.reported.failed` with code `00058` Illegal transition guard| `received → paid` directly| `409 invalid_transition` \+ allowed next states DGFiP outage resilience| `simulateCompliance: "timeout_30s"`| Delayed failure event — exercise your retry/alerting Flaky network| `simulateCompliance: "flaky_50pct"`| Random accept/fail — your handler must be idempotent ## Go-live checklist * ☐ Every French entity created with SIREN/SIRET and **registered** (annuaire entry live — verify with a directory search on your own SIREN). * ☐ Webhook endpoint deployed, **HMAC-verified** , idempotent, monotonic — and subscribed to the five event types above. * ☐ Buyer-side flows wired: approve / partial-approve / dispute / **refuse-with-motif (210)**. * ☐ Supplier-side **encaissement (212)** wired to your bank reconciliation (incl. partial collections). * ☐ `213` alerting in place — a technical reject must page someone; the invoice legally doesn’t exist until re-sent. * ☐ Sandbox matrix green, including the timeout and flaky simulators. * ☐ Sending: French required fields present (SIRET, buyer SIREN, delivery address if different, nature of operation; Service Exécutant for B2G). * ☐ Compliance audit trail persisted (`compliance.reported` events). Non-compliance has a price tag €50 per missing e-invoice (capped at €15,000/year) and €500 per missing e-reporting transmission (capped at €15,000/year) — per the 2026 finance law. The DGFiP has signalled leniency for good-faith businesses in the first months, but “we hadn’t integrated yet” is not a defence after the SME deadline. ## References * [France overview]() — deadlines, required fields, PPF error codes, refus/rejet motifs. * [Lifecycle explorer]() — all 14 statuses, interactive, with per-status code. * [API reference](<../../reference/index.html>) · [Webhook cookbook](<../../reference/webhooks.html>) · [Sandbox guide](<../../sandbox/index.html>). * [impots.gouv.fr]() — official reform portal & PA list; [external specifications]() (current v3.2). * [FNFE-MPE]() — AFNOR XP Z12-012/013/014 annexes and Schematrons. ======================================================================== # Italy · SDI compliance # Source: https://docs.get-flowie.com/compliance/it/index.html ======================================================================== --- title: "Italy · SDI compliance" description: "Sending invoices in Italy via Sistema di Interscambio (SDI): Codice Destinatario, FatturaPA, exchange formats, deadlines, error codes." canonical: "https://docs.get-flowie.com/compliance/it/" source: "https://docs.get-flowie.com/compliance/it/index.html" --- # Italy · SDI compliance Compliance · 🇮🇹 Italy # Italy — Sistema di Interscambio (SDI) ## TL;DR * E-invoicing has been **mandatory in Italy since 2019** for B2B and B2G; B2C since 2022. * Every invoice must transit **Agenzia delle Entrate's SDI hub** ; you can't bypass it. * Native format is **FatturaPA XML**. Flowie auto-converts UBL ↔ FatturaPA and routes via SDI for you. * Each invoice must be addressed via a **Codice Destinatario** (7 chars) or, for unregistered recipients, via certified email (**PEC**). * SDI delivers _three_ receipts per invoice: _RC_ (delivered), _NS_ (rejected), or _MC_ (recipient unreachable). Flowie surfaces these as webhook events. ## Background — the SDI flow Italy was the first country in the world to mandate B2B e-invoicing through a centralized hub. The flow: [code] Sender ERP → Intermediary (Flowie) → SDI → Recipient ↓ Receipts (RC/NS/MC) ← back through Flowie [/code] Flowie acts as your registered **intermediario**. We submit invoices on your behalf, store them for the legally-mandated 10 years, and forward SDI receipts to your webhook. ## Codice Destinatario Every Italian recipient has a **Codice Destinatario** (CD) — a 7-character routing code that tells SDI where to deliver. Three flavors: Recipient type| Code format| Source ---|---|--- Has its own SDI channel| 7 alphanumeric chars (e.g. `M5UXCR1`)| Provided by recipient. Public administration| 6-digit code (e.g. `UFY9MC`)| Indice PA — [indicepa.gov.it](). Has only PEC| `0000000` \+ `recipientPec` field| SDI uses the certified-email fallback. Unknown / private individual| `0000000`| SDI delivers via Agenzia portal. To resolve a CD from a SIRET-equivalent (Italian Codice Fiscale), use: [code] curl …/v1/companies/resolve?countryCode=IT&vatNumber=IT01234567890 \ -H "Authorization: Bearer $KEY" # → response.additionalIdentifiers includes "codiceDestinatario" [/code] ## FatturaPA & UBL — when to care SDI accepts only **FatturaPA XML 1.2.2**. If you send Flowie UBL or JSON, we transcode to FatturaPA before submission to SDI. The reverse is true for incoming: we transcode FatturaPA → UBL so your stack only ever deals with one format. If you must send raw FatturaPA (e.g. you already generate it from your ERP): [code] curl -X POST …/v1/documents/send \ -H "Authorization: Bearer $KEY" \ -d '{ "type":"invoice", "format":"ubl-xml", "from":"comp_…", "to":"0211:01234567890", "xml":"" }' [/code] ## Required fields for Italian invoices * seller.additionalIdentifiers[codiceFiscale]required 11- or 16-character Italian tax code. Auto-populated on company creation from the registry. * buyer.additionalIdentifiers[codiceDestinatario]required 7-char CD or `0000000` \+ `buyer.contact.pec`. * document.note (TipoDocumento)required SDI document type: `TD01` standard invoice, `TD04` credit note, `TD16` reverse-charge, `TD17` intra-EU services, `TD24` deferred invoice, … * document.lines[].vatCategoryrequired Italy uses Natura codes (`N1`–`N7`) on top of standard rates. Required when `vatRate = 0`. * payment.discountTermsoptional but enforced If present, `endDate` must precede `document.dueDate`. SDI rejects otherwise (`00400`). ## Document types — TipoDocumento (TD) Every Italian e-invoice carries a **TipoDocumento** (`TD`) code that tells SDI what kind of document it is — ordinary sale, credit note, self-invoice, integration for reverse charge, and so on. Set it via `document.note` (we map it into the FatturaPA `` field). Picking the wrong TD is a common cause of business-side errors and of [scarto codes `00471`–`00474`](<#error-codes>). **Deep dive & interactive explorer:** the full referential — every code filterable by family, click-to-detail with the rules and the exact Flowie call, plus a deep dive on each family — is on the dedicated [**Document types explorer**](). The table below is the summary. The complete current set (Agenzia delle Entrate guide v1.10, April 2025). Numbering jumps from `TD09` to `TD16` by design — `TD10`–`TD15` do not exist. TD| Descrizione (IT)| What it's for ---|---|--- `TD01`| Fattura| Ordinary invoice — standard B2B / B2C / B2G sale of goods or services. `TD02`| Acconto/anticipo su fattura| Advance / down payment against an invoice. `TD03`| Acconto/anticipo su parcella| Advance / down payment against a professional fee. `TD04`| Nota di credito| Credit note. `TD05`| Nota di debito| Debit note. `TD06`| Parcella| Professional fee invoice (lawyers, consultants, …). `TD07`| Fattura semplificata| Simplified invoice (total ≤ €400). `TD08`| Nota di credito semplificata| Simplified credit note. `TD09`| Nota di debito semplificata| Simplified debit note. `TD16`| Integrazione fattura da reverse charge interno| Self-integration of a **domestic** reverse-charge invoice. `TD17`| Integrazione/autofattura per acquisto servizi dall'estero| Integration / self-invoice for **services bought from abroad**. `TD18`| Integrazione per acquisto di beni intracomunitari| Integration for **intra-EU purchases of goods**. `TD19`| Integrazione/autofattura per acquisto di beni ex art. 17 c.2 DPR 633/72| Integration / self-invoice for goods bought from a non-resident but already in Italy. `TD20`| Autofattura per regolarizzazione e integrazione delle fatture| Self-invoice to regularise a missing / irregular supplier invoice. `TD21`| Autofattura per splafonamento| Self-invoice for exceeding the export-VAT ceiling (plafond). `TD22`| Estrazione beni da Deposito IVA| Withdrawal of goods from a VAT warehouse. `TD23`| Estrazione beni da Deposito IVA con versamento dell'IVA| Withdrawal from a VAT warehouse, with VAT payment. `TD24`| Fattura differita — art. 21 c.4 lett. a) DPR 633/72| Deferred invoice (goods delivered via DDT / services documented). `TD25`| Fattura differita — art. 21 c.4 lett. b) DPR 633/72| Deferred invoice for triangulation resale by the intermediary. `TD26`| Cessione di beni ammortizzabili e passaggi interni| Sale of depreciable assets / internal transfers between activities. `TD27`| Fattura per autoconsumo o per cessioni gratuite senza rivalsa| Own-consumption or free-of-charge transfer without VAT recovery. `TD28`| Acquisti da San Marino con IVA (fattura cartacea)| Purchases from San Marino with VAT (paper invoice received). `TD29`| Comunicazione per omessa o irregolare fatturazione (art. 6 c.8 D.Lgs. 471/97)| Buyer's notice to the tax authority of a supplier's omitted / irregular **domestic** invoicing (added v1.10; took over this case from `TD20`). **Self-invoice TDs need seller = buyer.** For `TD16`–`TD27` (integrations / self-invoices) SDI checks the cedente/prestatore against the cessionario/committente: `TD20`/`TD21`/`TD27` must have **seller = buyer** (`00472`), the foreign-purchase TDs require a **non-IT seller country** (`00473`), and ordinary `TD01` must have **seller ≠ buyer** (`00471`). ## Lifecycle & SDI receipts SDI returns a sequence of asynchronous XML messages. The full set (official fatturapa.gov.it definitions), and how each maps to a Flowie event: Msg| Nome (IT)| When it fires| Flowie event ---|---|---|--- `NS`| Notifica di scarto| File failed SDI checks (see [codes](<#error-codes>)) — **not** fiscally issued. Correct & resend within 5 days, same number/date.| `document.failed` `MT`| File dei metadati| Sent to the _recipient_ alongside the FatturaPA file — routing/metadata (number, date, sender, amounts).| `document.delivered` (`metadata.sdiMetadata` set) `RC`| Ricevuta di consegna| Passed checks **and** delivered to the recipient (via CD or PEC). Carries the delivery date.| `document.delivered` `MC`| Mancata consegna| Valid but SDI **can't currently deliver** (channel unreachable, mailbox full). Made available on the recipient's _Fatture e Corrispettivi_ portal; SDI keeps retrying.| `document.delivered`, `deliveryStatus="delivered_via_portal"` `AT`| Attestazione di avvenuta trasmissione (con impossibilità di recapito)| Valid but **undeliverable at all** (typically B2G channel persistently down). SDI issues a transmission certificate so the sender can deliver by other means.| `document.delivered`, `deliveryStatus="transmitted_undeliverable"` `EC`| Esito committente — `EC01` accettazione / `EC02` rifiuto| **B2G only.** The PA recipient's decision sent _to_ SDI within 15 days: accept (`EC01`) or reject (`EC02`).| `lifecycle.updated` `NE`| Notifica di esito| **B2G only.** SDI relays the recipient's `EC01`/`EC02` outcome back to the _sender_.| `lifecycle.updated` `SE`| Scarto esito committente| SDI rejected the recipient's `EC` message itself (inadmissible / non-conformant).| — `DT`| Notifica di decorrenza termini| **B2G only.** 15 days passed after delivery with **no** `EC` outcome → terms expired, SDI closes the flow (invoice considered processed).| `lifecycle.updated` **Flow:** send → checks fail ⇒ `NS` (stop, fix & resend) · checks pass ⇒ delivery: success ⇒ `RC` (+ `MT` to recipient) · temporary failure ⇒ `MC` · permanent failure ⇒ `AT`. **B2G only** , after delivery the PA may return `EC01`/`EC02` (relayed to the sender as `NE`); no answer in 15 days ⇒ `DT`. Private B2B/B2C have **no accept/reject step** — the recipient cannot formally refuse via SDI, so `RC` / `MC` is the terminal state. ## SDI scarto codes — reasons for a Notifica di scarto (NS) When SDI rejects (`scarta`) a FatturaPA file it returns a `NS` carrying one or more of these codes. The file is **not** fiscally issued — correct and resend within 5 days keeping the same number and date. Authoritative list: fatturapa.gov.it _Elenco controlli_ v1.7. The codes below are the complete set, grouped by what they check. ### Nomenclature, transmission & uniqueness (00001–00102, 00404) Code| Meaning| Fix ---|---|--- `00001`| Nome file non valido — wrong filename format.| `IT_.xml(.p7m)`, progressivo base-36. `00002`| Nome file duplicato — a file with this name was already sent.| Increment the progressivo in the filename. `00003`| File-size limit exceeded.| Max 5 MB (web) / 150 MB (SDICoop, SFTP). Split or compress attachments. `00102`| Signed file (`.p7m`) but the CAdES/XAdES signature is absent or malformed.| Re-sign with a valid qualified certificate (B2G requires a signature). `00404`| Fattura duplicata — same _sender VAT + year + number_ already accepted.| SDI dedupe; increment your numbering. (Distinct from `00002`, which is the filename.) `00409` / `00411`| The file is a duplicate of one already in processing / already processed within the batch.| Remove the duplicate from the lotto. ### Schema & signature integrity (00400-class structural) Code| Meaning| Fix ---|---|--- `00200`| File non conforme al formato — schema (XSD) validation failed.| Inspect `error.details[]`; validate against the FatturaPA 1.2.x / 1.9 XSD before sending. `00201`| More than the allowed number of schema errors (≥ 50 reported, processing aborted).| Fix the structural defects; re-validate. ### Sender / recipient identity & routing (00300–00330) Code| Meaning| Fix ---|---|--- `00300`| `IdFiscaleIVA` of the trasmittente not valid.| Check the transmitter's VAT id format/country. `00301`| `IdFiscaleIVA` of cedente/prestatore (seller) not valid.| Correct the seller VAT number. `00302`| `CodiceFiscale` of cedente/prestatore not valid.| Correct the seller codice fiscale. `00303`| `IdFiscaleIVA` of cessionario/committente (buyer) not valid.| Correct the buyer VAT number. `00305`| `CodiceFiscale` of cessionario/committente not valid.| Correct the buyer codice fiscale. `00306`| `CodiceDestinatario` not present in the _Indice PA_ (for a PA recipient).| Look up the office on [indicepa.gov.it](); for private buyers use their 7-char SDI code or `0000000`+PEC. `00309`| For a PA, `CodiceDestinatario = 0000000` (the catch-all) is not allowed.| PA recipients need their real 6-char office code. `00311`| `CodiceDestinatario` format invalid.| Exactly 6 chars (PA) or 7 chars (private). `00312`| For a private recipient the 7-char `CodiceDestinatario` is not a registered SDI channel.| Use a valid registered code, or `0000000` with a valid `PECDestinatario`. `00313`| `CodiceDestinatario = 0000000` but no `PECDestinatario` supplied.| Provide the recipient's PEC mailbox. `00320`| For a PA recipient, `PECDestinatario` must not be filled.| Remove the PEC; PA route is by office code only. `00330`| `IdFiscaleIVA` of trasmittente equals cessionario/committente — not allowed for that flow.| Check who is transmitting vs receiving. ### Amounts, VAT & rounding (00400–00430) Code| Meaning| Fix ---|---|--- `00400`| `Natura` present but an `AliquotaIVA` > 0 is also set (or vice-versa).| A zero-rate/exempt line needs a `Natura` code and rate 0; a taxed line needs a rate > 0 and **no** Natura. `00401`| `Natura` missing where `AliquotaIVA = 0`.| Supply the right `N1`–`N7` exemption code. `00403`| `DataScadenzaPagamento` earlier than invoice date.| Due date must be on/after the document date. `00411`| `RiferimentoNumeroLinea` in a discount/surcharge points to a non-existent line.| Fix the line reference. `00413`| `Natura` = `N6` (reverse charge) but `EsigibilitaIVA` set to split-payment (S).| N6 and split payment are mutually exclusive. `00414`| `Natura = N6.x` required when `EsigibilitaIVA` indicates reverse charge.| Use a specific `N6.*` sub-code (post-2021 granularity). `00415`| Only a generic `N2`/`N3`/`N6` used — the granular sub-codes are mandatory.| Use `N2.1/N2.2`, `N3.1…N3.6`, `N6.1…N6.9`. `00417`| Neither `IdFiscaleIVA` nor `CodiceFiscale` present for the buyer.| At least one buyer tax identifier is required. `00418`| `Data` of the invoice in `DatiGeneraliDocumento` is after the receipt date at SDI.| No future-dated invoices. `00419`| A VAT-summary row (`DatiRiepilogo`) is missing for an `AliquotaIVA`/`Natura` used on the lines.| Add the matching summary block per rate/nature. `00420`| `ImponibileImporto` in a summary row inconsistent with the lines of that rate.| Recompute the taxable base per rate. `00421`| `Imposta` in a summary row ≠ `ImponibileImporto × AliquotaIVA` (beyond 1-cent tolerance).| Recheck VAT rounding per summary row. `00422`| `ImponibileImporto` inconsistent with `PrezzoTotale` of the related lines.| Reconcile line totals to the summary base. `00423`| `PrezzoTotale` ≠ `PrezzoUnitario × (Quantità) ± sconti`.| Recompute the line total. `00424`| `Imposta` of a summary row doesn't match the declared rounding.| Align to standard rounding (2 decimals). `00425`| `Numero` of the document missing a numeric character.| The invoice number must contain at least one digit. `00427`| `EsigibilitaIVA = S` (split payment) but the buyer is not a PA / eligible entity.| Split payment only for qualifying public/listed buyers. `00430`| `TipoDocumento = TD01` but seller = buyer.| An ordinary invoice can't be self-addressed (use a self-invoice TD). ### TipoDocumento ↔ parties consistency (00471–00474) Code| Meaning| Fix ---|---|--- `00471`| `TipoDocumento` is `TD01/TD02/TD03/TD06` but cedente = cessionario (seller = buyer).| These ordinary types require seller ≠ buyer. `00472`| `TipoDocumento = TD16/TD17/TD18/TD19/TD20/TD22/TD23/TD28` but seller = buyer where the type forbids it (or vice-versa).| Self-invoice / integration types: set the cedente and cessionario per the type's rule (e.g. `TD20/21/27` need seller = buyer). `00473`| `TipoDocumento = TD17/TD18/TD19` (foreign purchase) but the _seller_ country is `IT`.| The cedente/prestatore on a foreign-purchase self-invoice must be a non-Italian country. `00474`| `TipoDocumento = TD28` (San Marino) but the seller country is not `SM`.| Use `TD28` only for purchases from San Marino. **Where the code lands in the API.** A scarto surfaces on the `document.failed` webhook and on `GET /v1/documents/{id}` as `error.code` (the `00xxx` value) plus a human-readable `error.message` and, for schema errors (`00200`), an `error.details[]` array naming the offending XML element/xpath. ## Testing your Italian integration What you want to test| How ---|--- SDI happy path| Sender VAT `IT00000000010`, recipient `0211:00000000099` with CD `FLOWIE0`. Codice Destinatario rejection| `simulateCompliance: "reject_00306"`. VAT mismatch| Send a line with `quantity: 0.333` and force-round → triggers `00417`. MC fallback (portal delivery)| Recipient CD `0000000` with no PEC → arrives as `document.delivered_via_portal`. ## FAQ ### Do I need a separate authorization in Italy? No. Flowie's intermediario credentials cover all our customers. You just need to grant us the SDI delegation in your Fisconline account once — the dashboard walks you through it. ### What about the 10-year storage requirement? Italian law requires every B2B invoice to be archived for 10 years in a "conservazione sostitutiva" environment. Flowie's archive complies with the Agenzia delle Entrate technical specs (DPCM 03/12/2013). No extra cost. ### Can I send a paper invoice in parallel? Legally, no — only the SDI-transmitted version counts. You can send a courtesy PDF copy via email, but it has no fiscal value. ## References **Primary sources** (Italian government & EU regulator): * [Agenzia delle Entrate · Fatturazione elettronica]() — Official taxpayer portal; technical specs, FAQ, ramp dates. * [FatturaPA · official portal]() — FatturaPA reference site (formats, schema, examples). * [Specifiche tecniche fatturazione B2B v1.9]() — Authoritative XML schema and validation rules (PDF). * [IndicePA]() — Public-administration directory for B2G Codice Univoco lookup. * [Fisconline / Servizi IVA]() — Where you delegate Flowie as _intermediario_ for SDI submission. * [Decreto Legge n. 66/2014 (Normattiva)]() — Foundational law mandating B2G e-invoicing. * [Legge di Bilancio 2018 · Art. 1 cc. 909-928]() — Extension to universal B2B clearance from 2019. * [EU Commission · eInvoicing in Italy]() — Pan-EU reference factsheet. * [OpenPeppol · Italy profile]() — Peppol BIS interaction with SDI. **Industry analyses** (cross-reference for the SDI mechanics): * [Sovos · Italy SDI mandate guide]() — Industry tracker — clearance model details. * [Pagero · Italy compliance updates]() — Industry compliance tracker. ======================================================================== # Italy · Document types (TipoDocumento TD01–TD29) # Source: https://docs.get-flowie.com/compliance/it/document-types.html ======================================================================== --- title: "Italy · Document types explorer — TipoDocumento (TD01–TD29)" description: "Every Italian SDI TipoDocumento (TD) code, interactive: filter by family, click any code for its Agenzia delle Entrate definition, when to use it, the seller/buyer & scarto rules, and the exact Flowie call. All 23 codes TD01–TD29 (v1.10), deep-dived and referenced against the public Agenzia delle Entrate sources." canonical: "https://docs.get-flowie.com/compliance/it/document-types" source: "https://docs.get-flowie.com/compliance/it/document-types.html" --- # Italy · Document types explorer — TipoDocumento (TD01–TD29) Compliance · 🇮🇹 Italy # Italian document types — the _TipoDocumento_ (TD) explorer Every Italian e-invoice carries a **TipoDocumento** (`TD`) code telling SDI what kind of document it is — an ordinary sale, a credit note, a self-invoice, an integration for reverse charge, and so on. Picking the wrong one is a top cause of business-side errors and of [scarto codes `00471`–`00474`](). This is the full referential — **all 23 codes (TD01–TD29)** — as an interactive explorer, plus a deep dive on every family and how to set each one with Flowie. Cross-checked against the public Agenzia delle Entrate sources at the [bottom](<#references>). Set it via `document.note` With Flowie you set the TipoDocumento through `document.note` on [`POST /v1/documents/send`](<../../reference/index.html#send-document>); we map it into the FatturaPA `` field. If you omit it, we default to `TD01` (ordinary invoice). ## What a TipoDocumento is (and the numbering gap) The TipoDocumento is a fixed 4-character code in the FatturaPA XML. The current set runs `TD01`–`TD29`, but **`TD10`–`TD15` do not exist** — the numbering jumps from `TD09` to `TD16` by design — so there are **23** live codes. The list is defined by the Agenzia delle Entrate _Guida alla compilazione_ (v1.10, April 2025) and the _Specifiche tecniche_ (Allegato A). The 2026 technical-spec refresh did not add or change any TD code; the last addition was `TD29` in 2025. ## Interactive explorer Filter by family, then click any code for its Agenzia delle Entrate definition, when to use it, the key seller/buyer rule and the exact Flowie call. Family All 23 Ordinarie 7 Note 4 Reverse charge 4 Autofatture 4 Operazioni speciali 4 Ordinarie & acconti Note credito/debito Reverse charge & estero Autofatture speciali Operazioni speciali Click a code above to see its definition, its rules, and the Flowie call that emits it. ## The families The 23 codes fall into five practical families (our grouping, for navigation): * **Ordinarie & acconti** — the everyday documents: ordinary invoice, advances, professional fees, simplified, deferred. * **Note** — credit and debit notes (ordinary and simplified). * **Reverse charge & estero** — integrations / self-invoices where the _buyer_ accounts for the VAT (domestic reverse charge and cross-border purchases). * **Autofatture speciali** — self-invoices where seller = buyer: regularisation, splafonamento, own-consumption, the omitted-invoice notice. * **Operazioni speciali** — VAT-warehouse withdrawals, depreciable-asset transfers, San Marino purchases. ## All 23 codes (TD01–TD29) The complete set (Agenzia delle Entrate _Guida alla compilazione_ v1.10). `TD10`–`TD15` are intentionally absent. TD| Descrizione (IT)| What it's for & how Flowie sets it ---|---|--- Ordinarie & acconti TD01| Fattura| Ordinary invoice — standard sale of goods/services (B2B/B2C/B2G). Default when `document.note` is omitted. TD02| Acconto/anticipo su fattura| Advance / down payment against an invoice. TD03| Acconto/anticipo su parcella| Advance / down payment against a professional fee. TD06| Parcella| Professional-fee invoice (lawyers, consultants, notaries…). TD07| Fattura semplificata| Simplified invoice (total ≤ €400). TD24| Fattura differita — art. 21 c.4 lett. a)| Deferred invoice (goods delivered via DDT, or services documented). TD25| Fattura differita — art. 21 c.4 terzo periodo lett. b)| Deferred invoice for triangulation resale by the intermediary. Note di credito / debito TD04| Nota di credito| Credit note — reduces/cancels a prior invoice; references the original. TD05| Nota di debito| Debit note — increases a prior invoice. TD08| Nota di credito semplificata| Simplified credit note. TD09| Nota di debito semplificata| Simplified debit note. Reverse charge & acquisti dall'estero TD16| Integrazione fattura da reverse charge interno| Self-integration of a **domestic** reverse-charge invoice. TD17| Integrazione/autofattura per acquisto servizi dall'estero| Integration / self-invoice for **services bought from abroad**. Seller country ≠ IT. TD18| Integrazione per acquisto di beni intracomunitari| Integration for **intra-EU purchases of goods**. Seller in EU, ≠ IT. TD19| Integrazione/autofattura per acquisto beni ex art. 17 c.2 DPR 633/72| Goods bought from a non-resident but already in Italy. Autofatture speciali (seller = buyer) TD20| Autofattura per regolarizzazione e integrazione delle fatture| Self-invoice to regularise/integrate a supplier document (intra-EU art. 46, art. 17 c.2). The domestic omitted-invoice _denuncia_ moved to `TD29`. TD21| Autofattura per splafonamento| Self-invoice for exceeding the export-VAT ceiling (plafond). TD27| Fattura per autoconsumo o cessioni gratuite senza rivalsa| Own-consumption or free-of-charge transfer without VAT recovery. TD29| Comunicazione per omessa/irregolare fatturazione (art. 6 c.8 D.Lgs. 471/97)| Buyer's notice to the tax authority of a supplier's omitted / irregular **domestic** invoice. Added v1.10 (2025); took this case over from `TD20`. Operazioni speciali TD22| Estrazione beni da Deposito IVA| Withdrawal of goods from a VAT warehouse. TD23| Estrazione beni da Deposito IVA con versamento dell'IVA| Withdrawal from a VAT warehouse, with VAT payment. TD26| Cessione di beni ammortizzabili e passaggi interni| Sale of depreciable assets / internal transfers between activities. TD28| Acquisti da San Marino con IVA (fattura cartacea)| Purchases from San Marino with VAT (paper invoice received). Seller country = SM. ## Deep dive · ordinarie & acconti **TD01 Fattura** is the workhorse — the ordinary invoice for the vast majority of sales. **TD02/TD03** cover advances (_acconto/anticipo_) against an invoice or a professional fee respectively; the eventual final document nets them out. **TD06 Parcella** is the fee invoice used by regulated professions. **TD07 Fattura semplificata** is allowed only for small totals (≤ €400) and carries a reduced field set. **TD24 / TD25 (fattura differita)** are the deferred-invoice types: TD24 for goods delivered under a _documento di trasporto_ (DDT) or documented services invoiced by the 15th of the following month; TD25 for the specific triangulation-resale case (art. 21 c.4 terzo periodo lett. b). All of these require **seller ≠ buyer** — self-addressing an ordinary type triggers scarto [`00471`]() / `00430`. ## Deep dive · note di credito e debito **TD04 Nota di credito** reduces or cancels a previously issued invoice (a return, a discount, an error); **TD05 Nota di debito** increases it. Both should reference the original document. **TD08 / TD09** are the simplified counterparts, paired with `TD07`. A credit note is a first-class SDI document — it is not a lifecycle status — and flows through the same [RC / MC / AT receipt]() path as an invoice. ## Deep dive · reverse charge & acquisti dall'estero These are the _integrazione_ / _autofattura_ types where the **buyer** accounts for the VAT and sends a document _to SDI_ to record it (the _esterometro_ replacement for cross-border). Getting the country of the _cedente/prestatore_ right is what SDI checks: * **TD16** — domestic reverse charge (e.g. construction subcontracting, scrap, certain electronics): the buyer integrates the supplier's Italian invoice. * **TD17** — services purchased from a **foreign** provider: seller country must be ≠ IT, else scarto [`00473`](). * **TD18** — intra-EU purchase of **goods** : seller is an EU non-IT party. * **TD19** — goods bought from a non-resident but physically already in Italy (art. 17 c.2 DPR 633/72). ## Deep dive · autofatture speciali (seller = buyer) In these the same party is both _cedente_ and _cessionario_ — SDI enforces **seller = buyer** (scarto [`00472`]() if not): * **TD20** — self-invoice to regularise or integrate a missing/irregular document; since v1.10 the pure domestic _omitted-invoice denuncia_ uses `TD29` instead, leaving TD20 for the intra-EU (art. 46) and art. 17 c.2 integration cases. * **TD21** — _splafonamento_ : an habitual exporter that exceeded its VAT-free plafond self-invoices the excess. * **TD27** — _autoconsumo_ / free-of-charge transfers without _rivalsa_ (no VAT charged to a customer). * **TD29** — the buyer's formal notice to the Agenzia delle Entrate that a supplier failed to issue (or issued an irregular) **domestic** invoice (art. 6 c.8 D.Lgs. 471/97). New in 2025. ## Deep dive · operazioni speciali * **TD22 / TD23 — Deposito IVA** : withdrawing goods from a VAT warehouse. TD22 when the VAT is not paid on extraction; TD23 when VAT is paid on extraction. * **TD26 — beni ammortizzabili & passaggi interni**: sale of depreciable assets or internal transfers between separately-accounted activities of the same taxpayer. * **TD28 — San Marino** : recording a purchase from San Marino for which a _paper_ invoice with VAT was received; seller country must be `SM`, else scarto [`00474`](). ## Seller = buyer & the scarto rules (00471–00474) SDI cross-checks the TipoDocumento against the parties and rejects (_Notifica di scarto_) inconsistent combinations: Scarto| Rule ---|--- `00471`| Ordinary types (`TD01/TD02/TD03/TD06`) with cedente = cessionario — these require seller ≠ buyer. `00472`| Self-invoice / integration types where the seller/buyer relationship is wrong — e.g. `TD20/TD21/TD27` require seller = buyer. `00473`| `TD17/TD18/TD19` (foreign purchase) but the _seller_ country is `IT` — the cedente must be non-Italian. `00474`| `TD28` (San Marino) but the seller country is not `SM`. The full scarto catalogue and the SDI receipt lifecycle (NS / RC / MC / AT / EC / NE / DT) are on the [Italy overview](). ## How Flowie models them You never send raw FatturaPA — you send structured data and set the type: [code] curl -X POST https://api.flowie.ink/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "invoice", "document": { "number": "2026/128", "currency": "EUR", "lines": [ ... ], "note": "TD24" }, "from": "IT01234567890", "to": "0208:9876543210" }' [/code] Flowie maps `document.note` into ``, validates the seller/buyer and country rules _before_ transmission (so you get a clear 4xx instead of an SDI `scarto`), and surfaces the SDI receipts as [webhooks](). For credit notes, send `type: "credit_note"` (Flowie sets `TD04`) and link the original. ## References — public sources * [Agenzia delle Entrate · Fatture e corrispettivi — Specifiche tecniche]() — the official hub for the FatturaPA specs and their updates. * [Guida alla compilazione delle fatture elettroniche e dell'esterometro]() — the normative TipoDocumento table (latest v1.10, April 2025). * [Allegato A · Specifiche tecniche]() — the FatturaPA XSD & code lists. * [Italy · SDI overview]() — Codice Destinatario, required fields, the receipt lifecycle and the full scarto catalogue. ======================================================================== # Belgium · Peppol BIS compliance # Source: https://docs.get-flowie.com/compliance/be.html ======================================================================== --- title: "Belgium · Peppol BIS compliance" description: "Belgian e-invoicing on Peppol BIS Billing 3.0: B2B mandate live since 1 January 2026, Mercurius public-sector hub, BE-CIUS profile, error codes. HERMES was decommissioned on 2025-12-31 — Belgium is now pure Peppol." canonical: "https://docs.get-flowie.com/compliance/be" source: "https://docs.get-flowie.com/compliance/be.html" --- # Belgium · Peppol BIS compliance Compliance · 🇧🇪 Belgium # Belgium — pure Peppol since 2026-01-01 Belgium runs no central regulator hub for B2B e-invoicing. The Peppol delivery **is** the compliance event. Flowie is a registered Peppol Access Point (national ID `be:flowie`) — directly, or via a specialized local partner where in-country presence is required — and your stack does not need a separate reporting integration. ⚠️ What changed — HERMES decommissioned 2025-12-31 If you previously wired Flowie's `HERMES` compliance reporter or filtered `compliance.reported` webhooks on `platform == "HERMES"`, that path is gone. The Belgian Federal Public Service Finance (FPS Finance / SPF Finances) shut HERMES down on **2025-12-31** after the July 2024 Business Experts Group review concluded the private Peppol Access Point market was mature enough to make the temporary government bridge unnecessary. Consultation-only access expired **2026-03-31**. Going forward: send Belgian invoices over Peppol with the BE-CIUS profile, full stop. No `compliance.reported` events fire for BE. See [Migration](<#migration>) below for the exact code changes. ## TL;DR * Belgium uses the **4-corner Peppol model** — no central hub for B2B. * **B2B mandate live since 2026-01-01** : structured invoices in Peppol BIS Billing 3.0 with the BE-CIUS profile, exchanged corner-to-corner over Peppol. * **B2G** still routes through **Mercurius** (the federal public-sector hub), mandatory since 2017. * **HERMES is gone** (decommissioned 2025-12-31). Belgian invoices have **no platform-side compliance report** — the Peppol exchange is the compliance. * Flowie auto-publishes Belgian companies to the Peppol SMP. Set `settings.autoCompliance.BE = false` to opt out. ## What changed — the HERMES retirement HERMES was a free, government-operated bridge run by FPS Finance that let small businesses send structured invoices to public-sector buyers (and, in its later iteration, was scheduled to act as a B2B reporting hub). It was always positioned as a _temporary_ bridge until the private Peppol Access Point market matured. Date| Event| Source ---|---|--- 2024-02-06| Belgium adopts the B2B e-invoicing law (Loi du 6 février 2024).| [Loi du 6 février 2024 (Moniteur belge)]() 2024-07| Business Experts Group reassesses HERMES and recommends decommissioning — private Peppol AP market deemed mature.| [efacture.belgium.be (FPS Finance)]() **2026-01-01**| B2B mandate goes live: all domestic B2B taxable transactions must use structured e-invoicing over Peppol.| [OpenPeppol · Belgium]() **End of 2025** (per FPS Finance)| HERMES **send** path decommissioned.| [HERMES portal · official notice]() **2026-03-31**| HERMES consultation-only window closes. Platform fully offline.| [HERMES portal · official notice]() ## Timeline 2017 → 2028 Date| Who| What ---|---|--- 2017-01-01| All BE businesses| Federal B2G via Mercurius (live, still in force). 2024-02-06| Legislators| Loi du 6 février 2024 enacted (B2B mandate). 2025-12-31| FPS Finance| HERMES send-path decommissioned. **2026-01-01**| Domestic B2B taxable transactions| Mandatory structured e-invoicing over Peppol BIS 3.0 (BE-CIUS). 2026-03-31| FPS Finance| HERMES consultation window closes. 2028-01-01 indicative| All B2B| Continuous transaction control (CTC) under EU ViDA timeline. Final Belgian implementation TBD; expect near-real-time reporting of invoice header data to FPS Finance. ## The 4-corner Peppol model Unlike France's PPF or Italy's SDI, Belgium does **not** route invoices through a central regulator. Every business connects to a Peppol Access Point (Flowie is one), and invoices flow corner-to-corner: [code] ┌─────────────┐ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │ Sender ERP │ →→→ │ Sender AP │ →→ │ Recipient AP │ →→ │ Recipient ERP │ │ │ │ (Flowie) │ │ (any Peppol) │ │ │ └─────────────┘ └────────────┘ └──────────────┘ └────────────────┘ ↓ [discovers recipient via SMP] [/code] There's no parallel leg to a regulator hub. The compliance trail you keep is your own: the Peppol Message Level Status (MLS) you receive back from the recipient AP, the lifecycle events you record in Flowie, and your accounting system. That's the audit trail if you're ever audited by FPS Finance. ## BE-CIUS profile — what's specific to Belgium Belgium uses Peppol BIS Billing 3.0 with a Core Invoice Usage Specification (CIUS) that adds these constraints on top of the European core (EN 16931): * **BTW number is mandatory** on both seller and buyer for all B2B (BE BIS rule `BR-BE-01`). * **OGM-VCS structured communication** on payments must follow the format `+++NNN/NNNN/NNNNN+++` when present, with valid mod-97 checksum. * **Embedded human-readable PDF** allowed via `document.attachments[]` for accounts-payable workflows. Optional, but widely expected. * **VAT exempt categories** must reference the BTW article (e.g. category code `"E"` with `"Article 39 BTW"` in the exemption reason). * **VAT category`K`** (intra-Community supply) is rejected when both seller and buyer are Belgian — the transaction is domestic, not intra-EU. ## Required fields for Belgian invoices * seller.vatNumberrequired Format `BE0123456789` (10 digits after `BE`). Flowie validates against the [KBO/BCE registry]() on company creation. * buyer.vatNumberrequired for B2B Same format. For B2C, omit `buyer.vatNumber` and Flowie skips the BE-BIS B2B rules. * payment.referencestringoptional, validated when present If used, must be OGM-VCS format. Flowie validates the mod-97 check-digit and rejects with `BR-BE-02` on bad checksum. * document.lines[].vatCategoryrequired Standard Peppol categories (`S`, `Z`, `E`, `AE`, `K`, `G`, `O`, `L`, `M`); BE rejects `K` if both parties are BE. * document.noteoptional For B2G, set the public-sector contract reference here. ## Mercurius — federal B2G hub Mercurius is the only Belgian regulator-side hub still in scope. For Belgian federal, regional, and local public buyers, the recipient is **always** Mercurius. The Peppol ID looks like: [code] 9925:BE-mercurius- [/code] Look up the OVO number for any public entity in the [Mercurius portal](), or query Flowie's directory: [code] curl https://back.p2p-flowie.com/exchange/v1/directory/search?country=BE&naceCodes=8411 \ -H "Authorization: Bearer $FLOWIE_KEY" [/code] ## Sending a Belgian invoice — end-to-end The same `POST /v1/documents/send` works for BE; nothing extra to wire compared to a generic Peppol send. Flowie applies the BE-CIUS validation when both VAT numbers start with `BE`: [code] curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \ -H "Authorization: Bearer $FLOWIE_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: inv-be-2026-0451" \ -d '{ "type": "invoice", "from": "comp_be_acme", "to": "0208:0123456789", "document": { "number": "INV-2026-0451", "issueDate": "2026-04-30", "dueDate": "2026-05-30", "currency": "EUR", "buyer": { "vatNumber": "BE0987654321" }, "lines": [{ "description": "Consulting — April 2026", "quantity": 10, "unit": "hours", "unitPrice": 150.00, "vatCategory": "S", "vatRate": 21 }], "payment": { "reference": "+++123/4567/89000+++", "iban": "BE68 5390 0754 7034" } } }' [/code] Response: a `document.sent` webhook fires when the recipient AP confirms receipt. No `compliance.reported` event will follow — that's intentional now. ## Lifecycle on Belgian invoices You can still call `POST /v1/documents/{id}/lifecycle` on Belgian invoices to record `approved`, `rejected`, `paid`, etc. — Flowie keeps the audit trail and emits `lifecycle.updated` webhooks. The difference vs France/Italy: Country| Lifecycle update emits| Compliance report ---|---|--- 🇫🇷 France| `lifecycle.updated` \+ `compliance.reported` (PPF)| Yes — Flowie reports to PPF 🇮🇹 Italy| `lifecycle.updated` \+ `compliance.reported` (SDI)| Yes — Flowie reports to SDI 🇧🇪 Belgium| `lifecycle.updated` only| **No** — Peppol delivery is the compliance If your code paths branch on `compliance.reported` for Belgium, treat the absence of that event as the success signal — the lifecycle update completing is the only thing you need. ## Validation errors BE-CIUS validation runs in Flowie before the invoice ever leaves the access point. The codes you see come from the standard Peppol BIS Billing 3.0 validator artifacts (EN 16931 business rules + the BE-CIUS schematron). The full ruleset is large — the table below highlights the rules most BE integrations hit. Rule family| What it checks| Where it surfaces ---|---|--- `BR-CO-*`| EN 16931 cross-line totals: line nets, document totals, VAT breakdowns must reconcile.| `422 Unprocessable Entity` on `POST /v1/documents/send` or `/validate`. Check `error.details[]` for the failing rule code and the offending XPath. `BR-BE-*`| BE-CIUS additions: BTW required on B2B parties, OGM-VCS structured-communication checksum, allowed VAT categories.| Same — synchronous `422`. Specific code names depend on the BE-CIUS schematron version Flowie ships; the response body always carries the rule code, the human description, and the XPath. VAT lookup| Seller / buyer BTW number active in KBO/BCE.| Caught at `POST /v1/companies` (company creation) — bad VATs never reach the send path. `MERC-*`| Mercurius B2G acceptance — OVO number recognised, schema accepted.| `document.failed` webhook with `errorCode` set, after the Peppol delivery hop. Validation moved from regulator to send-time Before HERMES retired, schematron failures on Belgian invoices surfaced as deferred `compliance.reported.failed` webhooks. Today the same checks run locally before the invoice leaves Flowie — failures are `422`s on the synchronous `POST /v1/documents/send` response, with the validator's own rule code in `error.details[].code`. Faster feedback, no extra event-handling glue. ## Testing your Belgian integration Use the [sandbox host](<../sandbox/index.html>) with a `flw_test_…` key. Two reproducible tests cover the cases most integrators care about: What you want to test| How ---|--- BE happy path (B2B)| Sender VAT `BE0000000001`, recipient `0208:TEST_OK`, both VATs populated. `document.sent` \+ `document.delivered` webhooks fire; **no** `compliance.reported`. Mercurius B2G send| Recipient `9925:BE-mercurius-99999`. Same response shape as a private recipient. Recipient unreachable| Recipient `0208:TEST_AP_FAIL` → `document.failed` webhook with `RECIPIENT_UNREACHABLE`. Validation rejection| Send with a deliberately broken UBL (e.g. mismatched line totals) → `422` on the synchronous response. The exact rule code comes from the BE-CIUS schematron and varies by validator version. For exhaustive negative testing of the validator, use `POST /v1/documents/validate` — it runs every BE-CIUS rule and returns the full `error.details[]` without attempting Peppol delivery. ## Migration: HERMES → Peppol If your stack assumed Flowie would auto-report Belgian invoices to HERMES, here's the diff: Used to| Now ---|--- Listen for `compliance.reported` with `data.platform == "HERMES"`| Drop the listener for BE. The event no longer fires. Branch on `data.platform == "HERMES"` in your webhook router| Remove the branch. `compliance.reported` only fires for FR (PPF) and IT (SDI). Set `settings.autoCompliance.HERMES` on a BE company| Field accepted but a no-op; remove on next config refresh. Filter `GET /v1/compliance/reports?platform=HERMES`| Returns historical rows only. New BE invoices won't add rows here. Reject-handling on `HER-001` / `HER-002` / `HER-007`| Move the equivalent reject-handling onto the synchronous `422` response from `POST /v1/documents/send`. Read `error.details[].code` (BE-CIUS schematron rule) and `error.details[].xpath` (where in the UBL it failed). The `compliance_reports` table itself keeps historical HERMES rows for audit — they're never deleted, they just stop being created. ## FAQ ### Do I need to register with anything new? No. Flowie publishes BE companies to the Peppol SMP automatically (the same registration that already let you send Peppol invoices anywhere in Europe). There is no successor to HERMES. ### Can I keep using paper invoices for B2C? Yes. The 2026-01-01 mandate is B2B only. B2C remains free format until further notice. ### What about the SME exemption? No exemption — the mandate covers **all** B2B taxable transactions regardless of company size, which is unusual for Europe. Plan accordingly. ### Is the BLOB-embedded PDF required? No, it's optional. But many recipients still prefer a human-readable rendering for accounts payable. Flowie generates it automatically when you send JSON. ### What about CTC (continuous transaction control) in 2028? The federal government has signalled intent to align with the EU ViDA timeline (CTC by 2028) but no concrete Belgian regulation exists yet. When it lands, Flowie will surface it as a regulator-side leg again — same shape as PPF/SDI today. We'll announce in the [changelog](<../changelog.html>). ### I had `HERMES_REPORT_URL` in my env. What now? You can remove it. Flowie no longer reads `HERMES_REPORT_URL` or `HERMES_REPORT_TOKEN` — the corresponding adapter has been deleted. Leaving the variables set is harmless but unused. ## References **Primary sources** (Belgian government & EU regulator): * [efacture.belgium.be]() — official Belgian e-invoicing portal (`belgium.be`); scope of the 2026-01-01 B2B mandate, exemptions, FAQ for taxpayers. * [Loi du 6 février 2024]() — full text of the Belgian e-invoicing law (NUMAC `2024001635`), as published in the _Moniteur belge_ on 2024-02-20. Modifies the VAT Code and Income Tax Code 1992. * [eJustice · official Moniteur belge entry]() — authoritative Belgian government version of the law. * [HERMES portal]() — official portal carrying the FPS Finance decommissioning notice (consultation-only access closed 2026-03-31). * [EU Commission · eInvoicing in Belgium]() — pan-European reference page; legal basis, mandate scope, Peppol BIS profile. * [OpenPeppol · Belgium country profile]() — authoritative Peppol facts maintained by OpenPeppol AISBL. * [Mercurius portal]() — federal B2G hub run by FPS BOSA. * [KBO / BCE]() — Belgian VAT-number registry (FPS Economy). **Industry analyses** (independent confirmation of the retirement timeline): * [Sovos · Belgium Sunsets Hermes]() — vendor regulatory update. * [Banqup · Belgium retires the HERMES platform]() — vendor analysis of the retirement decision. ======================================================================== # Austria · Peppol BIS B2G # Source: https://docs.get-flowie.com/compliance/at.html ======================================================================== --- title: "Austria — Peppol BIS · federal B2G mandate" description: "Austria e-invoicing: Peppol BIS B2G mandate live since 2014 (e-Rechnung.gv.at). No B2B mandate yet — alignment with EU ViDA expected post-2030." canonical: "https://docs.get-flowie.com/compliance/at" source: "https://docs.get-flowie.com/compliance/at.html" --- # Austria — Peppol BIS · federal B2G mandate Compliance · 🇦🇹 Austria Live mandate # Austria — Peppol BIS · federal B2G mandate Peppol BIS B2G mandate live since 2014 · No B2B mandate yet — regulator: [Bundesministerium für Finanzen (BMF)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Austria adopted **Peppol BIS 3.0** natively for federal B2G in 2014 — no national wrapper, no separate hub. * **No domestic B2B mandate** as of 2026; Austria has signalled alignment with the EU ViDA framework (target 2030–2032). * Public-sector recipients are routed through **e-Rechnung.gv.at** (ER>B portal) — Flowie resolves the Peppol ID for you. * Flowie is a registered Peppol Access Point (`9915:flowie` for AT) — or routed through a specialized local partner where in-country presence is required. ## Deadlines Date| Who| What ---|---|--- 2014-01-01| Federal contracting authorities| B2G e-invoicing mandatory (BGBl. I Nr. 32/2014). ≥ 2030| All B2B taxable supplies (expected)| Aligned with EU ViDA — not yet legislated; planning baseline only. ## Background Austria was one of the earliest Peppol adopters in the EU, going live with federal B2G in January 2014 via the **e-Rechnung.gv.at** portal (also called ER>B). The portal is operated by the Bundesministerium für Finanzen and acts as a Peppol-aware ingress for every federal contracting authority. Suppliers either upload directly through the portal or — much more commonly via Flowie — send a Peppol BIS 3.0 invoice that the recipient AP routes to the federal node automatically. B2B remains _voluntary_. The Austrian government has stated it will follow the EU ViDA timeline rather than introduce a national mandate ahead of the EU framework, so the first realistic B2B deadline is post-2030. ## Format profile * **Peppol BIS 3.0** (UBL or CII), no national CIUS for federal B2G beyond standard EN 16931. * Some federal authorities additionally accept **ebInterface 4.x / 5.x** (legacy XML) — Flowie auto-converts when the recipient declares ebInterface in its SMP record. * The `BuyerReference` on B2G must be the contracting authority's **Auftragsreferenz** (order reference); without it, ER>B rejects. ## Required fields * buyerReferencestringrequired for B2G Auftragsreferenz issued by the federal authority. Without it, e-Rechnung.gv.at rejects synchronously. * seller.vatNumberstringrequired Format `ATU12345678`. Validated against UID-Bestätigungsverfahren. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **e-Rechnung.gv.at (ER >B)**| `9915:AT-GOV-`| Federal authorities are listed in the ER>B directory. Land (state) and municipal authorities adopt at their own pace; about half are Peppol-reachable in 2026. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Federal B2G happy path| Sender VAT `ATU00000001`, recipient `9915:AT-GOV-TEST` in sandbox. Missing Auftragsreferenz| Send to a B2G recipient without `buyerReference` → AT-specific rejection echoed back. ## FAQ ### Do I need to register on e-Rechnung.gv.at to send to a federal authority? No, not when sending via Peppol — Flowie's AP delivers to the federal endpoint behind ER>B. You only register if you upload manually through the portal. ### Can I use ebInterface instead of Peppol BIS? Yes, and Flowie can render ebInterface 5.0 from the same JSON payload. But Peppol BIS is the strategic format and what every new authority accepts. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Austria]() — Pan-EU reference factsheet. * [OpenPeppol · Austria profile]() — Authoritative Peppol facts. * [BMF · Austrian Ministry of Finance]() — Tax authority owning e-invoicing policy. * [USP · e-Rechnung an die Verwaltung]() — Official B2G submission portal guide. * [e-Rechnung.gv.at]() — Federal e-invoicing platform. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Austria e-invoicing analysis]() — Industry tracker — formats and timelines. ======================================================================== # Bulgaria · NRA SAF-T # Source: https://docs.get-flowie.com/compliance/bg.html ======================================================================== --- title: "Bulgaria — SAF-T reporting & Peppol BIS" description: "Bulgaria e-invoicing: SAF-T reporting phasing in 2026–2028 (NRA), no B2B mandate yet but real-time reporting via SAF-T being introduced. Peppol BIS for cross-border." canonical: "https://docs.get-flowie.com/compliance/bg" source: "https://docs.get-flowie.com/compliance/bg.html" --- # Bulgaria — SAF-T reporting & Peppol BIS Compliance · 🇧🇬 Bulgaria Phased rollout # Bulgaria — SAF-T reporting & Peppol BIS SAF-T phase-in 2026–2028 · No domestic B2B mandate yet — regulator: [National Revenue Agency (НАП / NRA)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Bulgaria does **not** yet have a B2B e-invoicing mandate. Domestic invoices remain free-format. * The NRA is rolling out **SAF-T (Standard Audit File for Tax)** reporting in waves: largest taxpayers from 2026, mid-size 2027, all VAT-registered 2028. * Cross-border B2B follows EU rules; Peppol BIS is accepted but not mandated. * Flowie is a registered Peppol AP for Bulgaria (`9926:flowie`) — or routed through a specialized local partner — and ships SAF-T export from the same JSON payload. ## Deadlines Date| Who| What ---|---|--- 2026-01-01| Largest taxpayers (turnover > BGN 300M)| SAF-T monthly reporting begins. 2027-01-01| Mid-size taxpayers| SAF-T reporting onboarded. 2028-01-01| All VAT-registered businesses| SAF-T reporting universal. ## Background Bulgaria's e-invoicing strategy is reporting-led rather than transmission-led: the National Revenue Agency (NRA) is implementing **SAF-T** as the core obligation, modelled on the OECD standard already used in Portugal, Norway, and Poland. SAF-T is a structured XML export of the taxpayer's accounting data submitted monthly to the NRA. Once SAF-T is universal (2028), the NRA has signalled it may then layer a B2B e-invoicing mandate on top — but no legislation exists yet. For now: send invoices in any format that satisfies the customer; submit SAF-T monthly. Flowie produces the SAF-T file from the same data you send via `/v1/documents/send`. ## Format profile * Cross-border: standard **Peppol BIS 3.0** with no Bulgarian CIUS. * SAF-T file follows the NRA schema (XML, monthly cadence). Flowie generates it from your document history. ## Required fields * seller.vatNumberstringrequired for SAF-T Format `BG123456789`. Used as the SAF-T `TaxRegistrationNumber`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **No dedicated B2G hub**| `—`| Public-sector buyers receive invoices through their own ERP — there is no Mercurius-style central hub. Use the Peppol directory or the buyer-supplied Peppol ID. ## B2B reporting / clearance **NRA SAF-T** — Monthly tax-data export covering invoices, GL, AP/AR, stock movements. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- SAF-T export| Call `POST /v1/compliance/saft` with `country: "BG"` and a date range — sandbox returns a synthetic file. ## FAQ ### Do I need to send invoices via Peppol in Bulgaria? No. Bulgarian domestic invoices have no e-invoicing mandate. Peppol BIS is fully accepted for cross-border but is not required. ### Will SAF-T replace VAT returns? Eventually, yes. The NRA's stated direction is to drop the periodic VAT return once SAF-T is universal in 2028, but legislation has not yet codified the cutover. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Bulgaria]() — Pan-EU reference factsheet. * [NRA · National Revenue Agency]() — Tax authority overseeing SAF-T and e-reporting. * [CAIS EPP · public procurement platform]() — National e-procurement platform. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Bulgaria e-invoicing]() — Industry tracker — SAF-T 2026 rollout. * [Pagero · Bulgaria compliance updates]() — Industry compliance tracker. ======================================================================== # Croatia · Fiscalisation 2.0 # Source: https://docs.get-flowie.com/compliance/hr.html ======================================================================== --- title: "Croatia — Fiscalisation 2.0 B2B mandate" description: "Croatia e-invoicing: Fiscalisation 2.0 B2B mandate from 1 January 2026 for VAT taxpayers, structured invoices via national portal + Peppol BIS for cross-border." canonical: "https://docs.get-flowie.com/compliance/hr" source: "https://docs.get-flowie.com/compliance/hr.html" --- # Croatia — Fiscalisation 2.0 B2B mandate Compliance · 🇭🇷 Croatia Live mandate # Croatia — Fiscalisation 2.0 B2B mandate Fiscalisation 2.0 B2B mandate live since 1 January 2026 — regulator: [Porezna uprava (Tax Administration)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Croatia's **Fiscalisation 2.0** framework extended pre-existing B2C real-time fiscalisation to **B2B** on 1 January 2026. * All VAT-registered businesses must issue structured e-invoices and report each one to the national portal in real-time. * Domestic format: **UBL 2.1 with the HR-CIUS** ; cross-border via Peppol BIS 3.0. * Flowie's HR access point handles the fiscalisation handshake transparently — your call to `/v1/documents/send` emits the JIR/ZKI tokens automatically. ## Deadlines Date| Who| What ---|---|--- 2013-01-01| All cash-register B2C| Real-time fiscalisation (OIB, JIR, ZKI) — already live. **2026-01-01**| All VAT-registered B2B| Structured e-invoice + real-time fiscalisation report. 2027-01-01| Non-VAT businesses (planned)| Smaller taxpayers absorbed; legislation pending. ## Background Croatia has run real-time B2C _fiscalisation_ since 2013 — every retail receipt is reported to the Porezna uprava, which echoes back a **JIR** (unique invoice identifier) and the seller stamps a **ZKI** (issuer protection code). _Fiscalisation 2.0_ , in force since 1 January 2026, ports the same model to B2B: the invoice itself becomes structured (UBL 2.1) and is fiscalised in the same step. Practically: when Flowie sends a domestic HR invoice, our AP signs it, transmits to the recipient via Peppol, and posts the fiscalisation envelope to the Porezna uprava service — all inside one `/v1/documents/send` call. The response includes the JIR + ZKI as `complianceReceipt`. ## Format profile * **UBL 2.1 with HR-CIUS** for domestic B2B; Peppol BIS 3.0 for cross-border (HR is OpenPeppol member). * **OIB** (Croatian tax ID, 11 digits) is mandatory on both seller and buyer. Plain VAT is not accepted in lieu. * JIR + ZKI are returned by the fiscalisation service and embedded into the invoice as `cbc:UUID` and a custom signature element. ## Required fields * seller.taxId.oibstring (11 digits)required Croatian OIB. Validated by check-digit. * buyer.taxId.oibstring (11 digits)required for B2B Buyer OIB; mandatory for any domestic B2B invoice. * fiscalisation.operatorOibstringrequired OIB of the natural person operating the cash-register / issuing system. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Servis e-Račun (FINA)**| `9934:HR-FINA-`| Public-sector recipients route via FINA's Servis e-Račun, mandatory since 2019. Flowie resolves the Peppol ID for you. ## B2B reporting / clearance **Porezna uprava — Fiscalisation 2.0** — Real-time invoice register; every domestic B2B invoice posted within seconds of issue. Lifecycle status| Reported as ---|--- `issued`| Fiscalisation request sent → JIR + ZKI returned. `cancelled`| Storno fiscalisation message; original JIR referenced. `paid`| Optional payment confirmation; not always required. ## Error codes Code| Meaning| Fix ---|---|--- `HR-FISC-101`| OIB unknown to Porezna uprava.| Verify the OIB; if newly registered, wait 24h for the registry to propagate. `HR-FISC-205`| ZKI signature does not match the seller's certificate.| Sandbox uses a Flowie test cert; production needs the seller's FINA-issued cert linked to their organisation. `HR-CIUS-031`| Missing operator OIB.| Set `fiscalisation.operatorOib`. ## Testing in sandbox What you want to test| How ---|--- Domestic B2B happy path| Use `seller.taxId.oib = "12345678901"` in sandbox; JIR `SBX-...` echoed back. Force fiscalisation rejection| Send with `simulateCompliance: "reject_HR_FISC_101"`. ## FAQ ### Is the OIB the same as the VAT number? The OIB is the 11-digit tax identifier; the VAT number is `HR` \+ OIB for VAT-registered entities. Send the OIB as `seller.taxId.oib` and Flowie derives the VAT representation when needed. ### What about non-resident sellers invoicing into HR? If the seller is not OIB-registered, the invoice is not domestic — it follows EU cross-border rules and Peppol BIS without fiscalisation. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Croatia]() — Pan-EU reference factsheet. * [FINA · Servis eRačun za državu]() — National B2G platform operated by FINA. * [Ministarstvo financija · Porezna uprava]() — Tax administration — Fiscalization 2.0. * [Fiskalizacija portal]() — Official Fiscalization 2.0 portal. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Croatia Fiscalization 2.0]() — Industry tracker — 2026 B2B mandate. ======================================================================== # Cyprus · Peppol BIS # Source: https://docs.get-flowie.com/compliance/cy.html ======================================================================== --- title: "Cyprus — Peppol BIS B2G mandate (B2B voluntary)" description: "Cyprus e-invoicing: Peppol BIS B2G mandate live since 2019, no B2B mandate yet. EU ViDA alignment expected post-2030." canonical: "https://docs.get-flowie.com/compliance/cy" source: "https://docs.get-flowie.com/compliance/cy.html" --- # Cyprus — Peppol BIS B2G mandate (B2B voluntary) Compliance · 🇨🇾 Cyprus Live mandate # Cyprus — Peppol BIS B2G mandate (B2B voluntary) Peppol BIS B2G live · No B2B mandate yet — regulator: [Tax Department (Cyprus Ministry of Finance)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Cyprus implemented the EU 2014/55/EU directive on schedule with a **Peppol BIS B2G mandate** from 2019. * **No domestic B2B mandate** as of 2026. * All Cypriot public buyers are reachable via Peppol; no separate national hub. * Flowie is a registered Peppol AP for Cyprus — or routed through a specialized local partner where in-country presence is required. ## Deadlines Date| Who| What ---|---|--- 2019-04-18| Central government| B2G mandate (EU directive transposition). 2019-04-18| Sub-central public authorities| Same date — Cyprus did not stagger central vs. sub-central. ≥ 2030| B2B (expected)| EU ViDA alignment; not yet legislated. ## Background Cyprus was an early-but-quiet adopter — the B2G mandate was transposed on the EU schedule and quietly bedded down in 2019. There is no Cypriot national hub: every public authority connects directly via Peppol, and the Tax Department's role is purely tax-supervisory rather than transmission-related. ## Format profile * **Peppol BIS 3.0** with no Cypriot CIUS — invoices only need to satisfy EN 16931. * Tax ID: Cyprus uses 8 digits + 1 letter (e.g. `CY12345678X`). Both seller and buyer required for B2G. ## Required fields * seller.vatNumberstringrequired Format `CY12345678X`. * buyer.vatNumberstringrequired for B2G Format `CY12345678X`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **No central hub**| `9928:CY-`| Each ministry / department has its own Peppol participant ID. Look up via the Peppol Directory. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Cypriot B2G| Sender VAT `CY00000001A`, recipient `9928:CY-GOV-TEST`. ## FAQ ### Is there a Cypriot national format? No. Pure Peppol BIS 3.0 with EN 16931 — no national CIUS or extension. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Cyprus]() — Pan-EU reference factsheet. * [Cyprus Ministry of Finance · e-Invoicing portal]() — Official MoF e-invoicing portal. * [Treasury of the Republic of Cyprus]() — Treasury — Peppol Access Point owner. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Cyprus e-invoicing]() — Industry tracker — Peppol B2G voluntary. ======================================================================== # Czechia · ISDOC + Peppol # Source: https://docs.get-flowie.com/compliance/cz.html ======================================================================== --- title: "Czechia — ISDOC, Peppol BIS & B2G mandate" description: "Czechia e-invoicing: B2G via ISDOC and Peppol BIS, no B2B mandate yet. NÚKIB / Ministry of Finance e-invoicing portal." canonical: "https://docs.get-flowie.com/compliance/cz" source: "https://docs.get-flowie.com/compliance/cz.html" --- # Czechia — ISDOC, Peppol BIS & B2G mandate Compliance · 🇨🇿 Czechia Live mandate # Czechia — ISDOC, Peppol BIS & B2G mandate B2G mandate live · ISDOC + Peppol BIS · No B2B mandate yet — regulator: [Ministerstvo financí (Ministry of Finance)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Czechia's national format is **ISDOC 6.0** , a UBL-derived XML schema in use since 2009. * Public-sector buyers must accept both **ISDOC** and **Peppol BIS 3.0** since 2020. * **No B2B mandate** as of 2026; B2C remains free-format. * Flowie can render either ISDOC or Peppol BIS from the same JSON payload — auto-routed by the recipient's declared capability. ## Deadlines Date| Who| What ---|---|--- 2009-04-01| ISDOC standard published| Voluntary B2B/B2G adoption. 2019-04-18| Central government| Must accept e-invoices (EU 2014/55/EU). 2020-04-18| Sub-central public authorities| Mandate extended. ≥ 2030| B2B (expected)| EU ViDA timeline; no national legislation yet. ## Background Czechia developed **ISDOC** (Information System Document) before EU EN 16931 existed; it remains the legacy domestic format, especially in public-sector procurement systems that pre-date Peppol. Since 2020, Czech public buyers must accept both ISDOC and Peppol BIS. Flowie selects automatically based on the recipient's SMP capabilities. ## Format profile * **ISDOC 6.0** (UBL-2 derived) for legacy B2G channels. * **Peppol BIS 3.0** for cross-border and modern B2G. * Czech VAT: `CZ` \+ 8/9/10 digits. ## Required fields * seller.vatNumberstringrequired Format `CZ12345678` (or 9/10 digits). * formatstringoptional Set `format: "isdoc"` to force ISDOC rendering; default auto-selects. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Národní katalog e-fakturace (NEN)**| `0151:CZ-NEN-`| The NEN platform is a marketplace for public procurement; its e-invoicing module is the most common B2G ingress for Czechia. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes Code| Meaning| Fix ---|---|--- `ISDOC-001`| Schema validation failure on ISDOC.| Check the schematron details — usually a missing `id` attribute or a mistyped enum. ## Testing in sandbox What you want to test| How ---|--- Czech B2G via ISDOC| Set `format: "isdoc"` in your `/v1/documents/send` body; sandbox renders and returns the ISDOC bytes. ## FAQ ### Should I send ISDOC or Peppol BIS? Default to auto. Flowie checks the recipient's SMP record — if they advertise Peppol BIS, that's used; otherwise ISDOC. Setting `format` explicitly is only needed for legacy ERP integrations. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Czech Republic]() — Pan-EU reference factsheet. * [NEN · Národní elektronický nástroj]() — Mandatory national e-procurement platform. * [Ministry of Finance Czech Republic]() — Finance ministry — VAT and e-invoicing policy. * [Ministry of Regional Development (MMR)]() — MMR operates NEN platform. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Comarch · Czech Republic e-invoicing]() — Industry tracker — ISDOC, Peppol. ======================================================================== # Denmark · OIOUBL + Peppol # Source: https://docs.get-flowie.com/compliance/dk.html ======================================================================== --- title: "Denmark — OIOUBL, NemHandel & Peppol BIS" description: "Denmark e-invoicing: OIOUBL legacy + Peppol BIS via NemHandel. B2G mandate since 2005. Bookkeeping Act 2024 phasing in B2B requirements." canonical: "https://docs.get-flowie.com/compliance/dk" source: "https://docs.get-flowie.com/compliance/dk.html" --- # Denmark — OIOUBL, NemHandel & Peppol BIS Compliance · 🇩🇰 Denmark Live mandate # Denmark — OIOUBL, NemHandel & Peppol BIS OIOUBL/Peppol BIS · B2G live since 2005 · Bookkeeping Act phasing 2024–2026 — regulator: [Erhvervsstyrelsen (Danish Business Authority)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Denmark has had a **universal B2G e-invoicing mandate since 2005** — one of the oldest in Europe. * Domestic format: **OIOUBL** (a Danish UBL profile predating Peppol), still used by legacy public-sector ERPs. * The **Bookkeeping Act** (2024) requires every business to use a digital bookkeeping system that supports OIOUBL and Peppol BIS receipt by 2026. * Flowie auto-converts between OIOUBL and Peppol BIS — caller never needs to choose. ## Deadlines Date| Who| What ---|---|--- 2005-02-01| Public sector (B2G)| All public buyers must receive e-invoices (Lov om offentlige betalinger). 2024-07-01| Class B/C/D companies| Bookkeeping Act: must use a registered digital bookkeeping system. **2026-01-01**| Class A companies| Same Bookkeeping Act obligation extended to smaller companies. ## Background Denmark was the first EU country to mandate B2G e-invoicing — twenty years before the EU directive. The infrastructure is **NemHandel** ("easy commerce"), originally a closed Danish network using **OIOUBL** XML. NemHandel now bridges to Peppol so that an OIOUBL invoice from a Danish ERP reaches any European Peppol AP and vice-versa. The 2024 Bookkeeping Act (Bogføringsloven) is not strictly an e-invoicing mandate but it has the same effect: every commercially-active company must use a digital bookkeeping system that natively supports OIOUBL and Peppol BIS receipt — meaning the practical reach of e-invoicing in Denmark by 2026 is essentially every business. ## Format profile * **OIOUBL 2.1** for legacy NemHandel routes. * **Peppol BIS 3.0** for everything else; what the Bookkeeping Act normalised on. * Danish CVR number (8 digits) on both parties for B2G; for B2B it's required if available. ## Required fields * seller.cvrstring (8 digits)required for DK domestic Danish business registry number (CVR). * buyer.cvrstring (8 digits)required for B2G Public authority's CVR. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **NemHandel**| `0184:DK-`| Every Danish public buyer is registered on NemHandel and reachable via the EAN/GLN or CVR identifier scheme over Peppol. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes Code| Meaning| Fix ---|---|--- `OIOUBL-CHK-200`| OIOUBL schematron failure.| Inspect `error.details`; usually a profile-specific cardinality. ## Testing in sandbox What you want to test| How ---|--- DK B2G via NemHandel| Recipient `0184:DK-12345678` with valid sandbox CVR. Force OIOUBL rendering| Set `format: "oioubl"`. ## FAQ ### Is NemHandel separate from Peppol? Operationally yes, but bridged: Flowie's AP transparently routes a Peppol BIS invoice through the NemHandel bridge when the recipient is on the Danish-only side, and vice-versa. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Denmark]() — Pan-EU reference factsheet. * [OpenPeppol · Denmark profile]() — Authoritative Peppol facts. * [ERST · Danish Business Authority (Peppol Authority)]() — Danish Peppol Authority running NemHandel. * [NemHandel · national infrastructure]() — Danish national e-document network. * [Bookkeeping Act 2022 (Danish Business Authority)]() — Digital Bookkeeping Act mandate source. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Storecove · Denmark B2B mandate guide]() — Industry tracker — Bookkeeping Act rollout. ======================================================================== # Estonia · B2B-on-request # Source: https://docs.get-flowie.com/compliance/ee.html ======================================================================== --- title: "Estonia — B2B-on-request mandate & Peppol BIS" description: "Estonia e-invoicing: B2B-on-request mandate from July 2025 — buyers can demand a structured e-invoice. B2G universal. Peppol BIS via national directory." canonical: "https://docs.get-flowie.com/compliance/ee" source: "https://docs.get-flowie.com/compliance/ee.html" --- # Estonia — B2B-on-request mandate & Peppol BIS Compliance · 🇪🇪 Estonia Phased rollout # Estonia — B2B-on-request mandate & Peppol BIS B2B-on-request live since July 2025 · B2G universal · Peppol BIS — regulator: [Maksu- ja Tolliamet (Tax & Customs Board)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Estonia introduced **B2B-on-request** from 1 July 2025: any Estonian buyer registered as an e-invoice recipient can demand a structured invoice and the seller must provide one. * **B2G is universal** since 2019; recipients are registered in the Estonian e-invoicing register ([RIK]()). * Format: **Peppol BIS 3.0** \+ the legacy Estonian e-invoice XML (EVS 923:2014) for backward compat. * Flowie's AP is registered for Estonia under `9931:flowie`. ## Deadlines Date| Who| What ---|---|--- 2017-03-01| Central government| B2G receive obligation. 2019-07-01| All public authorities| B2G send obligation. **2025-07-01**| Domestic B2B (on-request)| Sellers must issue a structured e-invoice when the buyer is a registered e-invoice recipient. ≥ 2027| Universal B2B (expected)| Pending legislation; would convert on-request to mandatory. ## Background Estonia, fittingly, took the digital-first path. The B2B-on-request model bridges voluntary and mandatory: it doesn't force every seller to issue structured invoices, but it gives every buyer the right to demand one. In practice, a few months in, almost every B2B counterparty had registered as an e-invoice recipient — making the mandate _de facto_ universal even before the formal full-B2B step. Recipients self-register in the central e-invoicing register operated by RIK (Centre of Registers and Information Systems). Flowie checks the register at send-time; if the buyer is registered, we route via Peppol; if not, we fall back to PDF. ## Format profile * **Peppol BIS 3.0** is the canonical format. * Legacy **EVS 923:2014** XML still accepted by some older Estonian ERPs. * Estonian VAT: `EE` \+ 9 digits. ## Required fields * seller.vatNumberstringrequired Format `EE123456789`. * buyer.eInvoiceRegisteredbooleanauto-resolved Flowie populates this from the RIK register; you don't pass it. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Riigi e-arvete register (RIK)**| `9931:EE-`| Public buyers and B2B-registered companies share the same register; Flowie's `/v1/directory/search?country=EE` mirrors it. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes Code| Meaning| Fix ---|---|--- `EE-REG-404`| Buyer not registered in RIK e-invoice register.| Either fall back to PDF or ask the buyer to register (it's free and takes 5 minutes). ## Testing in sandbox What you want to test| How ---|--- Estonian B2B happy path| Buyer reg code `EE12345678`, sender VAT `EE100000001`. Buyer not registered| Buyer reg code `EE99999999` in sandbox → returns `EE-REG-404`. ## FAQ ### Do I need to query RIK before sending? No. Flowie does it for you on every send. The response carries `routing.eInvoiceRegistered: true|false` so you know whether the structured path or PDF path was used. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Estonia]() — Pan-EU reference factsheet. * [EMTA · Estonian Tax and Customs Board]() — Tax authority owning e-invoice policy. * [Ministry of Finance Estonia]() — Finance ministry — Accounting Act amendments. * [RIK · Business Register e-invoice receiver list]() — Registry of e-invoice receivers (buyer-choice). **Industry analyses** (vendor trackers — useful for cross-referencing): * [Pagero · Estonia compliance updates]() — Industry tracker — 2025/2027 timeline. ======================================================================== # Finland · Finvoice + Peppol # Source: https://docs.get-flowie.com/compliance/fi.html ======================================================================== --- title: "Finland — Finvoice, Peppol BIS & B2B-on-request" description: "Finland e-invoicing: B2B-on-request since April 2020, B2G universal, Finvoice 3.0 + Peppol BIS. Verohallinto (Tax Administration)." canonical: "https://docs.get-flowie.com/compliance/fi" source: "https://docs.get-flowie.com/compliance/fi.html" --- # Finland — Finvoice, Peppol BIS & B2B-on-request Compliance · 🇫🇮 Finland Live mandate # Finland — Finvoice, Peppol BIS & B2B-on-request B2B-on-request since 2020 · B2G universal · Finvoice + Peppol — regulator: [Verohallinto (Finnish Tax Administration)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Finland operates a **B2B-on-request** regime: since April 2020, any buyer can demand a structured EN-16931 invoice and the seller must comply. * Domestic format historically: **Finvoice 3.0** (Finnish UBL-derived). Modern channels use **Peppol BIS 3.0** — both interoperable. * **B2G universal** since 2010 via Valtiokonttori (State Treasury) and the OPUS hub. * Flowie auto-translates between Finvoice and Peppol BIS. ## Deadlines Date| Who| What ---|---|--- 2010-12-01| Central government| Receive-only B2G mandate (Valtiokonttori). **2020-04-01**| Domestic B2B| Buyer's right to request a structured invoice — de facto universal. 2027-03-01| Possible full B2B mandate| EU ViDA-aligned; Finnish Tax Administration consultation underway. ## Background Finland's B2B-on-request rule (Laki sähköisestä laskutuksesta, 241/2019) is structurally similar to Estonia's: the seller can't refuse a buyer who asks for a structured invoice. Combined with very high adoption rates (Finland has had rich B2B e-invoicing since the early 2000s), the rule means structured-invoice volume is already over 80% of B2B in Finland. ## Format profile * **Peppol BIS 3.0** is the strategic format. * **Finvoice 3.0** remains widely used; bidirectionally mappable to BIS. * Finnish business ID (Y-tunnus, format `NNNNNNN-N`) on both parties. ## Required fields * seller.businessIdstring (Y-tunnus)required Finnish business ID, e.g. `1234567-8`. * buyer.businessIdstringrequired for B2B Same format. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Valtiokonttori OPUS**| `0037:FI-`| Public-sector recipients identified by the Y-tunnus over Peppol scheme `0037`. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Finnish B2B happy path| Y-tunnus `1234567-8` on both sides; sandbox returns 201. Force Finvoice rendering| Set `format: "finvoice"`. ## FAQ ### Is Finvoice still required? Not strictly — Peppol BIS satisfies the legal obligation. But many older Finnish ERPs only consume Finvoice; Flowie renders it on the way out automatically when the recipient prefers it. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Finland]() — Pan-EU reference factsheet. * [OpenPeppol · Finland profile]() — Authoritative Peppol facts. * [Valtiokonttori · State Treasury e-invoicing]() — State Treasury — Finnish Peppol Authority. * [Valtiokonttori · Invoicing the State (Handi)]() — B2G submission via Handi/Basware portals. * [Finnish eInvoicing Act 241/2019 (Finlex)]() — National eInvoicing Act text. **Industry analyses** (vendor trackers — useful for cross-referencing): * [ecosio · Finland e-invoicing]() — Industry tracker — Finvoice/TEAPPSXML. ======================================================================== # Germany · XRechnung + ZUGFeRD # Source: https://docs.get-flowie.com/compliance/de.html ======================================================================== --- title: "Germany — XRechnung, ZUGFeRD & B2B mandate phase-in" description: "Germany e-invoicing: B2B mandate phasing 2025–2028 (Wachstumschancengesetz). XRechnung for B2G, ZUGFeRD/Factur-X for B2B. Bundesfinanzministerium." canonical: "https://docs.get-flowie.com/compliance/de" source: "https://docs.get-flowie.com/compliance/de.html" --- # Germany — XRechnung, ZUGFeRD & B2B mandate phase-in Compliance · 🇩🇪 Germany Phased rollout # Germany — XRechnung, ZUGFeRD & B2B mandate phase-in B2B mandate phasing 2025–2028 · XRechnung B2G · ZUGFeRD/Factur-X B2B — regulator: [Bundesministerium der Finanzen (BMF)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Germany's **Wachstumschancengesetz** (Growth Opportunities Act, 2024) introduced a phased B2B mandate. * **From 1 January 2025** every German B2B buyer must **receive** structured e-invoices. * Send obligation phases by company size: turnover > €800k from 2027; everyone from 2028. * Standard B2G format: **XRechnung** (a German Peppol BIS CIUS). B2B accepts **ZUGFeRD/Factur-X** (PDF/A-3 with embedded XML) and Peppol BIS. ## Deadlines Date| Who| What ---|---|--- 2017-04-18| Federal contracting authorities| B2G mandate live (XRechnung over Peppol). **2025-01-01**| All German B2B buyers| Must be able to receive structured e-invoices. 2026-12-31| Transition period ends| Paper invoices for B2B no longer accepted by default. **2027-01-01**| Sellers with turnover > €800k| Must send structured e-invoices. **2028-01-01**| All B2B sellers| Universal send obligation. ## Background Germany's mandate is structured as a **receive-first ramp** : every B2B buyer in Germany must already (as of 2025) accept a structured e-invoice. Sellers retain a transition window through 2026 to keep using paper / PDF, then must switch to structured by 2027 (large) or 2028 (all). Two structured formats coexist legally: **XRechnung** (XML-only Peppol BIS CIUS, federal-government-favoured) and **ZUGFeRD/Factur-X** (PDF/A-3 with embedded UBL/CII XML, B2B-favoured because the PDF stays human-readable). Flowie produces either from the same JSON payload. ## Format profile * **XRechnung 3.0.x** — strict CIUS, mandatory for federal B2G. * **ZUGFeRD 2.3 / Factur-X 1.0.7** — hybrid PDF/A-3 with embedded XML; the de facto B2B format. * **Peppol BIS 3.0** — accepted everywhere; transport for both XRechnung and Factur-X over Peppol. * **Leitweg-ID** required for federal B2G — a structured routing code distinct from the Peppol participant ID. ## Required fields * buyerReferencestringrequired for federal B2G Leitweg-ID (e.g. `04011000-1234512345-06`). Without it, the federal portal rejects. * seller.vatNumberstringrequired Format `DE123456789`. * seller.taxNumberstringalternative Steuernummer; allowed where the seller is not VAT-registered. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Zentrale Rechnungseingangsplattform des Bundes (ZRE) + OZG-RE**| `0204:DE-`| ZRE handles federal authorities; OZG-RE handles federal-state authorities. Both speak Peppol; the Leitweg-ID disambiguates the recipient. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes Code| Meaning| Fix ---|---|--- `BR-DE-01`| Missing Leitweg-ID for federal B2G recipient.| Set `buyerReference` to the Leitweg-ID supplied by the authority. `BR-DE-15`| Steuernummer or VAT number missing on seller.| Provide one of `seller.vatNumber` or `seller.taxNumber`. `XR-3.0-S-001`| XRechnung schematron failure.| Inspect `error.details`; usually a profile-specific code list violation. ## Testing in sandbox What you want to test| How ---|--- Federal B2G via XRechnung| Set `format: "xrechnung"`, recipient `0204:04011000-1234512345-06`. ZUGFeRD output| Set `format: "factur-x"` — sandbox returns the PDF/A-3 with embedded XML. Receive-obligation simulation| Send to a German recipient with `simulateCompliance: "receive_only_buyer"` — invoice marked deliverable but seller not yet send-mandated. ## FAQ ### Is ZUGFeRD legally equivalent to XRechnung? Yes for B2B. For federal B2G, XRechnung is the prescribed format. ZUGFeRD with the right XML profile (BASIC, EN 16931, EXTENDED) is otherwise interchangeable. ### Will paper still be allowed after 2028? Only between two parties who explicitly agree, and only outside the structured-format ramp's scope (e.g. simplified invoices < €250). The general direction is universal structured. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Germany]() — Pan-EU reference factsheet. * [OpenPeppol · Germany profile]() — Authoritative Peppol facts. * [BMF · FAQ E-Rechnung Wachstumschancengesetz]() — Official B2B mandate FAQ from finance ministry. * [KoSIT · XRechnung standard (xeinkauf.de)]() — National XRechnung CIUS authority. * [ZRE · Zentrale Rechnungseingangsplattform]() — Federal B2G e-invoicing portal. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Germany B2B mandate]() — Industry tracker — 2025-2028 timeline. ======================================================================== # Greece · myDATA # Source: https://docs.get-flowie.com/compliance/gr.html ======================================================================== --- title: "Greece — myDATA real-time reporting & Peppol BIS" description: "Greece e-invoicing: myDATA mandatory real-time tax reporting since 2021. AADE platform, Peppol BIS for cross-border. B2B e-invoicing extension expected 2026." canonical: "https://docs.get-flowie.com/compliance/gr" source: "https://docs.get-flowie.com/compliance/gr.html" --- # Greece — myDATA real-time reporting & Peppol BIS Compliance · 🇬🇷 Greece Live mandate # Greece — myDATA real-time reporting & Peppol BIS myDATA real-time reporting universal · Peppol BIS for cross-border — regulator: [Ανεξάρτητη Αρχή Δημοσίων Εσόδων (AADE)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Greece runs **myDATA** — every taxpayer transmits invoice headers in near-real-time to the AADE platform. * Mandatory since 2021 for all Greek VAT-registered businesses; no size threshold. * B2B _e-invoicing_ (vs. just _e-reporting_) is voluntary today but offered via accredited providers; uptake accelerating ahead of an expected mandate. * Cross-border: Peppol BIS 3.0; Flowie operates as an accredited Greek e-invoicing provider directly, or via a specialized local partner where AADE accreditation is held in-country. ## Deadlines Date| Who| What ---|---|--- **2021-10-01**| All Greek VAT-registered businesses| myDATA real-time reporting mandatory. 2024-04-01| Public-sector contracting| B2G via Peppol BIS for state suppliers. ≥ 2026| Universal B2B e-invoicing (expected)| AADE consultation underway; would convert myDATA reporting into full e-invoicing. ## Background myDATA (My Digital Accounting & Tax Application) is structurally a _continuous transaction control_ (CTC) regime: each invoice issued generates an HTTP call to AADE that returns a **MARK** (unique mark) and a **UID**. The seller stamps these onto the invoice; the buyer can verify with AADE. Flowie sends through myDATA on every Greek-issued invoice — the response includes `complianceReceipt.mark` and `complianceReceipt.uid`. Cross-border invoices to non-Greek buyers ride Peppol BIS as usual. ## Format profile * **myDATA invoice schema** (XML, AADE-defined) for the e-reporting payload. * **Peppol BIS 3.0** for cross-border B2B. * Greek VAT: `EL` \+ 9 digits (yes, `EL`, not `GR`, per ISO 3166 vs. EU VAT custom). ## Required fields * seller.vatNumberstringrequired Format `EL123456789`. * myData.invoiceTypecoderequired for myDATA Three-digit AADE invoice-type code (e.g. `1.1` = Sales Invoice). Flowie maps from `type` automatically when omitted. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **AADE Peppol gateway**| `9933:GR-`| Greek public buyers identified by their AFM (tax ID) over the Peppol GR scheme. ## B2B reporting / clearance **myDATA (AADE)** — Real-time transmission of invoice headers + MARK/UID issuance. Lifecycle status| Reported as ---|--- `issued`| MARK + UID issued by AADE. `cancelled`| Cancellation message; original MARK referenced. ## Error codes Code| Meaning| Fix ---|---|--- `myDATA-104`| Buyer AFM unknown.| Verify the buyer's AFM with AADE; new registrations propagate within 24h. `myDATA-201`| Invoice type code mismatch with line categories.| Flowie usually sets this; if you override, ensure it matches the AADE matrix. ## Testing in sandbox What you want to test| How ---|--- Greek domestic happy path| Seller AFM `EL000000001`, buyer AFM `EL000000002` in sandbox; MARK `SBX-...` echoed. Force myDATA rejection| `simulateCompliance: "reject_myDATA_104"`. ## FAQ ### Do I still need to file VAT returns if myDATA is real-time? Yes for now — the periodic VAT return remains, but it's pre-filled by AADE from myDATA data. Direction of travel is to drop the return entirely. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Greece]() — Pan-EU reference factsheet. * [OpenPeppol · Greece profile]() — Authoritative Peppol facts. * [AADE · myDATA platform]() — Tax authority myDATA real-time reporting. * [AADE · e-invoicing service providers]() — Licensed e-invoicing providers list. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Greece myDATA mandate]() — Industry tracker — 2026 phased B2B rollout. ======================================================================== # Hungary · NAV Online Számla # Source: https://docs.get-flowie.com/compliance/hu.html ======================================================================== --- title: "Hungary — NAV Online Számla real-time reporting" description: "Hungary e-invoicing: NAV Online Számla 3.0 real-time invoice reporting universal since 2021. Peppol BIS for cross-border. No structured-invoice mandate yet, but reporting is mandatory." canonical: "https://docs.get-flowie.com/compliance/hu" source: "https://docs.get-flowie.com/compliance/hu.html" --- # Hungary — NAV Online Számla real-time reporting Compliance · 🇭🇺 Hungary Live mandate # Hungary — NAV Online Számla real-time reporting NAV Online Számla 3.0 reporting universal since 2021 — regulator: [Nemzeti Adó- és Vámhivatal (NAV)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Hungary's **NAV Online Számla 3.0** requires every Hungarian-issued invoice to be reported to NAV within 5 minutes of issue. * Universal since July 2020 (B2B), extended to B2C in 2021. * Reporting only — the invoice itself can still be PDF or paper, though structured XML is increasingly preferred. * Flowie ships the NAV reporting envelope from the same payload you send via `/v1/documents/send`. ## Deadlines Date| Who| What ---|---|--- 2018-07-01| B2B invoices > HUF 100k VAT| Real-time reporting introduced. 2020-07-01| All B2B invoices| Threshold removed; universal B2B reporting. **2021-01-04**| B2C invoices| Reporting extended to B2C — universal scope. ≥ 2027| Structured-invoice send mandate (expected)| Legislation in consultation; ViDA-aligned. ## Background Hungary's NAV scheme is a **reporting-only** CTC: the seller still issues whatever invoice format the buyer expects, but in parallel must transmit a structured XML envelope to NAV. NAV stores the envelope, issues a transaction ID, and uses the data for VAT-gap analytics and pre-filling returns. ## Format profile * **NAV Online Számla 3.0 schema** (XML) for the reporting envelope. * **Peppol BIS 3.0** for cross-border B2B (B2G mandate also via Peppol). * Hungarian tax ID: 8 digits + check digit + 1 digit + 2-digit county code. ## Required fields * seller.taxId.hustringrequired for HU domestic Hungarian tax ID, e.g. `12345678-1-42`. * buyer.taxId.hustringrequired for B2B > HUF 0 Buyer's Hungarian tax ID. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Elektronikus Közbeszerzési Rendszer (EKR)**| `0190:HU-`| EKR is the central public procurement system; B2G e-invoices route through it. ## B2B reporting / clearance **NAV Online Számla 3.0** — Real-time XML envelope of every issued invoice; 5-minute reporting deadline. Lifecycle status| Reported as ---|--- `issued`| NAV transaction ID issued. `modified`| Modification message; original transaction referenced. `cancelled`| Cancellation message. ## Error codes Code| Meaning| Fix ---|---|--- `NAV-VAL-035`| Tax ID format invalid.| Hungarian tax IDs must follow the `NNNNNNNN-N-NN` shape. `NAV-OPER-010`| Reporting outside the 5-minute window.| Set the seller's clock correctly; or batch-send within the window. ## Testing in sandbox What you want to test| How ---|--- Hungarian B2B reporting| Seller tax ID `12345678-1-42` in sandbox; NAV transaction ID echoed. Force NAV rejection| `simulateCompliance: "reject_NAV_VAL_035"`. ## FAQ ### Is the structured XML the invoice itself or just a report? Today, just a report. The invoice may still be PDF or paper to the buyer. Direction of travel is to make the structured XML the invoice itself. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Hungary]() — Pan-EU reference factsheet. * [NAV · Online Számla portal]() — Mandatory RTIR portal — tax authority. * [NAV · National Tax and Customs Administration]() — Tax authority owning RTIR mandate. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Avalara · Hungary RTIR guide]() — Industry tracker — RTIR mechanics. * [EDICOM · Hungary RTIR / 2030 e-invoicing]() — Industry analysis — ViDA roadmap. ======================================================================== # Ireland · Peppol BIS # Source: https://docs.get-flowie.com/compliance/ie.html ======================================================================== --- title: "Ireland — Peppol BIS B2G mandate (B2B voluntary)" description: "Ireland e-invoicing: Peppol BIS B2G mandate live since 2019, no B2B mandate yet. Revenue Commissioners consultation on B2B e-invoicing underway." canonical: "https://docs.get-flowie.com/compliance/ie" source: "https://docs.get-flowie.com/compliance/ie.html" --- # Ireland — Peppol BIS B2G mandate (B2B voluntary) Compliance · 🇮🇪 Ireland Live mandate # Ireland — Peppol BIS B2G mandate (B2B voluntary) Peppol BIS B2G live since 2019 · No B2B mandate yet — regulator: [Revenue Commissioners](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Ireland transposed EU 2014/55/EU on schedule with a **Peppol BIS B2G mandate** from April 2019. * **No domestic B2B mandate** ; Revenue Commissioners are consulting on a future framework — outcome expected 2026. * Hub: Office of Government Procurement (OGP) operates the central Peppol gateway. * Flowie is a registered Peppol AP for Ireland — or routed through a specialized local partner where in-country presence is required. ## Deadlines Date| Who| What ---|---|--- 2019-04-18| Central government| B2G mandate live. 2020-04-18| Sub-central public authorities| Mandate extended. ≥ 2027| B2B (consultation)| Revenue Commissioners running stakeholder consultation; legislation TBD. ## Background Ireland's B2G mandate is unremarkable in the best way — it works. Adoption is high among central government and fully sufficient for an Irish supplier to invoice the State purely through Peppol. The B2B question is open: Revenue's 2025 consultation document floats both Italy-style CTC and France-style PDP frameworks. ## Format profile * **Peppol BIS 3.0** ; no Irish CIUS. * Irish VAT: `IE` \+ 7 digits + 1-2 letters (e.g. `IE1234567T`). ## Required fields * seller.vatNumberstringrequired Format `IE1234567T`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Office of Government Procurement (OGP)**| `9923:IE-`| Department of Finance / OGP operate the central Peppol gateway; individual departments register as participants. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- IE B2G happy path| Sender `IE1234567T`, recipient `9923:IE-GOV-TEST`. ## FAQ ### Will Ireland follow France or Italy? Unclear. The 2025 consultation explicitly contemplates both. A decision is expected during 2026. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Ireland]() — Pan-EU reference factsheet. * [OpenPeppol · Ireland profile]() — Authoritative Peppol facts. * [Revenue · VAT Modernisation eInvoicing]() — Revenue's phased ViDA implementation plan. * [Revenue · Office of the Revenue Commissioners]() — Tax authority owning ViDA preparations. **Industry analyses** (vendor trackers — useful for cross-referencing): * [KPMG · Ireland phased rollout]() — Industry analysis — 2028-2030 phases. ======================================================================== # Latvia · B2B mandate # Source: https://docs.get-flowie.com/compliance/lv.html ======================================================================== --- title: "Latvia — B2B mandate January 2026" description: "Latvia e-invoicing: B2B mandate phasing from 1 January 2026 (G2B already universal). Peppol BIS 3.0, State Revenue Service (VID) reporting." canonical: "https://docs.get-flowie.com/compliance/lv" source: "https://docs.get-flowie.com/compliance/lv.html" --- # Latvia — B2B mandate January 2026 Compliance · 🇱🇻 Latvia Live mandate # Latvia — B2B mandate January 2026 B2B mandate live since 1 January 2026 · G2B universal — regulator: [Valsts ieņēmumu dienests (VID — State Revenue Service)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **From 1 January 2026** , all Latvian VAT-registered businesses must issue and receive structured e-invoices for domestic B2B. * **G2B (government-to-business) e-invoicing universal since 2025**. * Format: **Peppol BIS 3.0**. No national CIUS; pure EN 16931. * Reporting to VID (State Revenue Service) on issued invoices is required — Flowie handles the reporting leg automatically. ## Deadlines Date| Who| What ---|---|--- 2025-01-01| G2B (government-to-business)| Public authorities must issue e-invoices to businesses. **2026-01-01**| All B2B taxable transactions| Universal mandate. Mandatory issue + receive. ## Background Latvia's mandate is structurally a Belgian-style one: pure Peppol BIS for transmission, plus a parallel reporting leg to VID for tax oversight. There's no national hub and no CTC pre-clearance — invoices are valid the moment they're issued and reported, not subject to government acceptance. ## Format profile * **Peppol BIS 3.0** ; no Latvian CIUS. * Latvian VAT: `LV` \+ 11 digits. * B2B reporting envelope is XML, mostly metadata (header + totals). ## Required fields * seller.vatNumberstringrequired Format `LV12345678901`. * buyer.vatNumberstringrequired for B2B Same format. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **ePakalpojumi (eService Portal)**| `9939:LV-`| Latvian public buyers reachable via Peppol; ePakalpojumi is the registry of public-sector participants. ## B2B reporting / clearance **VID e-invoicing reporting** — Header + totals report on every issued domestic B2B invoice. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- LV B2B happy path| Sender `LV40000000001`, buyer `LV40000000002`. ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Latvia]() — Pan-EU reference factsheet. * [VID · State Revenue Service]() — Tax authority collecting e-invoice data. * [Ministry of Finance Latvia]() — Finance ministry — e-invoicing law. * [Latvija.gov.lv · official portal (e-Address)]() — e-Address platform used for e-invoice transmission. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Latvia 2028 mandate]() — Industry tracker — 2026 B2G / 2028 B2B. ======================================================================== # Lithuania · E.sąskaita # Source: https://docs.get-flowie.com/compliance/lt.html ======================================================================== --- title: "Lithuania — E.sąskaita & i.MAS reporting" description: "Lithuania e-invoicing: E.sąskaita B2G platform, i.MAS / i.SAF-T tax reporting universal. Peppol BIS for cross-border. B2B mandate consultation underway." canonical: "https://docs.get-flowie.com/compliance/lt" source: "https://docs.get-flowie.com/compliance/lt.html" --- # Lithuania — E.sąskaita & i.MAS reporting Compliance · 🇱🇹 Lithuania Live mandate # Lithuania — E.sąskaita & i.MAS reporting E.sąskaita B2G universal · i.MAS reporting universal · No B2B mandate yet — regulator: [Valstybinė mokesčių inspekcija (VMI — State Tax Inspectorate)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **E.sąskaita** is Lithuania's national B2G e-invoicing platform; mandatory for all public buyers since 2017. * **i.MAS** (Smart Tax Administration System) requires every taxpayer to submit i.SAF-T accounting data periodically. * **No B2B e-invoicing mandate** yet, but consultation underway with target ≥ 2027. * Peppol BIS for cross-border; Flowie is a registered AP — directly, or via a specialized local partner where in-country presence is required. ## Deadlines Date| Who| What ---|---|--- 2017-07-01| Public-sector contracting| E.sąskaita mandatory for B2G. 2019-01-01| Large taxpayers| i.SAF-T reporting (annual). 2020-01-01| All taxpayers| i.SAF-T extended; periodic cadence by company size. ≥ 2027| B2B mandate (expected)| VMI consultation in progress. ## Background Lithuania has the most layered approach in the Baltics: a B2G platform (E.sąskaita), an SAF-T reporting regime (i.MAS / i.SAF-T), plus participation in Peppol for cross-border. The B2G platform sits in front of Peppol — invoices destined for Lithuanian public buyers are uploaded to E.sąskaita, which forwards via Peppol to the actual recipient. Flowie hides this — to the caller, it's just `POST /v1/documents/send`. ## Format profile * **Peppol BIS 3.0** for transport. * **i.SAF-T** XML format for tax reporting (separate from invoice transmission). ## Required fields * seller.vatNumberstringrequired Format `LT123456789`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **E.sąskaita**| `9937:LT-`| All Lithuanian public buyers receive through E.sąskaita; Flowie routes there transparently. ## B2B reporting / clearance **i.MAS / i.SAF-T** — Periodic SAF-T export covering accounting + invoice records. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Lithuania]() — Pan-EU reference factsheet. * [SABIS · national e-invoicing platform]() — Mandatory B2G platform replacing eSaskaita. * [VMI · State Tax Inspectorate]() — Tax authority operating SABIS. * [Ministry of Finance Lithuania]() — Finance ministry — e-invoicing policy. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Sovos · Lithuania e-invoicing guide]() — Industry analysis — SABIS / Peppol. ======================================================================== # Luxembourg · Peppol BIS # Source: https://docs.get-flowie.com/compliance/lu.html ======================================================================== --- title: "Luxembourg — Peppol BIS B2G mandate (phased)" description: "Luxembourg e-invoicing: Peppol BIS B2G mandate phased 2022–2023 by company size. No B2B mandate yet. CTIE national authority." canonical: "https://docs.get-flowie.com/compliance/lu" source: "https://docs.get-flowie.com/compliance/lu.html" --- # Luxembourg — Peppol BIS B2G mandate (phased) Compliance · 🇱🇺 Luxembourg Live mandate # Luxembourg — Peppol BIS B2G mandate (phased) Peppol BIS B2G universal · No B2B mandate yet — regulator: [Centre des Technologies de l'Information de l'État (CTIE)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Luxembourg phased a **Peppol BIS B2G mandate** by company size: large 2022, mid 2023, small 2023-Q4. * **No B2B mandate** as of 2026. * Public buyers reachable via Peppol; CTIE operates the national gateway. * Flowie is a registered Peppol AP. ## Deadlines Date| Who| What ---|---|--- 2022-05-18| Large companies (B2G)| Send mandate. 2022-10-18| Mid-size companies (B2G)| Send mandate. 2023-03-18| Small / micro companies (B2G)| Send mandate. ≥ 2030| B2B (expected)| EU ViDA framework. ## Background Luxembourg is unusual in the EU for explicitly phasing the B2G mandate by company size — most countries flip a single switch. The phasing is now complete; every supplier to a Luxembourgish public buyer must invoice via Peppol BIS. ## Format profile * **Peppol BIS 3.0** ; no Luxembourgish CIUS. * Luxembourgish VAT: `LU` \+ 8 digits. ## Required fields * seller.vatNumberstringrequired Format `LU12345678`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **CTIE Peppol gateway**| `9938:LU-`| Central CTIE-operated gateway; individual ministries published as participants. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Luxembourg]() — Pan-EU reference factsheet. * [CTIE · Information on electronic invoicing]() — Government IT Centre — Peppol Authority. * [Ministère de la Digitalisation]() — Ministry for Digitalisation — Peppol Authority. * [Guichet.lu · electronic invoicing]() — Official supplier portal for B2G submission. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Cleartax · Luxembourg e-invoicing guide]() — Industry tracker — Peppol BIS 3.0. ======================================================================== # Malta · Peppol BIS # Source: https://docs.get-flowie.com/compliance/mt.html ======================================================================== --- title: "Malta — Peppol BIS B2G mandate (B2B voluntary)" description: "Malta e-invoicing: Peppol BIS B2G mandate live since 2019 (CFR / Office of the Commissioner for Revenue). No B2B mandate yet." canonical: "https://docs.get-flowie.com/compliance/mt" source: "https://docs.get-flowie.com/compliance/mt.html" --- # Malta — Peppol BIS B2G mandate (B2B voluntary) Compliance · 🇲🇹 Malta Live mandate # Malta — Peppol BIS B2G mandate (B2B voluntary) Peppol BIS B2G live since 2019 · No B2B mandate yet — regulator: [Commissioner for Revenue (CFR)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Malta transposed the EU directive with a **Peppol BIS B2G mandate** from 2019. * **No B2B mandate** ; CFR has indicated alignment with EU ViDA rather than a national-only ramp. * Flowie is a registered Peppol AP. ## Deadlines Date| Who| What ---|---|--- 2019-04-18| Central government| B2G mandate live. 2020-04-18| Sub-central public authorities| Mandate extended. ≥ 2030| B2B (expected)| EU ViDA. ## Background Malta's regime is the textbook EU minimum: Peppol BIS for B2G, no national CIUS, no separate B2B mandate. ## Format profile * **Peppol BIS 3.0** ; no Maltese CIUS. * Maltese VAT: `MT` \+ 8 digits. ## Required fields * seller.vatNumberstringrequired Format `MT12345678`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **No central hub**| `9943:MT-`| Each authority publishes its own Peppol participant ID. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Malta]() — Pan-EU reference factsheet. * [CFR · Office of the Commissioner for Revenue]() — Tax authority — VAT and e-invoicing. * [Ministry for Finance and Employment Malta]() — Finance ministry — Legal Notices 403/404 of 2018. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Sovos · Malta B2G Peppol overview]() — Industry tracker — Peppol BIS 3.0 adoption. ======================================================================== # Netherlands · NLCIUS # Source: https://docs.get-flowie.com/compliance/nl.html ======================================================================== --- title: "Netherlands — Peppol BIS, NLCIUS & SimplerInvoicing" description: "Netherlands e-invoicing: B2G universal since 2017. NLCIUS profile + Peppol BIS. SimplerInvoicing community-led adoption. No B2B mandate yet." canonical: "https://docs.get-flowie.com/compliance/nl" source: "https://docs.get-flowie.com/compliance/nl.html" --- # Netherlands — Peppol BIS, NLCIUS & SimplerInvoicing Compliance · 🇳🇱 Netherlands Live mandate # Netherlands — Peppol BIS, NLCIUS & SimplerInvoicing Peppol-by-default · B2G universal · NLCIUS profile · No B2B mandate yet — regulator: [Belastingdienst](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Netherlands has a **universal B2G e-invoicing mandate** since 2017. * Domestic CIUS: **NLCIUS** (a Peppol BIS extension with extra Dutch profile rules). * **No formal B2B mandate** , but SimplerInvoicing — a community-driven adoption framework — has driven voluntary B2B uptake to > 60%. * Flowie is a registered Peppol AP and a SimplerInvoicing participant — directly, or via a specialized local partner where in-country presence is required. ## Deadlines Date| Who| What ---|---|--- 2017-01-01| Central government| B2G mandate live. 2019-04-18| All public authorities| EU directive transposition. ≥ 2030| B2B mandate (expected)| EU ViDA framework; Belastingdienst has indicated alignment without national front-running. ## Background The Netherlands' approach is community-led: rather than legislate B2B, the government and a coalition of trade associations created **SimplerInvoicing** (now NPa — Nederlandse Peppol Autoriteit), a non-binding framework that ERPs, Peppol APs, and payment providers all participate in. The result is voluntary B2B adoption that's higher than many mandated countries. ## Format profile * **Peppol BIS 3.0 with NLCIUS** for domestic B2G. * Standard Peppol BIS for cross-border. * Dutch BTW: `NL` \+ 9 digits + `B` \+ 2 digits (e.g. `NL123456789B01`). * NLCIUS adds: **OB-nummer** on payments, **FA-nummer** for B2G order references. ## Required fields * seller.vatNumberstringrequired Format `NL123456789B01`. * buyerReferencestringrequired for B2G FA-nummer (factuurordernummer) issued by the public buyer. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Digipoort**| `0106:NL-`| Digipoort is the central Logius-operated gateway; individual public buyers are reachable via the Peppol Directory. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes Code| Meaning| Fix ---|---|--- `NLCIUS-S-001`| NLCIUS schematron failure.| Inspect `error.details`; usually FA-nummer missing or wrong VAT category. ## Testing in sandbox What you want to test| How ---|--- NL B2G happy path| Sender `NL123456789B01`, recipient `0106:KVK-12345678`. ## FAQ ### Is NLCIUS strict? Stricter than vanilla EN 16931 — adds Dutch-specific cardinality on order references and payment fields. Flowie applies the right CIUS automatically based on the recipient. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in The Netherlands]() — Pan-EU reference factsheet. * [OpenPeppol · Netherlands profile]() — Authoritative Peppol facts. * [Nederlandse Peppolautoriteit (NPa)]() — Dutch Peppol Authority. * [Logius · e-factureren / Peppol]() — Government IT — Digipoort + Rijksoverheid Peppol AP. * [STPE · Stichting Peppol Education NL]() — Dutch Peppol governance / NL CIUS. **Industry analyses** (vendor trackers — useful for cross-referencing): * [ecosio · Netherlands e-invoicing]() — Industry tracker — SI-UBL / NLCIUS. ======================================================================== # Poland · KSeF # Source: https://docs.get-flowie.com/compliance/pl.html ======================================================================== --- title: "Poland — KSeF mandatory B2B clearance" description: "Poland e-invoicing: KSeF 2.0 mandatory for large taxpayers from February 2026, all VAT taxpayers from April 2026. FA(3) format replaces FA(2), 24/7 clearance." canonical: "https://docs.get-flowie.com/compliance/pl" source: "https://docs.get-flowie.com/compliance/pl.html" --- # Poland — KSeF mandatory B2B clearance Compliance · 🇵🇱 Poland Phased rollout # Poland — KSeF mandatory B2B clearance KSeF mandatory clearance · large taxpayers Feb 2026 · all April 2026 — regulator: [Ministerstwo Finansów (Ministry of Finance)](). _Facts last refreshed: 2026-09-14._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **KSeF** (Krajowy System e-Faktur) is Poland's central _clearance_ platform — every domestic invoice is submitted, validated, and assigned a KSeF number **before** being delivered to the buyer. * **Mandatory from 1 February 2026** for taxpayers with sales > PLN 200M; **1 April 2026** for everyone else. * Format: **FA(3)** — the Polish XML schema required by **KSeF 2.0** from 1 February 2026, replacing FA(2) on that date for all structured invoices regardless of the source document's date. Not interchangeable with Peppol BIS for domestic. * Cross-border invoices ride Peppol BIS as usual; only domestic KSeF. ## Deadlines Date| Who| What ---|---|--- 2022-01-01| Voluntary KSeF| Available for early adopters. **2026-02-01**| Large taxpayers (sales > PLN 200M)| KSeF 2.0 mandatory; FA(3) replaces FA(2) for everyone on this date. **2026-04-01**| All other VAT taxpayers| KSeF mandatory. 2027-01-01| Cash register integration| POS systems must connect to KSeF for B2C documents. ## Background Poland operates the most aggressive CTC regime in the EU: **clearance** , not just reporting. An invoice does not legally exist until KSeF accepts it and returns a **KSeF number**. The seller can then deliver the invoice to the buyer (in any format), with the KSeF number as proof of validity. Since 1 February 2026 the platform is **KSeF 2.0** and the schema is **FA(3)** , which supersedes FA(2) for every structured invoice — original, corrective and settlement alike — whatever the date of the underlying document. FA(3) adds invoice attachments, an employee-as-buyer marker and more flexible payment terms. Flowie's domestic Polish flow: `POST /v1/documents/send` → Flowie translates JSON to FA(3) → submits to KSeF → receives KSeF number and visualisation URL → returns those to the caller, then optionally delivers to the buyer (PDF or Peppol). ## Format profile * **FA(3)** XML schema, defined by the Polish Ministry of Finance, required by KSeF 2.0 from 1 February 2026. No alternative for domestic. * **Peppol BIS 3.0** for cross-border. * Polish NIP: 10 digits, prefixed with `PL` for VAT. ## Required fields * seller.taxId.nipstring (10 digits)required Polish NIP. * buyer.taxId.nipstring (10 digits)required for B2B Buyer NIP. * ksef.invoiceTypecodeauto-derived FA(3) document type code; derived from `type` when omitted. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **KSeF (covers public + private)**| `PL-NIP-`| KSeF is the universal Polish hub — public-sector recipients use it too. There's no separate B2G platform. ## B2B reporting / clearance **KSeF** — Clearance: invoice validated and assigned KSeF number before legal delivery. Lifecycle status| Reported as ---|--- `issued`| Submitted; KSeF number returned. `rejected`| Schema or business-rule failure; original FA(3) returned. `cancelled`| Cancellation message; original KSeF number referenced. Opt-out: `settings.autoCompliance.PL = false (only for cross-border-only sellers)` ## Error codes Code| Meaning| Fix ---|---|--- `KSEF-21100`| Schema validation failure on FA(3).| Inspect `error.details` for the offending element. `KSEF-21102`| NIP not registered with KSeF.| Either party not yet onboarded; verify with the buyer. `KSEF-22001`| Authentication token expired.| Flowie auto-refreshes; manual integrations must re-issue the JWT. ## Testing in sandbox What you want to test| How ---|--- KSeF happy path| Seller NIP `1111111111`, buyer NIP `2222222222`; sandbox returns synthetic KSeF number. Force KSeF rejection| `simulateCompliance: "reject_KSEF_21100"`. ## FAQ ### Can I send PDF to the buyer if KSeF accepted the FA(3)? Yes — once KSeF returns a number, the invoice exists. You may also deliver a human-readable PDF (with the KSeF number on it) to the buyer's mailbox, or send via Peppol if they prefer. ### Are foreign sellers obligated? Only if registered for Polish VAT. A foreign EU seller invoicing into Poland uses standard EU rules; KSeF is not required. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Poland]() — Pan-EU reference factsheet. * [OpenPeppol · Poland profile]() — Authoritative Peppol facts. * [KSeF · Krajowy System e-Faktur (MF)]() — National e-invoicing platform — Ministry of Finance. * [Podatki.gov.pl · KSeF info portal]() — Taxpayer guidance and FAQ portal. * [PEF · Platforma Elektronicznego Fakturowania]() — B2G Peppol-based platform. * [Podatki.gov.pl · KSeF 2.0 — zakres obowiązkowego KSeF]() — MF — KSeF 2.0 and FA(3) mandatory from 1 Feb 2026. **Industry analyses** (vendor trackers — useful for cross-referencing): * [vatcalc · Poland KSeF 2026 timeline]() — Industry tracker — Feb/Apr 2026 phased rollout. ======================================================================== # Portugal · ATCUD + SAF-T # Source: https://docs.get-flowie.com/compliance/pt.html ======================================================================== --- title: "Portugal — ATCUD, SAF-T & B2G mandate" description: "Portugal e-invoicing: ATCUD unique invoice code mandatory, SAF-T monthly reporting, B2G via FE-AP universal since 2021. Cross-border Peppol BIS." canonical: "https://docs.get-flowie.com/compliance/pt" source: "https://docs.get-flowie.com/compliance/pt.html" --- # Portugal — ATCUD, SAF-T & B2G mandate Compliance · 🇵🇹 Portugal Live mandate # Portugal — ATCUD, SAF-T & B2G mandate ATCUD + SAF-T universal · B2G via FE-AP · No B2B mandate yet — regulator: [Autoridade Tributária e Aduaneira (AT)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Portugal requires every invoice (B2B, B2C, B2G) to carry an **ATCUD** — a unique code generated by AT-certified software. * **SAF-T** reporting (monthly accounting export) is universal for VAT-registered businesses since 2008. * **B2G via FE-AP** (Faturação Eletrónica na Administração Pública) universal since 2021. * **Full B2B mandate** proposed for 2027; legislation pending. ## Deadlines Date| Who| What ---|---|--- 2008-01-01| All taxpayers| SAF-T monthly export. 2021-01-01| Public-sector contracting (B2G)| FE-AP universal. 2023-01-01| All invoices| ATCUD mandatory on every invoice. **≥ 2027**| B2B (proposed)| Universal e-invoicing mandate; AT consultation underway. ## Background Portugal's regime is multi-layered: every invoice carries an ATCUD (a 8-character code from a registered series), every taxpayer files SAF-T monthly, and every public-sector invoice goes through FE-AP. Flowie handles all three: the JSON payload you send is automatically annotated with an ATCUD from your registered series, included in the SAF-T monthly export, and routed via FE-AP for B2G recipients. ## Format profile * **CIUS-PT** for B2G (Peppol BIS extended with FE-AP rules). * **SAF-T (PT)** monthly XML export to AT. * **ATCUD** unique code on every invoice — registered through Portal das Finanças. * Portuguese NIF: 9 digits. ## Required fields * seller.taxId.nifstring (9 digits)required Portuguese NIF. * atcud.seriesstringrequired Registered invoice series ID; Flowie pre-registers and rotates. * buyer.taxId.nifstring (9 digits)required for B2B Buyer NIF. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **FE-AP (eSPap)**| `9946:PT-`| Public-sector hub operated by eSPap; B2G invoices route through FE-AP regardless of channel. ## B2B reporting / clearance **Portal das Finanças (SAF-T)** — Monthly SAF-T (PT) export — accounting + invoices. ## Error codes Code| Meaning| Fix ---|---|--- `ATCUD-MISS`| ATCUD missing or malformed.| Flowie generates from the registered series; manual integrations must call `/v1/compliance/pt/atcud` first. `FEAP-PT-101`| FE-AP profile validation failed.| Inspect `error.details` — usually a public-sector procurement code missing. ## Testing in sandbox What you want to test| How ---|--- PT B2G via FE-AP| Recipient `9946:PT-500000000`; sandbox returns synthetic ATCUD. SAF-T export| `POST /v1/compliance/saft` with `country: "PT"`. ## FAQ ### Do I need to be AT-certified to issue invoices in Portugal? The seller's billing software must be — and Flowie is. Customers using Flowie inherit the certification. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Portugal]() — Pan-EU reference factsheet. * [Portal das Finanças (AT)]() — Tax authority main portal. * [e-Fatura portal]() — Official AT e-fatura portal. * [AT · SAF-T (PT) technical questions]() — Official SAF-T PT specification reference. * [ESPAP · CIUS-PT / FE-AP B2G platform]() — Shared services — public sector e-invoicing. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Portugal e-invoicing]() — Industry tracker — CIUS-PT / SME 2025 deadline. ======================================================================== # Romania · RO e-Factura # Source: https://docs.get-flowie.com/compliance/ro.html ======================================================================== --- title: "Romania — RO e-Factura mandatory clearance" description: "Romania e-invoicing: RO e-Factura universal B2B since July 2024. ANAF clearance platform, UBL/CII format. SAF-T (D406) reporting universal." canonical: "https://docs.get-flowie.com/compliance/ro" source: "https://docs.get-flowie.com/compliance/ro.html" --- # Romania — RO e-Factura mandatory clearance Compliance · 🇷🇴 Romania Live mandate # Romania — RO e-Factura mandatory clearance RO e-Factura mandatory clearance universal since July 2024 — regulator: [Agenția Națională de Administrare Fiscală (ANAF)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Romania's **RO e-Factura** is a clearance system: every B2B invoice is submitted to ANAF, validated, and a **signed PDF copy** is returned before legal delivery. * **Universal B2B** since 1 July 2024. * Format: **UBL or CII** with the Romanian **RO_CIUS**. * Plus: **SAF-T (D406)** monthly reporting for large taxpayers (extended to all by 2026). ## Deadlines Date| Who| What ---|---|--- 2022-07-01| High-fiscal-risk products (B2B)| RO e-Factura mandatory for selected sectors. 2024-01-01| All B2B reporting (5-day window)| Reporting obligation universal. **2024-07-01**| All B2B clearance| Full clearance — invoices invalid without ANAF acceptance. 2025-01-01| B2C extension| RO e-Factura extended to B2C invoices. 2026-01-01| All taxpayers SAF-T| D406 monthly reporting universal. ## Background Romania's mandate is structurally the same as Italy's SDI: clearance, not just reporting. The difference is speed of rollout — Romania went universal in 18 months, the most aggressive timeline in the EU. Flowie's ANAF integration handles certificate-based authentication, UBL conversion, and clearance polling transparently. ## Format profile * **RO_CIUS** on top of UBL 2.1 or CII. * **SAF-T (D406)** for tax reporting. * Romanian CUI: `RO` \+ 2-10 digits. ## Required fields * seller.taxId.cuistringrequired Romanian fiscal code (CUI), with or without `RO` prefix. * buyer.taxId.cuistringrequired for B2B Buyer CUI. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **RO e-Factura (ANAF)**| `RO-CUI-`| RO e-Factura covers public and private alike — single clearance entry point. ## B2B reporting / clearance **RO e-Factura** — Clearance + signed PDF return; invoice not legally valid until ANAF accepts. Lifecycle status| Reported as ---|--- `issued`| Submitted; ANAF returns signed XML. `rejected`| Schema or business-rule failure. `cancelled`| Cancellation message. ## Error codes Code| Meaning| Fix ---|---|--- `RO-EFACT-101`| CUI not registered with RO e-Factura.| Verify the buyer is registered in the ANAF directory. `RO-EFACT-205`| Schema validation failure.| Inspect `error.details`. ## Testing in sandbox What you want to test| How ---|--- RO e-Factura happy path| Seller CUI `RO12345678`, buyer CUI `RO87654321`; sandbox returns signed XML. Force ANAF rejection| `simulateCompliance: "reject_RO_EFACT_101"`. ## FAQ ### How long does ANAF take to clear? Typically < 30 seconds, occasionally up to a few minutes during peaks. Flowie's `/v1/documents/send` blocks until clearance returns; if you need async, use `?waitFor=submitted`. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Romania]() — Pan-EU reference factsheet. * [ANAF · National Tax Administration Agency]() — Tax authority — RO e-Factura mandate. * [ANAF · SPV Virtual Private Space]() — Mandatory communication channel for e-Factura. * [Ministry of Finance Romania]() — Finance ministry — Law 199/2020 transposition. **Industry analyses** (vendor trackers — useful for cross-referencing): * [ecosio · Romania ANAF RO e-Factura]() — Industry analysis — clearance model details. ======================================================================== # Slovakia · IS EFA # Source: https://docs.get-flowie.com/compliance/sk.html ======================================================================== --- title: "Slovakia — IS EFA & Peppol BIS" description: "Slovakia e-invoicing: IS EFA (Information System for Electronic Invoicing) phased B2G mandate. No B2B mandate yet. Finančná správa." canonical: "https://docs.get-flowie.com/compliance/sk" source: "https://docs.get-flowie.com/compliance/sk.html" --- # Slovakia — IS EFA & Peppol BIS Compliance · 🇸🇰 Slovakia Phased rollout # Slovakia — IS EFA & Peppol BIS IS EFA phased B2G · No B2B mandate yet — regulator: [Finančná správa Slovenskej republiky](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Slovakia's **IS EFA** (Informačný systém elektronickej fakturácie) is the central B2G platform. * Phased rollout: large public buyers first, full public-sector by 2027. * **No B2B mandate** ; consultation on universal e-invoicing in early stages. * Cross-border: Peppol BIS. ## Deadlines Date| Who| What ---|---|--- 2022-04-01| Pilot| Voluntary IS EFA participation. 2025-01-01| Central government| IS EFA mandatory for receive. 2027-01-01| All public authorities (planned)| IS EFA universal B2G. ## Background Slovakia's IS EFA is a clearance-style B2G platform — invoices to public buyers transit IS EFA which validates and forwards. Flowie's AP routes to IS EFA transparently when the recipient is a registered Slovak public authority. ## Format profile * **Peppol BIS 3.0** for transport. * Slovak DIČ (tax ID): 10 digits, prefixed with `SK` for VAT. ## Required fields * seller.vatNumberstringrequired Format `SK1234567890`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **IS EFA**| `9919:SK-`| Slovak public buyers identified by IČO via Peppol scheme `9919`. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Slovakia]() — Pan-EU reference factsheet. * [OpenPeppol · Slovakia profile]() — Authoritative Peppol facts. * [Finančná správa SR]() — Financial Administration — IS EFA mandate. * [Ministerstvo financií SR]() — Finance ministry — VAT Act / e-invoicing. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Slovakia 2027 mandate]() — Industry tracker — IS EFA 2027 rollout. ======================================================================== # Slovenia · UJP # Source: https://docs.get-flowie.com/compliance/si.html ======================================================================== --- title: "Slovenia — UJP B2G & Peppol BIS" description: "Slovenia e-invoicing: UJP B2G platform universal since 2015, Peppol BIS for cross-border. B2B mandate consultation underway." canonical: "https://docs.get-flowie.com/compliance/si" source: "https://docs.get-flowie.com/compliance/si.html" --- # Slovenia — UJP B2G & Peppol BIS Compliance · 🇸🇮 Slovenia Live mandate # Slovenia — UJP B2G & Peppol BIS UJP B2G universal since 2015 · No B2B mandate yet — regulator: [Finančna uprava Republike Slovenije (FURS)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **UJP** (Uprava Republike Slovenije za javna plačila) is Slovenia's central B2G hub — mandatory since 2015. * **No B2B mandate** ; FURS consultation underway with target 2027. * Format: **e-SLOG 2.0** for legacy B2G; Peppol BIS 3.0 increasingly preferred. * Slovenia is also a launch user of the EU's ViDA pilot. ## Deadlines Date| Who| What ---|---|--- 2015-01-01| Public-sector contracting| UJP mandatory for all suppliers to public buyers. ≥ 2027| B2B mandate (consultation)| FURS reviewing options. ## Background Slovenia's UJP is a payments-and-invoicing hub: public buyers pay through UJP and receive invoices through it. The platform pre-dates Peppol but now bridges to it; Flowie's AP routes to UJP automatically when the recipient is registered. ## Format profile * **e-SLOG 2.0** for legacy UJP routes. * **Peppol BIS 3.0** for new B2G and cross-border. * Slovenian VAT: `SI` \+ 8 digits. ## Required fields * seller.vatNumberstringrequired Format `SI12345678`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **UJP**| `9929:SI-`| Slovenian public buyers identified by Matična številka (MAT) via Peppol scheme `9929`. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Slovenia]() — Pan-EU reference factsheet. * [UJP · Public Payments Administration]() — B2G central e-invoicing entry point. * [UJP eRačun portal]() — Free SME e-invoice portal. * [FURS · Financial Administration of Slovenia]() — Tax authority — VAT compliance. **Industry analyses** (vendor trackers — useful for cross-referencing): * [ddd · Slovenia B2B e-invoicing]() — Industry tracker — 2027 B2B draft bill. ======================================================================== # Spain · Veri*Factu + FACe # Source: https://docs.get-flowie.com/compliance/es.html ======================================================================== --- title: "Spain — Veri*Factu, Crea y Crece, FACe" description: "Spain e-invoicing: Veri*Factu postponed to January 2027 (corporate income tax payers) / July 2027 (all other taxpayers) by RDL 15/2025. Crea y Crece B2B mandate phasing 2026–2028. FACe B2G universal." canonical: "https://docs.get-flowie.com/compliance/es" source: "https://docs.get-flowie.com/compliance/es.html" --- # Spain — Veri*Factu, Crea y Crece, FACe Compliance · 🇪🇸 Spain Phased rollout # Spain — Veri*Factu, Crea y Crece, FACe Veri*Factu reporting · Crea y Crece B2B mandate · FACe B2G — regulator: [Agencia Estatal de Administración Tributaria (AEAT)](). _Facts last refreshed: 2026-09-14._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **Veri*Factu** — every taxpayer using billing software must use a Veri*Factu-certified system that hashes each invoice and (optionally) reports to AEAT in real-time. * **Veri*Factu is not live yet.** Real Decreto-ley 15/2025 of 2 December 2025 (BOE-A-2025-24446) postponed it to **1 January 2027** for corporate income tax payers and **1 July 2027** for all remaining taxpayers. * **Crea y Crece** (Law 18/2022) introduces a B2B e-invoicing mandate phased 2026–2028 by company size. * **FACe** is the B2G hub, mandatory for public-sector recipients since 2015. * Format: **Facturae 3.2.x** for FACe legacy + **Peppol BIS** for cross-border. ## Deadlines Date| Who| What ---|---|--- 2015-01-15| Public-sector contracting (B2G)| FACe mandatory. **≥ 2026-Q4**| Large taxpayers (Crea y Crece)| B2B e-invoicing mandate (date pending royal decree). **2027-01-01**| Corporate income tax payers| Veri*Factu obligation begins (postponed from 2025-07-01, then 2026-01-01, by RDL 15/2025). **2027-07-01**| All remaining taxpayers| Veri*Factu obligation extended (postponed from 2026-07-01 by RDL 15/2025). **≥ 2028**| All taxpayers (Crea y Crece)| Universal B2B mandate. ## Background Spain runs three parallel regimes that often confuse newcomers: **Veri*Factu** is about _billing-software certification_ : any software used to issue Spanish invoices must hash and chain them, and may (or, where ordered, must) transmit to AEAT in real-time. It is _not in force yet_ — Real Decreto-ley 15/2025 of 2 December 2025 pushed the obligation to 1 January 2027 for corporate income tax payers and 1 July 2027 for everyone else. Build against it now; the hash chain is unchanged by the delay. **Crea y Crece** is the upcoming _B2B e-invoicing mandate_ proper — invoices in structured format between businesses. Phased rollout dates are still subject to the implementing royal decree but tracking 2026–2028. **FACe** is the long-running _B2G hub_. Suppliers to Spanish public buyers send Facturae XML through FACe; Flowie does this transparently. ## Format profile * **Facturae 3.2.x** for B2G via FACe. * **Peppol BIS 3.0** for cross-border B2B. * **Veri*Factu hash chain** on every domestic invoice once the obligation starts (2027). * Spanish NIF/CIF: 8 digits + 1 letter (or 1 letter + 7 digits + 1 letter). ## Required fields * seller.taxId.nifstringrequired Spanish NIF/CIF. * buyer.taxId.nifstringrequired for B2B Buyer NIF/CIF. * verifactu.previousHashstringauto-managed Hash chain link; Flowie maintains the per-issuer chain. * buyerReferencestringrequired for FACe B2G Three administrative codes (oficina contable, órgano gestor, unidad tramitadora) supplied by the public buyer. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **FACe (Punto General de Entrada de Facturas Electrónicas)**| `0009:ES-FACE-`| FACe accepts Facturae 3.2.x; Flowie renders it from the same JSON payload. The three administrative codes (DIR3) must be set on `buyerReference`. ## B2B reporting / clearance **Veri*Factu (AEAT)** — Hash-chain certification + optional real-time transmission of invoice headers. Lifecycle status| Reported as ---|--- `issued`| Hash recorded; if real-time mode, transmitted to AEAT. `cancelled`| Cancellation event in the chain. Opt-out: `settings.autoCompliance.ES.veriFactu = "hash-only" (no real-time transmission)` ## Error codes Code| Meaning| Fix ---|---|--- `VERI-CHAIN-101`| Veri*Factu hash chain broken.| Don't manually edit the chain. Flowie maintains it; if you detect divergence, call `/v1/compliance/es/veri-factu/repair`. `FACE-DIR3-MISS`| DIR3 administrative codes missing.| Set the three codes on `buyerReference`; format `OC|OG|UT`. `ESCIUS-S-007`| Facturae profile validation failed.| Inspect `error.details`. ## Testing in sandbox What you want to test| How ---|--- FACe B2G| Recipient `0009:ES-FACE-A12345678|B12345678|C12345678`. Veri*Factu hash-only| Set `verifactu.mode: "hash-only"`; chain returned, no AEAT transmission. Force Crea y Crece rejection| `simulateCompliance: "reject_CREAYCRECE_001"`. ## FAQ ### Is Veri*Factu the same as Crea y Crece? No. Veri*Factu is about billing-software certification (starting January 2027 for corporate income tax payers, July 2027 for the rest); Crea y Crece is a separate B2B e-invoicing mandate (phasing from 2026). They overlap but are distinct obligations. ### Do I still need to register with FACe? Only if you upload invoices manually. When sending via Flowie's AP, FACe is the recipient and we route there transparently. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Spain]() — Pan-EU reference factsheet. * [FACe · Punto General de Entrada]() — Official B2G e-invoice gateway. * [FACeB2B platform]() — Official B2B subcontractor invoice platform. * [AEAT · Agencia Tributaria (VeriFactu)]() — Tax agency — VeriFactu / Crea y Verifica. * [Ley 18/2022 Crea y Crece (BOE)]() — B2B e-invoicing mandate law text. * [Real Decreto-ley 15/2025 (BOE-A-2025-24446)]() — Postpones Veri*Factu to 1 Jan 2027 / 1 Jul 2027. * [AEAT · Nota informativa — ampliación del plazo]() — Tax agency note on the extended deadline. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Marosa · VeriFactu Spain guide]() — Industry analysis — VeriFactu rollout. ======================================================================== # Sweden · Peppol BIS # Source: https://docs.get-flowie.com/compliance/se.html ======================================================================== --- title: "Sweden — Peppol BIS B2G & SFTI" description: "Sweden e-invoicing: B2G universal since April 2019. SFTI/Peppol BIS 3.0. No B2B mandate yet — alignment with EU ViDA." canonical: "https://docs.get-flowie.com/compliance/se" source: "https://docs.get-flowie.com/compliance/se.html" --- # Sweden — Peppol BIS B2G & SFTI Compliance · 🇸🇪 Sweden Live mandate # Sweden — Peppol BIS B2G & SFTI Peppol BIS B2G universal since 2019 · SFTI · No B2B mandate yet — regulator: [Skatteverket](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Sweden has a **universal B2G mandate** since April 2019. * Domestic standard: **Peppol BIS 3.0** ; SFTI is the legacy national framework which has converged on Peppol. * **No B2B mandate** ; Skatteverket has stated alignment with EU ViDA rather than national front-running. * Flowie is a registered Peppol AP (DIGG-recognised) — directly, or via a specialized local partner where in-country presence is required. ## Deadlines Date| Who| What ---|---|--- 2019-04-01| All public buyers| B2G mandate live (DIGG). ≥ 2030| B2B (expected)| EU ViDA. ## Background Sweden's Peppol adoption is led by **DIGG** (Agency for Digital Government), which operates the national authority and certifies APs. The SFTI framework (Single Face To Industry) pre-dates Peppol but has fully converged on it; in practice, Peppol BIS is the only format that matters in 2026. ## Format profile * **Peppol BIS 3.0** ; no Swedish CIUS. * Swedish organisation number: 10 digits (`NNNNNN-NNNN`). ## Required fields * seller.orgNumberstringrequired Swedish org. number, e.g. `5560000001`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **DIGG (national Peppol authority)**| `0007:SE-`| Swedish public buyers identified by org. number via Peppol scheme `0007`. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- SE B2G happy path| Sender `0007:5560000001`, recipient `0007:2021000001`. ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Sweden]() — Pan-EU reference factsheet. * [OpenPeppol · Sweden profile]() — Authoritative Peppol facts. * [DIGG · Peppol Authority]() — Swedish Peppol Authority. * [Skatteverket · e-faktura till Skatteverket]() — Tax Agency — receiving e-invoices via Peppol. * [SFS 2018:1277 · law on e-invoices in public procurement]() — B2G mandate law text. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Sweden Peppol B2G]() — Industry tracker — Peppol BIS 3.0 in SE. ======================================================================== # Norway · EHF + Peppol # Source: https://docs.get-flowie.com/compliance/no.html ======================================================================== --- title: "Norway — EHF, Peppol BIS & SAF-T" description: "Norway e-invoicing: EHF B2G universal since 2012, SAF-T reporting universal, Peppol BIS via DFØ. No B2B mandate yet — Skatteetaten consultation underway." canonical: "https://docs.get-flowie.com/compliance/no" source: "https://docs.get-flowie.com/compliance/no.html" --- # Norway — EHF, Peppol BIS & SAF-T Compliance · 🇳🇴 Norway Live mandate # Norway — EHF, Peppol BIS & SAF-T EHF/Peppol BIS B2G universal since 2012 · SAF-T universal — regulator: [Skatteetaten (Norwegian Tax Administration)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Norway has a **B2G mandate since 2012** via **EHF** (Elektronisk handelsformat — a Norwegian Peppol BIS profile). * **SAF-T reporting universal** on demand by Skatteetaten — every taxpayer must produce SAF-T NO XML when audited. * **No B2B mandate** ; Skatteetaten consultation underway (target 2027). * Norway is a full Peppol Authority via DFØ (Direktoratet for forvaltning og økonomistyring). ## Deadlines Date| Who| What ---|---|--- 2012-07-01| Central government| EHF mandatory for B2G suppliers. 2019-04-01| All public authorities| EHF/Peppol BIS universal. 2020-01-01| All taxpayers| SAF-T NO on-demand obligation. ≥ 2027| B2B mandate (consultation)| Skatteetaten reviewing options. ## Background Norway is, despite not being an EU member, one of the most Peppol-mature countries in Europe. EHF was the first widely-deployed Peppol BIS profile and remains the strategic format. DFØ runs the national Peppol authority; Skatteetaten the tax side. ## Format profile * **Peppol BIS 3.0 / EHF** ; no other format relevant. * **SAF-T NO** XML on tax-authority demand. * Norwegian org. number: 9 digits. ## Required fields * seller.orgNumberstringrequired Norwegian org. number (9 digits). ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **DFØ (national Peppol authority)**| `0192:NO-`| Norwegian public buyers identified by org. number via Peppol scheme `0192`. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- NO B2G happy path| Sender `0192:910000001`, recipient `0192:980000001`. ## FAQ ### Is EHF different from Peppol BIS? EHF 3.0 is structurally a Peppol BIS 3.0 profile with Norwegian extensions. Practically, you send _Peppol BIS_ ; Flowie selects the EHF subset when the recipient is Norwegian. ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Norway]() — Pan-EU reference factsheet. * [OpenPeppol · Norway profile]() — Authoritative Peppol facts. * [DFØ · Peppol Authority page]() — Norwegian Peppol Authority. * [Anskaffelser.dev · EHF Billing 3.0 spec]() — Official EHF national CIUS specification. * [ELMA · Norwegian SMP registry]() — Peppol SMP registry of receivers. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Logiq · Norway e-invoicing guide]() — Industry tracker — EHF / Peppol BIS. ======================================================================== # Iceland · Peppol-aligning # Source: https://docs.get-flowie.com/compliance/is.html ======================================================================== --- title: "Iceland — Peppol BIS B2G adoption" description: "Iceland e-invoicing: B2G adoption ramping via Peppol BIS, RSK (Iceland Revenue & Customs) tax reporting. No B2B mandate." canonical: "https://docs.get-flowie.com/compliance/is" source: "https://docs.get-flowie.com/compliance/is.html" --- # Iceland — Peppol BIS B2G adoption Compliance · 🇮🇸 Iceland Phased rollout # Iceland — Peppol BIS B2G adoption Peppol BIS B2G adoption · No B2B mandate yet — regulator: [Skatturinn (Iceland Revenue & Customs)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Iceland is an EEA / EFTA country aligning on EU e-invoicing standards. * B2G is **voluntary today** but rising; central government accepts Peppol BIS. * **No B2B mandate**. * Flowie is a registered Peppol AP for Iceland — or routed through a specialized local partner where in-country presence is required. ## Deadlines Date| Who| What ---|---|--- ≥ 2027| B2G mandate (planned)| Government has signalled alignment with the EU directive. ## Background Iceland's e-invoicing landscape is small (population ~400k) but increasingly Peppol-aligned. There is no formal mandate yet, but central government and large enterprises have begun receiving Peppol BIS as a matter of course. ## Format profile * **Peppol BIS 3.0**. * Icelandic kennitala (10-digit national ID, used for both individuals and companies). ## Required fields * seller.kennitalastring (10 digits)required Icelandic registry number. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **FJS (national Peppol gateway)**| `0196:IS-`| Icelandic public buyers identified by kennitala via Peppol scheme `0196`. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Iceland]() — Pan-EU reference factsheet. * [OpenPeppol · Iceland profile]() — Authoritative Peppol facts. * [FJS · Financial Management Authority]() — State Accounting Office — eInvoice technical requirements. * [Island.is · Fjársýslan procurement]() — State procurement portal. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Unimaze · Iceland e-invoicing]() — Industry tracker — TS-236 / Peppol use. ======================================================================== # Liechtenstein · Peppol # Source: https://docs.get-flowie.com/compliance/li.html ======================================================================== --- title: "Liechtenstein — Peppol BIS adoption (small market)" description: "Liechtenstein e-invoicing: small EEA market, mostly aligned with Switzerland operationally. Peppol BIS adoption via Swiss APs." canonical: "https://docs.get-flowie.com/compliance/li" source: "https://docs.get-flowie.com/compliance/li.html" --- # Liechtenstein — Peppol BIS adoption (small market) Compliance · 🇱🇮 Liechtenstein Voluntary # Liechtenstein — Peppol BIS adoption (small market) Peppol BIS available · No mandate · Small market — regulator: [Steuerverwaltung Liechtenstein](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Liechtenstein is an EEA member with the EU VAT framework but operates very small volumes. * **No e-invoicing mandate** ; Peppol BIS is accepted on a voluntary basis. * Most cross-border traffic flows through Swiss or Austrian APs given the customs-union arrangement. ## Deadlines _No live mandate dates today — pure voluntary regime._ ## Background Liechtenstein's market is too small to operate independent national infrastructure for e-invoicing. In practice, Peppol BIS works fine; Flowie's AP serves Liechtensteinish recipients directly. ## Format profile * **Peppol BIS 3.0**. * Liechtensteinish FL VAT: `CHE-NNN.NNN.NNN MWST` (shared registry with Switzerland). ## Required fields * seller.vatNumberstringrequired FL VAT shares the Swiss UID format. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Landesverwaltung**| `9930:LI-`| Public-sector recipients reachable via Peppol; very low volume. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ _Open questions? Email[compliance@flowie.fr]() — we answer within 24h._ ## References **Primary sources** (government / regulator / standards body): * [EU Commission · eInvoicing in Liechtenstein]() — Pan-EU reference factsheet. * [LLV · Public Procurement Department]() — National public procurement authority. * [LLV · Steuerverwaltung (Tax Administration)]() — Tax administration — VAT policy. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Sovos · Liechtenstein e-invoicing]() — Industry tracker — voluntary B2G framework. ======================================================================== # United Kingdom · MTD + Peppol NHS # Source: https://docs.get-flowie.com/compliance/uk.html ======================================================================== --- title: "United Kingdom — Making Tax Digital, NHS Peppol & e-invoicing consultation" description: "UK e-invoicing: Making Tax Digital VAT reporting universal, NHS Peppol mandate (B2G health), no general B2B mandate yet. HMRC consultation underway." canonical: "https://docs.get-flowie.com/compliance/uk" source: "https://docs.get-flowie.com/compliance/uk.html" --- # United Kingdom — Making Tax Digital, NHS Peppol & e-invoicing consultation Compliance · 🇬🇧 United Kingdom Phased rollout # United Kingdom — Making Tax Digital, NHS Peppol & e-invoicing consultation MTD VAT reporting universal · NHS Peppol B2G · No general B2B mandate — regulator: [HM Revenue & Customs (HMRC)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **Making Tax Digital (MTD)** for VAT is universal — every VAT-registered UK business submits quarterly VAT data via API. * **NHS Peppol mandate** (PEPPOL) requires healthcare suppliers to use Peppol BIS for NHS B2G since 2021. * **No general B2B e-invoicing mandate** ; HMRC consultation closed February 2025, results expected during 2026. * Cross-border: Peppol BIS via Flowie's UK AP. ## Deadlines Date| Who| What ---|---|--- 2019-04-01| All VAT-registered UK businesses| MTD for VAT launched. 2021-04-01| NHS suppliers| Peppol BIS mandatory for NHS England trading. ≥ 2027| General B2B mandate (under consultation)| HMRC reviewing — Italy-style or France-style framework not yet selected. ## Background The UK is in flux. MTD has digitised VAT _reporting_ for years, but the underlying invoice can still be paper. The 2025 HMRC consultation on full B2B e-invoicing closed in February with high response volume; the government is now sifting between a France-PDP-style decentralised model and an Italy-SDI-style central clearance model. A decision is expected during 2026. In the meantime, NHS Peppol (often just called PEPPOL within the NHS) is the most mature B2G regime — every supplier to NHS England must transact via Peppol BIS. ## Format profile * **Peppol BIS 3.0** for NHS B2G and cross-border. * **MTD VAT JSON** for HMRC quarterly returns (separate from invoice format). * UK VAT: `GB` \+ 9 digits. ## Required fields * seller.vatNumberstringrequired Format `GB123456789`. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **NHS Peppol (NHS England)**| `0088:GB-NHS-`| NHS providers identified by ODS code; all NHS suppliers must transact via Peppol. ## B2B reporting / clearance **HMRC MTD** — Quarterly VAT return via API; not invoice-level. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- UK NHS Peppol| Recipient `0088:GB-NHS-RR8`; sandbox returns 201. MTD return submission| `POST /v1/compliance/uk/mtd` with the VAT period. ## FAQ ### Will the UK adopt the EU's ViDA framework? Unclear. Post-Brexit, the UK is free to chart its own course; HMRC has been studying both EU and non-EU regimes (Australia's Peppol-by-default, Singapore's InvoiceNow). The 2026 decision will reveal which way they go. ## References **Primary sources** (government / regulator / standards body): * [GOV.UK · Promoting electronic invoicing consultation response]() — HMRC/DBT 2025 consultation response. * [GOV.UK · e-invoicing overhaul announcement]() — Official 2025 government announcement. * [HMRC · His Majesty's Revenue and Customs]() — Tax authority owning future mandate. * [OpenPeppol · England NHS profile]() — NHS Peppol Authority — only UK profile. **Industry analyses** (vendor trackers — useful for cross-referencing): * [ICAS · Autumn Budget 2025 e-invoicing]() — Industry analysis — April 2029 mandate. * [vatcalc · UK 2029 mandatory B2B e-invoicing]() — Industry tracker — UK 2029 timeline. ======================================================================== # Switzerland · Peppol BIS # Source: https://docs.get-flowie.com/compliance/ch.html ======================================================================== --- title: "Switzerland — Peppol BIS B2G ramp & Bundesverwaltung" description: "Switzerland e-invoicing: federal B2G ramp via Peppol BIS / Bundesverwaltung. No federal B2B mandate. Eidgenössische Steuerverwaltung (ESTV)." canonical: "https://docs.get-flowie.com/compliance/ch" source: "https://docs.get-flowie.com/compliance/ch.html" --- # Switzerland — Peppol BIS B2G ramp & Bundesverwaltung Compliance · 🇨🇭 Switzerland Phased rollout # Switzerland — Peppol BIS B2G ramp & Bundesverwaltung Federal B2G ramping · No B2B mandate · Peppol BIS — regulator: [Eidgenössische Steuerverwaltung (ESTV)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Switzerland is not an EU/EEA member. There's **no federal B2B mandate**. * Federal B2G is **ramping toward universal Peppol BIS receipt** — already true for most departments. * Some cantons run their own e-invoicing platforms; Flowie's AP routes correctly based on the recipient. * Cross-border to Switzerland from EU works fine over Peppol — Switzerland is a full Peppol participant. ## Deadlines Date| Who| What ---|---|--- 2016-01-01| Federal contracting > CHF 5k| B2G e-invoicing accepted (not yet mandatory). 2024-01-01| Federal contracting universal receipt| All federal departments accept Peppol BIS. No date| B2B mandate| Not on the agenda; market-led adoption only. ## Background Switzerland's approach is voluntary and market-led. The federal government accepts Peppol BIS but doesn't mandate it; cantons follow their own paths. Switzerland is, however, a full [Peppol]() participant via OpenPeppol membership, and Flowie operates a Swiss-registered AP that handles the routing nuances — including the legacy Bundesverwaltung gateway. ## Format profile * **Peppol BIS 3.0**. * Swiss UID: `CHE-NNN.NNN.NNN`. * Some cantons accept Swico-formatted XML for legacy reasons; rare. ## Required fields * seller.uidstringrequired Swiss UID. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Bundesverwaltung Peppol gateway**| `0183:CHE-`| Swiss federal departments registered as Peppol participants under scheme `0183`. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ ### Will Switzerland follow the EU's ViDA? Not formally — Switzerland charts its own course. Practically, alignment is high because Swiss businesses trade heavily with EU counterparts. ## References **Primary sources** (government / regulator / standards body): * [EFV · Receiving e-bills from the Confederation]() — Federal Finance Administration B2G portal. * [EFV · Submitting e-bills to the Confederation]() — Supplier guide for federal e-invoicing. * [EFV · List of administrative units]() — Registered federal e-bill recipients. **Industry analyses** (vendor trackers — useful for cross-referencing): * [ecosio · Switzerland compliance]() — Industry analysis — eCH-0069 / swissDIGIN. * [Basware · Switzerland compliance map]() — Industry compliance tracker. ======================================================================== # United Arab Emirates · FTA e-invoicing (Peppol 5-corner) # Source: https://docs.get-flowie.com/compliance/ae.html ======================================================================== --- title: "United Arab Emirates — FTA e-invoicing · Peppol 5-corner" description: "UAE e-invoicing: voluntary pilot from July 2026, mandatory from January 2027 for revenue of AED 50m or more, all other VAT-registered July 2027, government October 2027. PINT AE on UBL 2.1. B2C out of scope." canonical: "https://docs.get-flowie.com/compliance/ae" source: "https://docs.get-flowie.com/compliance/ae.html" --- # United Arab Emirates — FTA e-invoicing · Peppol 5-corner Compliance · 🇦🇪 United Arab Emirates Phased rollout # United Arab Emirates — FTA e-invoicing · Peppol 5-corner Peppol 5-corner model · voluntary pilot 1 July 2026 · first mandate 1 January 2027 — regulator: [Federal Tax Authority (FTA) / Ministry of Finance](). _Facts last refreshed: 2026-09-14._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * UAE has chosen the **Peppol 5-corner model** — sender AP, receiver AP, plus a real-time copy to the FTA's Data Reporting Platform (DRP). * Format is **PINT AE** — a UAE national CIUS on top of Peppol International (UBL 2.1). * **1 July 2026 is the voluntary pilot, not a mandate.** The first mandatory phase is **1 January 2027** , for taxpayers with annual revenue of **AED 50 m or more** ; all other VAT-registered businesses follow on 1 July 2027, and government entities on 1 October 2027. * Suppliers must contract an **Accredited Service Provider (ASP)** registered with the Ministry of Finance — Flowie is registered. Phase 1 taxpayers must appoint one by **30 October 2026** (extended from 31 July 2026); everyone else by 31 March 2027. * **B2C is out of scope.** The mandate covers B2B and B2G only — Article 4 of Ministerial Decision 243 of 2025 excludes B2C until a further decision by the Minister. Certain financial and airline services are also excluded. * Penalties bite hard: AED 2,500 per non-compliant invoice (first violation), AED 10,000 per failure to transmit through the DRP. ## Deadlines Date| Who| What ---|---|--- 2026-02-23| All taxpayers| MoF publishes _UAE Electronic Invoicing Guidelines v1.0_ \+ PINT AE technical spec. **2026-07-01**| Voluntary participants| Pilot phase opens — voluntary adoption only, no obligation attaches on this date. **2026-10-30**| Phase 1 taxpayers (revenue ≥ AED 50 m)| Deadline to appoint an Accredited Service Provider (extended from 2026-07-31). **2027-01-01**| Revenue ≥ AED 50 m| Phase 1: PINT AE issuance + DRP reporting mandatory. 2027-03-31| All other VAT-registered + government entities| Deadline to appoint an Accredited Service Provider. 2027-07-01| All other VAT-registered| Phase 2 — mandatory, including most free zone entities. 2027-10-01| Government entities| Phase 3 — mandatory. ## Background The UAE is the first MENA country to adopt the **Peppol 5-corner** model rather than a centralised clearance like KSA's Fatoora. The Federal Tax Authority and Ministry of Finance published the official _UAE Electronic Invoicing Guidelines v1.0_ in February 2026, locking in the technical and legal framework. How it works: the seller's ASP validates the invoice, converts it to **PINT AE** , transmits to the buyer's ASP over Peppol, and the FTA's **Data Reporting Platform (DRP)** receives a real-time copy as a fifth corner. **Mind the two dates.** 1 July 2026 opens a _voluntary pilot_ — selected and volunteering taxpayers test the system, and nothing is compulsory. The obligation itself starts **1 January 2027** for taxpayers with annual revenue of AED 50 m or more, extends to all other VAT-registered businesses on 1 July 2027, and reaches government entities on 1 October 2027. Ministerial Decisions 243 and 244 of 2025 set that scope and timeline. **Scope is B2B and B2G only.** B2C transactions sit outside the mandate under Article 4 of Ministerial Decision 243 of 2025 — excluded until the Minister issues a further decision — as do certain financial services and airline services. PINT AE adds UAE-specific fields on top of the Peppol International base — TRN (Tax Registration Number), HS codes for goods lines, currency + exchange rate, IRN (Invoice Reference Number). ## Format profile * **PINT AE** — UAE national CIUS on Peppol International Invoice (UBL 2.1). * Both seller and buyer **TRN** (15-digit Tax Registration Number) mandatory. * Goods lines: HS code mandatory; service lines exempted from HS. * Currency + exchange rate to AED required when invoicing in non-AED currency. * Cross-border invoices (export, free zone) follow the same PINT AE schema. ## Required fields * seller.trnstring (15 digits)required UAE Tax Registration Number — validated by the FTA. * buyer.trnstring (15 digits)required for B2B Buyer TRN; mandatory for any domestic B2B invoice. * lines[].hsCodestringrequired for goods HS code for tangible goods; not required for services. * invoice.irnstringrequired Unique Invoice Reference Number — issued by the seller's ASP. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **FTA Data Reporting Platform (DRP)** — Receives a real-time copy of every PINT AE invoice as the 5th corner. Not a clearance — invoice validity does not depend on DRP acknowledgement. Lifecycle status| Reported as ---|--- `acknowledged`| DRP received the invoice copy. `rejected`| Schema or business-rule violation; corrected document required. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- UAE happy path| Sender TRN `100000000000003`, recipient any UAE TRN-registered entity in Flowie sandbox. ## FAQ ### Do I need to appoint an ASP if I'm in a free zone? For Phase 2 (1 July 2027), yes — the mandate covers most free zone entities including DMCC, JAFZA, ADGM. Designated zones with goods-only operations may have a narrower scope; check the MoF guidelines. ### Is the DRP a clearance like Fatoora? No. The DRP receives a copy in real time but doesn't gate invoice validity. The buyer can still book the invoice if the DRP is offline; rejections are handled out-of-band. ### Do I have to be live on 1 July 2026? No. 1 July 2026 opens a _voluntary pilot_. The first binding date is 1 January 2027, and only for taxpayers with annual revenue of AED 50 m or more. What does fall in 2026 is the administrative step: Phase 1 taxpayers must have appointed an Accredited Service Provider by 30 October 2026 (the Ministry of Finance extended this from 31 July 2026). ### Are my sales to consumers in scope? No. The UAE mandate covers B2B and B2G only; B2C is excluded under Article 4 of Ministerial Decision 243 of 2025 until the Minister decides otherwise. Certain financial services and airline services are likewise excluded. Plan for B2C to be brought in later rather than never. ## References **Primary sources** (government / regulator / standards body): * [Ministry of Finance UAE · e-Invoicing]() — Official MoF e-invoicing portal. * [Federal Tax Authority]() — FTA — administers VAT and the Data Reporting Platform. * [OpenPeppol · UAE profile]() — PINT AE Peppol profile. * [MoF · Ministerial Decision 244 of 2025]() — Sets the phased timeline — pilot Jul 2026, mandate Jan 2027. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Avalara · UAE e-invoicing 2026 readiness]() — Industry analysis — ASP onboarding. * [KPMG · UAE technical guidance]() — Industry analysis — PINT AE fields. ======================================================================== # Australia · Peppol e-invoicing via the ATO # Source: https://docs.get-flowie.com/compliance/au.html ======================================================================== --- title: "Australia — Peppol e-invoicing via the ATO" description: "Australia e-invoicing: Peppol PINT A-NZ, ATO is the Peppol Authority. Federal B2G default by Dec 2026; B2B remains voluntary but strongly encouraged." canonical: "https://docs.get-flowie.com/compliance/au" source: "https://docs.get-flowie.com/compliance/au.html" --- # Australia — Peppol e-invoicing via the ATO Compliance · 🇦🇺 Australia Phased rollout # Australia — Peppol e-invoicing via the ATO Peppol PINT A-NZ · federal default by Dec 2026 · ATO Peppol Authority — regulator: [Australian Taxation Office (ATO)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * The ATO is Australia's **Peppol Authority** (since 31 Oct 2019); all federal NCEs (Non-Corporate Commonwealth Entities) accept Peppol e-invoicing. * Format: **PINT A-NZ** — joint Peppol CIUS with New Zealand (replaced the legacy A-NZ BIS extension on 15 May 2025). * Federal procurement target: **30% Peppol invoices by 1 Jul 2026** , automated send-and-receive across NCEs by **December 2026**. * **No B2B mandate** ; adoption is voluntary but strongly encouraged via federal procurement preference and 5-day payment terms. * Flowie operates an Australian-registered Peppol AP (`0151:`) — directly, or via a specialized local partner where in-country presence is required. ## Deadlines Date| Who| What ---|---|--- **2019-10-31**| ATO becomes Australian Peppol Authority| Joins OpenPeppol. **2022-07-01**| All federal NCEs| Mandatory Peppol receipt capability for B2G. **2025-05-15**| All Peppol senders| Migration to PINT A-NZ; legacy A-NZ BIS deprecated. **2026-07-01**| Federal NCEs| 30% of received invoices via Peppol target. **2026-12-31**| Federal NCEs| Automated Peppol send + receive default. ## Background Australia adopted Peppol in 2019 as a deliberate procurement-modernisation move and made the ATO the national Peppol Authority. Federal Non-Corporate Commonwealth Entities (NCEs) had to be Peppol-receive-capable by July 2022; the 2024–25 Federal Budget commits them to Peppol-default exchange by December 2026, with an interim 30% receipt target on 1 July 2026. There is **no B2B mandate** in the current federal plan, but the government nudges adoption via federal procurement preferences (faster onboarding, shorter payment terms) and via the Strategic Reform Agenda. Adoption is strongest in B2G, large enterprise, and accounting-software-led SMB segments (Xero, MYOB, QuickBooks all support Peppol natively). Format is **PINT A-NZ** , a joint Australia–New Zealand CIUS on Peppol International Invoice. The legacy A-NZ Peppol BIS 3.0 extension was deprecated on 15 May 2025. ## Format profile * **PINT A-NZ** on UBL 2.1 — joint AU–NZ CIUS. * Seller and buyer **ABN** (Australian Business Number, 11 digits) used as Peppol participant ID under scheme `0151`. * GST breakdown required; rounding rules per ATO. * B2G: BuyerReference must equal the agency-issued purchase order or contract reference. ## Required fields * seller.abnstring (11 digits)required Australian Business Number; used as `0151` Peppol participant ID. * seller.gstRegisteredbooleanrequired If true, GST lines must show ATO-compliant tax codes. * buyerReferencestringrequired for B2G Agency-issued purchase order / contract reference; without it, B2G recipients reject. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Federal NCEs via Peppol**| `0151:`| All federal Non-Corporate Commonwealth Entities are Peppol-reachable via their ABN. State and local government adoption varies — Flowie's directory tracks coverage. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Australia happy path| Sender ABN `53004085616`, recipient any ABN in Flowie sandbox. ## FAQ ### Is there real-time tax-authority reporting? No — Australia has not adopted a 5-corner CTC model. The ATO receives no real-time copy of B2B invoices; Peppol delivery is purely 4-corner. ## References **Primary sources** (government / regulator / standards body): * [ATO · eInvoicing]() — Australian Peppol Authority landing page. * [ATO · Peppol service provider register]() — Accredited APs in Australia. * [OpenPeppol · Australia profile]() — PINT A-NZ Peppol profile. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Avalara · Australia 2026 deadlines]() — Industry analysis — federal procurement default. ======================================================================== # New Zealand · Peppol e-invoicing via MBIE # Source: https://docs.get-flowie.com/compliance/nz.html ======================================================================== --- title: "New Zealand — Peppol e-invoicing via MBIE" description: "New Zealand e-invoicing: Peppol PINT A-NZ, MBIE is the Peppol Authority. Government agencies handling >2,000 invoices/year must send and receive Peppol from 1 Jan 2026." canonical: "https://docs.get-flowie.com/compliance/nz" source: "https://docs.get-flowie.com/compliance/nz.html" --- # New Zealand — Peppol e-invoicing via MBIE Compliance · 🇳🇿 New Zealand Phased rollout # New Zealand — Peppol e-invoicing via MBIE Peppol PINT A-NZ · MBIE Peppol Authority · NZ$33m supplier mandate Jan 2027 — regulator: [Ministry of Business, Innovation and Employment (MBIE)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * MBIE is New Zealand's **Peppol Authority** ; central-government agencies have had to _receive_ Peppol e-invoices since March 2022. * From **1 Jan 2026** , agencies handling > 2,000 domestic trade invoices/year must also _send_ Peppol e-invoices. * From **1 Jan 2027** , large suppliers (revenue > **NZ$33 m** in each of the previous two years) must invoice government via Peppol. * Format: **PINT A-NZ** (joint AU–NZ CIUS, mandatory since 15 May 2025; legacy A-NZ BIS deprecated). * Mandated agencies must pay 95% of Peppol invoices within 5 business days — strong commercial incentive. ## Deadlines Date| Who| What ---|---|--- **2022-03-31**| Central government agencies| Mandatory to receive Peppol e-invoices. **2025-05-15**| All Peppol senders| Migration to PINT A-NZ; legacy A-NZ BIS deprecated. **2026-01-01**| Agencies handling > 2,000 invoices/yr| Must also send Peppol e-invoices; pay 95% within 5 business days. **2027-01-01**| Suppliers with revenue > NZ$33 m (last 2 yrs)| Must invoice government via Peppol. ## Background New Zealand and Australia jointly run Peppol in the region — same CIUS (PINT A-NZ), aligned timelines, single trans-Tasman registry. MBIE is the NZ Peppol Authority and accredits Access Points for the New Zealand domain. Phase 1 (2022) made central-government agencies _receive-capable_. Phase 2 (1 January 2026) makes the larger agencies _send-capable_ and obliges them to pay Peppol invoices in 5 business days — a strong commercial pull factor for suppliers. Phase 3 (1 January 2027) flips the obligation onto large suppliers: revenue > NZ$33 m for two consecutive years means you _must_ bill central government over Peppol. There is **no B2B mandate** and no central CTC. Peppol delivery is pure 4-corner; MBIE does not receive a copy. Since May 2025, sending invoices in the legacy A-NZ Peppol BIS 3.0 has been removed — only PINT A-NZ is accepted on the network. ## Format profile * **PINT A-NZ** on UBL 2.1 — joint AU–NZ CIUS. * Seller and buyer **NZBN** (New Zealand Business Number, 13 digits) used as Peppol participant ID under scheme `0088`. * GST registration number required for GST-registered sellers (8 digits). * Government B2G uses agency-issued purchase order in `BuyerReference`. ## Required fields * seller.nzbnstring (13 digits)required NZ Business Number; used as Peppol participant ID. * seller.gstNumberstring (8 digits)required for GST-registered NZ GST number — required to claim GST on the invoice. * buyerReferencestringrequired for B2G Agency-issued purchase order; required to qualify for the 5-day payment SLA. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Central government agencies via Peppol**| `0088:`| Mandated agencies are listed on the MBIE eInvoicing register. Agencies handling > 2,000 invoices/yr must pay 95% of Peppol invoices within 5 business days. ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- New Zealand happy path| Sender NZBN `9429000000000`, recipient any NZBN in Flowie sandbox. ## FAQ ### Is the legacy A-NZ Peppol BIS still accepted? No — sending in legacy A-NZ BIS was removed on 15 May 2025. PINT A-NZ is the only supported specification on the network. ## References **Primary sources** (government / regulator / standards body): * [MBIE · eInvoicing]() — New Zealand Peppol Authority. * [OpenPeppol · New Zealand profile]() — PINT A-NZ Peppol profile. * [Inland Revenue NZ]() — Tax authority. **Industry analyses** (vendor trackers — useful for cross-referencing): * [vatcalc · New Zealand B2G boost]() — Industry analysis — 2026/2027 mandate. ======================================================================== # China · fully digitalized e-fapiao (Golden Tax IV) # Source: https://docs.get-flowie.com/compliance/cn.html ======================================================================== --- title: "China — Fully Digitalized e-fapiao · Golden Tax IV" description: "China e-invoicing: Fully digitalized e-fapiao universal under Golden Tax IV since 2024-2025; new VAT Law in force from 1 Jan 2026 codifies the regime." canonical: "https://docs.get-flowie.com/compliance/cn" source: "https://docs.get-flowie.com/compliance/cn.html" --- # China — Fully Digitalized e-fapiao · Golden Tax IV Compliance · 🇨🇳 China Live mandate # China — Fully Digitalized e-fapiao · Golden Tax IV Fully digital e-fapiao · Golden Tax IV nationwide · new VAT Law 2026 — regulator: [State Taxation Administration (STA)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * China replaced paper and legacy e-fapiao with the **Fully Digitalized Electronic Fapiao** through 2021–2024 pilots, reaching nationwide rollout by 2024–2025. * Underpinning system: **Golden Tax Phase IV** — AI/big-data-driven tax monitoring connected to the People's Bank, SAMR, and other ministries. * From **1 January 2026** , supporting regulations for the new VAT Law take effect — codifying e-fapiao as the standard invoice format. * No QR-code clearance per se: every fapiao is issued _through_ the STA platform — there is no off-platform legal invoice. * Format: STA-mandated XML/JSON; tightly coupled to seller registration on the STA platform. ## Deadlines Date| Who| What ---|---|--- **2021-12-01**| Pilot — 5 provinces| Fully digital e-fapiao introduced. **2022-2024**| Geographical rollout| Pilot extends across all provinces. **2024-12-01**| All taxpayers (general + small-scale)| Permitted nationwide; paper and earlier electronic formats progressively phased out. **2026-01-01**| All VAT-registered| New VAT Law supporting regulations in force; e-fapiao codified. ## Background China's tax administration has digitalised in waves. The legacy _VAT special invoice_ required dedicated tax-control hardware (UKey or USB token) and printed paper output. **Fully Digitalized e-fapiao** , piloted from December 2021 in Shanghai, Guangdong, and Inner Mongolia, replaces both. From December 2024 the STA permits every taxpayer — general and small-scale — to issue fully digital e-fapiao, and through 2025 paper and legacy electronic formats are progressively phased out. Underneath sits **Golden Tax Phase IV** — the STA's AI/big-data infrastructure that cross-references invoice data with bank flows, business registry data (SAMR), customs data, and more, in near real time. On 1 January 2026, supporting regulations for the new _People's Republic of China VAT Law_ take effect, codifying e-fapiao as the standard invoice. Practically, since every fapiao is issued _through_ the STA platform, there is no concept of an off-platform legal invoice — the platform itself is the issuance system, not just a clearing layer. ## Format profile * **STA-mandated XML or JSON** (depending on issuance channel — STA portal vs API). * Two flavours: _VAT special invoice_ (B2B with input-tax credit) and _VAT ordinary invoice_ (B2C and exempt). * Seller's **USCC** (Unified Social Credit Code, 18 characters) mandatory. * No more UKey/Tax UKey for fully digital path; signing handled server-side by the STA platform. ## Required fields * seller.usccstring (18 chars)required Unified Social Credit Code. * buyer.usccstring (18 chars)required for B2B Buyer USCC; required to issue a VAT special invoice. * invoice.fapiaoTypestringrequired `VAT_SPECIAL` (B2B with credit) or `VAT_ORDINARY` (B2C / exempt). ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **STA Golden Tax IV** — Issuance + immediate reporting; Golden Tax IV cross-references with Big Data sources in near-real-time. Lifecycle status| Reported as ---|--- `issued`| Fapiao issued through the STA platform; immediately legally valid. `voided`| Voided within the platform's allowed window. `red`| Red-letter fapiao (offsetting / correction) issued. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- China happy path| Sender USCC `91110000XXXXXXXXXX`, recipient USCC in Flowie sandbox; fapiao number echoed back. ## FAQ ### Is Peppol used in China? No. China operates a fully national stack and is unlikely to adopt Peppol. Cross-border to/from China typically combines a fapiao for the China leg and a separate commercial invoice for the foreign leg. ## References **Primary sources** (government / regulator / standards body): * [State Taxation Administration (English)]() — STA — operates Golden Tax IV. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · China e-fapiao]() — Industry analysis — fully digital e-fapiao. * [Sovos · China Golden Tax IV]() — Industry tracker. * [China Briefing · Golden Tax IV explainer]() — Industry analysis — AI/big-data tax monitoring. ======================================================================== # Egypt · ETA e-invoicing & e-receipt clearance # Source: https://docs.get-flowie.com/compliance/eg.html ======================================================================== --- title: "Egypt — ETA e-invoicing & e-receipt clearance" description: "Egypt e-invoicing: ETA mandatory clearance for B2B/B2G live since 2021, e-receipt for B2C. 2025-26 reforms lower the registration threshold to EGP 250k." canonical: "https://docs.get-flowie.com/compliance/eg" source: "https://docs.get-flowie.com/compliance/eg.html" --- # Egypt — ETA e-invoicing & e-receipt clearance Compliance · 🇪🇬 Egypt Live mandate # Egypt — ETA e-invoicing & e-receipt clearance ETA clearance live for B2B/B2G · e-receipt expanding for B2C — regulator: [Egyptian Tax Authority (ETA)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Egypt's **e-invoicing** portal handles B2B and B2G real-time clearance; **e-receipt** is the parallel B2C channel. * Clearance has been universal for VAT-registered entities since the 2021–2023 wave rollout; the 2026 reform pulls in smaller taxpayers via a lowered **EGP 250,000** revenue threshold (down from EGP 500k). * Format: JSON or XML against the ETA schema; mandatory **HSM-based digital signature** (USB token or hardware module) for B2B/B2G. * B2C e-receipts must carry an ETA-validated **QR code** from 2026. * Heavy penalties: EGP 20,000 + EGP 1,000/day for non-registration; tiered fines up to EGP 10,000 per invoice for late reporting. ## Deadlines Date| Who| What ---|---|--- **2020-11-15**| Pilot — 134 large taxpayers| Phase 0 e-invoicing live. **2021-2023**| Waves 1–9| All VAT-registered companies onboarded by April 2023. **2022-2024**| B2C e-receipt waves| Mandatory B2C e-receipt rolled out by sector and turnover. **2026-03-31**| All taxpayers ≥ EGP 250k revenue| Resolution 281 of 2025: registration deadline at the lowered threshold. **2026**| All B2C| Every printed e-receipt must display an ETA-validated QR code. ## Background Egypt's Tax Authority (ETA) launched its e-invoicing programme in late 2020 and reached universal B2B/B2G coverage by April 2023. A separate _e-receipt_ system covers B2C, expanding wave by wave. The mandate is a hard **clearance** : an invoice not validated by the ETA portal is not legally valid for VAT purposes. Sellers post JSON or XML to the ETA API, sign with an HSM-issued certificate, and receive a UUID + acknowledgement. The buyer can verify the invoice via QR code on a public ETA endpoint. Resolution 281 of 2025 (effective in 2026) lowered the registration threshold from EGP 500,000 to **EGP 250,000** annual revenue and introduced a three-tier penalty regime that escalates from a warning flag to suspension of issuance ability. ## Format profile * **JSON or XML** per the ETA schema (versioned; current production version is v1.0+). * Seller TIN + buyer TIN required for B2B; B2C uses anonymised buyer block. * **HSM-based digital signature** mandatory for B2B/B2G (USB token or HSM). * Goods classification uses **GS1/EGS** codes; service lines use **EGS service codes**. * B2C e-receipt: simpler schema, near-real-time submission (within minutes of issuance). ## Required fields * seller.tinstring (9 digits)required Egyptian TIN. * lines[].itemCodestringrequired GS1 GTIN or EGS code per the ETA classification. * invoice.signatureobjectrequired for B2B/B2G PKCS#7 / CAdES envelope produced by the seller's HSM token. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **ETA e-invoicing portal** — Real-time clearance for B2B/B2G; near-real-time reporting for B2C via the e-receipt channel. Lifecycle status| Reported as ---|--- `submitted`| Invoice posted to ETA — pending validation. `valid`| Cleared; UUID returned; invoice may be delivered to buyer. `invalid`| Validation failed; correct and resubmit. `cancelled`| Storno acknowledged within the cancellation window. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Egypt B2B happy path| Sender TIN `123456789`, recipient any ETA-registered TIN in Flowie sandbox; UUID echoed back. ## FAQ ### Do I need an HSM token to issue e-invoices in Egypt? For B2B/B2G yes — the signature must be produced on a hardware token or HSM bound to the seller's TIN. Flowie offers a managed HSM service for staging; production requires the seller's own ETA-approved certificate. ## References **Primary sources** (government / regulator / standards body): * [Egyptian Tax Authority (English)]() — ETA — tax authority. * [ETA · eInvoicing portal]() — Production e-invoicing portal. * [ETA · eInvoicing & eReceipt SDK]() — Technical integration documentation. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Egypt e-invoicing]() — Industry tracker — wave timeline. * [Pagero · Egypt compliance]() — Industry tracker. ======================================================================== # Israel · ITA real-time clearance (allocation numbers) # Source: https://docs.get-flowie.com/compliance/il.html ======================================================================== --- title: "Israel — ITA real-time clearance · allocation numbers" description: "Israel e-invoicing: ITA real-time clearance via SHAAM platform. Threshold drops to NIS 10,000 from 1 Jan 2026 and NIS 5,000 from 1 Jun 2026 — effectively all VAT B2B." canonical: "https://docs.get-flowie.com/compliance/il" source: "https://docs.get-flowie.com/compliance/il.html" --- # Israel — ITA real-time clearance · allocation numbers Compliance · 🇮🇱 Israel Live mandate # Israel — ITA real-time clearance · allocation numbers ITA allocation-number clearance · accelerated 2026 thresholds — regulator: [Israel Tax Authority](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Israel runs a **clearance CTC model** : every B2B invoice above the threshold must be cleared by the Israel Tax Authority (ITA) before delivery. * Clearance returns an **allocation number** — without it the buyer _cannot deduct input VAT_. * Threshold timeline (accelerated in late 2025): NIS 25,000 (May 2024) → NIS 20,000 (Jan 2025) → **NIS 10,000 (Jan 2026)** → **NIS 5,000 (Jun 2026)**. * Format is JSON over the ITA's **SHAAM** API; no Peppol involvement. * Flowie obtains the allocation number synchronously and stamps it onto the invoice you deliver to the buyer. ## Deadlines Date| Who| What ---|---|--- **2024-05-05**| Invoices ≥ NIS 25,000| Clearance live — voluntary trial period ended. **2025-01-01**| Invoices ≥ NIS 20,000| Threshold tightened. **2026-01-01**| Invoices ≥ NIS 10,000| Accelerated by ITA in December 2025. **2026-06-01**| Invoices ≥ NIS 5,000| Final threshold — originally planned for 2028, brought forward. ## Background Israel introduced a centralised clearance regime in May 2024 to combat VAT fraud (estimated at NIS 6 bn/year). The mechanism is simple but unforgiving: before a B2B invoice can be issued above the threshold, the seller submits the invoice in JSON to the ITA's SHAAM platform, which validates it in real time and returns an **allocation number**. Only an invoice carrying a valid allocation number lets the buyer claim input VAT — so commercially, no allocation = no payment. The original plan staged the threshold reduction over four years (down to NIS 5,000 by 2028). In December 2025, the ITA _accelerated_ the schedule: NIS 10,000 from January 2026, NIS 5,000 from June 2026. By mid-2026, effectively every B2B invoice in Israel goes through clearance. Flowie integrates with SHAAM directly. The `/v1/documents/send` call returns the allocation number on the response, and the printable PDF carries it under the seller TIN. ## Format profile * **JSON** per the ITA technical specification (no UBL). * Seller and buyer **TIN** (9-digit identifier, sometimes called Osek) mandatory. * Threshold compares the invoice _net of VAT_ — invoices below the threshold are out of scope of clearance. * Cleared invoice carries the allocation number in a dedicated field; the buyer's accounting system uses it to claim input VAT. ## Required fields * seller.tinstring (9 digits)required Israeli TIN / Osek number. * buyer.tinstring (9 digits)required for B2B Buyer TIN; without it the invoice cannot clear. * invoice.allocationNumberstringreturned by ITA Allocation number echoed back on clearance; must be embedded in the invoice delivered to the buyer. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **Israel Tax Authority — SHAAM** — Real-time clearance of every B2B invoice above the threshold. Invoice not deductible by the buyer until allocation number is issued. Lifecycle status| Reported as ---|--- `cleared`| Allocation number returned; invoice may be delivered to buyer. `rejected`| Validation failed; correct and resubmit. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Israel happy path| Sender TIN `123456789`, invoice net > NIS 5,000 in Flowie sandbox; allocation number `SBX-IL-...` returned. ## FAQ ### What about invoices below the threshold? They don't go through clearance and don't carry an allocation number. The buyer can still deduct input VAT under standard rules. But once you cross the threshold (NIS 5,000 from June 2026), clearance becomes mandatory. ## References **Primary sources** (government / regulator / standards body): * [Israel Tax Authority (English)]() — ITA — tax authority owning the CTC mandate. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Sovos · Israel CTC accelerated timeline (Dec 2025)]() — Industry analysis — accelerated 2026 thresholds. * [Pagero · Israel compliance]() — Industry tracker. * [EDICOM · Israel CTC clearance model]() — Industry analysis — SHAAM / allocation numbers. ======================================================================== # India · GST e-invoicing (IRP & IRN) # Source: https://docs.get-flowie.com/compliance/in.html ======================================================================== --- title: "India — GST e-invoicing · IRP & IRN" description: "India e-invoicing: GST IRP-issued IRN required for every B2B invoice from taxpayers with turnover > ₹5 crore. 30-day reporting deadline applies to ≥ ₹10 crore." canonical: "https://docs.get-flowie.com/compliance/in" source: "https://docs.get-flowie.com/compliance/in.html" --- # India — GST e-invoicing · IRP & IRN Compliance · 🇮🇳 India Live mandate # India — GST e-invoicing · IRP & IRN Mandatory IRN issuance via GST IRP · ₹5 crore threshold — regulator: [GST Network (GSTN) / Central Board of Indirect Taxes (CBIC)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Every B2B invoice from a taxpayer above the threshold must be sent to an **Invoice Registration Portal (IRP)** , which returns an **IRN** (Invoice Reference Number) + signed QR code. * Threshold has stepped down repeatedly: ₹500 cr (2020) → ₹100 cr → ₹50 cr → ₹20 cr → ₹10 cr → **₹5 cr (since 1 Aug 2023)** — unchanged for 2026. * From **April 2025** , taxpayers ≥ ₹10 cr must report invoices to the IRP within **30 days** of issuance — late = invoice rejected. * Format: JSON to the IRP API; the IRP signs and returns; you embed the QR + IRN on the printed invoice. * Multiple IRPs operate (NIC IRP1/IRP2, IRIS, ClearTax, etc.) — Flowie selects automatically. ## Deadlines Date| Who| What ---|---|--- **2020-10-01**| Turnover > ₹500 cr| Phase 1 — IRN mandatory. **2021–2022**| ₹100 cr → ₹50 cr → ₹20 cr| Phased threshold reductions. **2023-08-01**| Turnover > ₹5 cr| Current universal threshold. **2025-04-01**| Turnover ≥ ₹10 cr| 30-day reporting deadline enforced — late submissions rejected by IRP. ## Background India's e-invoicing regime sits inside the Goods and Services Tax (GST) framework operated by GSTN. It's a **clearance + reporting** hybrid: the seller produces the invoice in their billing system, posts the JSON to an IRP, receives an **Invoice Reference Number (IRN)** \+ digitally signed QR code, and embeds them on the printed invoice. The IRP also auto-populates GSTR-1 (sales return) and the e-Way Bill system, eliminating duplicate data entry. Coverage has expanded by lowering the turnover threshold: starting at ₹500 crore in October 2020 and now sitting at ₹5 crore since August 2023. As of 2026, the threshold remains at ₹5 crore. A separate _30-day reporting deadline_ applies to taxpayers ≥ ₹10 crore — invoices not reported within 30 days of issue are rejected by the IRP and considered invalid for GST. There are multiple IRPs (NIC operates two; IRIS, ClearTax, ENS Portal, EY-Cygnet, and others run private ones). Flowie load-balances between them and falls back automatically on outages — a real concern given India's invoice volume. ## Format profile * **JSON** per the GST e-invoice schema (currently v1.1). * Seller and buyer **GSTIN** (15-character GST identifier) mandatory for B2B. * **HSN codes** required (4 digits if turnover < ₹5 cr; 6 digits otherwise). * IRP returns: IRN (64-char hash), digitally signed QR, signed invoice (JWS). * Out of scope: B2C, sales by non-GST-registered, financial services exempt entities. ## Required fields * seller.gstinstring (15 chars)required Indian GST Identification Number. * buyer.gstinstring (15 chars)required for B2B Buyer GSTIN. * lines[].hsnCodestring (4 or 6 digits)required HSN code; minimum digit count depends on seller turnover. * invoice.placeOfSupplystring (2-digit state code)required Determines IGST vs CGST+SGST split. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **GST Invoice Registration Portal (IRP)** — Real-time clearance + auto-population of GSTR-1 and e-Way Bill. IRN is the legal proof of GST invoice. Lifecycle status| Reported as ---|--- `registered`| IRN issued; invoice is GST-valid. `rejected`| Validation failed (duplicate, GSTIN mismatch, missing HSN, late beyond 30 days). `cancelled`| Cancellation accepted within 24h of registration. ## Error codes Code| Meaning| Fix ---|---|--- `2150`| Duplicate IRN — invoice already registered.| Check whether a previous attempt succeeded; retry only if confirmed not registered. `2172`| Document date is older than 30 days.| Applies to taxpayers ≥ ₹10 cr — invoice must be registered within 30 days of issue. `2189`| Buyer GSTIN inactive or cancelled.| Verify GSTIN with the public GSTN search before invoicing. ## Testing in sandbox What you want to test| How ---|--- India happy path| Sender GSTIN `27AAACG0527D1ZK`, recipient any active GSTIN in Flowie sandbox; IRN echoed back. ## FAQ ### Does e-invoicing replace GSTR-1? No, but it auto-populates GSTR-1 from registered invoices, drastically reducing manual entry. You still file GSTR-1, GSTR-3B, etc. ## References **Primary sources** (government / regulator / standards body): * [GST Network (GSTN)]() — GST portal — operates IRP. * [GST e-invoice portal (NIC IRP)]() — NIC-operated Invoice Registration Portal. * [Central Board of Indirect Taxes (CBIC)]() — CBIC — issues notifications setting thresholds. **Industry analyses** (vendor trackers — useful for cross-referencing): * [ClearTax · India e-invoicing]() — Industry guide — thresholds + 30-day rule. ======================================================================== # Japan · JP PINT (Peppol via the Digital Agency) # Source: https://docs.get-flowie.com/compliance/jp.html ======================================================================== --- title: "Japan — JP PINT · Peppol via Digital Agency" description: "Japan e-invoicing: Peppol JP PINT, Digital Agency is the Peppol Authority. Voluntary alongside the Qualified Invoice (Invoice Retention) System; 50% input tax credit cap from Oct 2026." canonical: "https://docs.get-flowie.com/compliance/jp" source: "https://docs.get-flowie.com/compliance/jp.html" --- # Japan — JP PINT · Peppol via Digital Agency Compliance · 🇯🇵 Japan Voluntary # Japan — JP PINT · Peppol via Digital Agency Peppol JP PINT · voluntary network on top of Qualified Invoice System — regulator: [Digital Agency / National Tax Agency (NTA)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **Qualified Invoice System** is mandatory since 1 October 2023 — to claim consumption-tax input credit, the seller must be a registered Qualified Invoice Issuer. * Peppol **JP PINT** is the Digital Agency's recommended _format_ for exchanging qualified invoices electronically — but using Peppol itself is voluntary. * From **1 Oct 2026** , input-tax credit on invoices from non-qualified issuers drops from 80% to **50%** (further drop to 0% by Oct 2029). * Digital Agency = Japan Peppol Authority (since 2022); the National Tax Agency (NTA) owns the Qualified Invoice rules. * Invoice numbers must include the seller's **13-digit registration number** prefixed with `T`. ## Deadlines Date| Who| What ---|---|--- **2022-09**| Digital Agency joins OpenPeppol| Japan Peppol Authority established. **2023-10-01**| All taxable persons| Qualified Invoice System mandatory; T-prefixed registration numbers required. **2026-10-01**| All taxable persons| Transition: input-tax credit on non-qualified invoices drops to 50%. **2029-10-01**| All taxable persons| Final transition: input-tax credit on non-qualified invoices drops to 0%. ## Background Japan operates two parallel layers. The mandatory layer is the **Qualified Invoice (Retention) System** — operated by the National Tax Agency, in force since 1 October 2023. To claim consumption-tax input credit, the seller must be a registered Qualified Invoice Issuer with a **T-prefixed 13-digit registration number** ; the invoice (paper or electronic) must carry that number plus per-rate tax breakdowns. The voluntary layer is **Peppol JP PINT** , run by the Digital Agency as Japan Peppol Authority. JP PINT is a Japanese CIUS on Peppol International Invoice — the recommended electronic format for exchanging qualified invoices. There's no obligation to use Peppol; many Japanese taxpayers exchange qualified invoices as PDFs by email. But adoption is rising as ERPs add support, and JP PINT is the path forward. The transition has teeth via _tax economics_ : from 1 October 2026 the input-tax credit on invoices from non-qualified issuers drops from 80% to 50%; on 1 October 2029 it drops to 0%. So practically, every B2B seller must either become a Qualified Invoice Issuer or accept losing customers. ## Format profile * **JP PINT** — Japanese CIUS on Peppol International Invoice (UBL 2.1). * Seller's **Qualified Invoice Issuer registration number** (T + 13 digits) mandatory. * Per-rate consumption-tax breakdown (10%, reduced 8%, exempt) required. * Buyer's name and address required; full TIN/TRN not always required for B2C. ## Required fields * seller.qualifiedInvoiceNumberstring (T + 13 digits)required Qualified Invoice Issuer registration number; without it the invoice does not entitle the buyer to input-tax credit. * seller.corporateNumberstring (13 digits)required Japanese Corporate Number; used as Peppol participant ID under scheme `0188`. * lines[].taxRatestringrequired `10%`, `8%` (reduced), or `exempt`. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Japan happy path| Sender corporate number `1234567890123`, qualified invoice number `T1234567890123`, recipient any JP corporate in Flowie sandbox. ## FAQ ### Do I have to use Peppol in Japan? No. The Qualified Invoice System accepts paper, PDF, and any electronic format the parties agree on. Peppol JP PINT is the recommended electronic format and is rapidly becoming the default in B2B ERP integrations. ## References **Primary sources** (government / regulator / standards body): * [Digital Agency · JP PINT]() — Japan Peppol Authority. * [National Tax Agency · Qualified Invoice System]() — NTA — Invoice Retention System. * [OpenPeppol · Japan profile]() — JP PINT Peppol profile. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Japan Qualified Invoice + Peppol]() — Industry tracker. ======================================================================== # South Korea · NTS e-Tax invoice system # Source: https://docs.get-flowie.com/compliance/kr.html ======================================================================== --- title: "South Korea — NTS e-Tax invoice system" description: "South Korea e-invoicing: NTS e-Tax invoice mandatory for all corporations and individuals with KRW 80m+ turnover. One of the world's earliest CTC regimes (since 2011)." canonical: "https://docs.get-flowie.com/compliance/kr" source: "https://docs.get-flowie.com/compliance/kr.html" --- # South Korea — NTS e-Tax invoice system Compliance · 🇰🇷 South Korea Live mandate # South Korea — NTS e-Tax invoice system NTS e-Tax invoice · universal corporate clearance since 2011 — regulator: [National Tax Service (NTS)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Korea introduced its **e-Tax invoice** regime in 2011 — one of the earliest national CTC mandates anywhere. * Mandatory for **all corporations** ; sole proprietors are absorbed by descending revenue threshold (KRW 300m → KRW 100m → **KRW 80m since Jul 2024**). * Invoices must be issued, signed, and reported to NTS via the **HomeTax** portal within **1 day of issuance**. * Format: NTS-mandated XML; HSM-issued certificate signs each invoice. * No Peppol, no overlapping mandate. Stable — no announced changes for 2026. ## Deadlines Date| Who| What ---|---|--- **2011-01-01**| All corporations| e-Tax invoice mandatory. **2014-07-01**| Sole proprietors > KRW 1 bn turnover| Threshold rolled out. **2019-2023**| Threshold steps down: KRW 300m → 100m| Sole proprietors absorbed. **2024-07-01**| Sole proprietors > KRW 80m turnover| Current threshold — unchanged for 2026. ## Background South Korea's e-Tax invoice system, launched in 2011, is one of the earliest and most comprehensive national CTC programmes in the world. Operated by the National Tax Service (NTS) via the **HomeTax** portal, the regime requires every corporation — and any sole proprietor with the previous-year turnover above KRW 80 million — to issue invoices in the NTS-mandated XML schema, sign them with an HSM-issued tax-purpose certificate, and transmit to NTS within **1 day of issuance**. Late or non-issuance attracts penalties (typically 1% of the invoice amount). Korea was originally proud that e-Tax pre-dated SAF-T and continuous-control regimes elsewhere; the system has been stable for over a decade with the only ongoing change being progressive lowering of the sole-proprietor threshold (most recently to KRW 80m in July 2024). There is no Peppol involvement, no plan to migrate, and no announced changes for 2026. ## Format profile * **NTS XML schema** (national, not UBL). * Seller and buyer **BRN** (Business Registration Number, 10 digits) mandatory. * **Tax-purpose certificate** issued by KISA-accredited CA required to sign each invoice. * Issuance + transmission must complete within 1 day of the invoice issue date; subsequent days incur penalties. ## Required fields * seller.brnstring (10 digits)required Korean Business Registration Number — formatted as `NNN-NN-NNNNN`. * buyer.brnstring (10 digits)required for B2B Buyer BRN. * seller.taxCertificatePKCS#12required KISA-accredited tax-purpose certificate; binds the seller's NTS identity to invoice signatures. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **NTS HomeTax** — Issuance, signing, and reporting of every corporate-issued tax invoice within 1 day of issue. Lifecycle status| Reported as ---|--- `issued`| Invoice signed and registered with NTS. `late`| Issued but reported beyond 1 day — penalty applies. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- South Korea happy path| Sender BRN `123-45-67890`, recipient any KR BRN in Flowie sandbox. ## FAQ ### Is there a Peppol path in Korea? No. Korea operates a fully national stack via HomeTax; Peppol is not adopted. ## References **Primary sources** (government / regulator / standards body): * [National Tax Service (English)]() — Tax authority. * [HomeTax portal]() — e-Tax invoice issuance + reporting. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Sovos · South Korea e-Tax Invoice]() — Industry analysis — long-running CTC. * [EDICOM · South Korea e-invoicing]() — Industry tracker — sole proprietor thresholds. ======================================================================== # Malaysia · LHDN MyInvois (clearance model) # Source: https://docs.get-flowie.com/compliance/my.html ======================================================================== --- title: "Malaysia — LHDN MyInvois · clearance model" description: "Malaysia e-invoicing: LHDN MyInvois real-time clearance phased Aug 2024 → Jan 2026. Threshold raised to RM 1m turnover; below is exempt." canonical: "https://docs.get-flowie.com/compliance/my" source: "https://docs.get-flowie.com/compliance/my.html" --- # Malaysia — LHDN MyInvois · clearance model Compliance · 🇲🇾 Malaysia Live mandate # Malaysia — LHDN MyInvois · clearance model MyInvois clearance · phased rollout completing Jan 2026 (RM 1m floor) — regulator: [Inland Revenue Board of Malaysia (LHDN / IRBM)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Malaysia mandates **real-time clearance** through LHDN's MyInvois portal — the seller submits, LHDN validates, and a **UUID + QR code** are returned for embedding on the invoice. * Phased by turnover: > RM 100m (Aug 2024), > RM 25m (Jan 2025), > RM 5m (Jul 2025), > **RM 1m (Jan 2026)**. * December 2025: cabinet **raised the floor to RM 1m** (was RM 500k) and cancelled the originally-planned 5th wave for < RM 500k taxpayers. * Each phase has a **6-month relaxation period** with consolidated invoicing allowed and no penalties for late submission. * Format: **UBL 2.1 with MY CIUS** ; covers B2B, B2C, B2G, self-billed, and cross-border. ## Deadlines Date| Who| What ---|---|--- **2024-08-01**| Turnover > RM 100 m| Wave 1 mandatory. **2025-01-01**| Turnover RM 25–100 m| Wave 2 mandatory. **2025-07-01**| Turnover RM 5–25 m| Wave 3 mandatory. **2026-01-01**| Turnover RM 1–5 m| Wave 4 mandatory — final wave. Cancelled| Turnover < RM 1m| Wave 5 cancelled in December 2025; SMEs below RM 1m are exempt. ## Background Malaysia's **MyInvois** programme, operated by LHDN/IRBM, is a hard clearance regime modelled on Latin American CTC. Sellers submit each invoice to MyInvois in UBL 2.1 (or via a free LHDN portal for low-volume taxpayers); LHDN validates business rules and TIN registrations in real time; a successful clearance returns a **UUID** \+ a QR code that the seller embeds on the printable invoice. Without that UUID the invoice has no legal effect for tax. Each wave has come with a 6-month _relaxation period_ during which businesses can issue consolidated month-end invoices for B2B/B2C and incur no Section 120 penalties. After relaxation, fines run from RM 200 to RM 20,000 per non-compliant document. In December 2025, the cabinet approved an SME exemption: turnover below **RM 1 million** is now exempt (the threshold was raised from RM 500k), and the originally-planned Wave 5 capturing < RM 500k businesses was cancelled. Wave 4 (1 January 2026) covers RM 1–5 m and is therefore the final wave. ## Format profile * **UBL 2.1 with MY CIUS** (also accepts JSON variants). * Seller and buyer **TIN** (Malaysian Tax Identification Number, 13 chars) mandatory. * **SST registration number** required if the seller is SST-registered. * Validation includes TIN registration check, business code (MSIC), classification code per LHDN catalogue. * B2C: consolidated invoice allowed (one per month) unless the buyer requests an individual e-invoice. ## Required fields * seller.tinstring (13 chars)required Malaysian TIN — prefix `IG/OG/PG/EI/...` \+ digits. * seller.brnstringrequired Business Registration Number (SSM). * seller.msicCodestring (5 digits)required Malaysia Standard Industrial Classification of the seller's main activity. * buyer.tinstring (13 chars)required for B2B Buyer TIN; LHDN validates registration. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **LHDN MyInvois** — Real-time clearance for B2B/B2C/B2G/self-billed/cross-border. Invoice not legal until cleared. Lifecycle status| Reported as ---|--- `submitted`| Invoice posted to MyInvois — pending validation. `valid`| UUID + QR returned; invoice may be delivered to buyer. `invalid`| Validation failed; correct and resubmit. `cancelled`| Cancellation accepted within 72h of clearance. `rejected`| Buyer-initiated rejection (within 72h). ## Error codes Code| Meaning| Fix ---|---|--- `BadStructure`| Invoice schema validation failed.| Compare against the LHDN UBL profile; common cause is missing classification or MSIC code. `DuplicateSubmission`| Same invoice submitted twice.| MyInvois deduplicates by document number per seller TIN; verify before retrying. `DS302`| TIN not registered with LHDN.| Verify the buyer TIN through MyInvois TIN search before sending. ## Testing in sandbox What you want to test| How ---|--- Malaysia happy path| Sender TIN `IG12345678901`, recipient any registered MY TIN in Flowie sandbox; UUID echoed back. ## FAQ ### Is Peppol used in Malaysia? Not for the LHDN clearance. Malaysia operates a national MyInvois platform with its own UBL profile. Some interoperability with Peppol is on LHDN's roadmap but not yet live. ## References **Primary sources** (government / regulator / standards body): * [LHDN · IRBM E-Invoice]() — Tax authority e-invoicing landing page. * [MyInvois portal]() — Production clearance portal. **Industry analyses** (vendor trackers — useful for cross-referencing): * [RTC Suite · RM 1m threshold (Dec 2025)]() — Industry analysis — Wave 5 cancellation. * [ClearTax · Malaysia e-invoicing phases]() — Industry tracker. ======================================================================== # Saudi Arabia · ZATCA Fatoora (clearance model) # Source: https://docs.get-flowie.com/compliance/sa.html ======================================================================== --- title: "Saudi Arabia — ZATCA Fatoora · clearance model" description: "Saudi Arabia e-invoicing: ZATCA Fatoora real-time clearance live since 2021. Phase 2 integration rolling out in waves through 2026 (Wave 24 reaches taxpayers > SAR 375k by 30 June 2026)." canonical: "https://docs.get-flowie.com/compliance/sa" source: "https://docs.get-flowie.com/compliance/sa.html" --- # Saudi Arabia — ZATCA Fatoora · clearance model Compliance · 🇸🇦 Saudi Arabia Live mandate # Saudi Arabia — ZATCA Fatoora · clearance model Mandatory clearance via Fatoora portal · live since 2021 — regulator: [ZATCA · Zakat, Tax and Customs Authority](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Saudi Arabia mandates **real-time clearance** through ZATCA's Fatoora portal — no clearance, no legal invoice. * **Phase 1 (Generation)** has applied to _all_ VAT taxpayers since December 2021; **Phase 2 (Integration)** rolls out in waves by turnover. * Wave 24 (announced 2025-10) brings every taxpayer with VAT-able revenue > **SAR 375,000** in 2022/2023/2024 into Phase 2 by **30 June 2026** — practically the whole VAT register. * Format: **UBL 2.1 with KSA-specific extensions** (TLV-encoded QR code, cryptographic stamp, hash chain). * Flowie integrates directly with Fatoora as an EGS (E-invoice Generation Solution); no Peppol wrapping. ## Deadlines Date| Who| What ---|---|--- **2021-12-04**| All VAT taxpayers| Phase 1 (Generation) — invoices must be issued in structured format with QR code. **2023-01-01**| Wave 1 (turnover > SAR 3 bn in 2021)| Phase 2 integration with Fatoora live. **2024-2025**| Waves 2–22| Phase 2 integration rolled out by descending turnover bands. **2026-03-31**| Wave 23 (turnover > SAR 750k)| Phase 2 integration deadline. **2026-06-30**| Wave 24 (turnover > SAR 375k)| Phase 2 integration deadline — captures essentially the full VAT register. ## Background Saudi Arabia operates the most ambitious continuous-transaction-control programme in the Middle East. ZATCA's **Fatoora** platform (literally 'invoice') went live in two phases: _Phase 1 (Generation)_ in December 2021 obliged every VAT-registered taxpayer to abandon free-form PDFs and issue invoices in a structured format with a QR code; _Phase 2 (Integration)_ from January 2023 plugs each taxpayer's billing system into Fatoora for real-time clearance (B2B and B2G) or near-real-time reporting (B2C, within 24 hours). Phase 2 is rolled out by **waves** , each capturing taxpayers above a descending turnover threshold. By Wave 24 (June 2026), the threshold is SAR 375,000 — effectively the VAT registration floor — so the regime becomes universal. Practically: Flowie's KSA endpoint signs the invoice with the seller's CSID-issued certificate, posts the JSON envelope to Fatoora, receives the cleared invoice with cryptographic stamp + UUID, and only then delivers the legal copy to the buyer. The QR code embedded in the printable invoice resolves to the Fatoora-side validation record. ## Format profile * **UBL 2.1 with the KSA CIUS** — Saudi-specific QR code (TLV-encoded), cryptographic stamp, previous invoice hash (chained). * Two invoice classes: **Standard (B2B/B2G)** requires clearance before issuance; **Simplified (B2C)** requires reporting within 24 h. * Seller must hold a **CSID** (Cryptographic Stamp Identifier) issued by ZATCA; the EGS uses it to sign every invoice. * Mandatory fields beyond EN 16931: `cbc:UUID`, `cac:AdditionalDocumentReference` for ICV (invoice counter) and PIH (previous invoice hash). ## Required fields * seller.vatNumberstring (15 digits)required KSA VAT registration number — starts with 3, ends with 03. * seller.crNumberstringrequired Commercial Registration number; appears in `PartyIdentification`. * seller.csidstringrequired Cryptographic Stamp Identifier issued by ZATCA — bound to the EGS device. * invoice.icvintegerrequired Invoice Counter Value — monotonically increasing per EGS device. * invoice.pihstring (base64 SHA-256)required Previous Invoice Hash — chains every invoice to the previous one. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **Fatoora** — Real-time clearance for B2B/B2G; near-real-time reporting (24h) for B2C. Invoice not legally valid until cleared. Lifecycle status| Reported as ---|--- `cleared`| ZATCA validation passed; UUID + cryptographic stamp returned. Invoice may be delivered to buyer. `rejected`| Validation failed; correct and resubmit. Original invoice never legally existed. `reported`| B2C simplified invoice acknowledged within the 24h window. ## Error codes Code| Meaning| Fix ---|---|--- `BR-KSA-01`| QR code missing or malformed.| Ensure the TLV QR is generated with all 9 mandatory tags and base64-encoded. `BR-KSA-08`| Invoice counter (ICV) not monotonically increasing.| ICV must increment by 1 per invoice on the same EGS device — never reset. `BR-KSA-29`| Previous invoice hash (PIH) does not match the chain.| PIH must equal the SHA-256 base64 of the previous cleared invoice's signed XML. ## Testing in sandbox What you want to test| How ---|--- KSA happy path| Sender VAT `300000000000003`, recipient any KSA tax-registered entity in Flowie sandbox. Force clearance rejection| Send with `simulateCompliance: "reject_KSA_BR_29"`. ## FAQ ### Do I need ZATCA accreditation as a software vendor? You don't need ZATCA accreditation per se, but the EGS (your billing system) must pass ZATCA's compliance test in the Fatoora simulation portal and be onboarded with a CSID. Flowie ships pre-onboarded EGS profiles that you bind to your seller registration in one call. ### Is Peppol used in Saudi Arabia? No. KSA does not use Peppol — ZATCA operates its own clearance network. Flowie still exposes the same JSON to you; we adapt to Fatoora behind the scenes. ## References **Primary sources** (government / regulator / standards body): * [ZATCA · Zakat, Tax and Customs Authority]() — Tax authority owning Fatoora. * [ZATCA · E-Invoicing roll-out phases]() — Official wave-by-wave timeline. * [Fatoora · simulation portal]() — Production / simulation portal for EGS onboarding. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EY · Saudi Arabia Phase 2 Wave 23]() — Industry analysis — wave criteria. * [Sovos · Saudi Arabia e-invoicing]() — Industry tracker — KSA CIUS / Phase 2. ======================================================================== # Singapore · InvoiceNow (Peppol 5-corner with IRAS) # Source: https://docs.get-flowie.com/compliance/sg.html ======================================================================== --- title: "Singapore — InvoiceNow · Peppol 5-corner with IRAS" description: "Singapore InvoiceNow: Peppol-based national e-invoicing, mandatory for new GST registrants from April 2026 and rolling out to all GST-registered businesses by 2031." canonical: "https://docs.get-flowie.com/compliance/sg" source: "https://docs.get-flowie.com/compliance/sg.html" --- # Singapore — InvoiceNow · Peppol 5-corner with IRAS Compliance · 🇸🇬 Singapore Phased rollout # Singapore — InvoiceNow · Peppol 5-corner with IRAS Peppol InvoiceNow + GST 5-corner reporting · phased through 2031 — regulator: [Inland Revenue Authority of Singapore (IRAS)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **InvoiceNow** is Singapore's nationwide Peppol network, run by IMDA since 2019; **GST InvoiceNow** adds IRAS as the 5th corner for tax reporting. * Newly incorporated companies registering for GST voluntarily had to comply from **1 Nov 2025** ; all new voluntary registrants from **1 Apr 2026**. * All existing GST-registered businesses are absorbed in waves: 2028 (≤ S$200k), 2029 (≤ S$1m), 2030 (≤ S$4m), **2031 ( > S$4m)**. * Format: **PINT-SG** (Peppol International Invoice — Singapore CIUS) on UBL 2.1. * IMDA = Peppol Authority; IRAS = tax authority receiving the 5-corner copy. Flowie is registered with IMDA. ## Deadlines Date| Who| What ---|---|--- **2019-01-09**| All businesses (voluntary)| InvoiceNow Peppol network launched by IMDA. **2025-05-01**| GST-registered (voluntary)| Soft launch of GST InvoiceNow. **2025-11-01**| Newly incorporated companies registering for GST voluntarily| GST InvoiceNow mandatory. **2026-04-01**| All new voluntary GST registrants| GST InvoiceNow mandatory. 2028-04-01| Existing GST-registered, supplies ≤ S$200k| Mandatory. 2029-04-01| Existing GST-registered, supplies ≤ S$1m| Mandatory. 2030-04-01| Existing GST-registered, supplies ≤ S$4m| Mandatory. 2031-04-01| All remaining GST-registered (> S$4m)| Universal scope reached. ## Background Singapore was the first country outside Europe to join OpenPeppol as a Peppol Authority (IMDA, 2018) and built **InvoiceNow** as the national e-invoicing network on top. For the first six years it was purely voluntary — well-adopted in B2G via Vendors@Gov but light in B2B. The 2024 IRAS announcement of **GST InvoiceNow** changes that. From 2025–2031, all GST-registered businesses must transmit invoice data via the InvoiceNow network so IRAS receives a real-time copy as the fifth corner of a Peppol 5-corner model. The phased rollout starts with new GST registrants and ends with all GST-registered businesses by April 2031. Format is **PINT-SG** , a Singapore CIUS on Peppol International. Flowie's Singapore AP — registered with IMDA — handles routing to the buyer plus the IRAS reporting copy. Both are emitted from the same `/v1/documents/send` call. ## Format profile * **PINT-SG** (Peppol International Invoice — Singapore CIUS) on UBL 2.1. * Seller and buyer **UEN** (Unique Entity Number) used as Peppol participant ID under scheme `0195:SG-UEN-...`. * GST breakdown lines required even for zero-rated supplies. * B2C invoices are out of scope of GST InvoiceNow but may still flow over InvoiceNow voluntarily. ## Required fields * seller.uenstringrequired Singapore Unique Entity Number; used as the Peppol participant ID. * seller.gstNumberstringrequired for GST-registered Singapore GST registration number — required to claim GST on the invoice. * lines[].gstCategorystringrequired Standard, Zero-rated, Exempt, or Out-of-scope per IRAS GST category codes. ## Public sector (B2G) Hub| Peppol identifier scheme| Lookup ---|---|--- **Vendors@Gov / InvoiceNow**| `0195:SG-UEN-`| Government recipients are reachable on InvoiceNow via their UEN. Vendors@Gov is the legacy submission portal but Peppol delivery is now the recommended path. ## B2B reporting / clearance **IRAS · GST InvoiceNow** — 5-corner reporting copy of every GST-relevant invoice; auto-populates GST F5 returns. Lifecycle status| Reported as ---|--- `submitted`| InvoiceNow message accepted; IRAS will receive a copy. `rejected`| Schema or business-rule violation; corrected document required. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Singapore happy path| Sender UEN `200012345A`, recipient any UEN in Flowie sandbox. ## FAQ ### Is InvoiceNow mandatory for non-GST businesses? No. Only GST-registered businesses are within scope of the GST InvoiceNow rollout. Non-GST businesses can use InvoiceNow voluntarily, which is increasingly common since government suppliers must. ## References **Primary sources** (government / regulator / standards body): * [IRAS · GST InvoiceNow Requirement]() — Authoritative GST InvoiceNow rules. * [IMDA · Peppol Service Provider Accreditation]() — Singapore Peppol Authority. * [OpenPeppol · Singapore profile]() — PINT-SG Peppol profile. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Avalara · Singapore InvoiceNow 2026-2031]() — Industry analysis — phased timeline. ======================================================================== # Thailand · Revenue Department e-Tax invoice # Source: https://docs.get-flowie.com/compliance/th.html ======================================================================== --- title: "Thailand — Revenue Department e-Tax invoice" description: "Thailand e-invoicing: RD e-Tax invoice/e-Receipt voluntary, no turnover threshold. ETDA-aligned XML schema; full-system path with digital signature or simplified Email path." canonical: "https://docs.get-flowie.com/compliance/th" source: "https://docs.get-flowie.com/compliance/th.html" --- # Thailand — Revenue Department e-Tax invoice Compliance · 🇹🇭 Thailand Voluntary # Thailand — Revenue Department e-Tax invoice Voluntary e-Tax invoice/e-Receipt · ETDA-aligned XML · no mandate yet — regulator: [Revenue Department / ETDA](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Thailand's **e-Tax Invoice & e-Receipt** regime, run by the Revenue Department with ETDA-aligned standards, is _voluntary_. * Two paths: **full system** (digital signature, any taxpayer) and **e-Tax Invoice by Email** (time stamp only, for small businesses ≤ THB 30 m). * Format: ETDA-compliant XML, digitally signed; storage 5 years. * No turnover threshold for voluntary registration; no firm mandate timetable as of 2026. * If you operate B2B in Thailand at scale, voluntary adoption has tax-administration benefits (faster refunds) and customer experience benefits. ## Deadlines Date| Who| What ---|---|--- **2012-01**| Voluntary launch| Revenue Department issues regulations for e-Tax Invoice & e-Receipt. **2017-2019**| Email path| e-Tax Invoice by Email available for SME (≤ THB 30 m). No date| Mandate| No firm mandate; voluntary regime continues. ## Background Thailand has had a voluntary e-Tax invoice regime since 2012, operated by the Revenue Department in coordination with the Electronic Transactions Development Agency (ETDA). The full system requires a digital certificate from a Thailand CA, ETDA-compliant XML, and submission to the RD portal. A simplified _e-Tax Invoice by Email_ path uses time-stamp-only authentication and is open to businesses with annual revenue ≤ THB 30 m. Despite repeated indications that mandatory e-invoicing is on the roadmap, no firm mandate timetable exists as of 2026. The RD continues to encourage adoption — voluntary registrants benefit from accelerated VAT refund processing and integration with ETDA's e-services. Storage of e-Tax invoices is required for 5 years. Practically, Thailand sits between China-style universal mandates and Singapore-style Peppol leadership: a transitional voluntary model with strong infrastructure but no compulsion. ## Format profile * **ETDA-compliant XML** (national format; not UBL/Peppol). * Seller's **13-digit tax ID** mandatory. * Full system: digital signature from a Thai CA; e-Tax by Email: time stamp only. * Storage: 5 years from issuance. ## Required fields * seller.taxIdstring (13 digits)required Thai tax identification number. * seller.digitalCertificatePKCS#12required for full system Certificate from a Thai CA; required for the full e-Tax invoice path. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance _No central B2B reporting hub — pure transmission only._ ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox Generic sandbox patterns apply — see [Sandbox guide](<../sandbox/index.html>). ## FAQ ### Should I voluntarily adopt e-Tax in Thailand? If you have material Thai B2B volume, yes — VAT refunds are faster and integration with ETDA simplifies downstream tax filings. Flowie's Thailand path issues both the full and email flavours from the same JSON. ## References **Primary sources** (government / regulator / standards body): * [Revenue Department (English)]() — Thai Revenue Department e-Service overview. * [RD · e-Tax Invoice & e-Receipt portal]() — Production portal. * [ETDA · Electronic Transactions Development Agency]() — Standards body for e-Tax invoice. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EDICOM · Thailand e-invoicing]() — Industry tracker — voluntary regime. ======================================================================== # Türkiye · GİB e-Fatura & e-Arşiv # Source: https://docs.get-flowie.com/compliance/tr.html ======================================================================== --- title: "Türkiye — GİB e-Fatura & e-Arşiv" description: "Türkiye e-invoicing: GİB e-Fatura mandatory for B2B above TRY 3m turnover; e-Arşiv mandatory for B2C and out-of-portal B2B from 2026. One of the longest-running CTC regimes." canonical: "https://docs.get-flowie.com/compliance/tr" source: "https://docs.get-flowie.com/compliance/tr.html" --- # Türkiye — GİB e-Fatura & e-Arşiv Compliance · 🇹🇷 Türkiye Live mandate # Türkiye — GİB e-Fatura & e-Arşiv GİB e-Fatura since 2014 · e-Arşiv universal from 2026 — regulator: [Gelir İdaresi Başkanlığı (GİB)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * Türkiye runs one of the world's most mature CTC regimes — **e-Fatura** (B2B clearance) since 2014 and **e-Arşiv** (B2C / out-of-portal B2B reporting) since 2017. * e-Fatura threshold: B2B turnover > **TRY 3 m** ; ecommerce/real-estate/construction > TRY 500k; some sectors mandatory regardless of turnover. * From **1 Jan 2026** , the TRY 3,000 e-Arşiv threshold is removed — **all invoices must be electronic** regardless of value, with very narrow exceptions. * Format: **UBL-TR 2.1** (Turkish UBL CIUS); e-Fatura cleared via the GİB portal, e-Arşiv reported within 24h. * Updated technical standards effective 2 February 2026. ## Deadlines Date| Who| What ---|---|--- **2014-04-01**| Large taxpayers| e-Fatura mandatory. **2017**| B2C reporting| e-Arşiv introduced. **2020-2024**| Phased threshold reductions| e-Fatura threshold steps down through TRY 5m / 3m by sector. **2026-01-01**| All taxpayers| TRY 3,000 e-Arşiv threshold removed — universal e-invoice obligation. **2026-02-02**| All taxpayers| Updated UBL-TR technical standards in effect. ## Background Türkiye introduced e-Fatura in 2014 and has progressively expanded scope ever since. The regime is **two-tracked** : **e-Fatura** covers B2B between two registered taxpayers — the seller submits to the GİB portal, GİB validates and forwards to the buyer's GİB account, and the invoice is legally valid only after this clearance. Mandatory above TRY 3 m turnover (lower thresholds for ecommerce, real estate, construction, professional intermediaries, jewellery). **e-Arşiv** covers B2C and B2B with non-registered counterparties — the seller issues directly to the buyer (PDF/print) and reports to GİB within 24 hours. From 1 January 2026, the TRY 3,000 floor is removed: every invoice must be either e-Fatura or e-Arşiv. Format is **UBL-TR 2.1** (Turkish UBL CIUS, locally maintained by GİB). New technical standards take effect 2 February 2026; suppliers must update their integrations or lose clearance access. ## Format profile * **UBL-TR 2.1** (Turkish UBL CIUS, GİB-maintained). * Seller and buyer **VKN** (Vergi Kimlik Numarası, 10 digits) for legal entities or **TCKN** (11 digits) for individuals. * e-Fatura: cleared via GİB portal; e-Arşiv: reported within 24h. * Mandatory **financial seal** (mali mühür) issued by TÜBİTAK for legal entities; e-Imza for sole proprietors. ## Required fields * seller.vknstring (10 digits)required Vergi Kimlik Numarası — Turkish tax ID for legal entities. * buyer.vknstring (10 digits) or TCKN (11)required for B2B Buyer VKN or TCKN. * invoice.maliMuhurobjectrequired Financial seal signature; for legal entities only TÜBİTAK-issued seals are accepted. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **GİB e-Fatura / e-Arşiv portal** — e-Fatura: real-time clearance for B2B between registered taxpayers. e-Arşiv: 24h reporting for B2C and out-of-portal B2B. Lifecycle status| Reported as ---|--- `cleared`| GİB validation passed; e-Fatura forwarded to buyer. `reported`| e-Arşiv submission acknowledged within 24h. `rejected`| Validation or buyer rejection (B2B). ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Türkiye happy path| Sender VKN `1234567890`, recipient VKN in Flowie sandbox. ## FAQ ### How do I tell whether to send e-Fatura or e-Arşiv? Look up the buyer's VKN in the GİB e-Fatura registry: if they're registered, you must send via e-Fatura (clearance through GİB). If not, e-Arşiv applies. Flowie's TR connector resolves this automatically per invoice. ## References **Primary sources** (government / regulator / standards body): * [Gelir İdaresi Başkanlığı (GİB)]() — Turkish Revenue Administration. * [GİB · e-Fatura portal]() — Production e-Fatura portal. **Industry analyses** (vendor trackers — useful for cross-referencing): * [Avalara · Turkey e-invoicing]() — Industry tracker. * [vatcalc · Turkey e-Fatura / e-Arşiv update]() — Industry analysis — 2026 universal mandate. ======================================================================== # Vietnam · GDT mandatory e-invoice (Decree 70/2025) # Source: https://docs.get-flowie.com/compliance/vn.html ======================================================================== --- title: "Vietnam — GDT mandatory e-invoice · Decree 70/2025" description: "Vietnam e-invoicing: GDT mandatory e-invoice universal since 1 Jul 2022. Decree 70/2025 (effective 1 Jun 2025) extends scope to POS retail and foreign suppliers." canonical: "https://docs.get-flowie.com/compliance/vn" source: "https://docs.get-flowie.com/compliance/vn.html" --- # Vietnam — GDT mandatory e-invoice · Decree 70/2025 Compliance · 🇻🇳 Vietnam Live mandate # Vietnam — GDT mandatory e-invoice · Decree 70/2025 Universal e-invoice since 2022 · Decree 70 expansion 2025-2026 — regulator: [General Department of Taxation (GDT)](). _Facts last refreshed: 2026-07-13._ Coverage model Flowie operates a registered Peppol Access Point in this jurisdiction **directly** where we hold national accreditation, **or via a vetted local partner** registered with the in-country regulator where on-the-ground presence is required (KSeF, SDI intermediario, ZATCA service-provider, etc.). Either way, you call the same `POST /v1/documents/send`. ## TL;DR * **Mandatory for every taxpayer** since 1 July 2022 — Vietnam was an early Asian mover on universal e-invoicing. * **Decree 70/2025/ND-CP** (effective 1 Jun 2025) overhauls Decree 123/2020: tighter timing, expanded scope, new rectification rules. * POS retail (hospitality, F&B, personal services) with revenue > **VND 1 bn** /yr must connect their cash registers to the GDT system in real time. * Foreign suppliers without permanent establishment selling digital services in Vietnam can voluntarily register on the GDT portal to issue e-invoices. * Two flavours: e-invoice _with_ verification code from GDT (real-time clearance) or _without_ (registered taxpayer self-issuance). ## Deadlines Date| Who| What ---|---|--- **2022-07-01**| All organisations and businesses| Mandatory e-invoice — universal scope. **2025-06-01**| All taxpayers| Decree 70/2025 in force — POS, foreign suppliers, tighter timing. **2026-01-16**| All taxpayers| Decree 310/2025 restructures penalty framework for invoice violations. ## Background Vietnam mandated e-invoicing universally from 1 July 2022 — earlier than most ASEAN peers. The General Department of Taxation (GDT) operates a national e-invoice platform: every VAT-registered taxpayer must issue invoices in the GDT-prescribed XML, sign with a tax-purpose digital certificate, and either obtain a **verification code from GDT** in real time (clearance flavour, mandatory for higher-risk taxpayers) or self-issue with subsequent reporting (registered-taxpayer flavour). **Decree 70/2025/ND-CP** , effective 1 June 2025, amends the predecessor Decree 123/2020 and tightens the regime: stricter timing rules (often same-day reporting), expanded scope to **POS cash registers** connected in real time for retail/hospitality/F&B with revenue above VND 1 bn/yr, and inclusion of **foreign suppliers** of digital services who can voluntarily register on the GDT portal to issue Vietnamese e-invoices. From 16 January 2026, _Decree 310/2025/ND-CP_ restructures the administrative-penalty framework for invoice violations. Storage requirement: minimum 10 years. ## Format profile * **GDT XML** (national; not UBL). * Seller and buyer **tax code** (10 or 13 digits) mandatory. * Two issuance flavours: with GDT verification code (clearance) or without (self-issuance + reporting). * Tax-purpose **digital signature certificate** from a GDT-approved CA mandatory. * POS retail: cash register must connect to GDT in real time; per-transaction transmission. ## Required fields * seller.taxCodestring (10 or 13 digits)required Vietnamese tax code. * seller.digitalCertificatePKCS#12required Tax-purpose certificate from a GDT-approved CA. * invoice.gdtVerificationCodestringrequired for clearance flavour Returned by GDT after real-time validation. ## Public sector (B2G) _Combined private + public flow — no dedicated B2G hub for this country._ ## B2B reporting / clearance **GDT national e-invoice platform** — Real-time clearance (high-risk taxpayers) or near-real-time reporting (registered taxpayers). Universal scope. Lifecycle status| Reported as ---|--- `issued`| Invoice signed and either cleared or reported. `cancelled`| Cancellation or replacement under Decree 70 rectification rules. `rejected`| GDT rejected the clearance request. ## Error codes _Generic Peppol BIS schematron error codes apply (`BR-*`, `EN16931-*`); no country-specific overlays._ ## Testing in sandbox What you want to test| How ---|--- Vietnam happy path| Sender tax code `0123456789`, recipient any VN tax code in Flowie sandbox. ## FAQ ### Do I need a Vietnamese digital certificate? Yes — invoices must be signed with a certificate from a GDT-approved CA bound to the seller's tax code. Flowie's Vietnamese partnership covers this onboarding. ## References **Primary sources** (government / regulator / standards body): * [General Department of Taxation (English)]() — Vietnamese tax authority. * [GDT · e-tax portal for foreign suppliers]() — Foreign-supplier registration portal. **Industry analyses** (vendor trackers — useful for cross-referencing): * [EY Vietnam · April 2025 tax alert (Decree 70)]() — Industry analysis — Decree 70 effective date. * [EDICOM · Vietnam e-invoicing]() — Industry tracker. ======================================================================== # Developer portal # Source: https://docs.get-flowie.com/developers/index.html ======================================================================== --- title: "Flowie Exchange API — developer portal, API docs, OpenAPI spec, SDK, MCP server" description: "Flowie Exchange API developer portal: get an API key with no signup, read the OpenAPI spec, call the sandbox, connect over MCP or A2A, and ship an e-invoicing integration across 47 countries." canonical: "https://docs.get-flowie.com/developers/" source: "https://docs.get-flowie.com/developers/index.html" --- # Flowie Exchange API — developer portal Everything needed to ship an integration: a key you can mint yourself in one call, the machine-readable contract, a live sandbox, and native agent transports. No sales call, no signup form. ## Get an API key right now No signup and no human in the loop. One unauthenticated call returns a working key plus a seeded company you can immediately send and receive documents as: [code] curl -X POST https://back.flowie.ink/exchange/v1/sandbox/bootstrap \ -H "Content-Type: application/json" -d "{}" [/code] The response carries `apiKey`, `organizationId`, a seeded `company`, and `nextSteps`. Use it straight away: [code] curl https://back.flowie.ink/exchange/v1/documents?limit=1 \ -H "Authorization: Bearer flw_test_..." [/code] ## Official SDK [code] npm install flowie-exchange [/code] [flowie-exchange on npm]() — mint a sandbox key, send a document and advance its lifecycle in a few lines. ## Quickstart — send your first invoice [code] curl -X POST https://back.flowie.ink/exchange/v1/documents/send \ -H "Authorization: Bearer \$KEY" -H "Content-Type: application/json" -d @- < **Note the field isnumber, not invoiceNumber** — the API validates on document.number. ## Documentation Resource| URL ---|--- API reference — every endpoint| [/reference/](<../reference/>) OpenAPI 3.1 contract| [/openapi.json](<../openapi.json>) Authentication for agents| [/auth.md](<../auth.md>) Webhooks| [/reference/webhooks](<../reference/webhooks.html>) Errors| [/reference/errors](<../reference/errors.html>) Data model| [/reference/data-model](<../reference/data-model.html>) Send an invoice| [/guides/send-invoice](<../guides/send-invoice.html>) Receive invoices| [/guides/receive-invoices](<../guides/receive-invoices.html>) Country compliance (47)| [/compliance/](<../compliance/>) Postman collection| [/postman_collection.json](<../postman_collection.json>) Changelog| [/changelog](<../changelog.html>) ## Sandbox Base URL https://back.flowie.ink/exchange. Production is https://back.p2p-flowie.com/exchange. Sandbox keys expire after 7 days, carry a seeded company, and never touch a real network. See [/sandbox/](<../sandbox/>) and the interactive [playground](<../playground/>). ## For AI agents The API is callable natively over two agent protocols, and the docs are published in machine-readable form. Surface| URL ---|--- MCP (Model Context Protocol), 40 tools| https://back.flowie.ink/exchange/mcp MCP server card| [/.well-known/mcp/server-card.json](<../.well-known/mcp/server-card.json>) A2A (Agent-to-Agent) JSON-RPC| https://back.flowie.ink/exchange/a2a A2A agent card| [/.well-known/agent-card.json](<../.well-known/agent-card.json>) Agent skills index| [/.well-known/agent-skills/index.json](<../.well-known/agent-skills/index.json>) Page index for LLMs| [/llms.txt](<../llms.txt>) Full corpus, one file| [/llms-full.txt](<../llms-full.txt>) Share access with an agent| [/share-access](<../share-access.html>) ## Server-to-server: OAuth 2.0 client credentials For a backend that runs unattended — an ERP or DCS gateway, a nightly sync, a webhook consumer — with no user to sign in. **Provisioning.** No self-service and no dynamic registration. Flowie creates a dedicated M2M application per integration (so two integrations for one customer can be revoked independently), backed by a technical account that is made a _member of your organisation with a role_. That membership is what grants access: the organisations a machine token may act for are the ones its technical account belongs to. The membership is _not_ written into the token — a client_credentials token names no organisation at all — so you name the one you are acting for in the X-Flowie-Organization-Id header, and the API checks it against that membership. Staging and production are separate applications with separate credentials. [code] curl -X POST https://login.flowieapp.io/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "client_credentials", "client_id": "...", "client_secret": "...", "audience": "https://auth.flowie.me" }' [/code] Token endpoint: `https://login.flowieapp.io/oauth/token` for sandbox/pilot, `https://login.flowie.me/oauth/token` for production — the dedicated authentication domains Flowie moved to in June 2026, and the ones your IT will have allow-listed. The former tenant domains (`*.eu.auth0.com`) still issue tokens the API accepts, so existing integrations keep working. Better still, read the endpoint from `/.well-known/openid-configuration` rather than hard-coding it. The `audience` is required and is _not_ the API base URL. Get it wrong and the token endpoint refuses outright with `403 access_denied` — there is no token to misuse. That error reads like a credentials problem and usually is not one: it means the client is not authorised for that audience, either because it is misspelled or because the client has not been granted access to the Flowie API on the authorization server. Re-checking the client secret will not fix it. **Two grants, two similar 403s.** A machine client is authorised twice: for the _audience_ on the authorization server (missing → `403 access_denied` at the token endpoint), and by its technical account being a _member of the organisation it names_ (missing → `403 Token does not grant access to organization '…'. Available: none.` on the first API call). Getting a token proves only the first. Cache the token and renew it on `expires_in` (currently 24 h) rather than minting one per call. [code] curl https://back.flowie.ink/exchange/v1/documents \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "X-Flowie-Organization-Id: 019b47ba-..." [/code] **The organisation header is not optional here.** An Auth0 `client_credentials` token has an empty `_permissions` claim and carries no organisation, so this header is not an override — it is the only thing naming the tenant. Send it on every call. **Mind the header spelling.** This API reads `X-Flowie-Organization-Id` (or `Organization-Id`; header names are case-insensitive). It does _not_ read `x-organization-id`, which other Flowie services accept — a request sending that spelling here is served as though no organisation had been named, which for a machine token means a `403` on every call, and for a multi-organisation user JWT means silently acting as the wrong one. **If every call returns`403 No organization found in token`** while the token is valid and correctly signed, no organisation was named: add the header, or check its spelling. If instead you get `403 Token does not grant access to organization '…'`, the header arrived and the technical account is not a member of that organisation — report it, retrying or re-minting will not fix it. (`Available: none` in that message is normal for a machine token, which enumerates no organisations of its own.) **Rate limits.** A machine token is budgeted per OAuth client rather than per IP address, so two integrations under one customer never throttle each other. The default is 600 requests/minute and every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` — pace on those rather than a hard-coded number, since the budget is configurable per organisation. Full details in the [authentication guide](<../auth.md>). **Try it before you write code.** The [Postman collection](<../postman_collection.json>) carries the grant: set `tokenUrl`, `clientId`, `clientSecret`, `audience` and `organizationId`, send _auth → Get an access token_ , and every other request goes out authenticated and with the organisation header attached. ## Rate limits and versioning Responses carry `x-ratelimit-limit` and `x-ratelimit-remaining`; a `429` carries `Retry-After`. The API is versioned in the URL path (`/v1/`). Breaking changes ship under a new path version; deprecations are announced in the [changelog](<../changelog.html>) before removal. ## Agent skills [code] npx skills add FlowieAI/docs [/code] Four installable skills — send an e-invoice, check reachability, track lifecycle, look up country rules. [Source](). ## For AI coding agents Instructions for agents writing code against this API — auth, gotchas, conventions — live in [AGENTS.md]() in our public [docs repo](). ## Support Questions: [contact](<../contact.html>) · [support@flowie.fr](). Status: [flowie.betteruptime.com](). [/code] ======================================================================== # Webhook fixtures # Source: https://docs.get-flowie.com/fixtures/index.html ======================================================================== --- title: "Webhook fixtures" description: "Downloadable JSON fixtures for every Flowie Exchange webhook event. Drop-in test payloads for your handler." canonical: "https://docs.get-flowie.com/fixtures/" source: "https://docs.get-flowie.com/fixtures/index.html" --- # Webhook fixtures Fixtures # Webhook payload fixtures Drop these into your handler tests. Every fixture is a real payload Flowie has actually sent — schema-stable across patch releases. The shape mirrors the [events API](<../reference/index.html#events>) and matches what the [webhook envelope](<../reference/webhooks.html#payload>) documents. Use them in tests, not in production handlers The `id` values are deterministic — re-using them as real event IDs in your dedupe table will mask actual duplicates. Generate fresh IDs in tests if needed. ## document.received Fired when an incoming Peppol document is persisted. Delivered before any user-visible side-effect runs. document.received.json document.received Copy JSON [Download]() Preview [code] Loading… [/code] ## document.sent Fired when an outgoing document has been handed off to the recipient access point. Delivery is not yet confirmed. document.sent.json document.sent Copy JSON [Download]() Preview [code] Loading… [/code] ## document.delivered Fired when the recipient access point confirms final delivery. Final state for outgoing documents. document.delivered.json document.delivered Copy JSON [Download]() Preview [code] Loading… [/code] ## document.failed Fired when delivery permanently fails (recipient rejected, schema error, all retries exhausted). `willRetry` is always `false` at this point. document.failed.json document.failed Copy JSON [Download]() Preview [code] Loading… [/code] ## document.updated Fired when document metadata changes (tags, assignee, archive state). The `changes` array describes the field-level diff. document.updated.json document.updated Copy JSON [Download]() Preview [code] Loading… [/code] ## lifecycle.updated · approved lifecycle.updated.approved.json lifecycle.updated Copy JSON [Download]() Preview [code] Loading… [/code] ## lifecycle.updated · paid Note the `compliance.willReportTo` field — your stack should not also report to PPF (FR) or SDI (IT), Flowie does it. Belgian invoices have `willReportTo: []` (HERMES decommissioned 2025-12-31; Peppol delivery is the compliance event). lifecycle.updated.paid.json lifecycle.updated Copy JSON [Download]() Preview [code] Loading… [/code] ## lifecycle.updated · rejected `reasonCode` follows Peppol BIS rejection codes (`QUA`, `PRI`, `TAX`, …). lifecycle.updated.rejected.json lifecycle.updated Copy JSON [Download]() Preview [code] Loading… [/code] ## company.smp_registered company.smp_registered.json company.smp_registered Copy JSON [Download]() Preview [code] Loading… [/code] ## compliance.reported compliance.reported.json compliance.reported Copy JSON [Download]() Preview [code] Loading… [/code] ## compliance.reported.failed The `remediationDocUrl` deep-links to the country-specific error code in the compliance pages. compliance.reported.failed.json compliance.reported.failed Copy JSON [Download]() Preview [code] Loading… [/code] ## Download all fixtures as a bundle Available on GitHub for vendoring into your test repo: [code] curl -sL https://github.com/flowie-fr/exchange-api-docs/archive/main.tar.gz \ | tar -xz --strip=2 exchange-api-docs-main/docs/fixtures \ -C ./tests/fixtures [/code] Or via npm/PyPI helper packages (work in progress; subscribe to the [changelog](<../changelog.html>)). ======================================================================== # Changelog # Source: https://docs.get-flowie.com/changelog.html ======================================================================== --- title: "Changelog" description: "Every change to the Flowie Exchange API, newest first. Additive, deprecation, and breaking changes are all tracked here." canonical: "https://docs.get-flowie.com/changelog" source: "https://docs.get-flowie.com/changelog.html" --- # Changelog Changelog # What's new Every change to the Flowie Exchange API, newest first. Additive changes land continuously under `/v1/`; deprecations are announced six months in advance and flagged with a `Sunset` response header. How to read this New additive — always safe to adopt. Changed behavior refined — read carefully. Deprec sunset date announced. Break only ever in a new major (`/v2/…`). Fix bug fix. ## v3.18.3 — A company with no declared line is still addressable 2026-09-18 Found by running the signing flow end to end rather than reading the spec: the port could be opened and then not signed. Fix **`addressScope` defaults to the company’s own SIREN and SIRET** when the annuaire holds no addressing line and the company is not on Peppol. It used to come back empty, which made the mandate miss a decree item — so [Change platform]() offered a signature and `POST /v1/portability/requests/{ref}/mandate/signature/document` then refused it with `409 addressScope still missing`. A French company is addressable by its SIREN and SIRET whether or not anyone declared a line for it; both go in, because a port covering the legal unit but not the establishment leaves invoices addressed to the SIRET arriving at the platform you are leaving. A declared line still wins, and a non-French registration gets no invented scope. ## v3.18.2 — Sign the agreement with your own document 2026-09-18 There were two ways to sign, and neither fits a taxpayer whose legal representative is not a Flowie user: an approval check needs an account, and asserting a paper signature keeps nothing at all. New **`POST /v1/portability/requests/{ref}/mandate/signature/document` — upload the agreement you signed.** Multipart: the file, who signed it, when. We store the document under your organization and record the signature bound to it, so it replays onto the mandate like any other and `mandateGaps` closes. New **`agreementSha256` is the digest of _your_ file, not of our rendering.** They are different documents — you may have signed your own wording, or your advocate’s — and recording ours as the one signed would be a statement we cannot support. `mandateSha256` still binds to the decree content, so amending the mandate afterwards invalidates this signature exactly as it invalidates an in-app one. New **The same thing from[Change platform](), without an account.** When a request comes back needing a signature, the page asks for the name, the date and the file, and records it. Name, date and file together or not at all: a name with no document is the assertion the platform stopped accepting. Changed **A signature cannot be dated in the future** (`400`), a file over 10 MB is refused (`413`), and an agreement that already has a signature cannot receive a second one (`409`) — the evidence chain folds signatures in order, so accepting one would replace the recorded signatory, instant and document with no trace of the first. An act dated after the moment it is recorded is worse than an acknowledged gap. ## v3.18.1 — Signing a mandate reaches the services it depends on 2026-09-18 Shipped hours earlier in [v3.16.23](<#2026-09-18-sign-in-app>), and it would have failed on the first real call. Fix **`POST /v1/portability/requests/{ref}/mandate/signature` called approval and the documents service with no credentials.** Both calls opened their own HTTP client, so neither carried the bearer token nor the actor headers the documents service authorises a write by — every attempt would have come back `401`/`403` and surfaced to you as `502 The approval service did not answer`, which reads like an outage rather than a missing credential. They now go through the same authenticated path as every other internal call. Fix **The check is scoped to your organization again.** Approval reads `X-Flowie-Organization-Id`; the shared client sends `X-Organization-Id`. A call carrying only one of the two is either unscoped or unauthenticated, so both go out now. ## v3.18.0 — Agent plugin: the tools, the skills and the CLI on one page 2026-09-18 The MCP server and the hosted agent skills existed but were documented apart, and the skills were not documented at all. There is now one page that sets an agent up. New **[Agent plugin]() — public beta.** One setup covering all three agent surfaces: the MCP server (`/mcp` curated, `/mcp/full`), the 14 hosted agent skills, and the CLI. Copy-paste quickstarts for Claude Code, Cursor, VS Code and Codex, a one-request check that the connection works, and an explicit statement of what beta covers — endpoints, auth and the skill index URL are under the [deprecation policy](); individual tool names and skill contents can still change and are announced here. New **The agent skills are documented.** Fourteen skills have been served at `/.well-known/agent-skills/index.json` with a per-skill `sha256` digest, and no page named them. They are now listed, with what each one covers and how to load it in a client that does not read the index by itself. Fix **A failed MCP mount now stops the process instead of logging a warning.** When a dependency resolve broke `FastApiMCP`, the app started healthy and `/mcp` answered `404` on every environment while the documentation advertised 34 tools. All three mounts — curated, full and discovery — now raise at import, so a rollout catches the failure rather than a customer. ## v3.17.2 — The same correction, on the other four deposit paths 2026-09-18 v3.17.0 stopped a raw-body deposit being stored under a type its bytes contradict. It reached one of the five ways to deposit a file; this reaches the rest. Fix **A file sent as a base64`file` attachment is stored under the type its bytes actually are.** That covers JSON mode on [`POST /v1/documents/send`](), `POST /v1/documents/send/batch` and the event path. A PDF declared as `application/json` — the default most HTTP clients send — was persisted as JSON, because the declared type was believed whenever the caller supplied one and the bytes were only consulted when they had not. Nothing that is accepted today starts being refused, and a deposit whose declaration was already right is unaffected. Fix **A declaration more precise than the bytes survives, including the ones an allowlist had missed.** Only an unambiguous binary container overrides the declaration, and text is never second-guessed. A container subtype is kept whenever it says more than the container does: a structured syntax suffix that matches the bytes ([RFC 6839]()’s `+zip` / `+gzip` — this is how a signed `application/vnd.etsi.asic-e+zip` e-invoice keeps its identity), and any type in the vendor tree ([RFC 6838]() `application/vnd.…`), which names one specific format and so is never the generic container itself — an `.xlsm`, `.ppsx`, `.odg` or `.apk` is no longer flattened to `application/zip`. A suffix that _contradicts_ the bytes is still a mislabel and is still corrected. Fix **A declaration that names no type at all no longer becomes one.** `contentType=pdf` — a file extension where a media type belongs — used to be stored verbatim as `pdf`; it is now read from the bytes as `application/pdf`. A declaration that is only parameters (`; charset=utf-8`, which is what an ERP concatenating an empty base type sends) or only whitespace now falls back to the sniff instead of being persisted as the document’s media type. ## v3.17.1 — 0147 is Shine, and the Confidence column is back 2026-09-18 Fix **`0147` belongs to _Shine_ , not _PAYFLOWS_.** PAYFLOWS held it by inference: its own SIREN routes on `0147` in the PPF annuaire. That is because it is a Shine client, not because the number is its own. Shine states the number in its terms of use (§4.1), so the row is now `published` with that quote. `GET /v1/portability/platforms` returns `matricule: "0147"` on Shine and `null` on PAYFLOWS. Fifteen rows are `published` and 63 `inferred`. 92 of 165 rows still carry a matricule. New **The directory shows a Confidence column again.** It says how each number was established (_confirmed_ , _published_ , _inferred_ or _unknown_), and it sorts. Like the confidence filter, it appears only to a signed-in reader, the same reader who can see the numbers. ## v3.17.0 — `gzip` request bodies, and a deposit stored as what it is 2026-09-18 Both changes are additive: no request that is accepted today starts being refused. New **`Content-Encoding: gzip` is decompressed on the way in.** Send a gzip-compressed body (`x-gzip` and lists that reduce to gzip, such as `identity, gzip`, work too) on any endpoint, including the raw-body form of `POST /v1/documents/send`, and the handler reads the payload you meant to send. Useful when the document carries an embedded PDF: an ERP invoice of that shape typically drops by about a third on the wire. A body this service cannot inflate — an encoding it does not implement, a payload that is not really gzip, or one that would expand past its ceiling — is passed through untouched, exactly as before, so nothing that worked stops working. Fix **A raw-body deposit is stored under the type its bytes actually are.** Raw-body mode keeps the payload verbatim under the `contentType` and `filename` you pass in the query string, and nothing checked that the two agreed — a compressed or binary payload deposited as `application/xml` was persisted as if it were XML, so nothing downstream could read it. The declared type is now corrected from the first bytes, the same way the stored file extension already was. Only an unambiguous binary container overrides the declaration; text is never second-guessed, and a more specific declaration wins over a broader one (an `.xlsx` stays an `.xlsx`, not a zip). Changed **Two consequences of that correction, for clients that read them.** `storedFormat` now answers `gzip` for a gzip payload where it answered `binary`, on every deposit route. And when you send no `Idempotency-Key`, the implicit key is derived from the payload including its content type — so a deposit whose declared type was wrong hashes differently after this release, and a retry that crosses the upgrade mints a new document rather than replaying the old one. Only previously-mislabelled deposits are affected; a correctly declared one keeps the same key. ## v3.16.23 — The designation agreement can be signed in Flowie 2026-09-18 A mandate could be _recorded_ as signed — on paper, or in your own tool — but there was nowhere to actually sign one. That left the act the whole port rests on outside the platform. New **[`POST /v1/portability/requests/{ref}/mandate/signature`]() puts the agreement in front of someone.** The text is rendered from the mandate the request already holds — the five items article 242 _nonies_ E bis requires — and an approval check is opened on it for the Flowie users you name. They sign by deciding; what is stored is their decision, its instant and its author, none of which the API supplies. The response carries the text as presented and its digest, so you can show exactly what was signed. A request opened on an identifier alone is refused with `409` rather than rendering the agreement with blanks where the decree wants values. New **[`GET /v1/portability/requests/{ref}/mandate/signature`]() reads the decision back.** `signed` stays `false` while the check is open, refused or withdrawn. The first read that finds a passed one writes the signature into the hash-linked evidence chain, so the request carries its `signedAt` from then on whether or not this endpoint is called again. New **Only a decision on _this_ agreement is a signature.** The check carries the context key `portability::`. An approval vote is scoped to the object it hangs on, so without that key a decision taken on the same document for another reason would read here as a signature. Move the _date d’effet_ or the address scope and the digest moves with it — the earlier decision stops answering for the new agreement, because it is not the agreement that was signed. ## v3.16.22 — The request says what the agreement still needs 2026-09-17 A request opened from [Change platform]() showed its four deadlines and nothing else, so it read as complete — while the plan directly above it said, correctly, that a dated and signed designation agreement is required. New **`mandateGaps` on [`POST /v1/portability/requests`]() and `GET /v1/portability/requests/{ref}`.** Which decree items are still missing — `signatory` when nobody is named, `signature` while the agreement is unsigned. They were only ever inside the first entry of the evidence chain, so a client had to dig for the one fact that decides whether a port can proceed. It sits _beside_ the mandate, never inside it: that object is hashed, and a derived list folded in would change its own digest the moment you signed it. Fix **The page no longer says nothing is missing when the agreement is.** “Nothing missing: this company can be filed as it stands” was about the _company’s identifiers_ , four lines above a block asking for a signed agreement that had never been collected. It now reads “this company is fully identified”, and the receipt lists what the agreement still needs in words. _You re-grant_ became _You provide_ in all three languages. ## v3.16.21 — A name in a field is no longer a signature 2026-09-17 The designation agreement is what authorises a port: it is what the outgoing platform may object to as `mandate_invalid`, and what the administration asks for when a port is contested. Changed **`signatory` declares who will sign; it no longer signs.** Supplying the name used to stamp `signedAt` with the server’s clock, so the mandate claimed to be signed when nobody had seen a document and nothing was kept. `signedAt` now stays `null` until an act is recorded, and `mandateGaps` reports `signature` until then — a gap the outgoing platform would otherwise raise five business days later. A client that read `signedAt` as proof was reading its own input echoed back. New **`signedAt` and `signatureMethod` on [`POST /v1/portability/requests`]().** A mandate signed outside Flowie — on paper, or in your own tool — is signed, and these record it: you assert the instant, we keep it verbatim in the hash-linked evidence chain with how it was taken (`paper`, `external`, `approval`). What the platform will not do is invent the instant on your behalf. New **A signature is bound to the agreement it signed.** The record carries the SHA-256 of the mandate’s data and of the rendered document, so amending the _date d’effet_ or the address scope afterwards leaves a signature that no longer verifies — the port that was agreed to is not the port being made. The two digests are separate on purpose: rewording the document does not void past signatures. ## v3.16.20 — The deadlines read as dates, in the page’s own language 2026-09-17 The four clocks on [Change platform]() are legal deadlines — notification, objection window, _date d’effet_ , twelve-month continuity — and every one of them printed exactly as the API spells it. Fix **`2026-09-21` became _21 septembre 2026_ on the French page, _21 settembre 2026_ on the Italian one.** An ISO date on a French page is foreign at best and ambiguous at worst: a reader who takes it for a day-month reads the objection window as March. The month is spelled out in the page’s own language, which no locale can misread, and the ISO value stays in the `