# 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 ======================================================================== --- source: https://docs.get-flowie.com/index.html --- API v3.0 · Released April 2026 # One API for every e-invoicing network. From freelancer to platform. 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 ### 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. ]() ### [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. ]() ======================================================================== # API reference # Source: https://docs.get-flowie.com/reference/index.html ======================================================================== --- source: https://docs.get-flowie.com/reference/index.html --- 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` in production · `https://back.flowie.ink` in sandbox. All endpoints below are prefixed with `/v1`. ### Quick index * [Postman collection](<#postman>) * [Authentication](<#authentication>) * [Idempotency](<#idempotency>) * [Pagination](<#pagination>) * [Rate limits](<#rate-limits>) * [Errors](<#errors>) * [Versioning](<#versioning>) * [Companies](<#create-company>) * [Documents](<#send-document>) * [Lifecycle](<#update-lifecycle>) * [Directory](<#search-directory>) * [Partners](<#create-partner>) * [Purchase orders](<#list-purchase-order-invoices>) * [Webhooks](<#create-webhook>) * [Platform](<#platform-onboard>) * [AFNOR](<#afnor-submit>) ## 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 two collection variables: * `baseUrl` — `https://back.flowie.ink/exchange` (sandbox) or `https://back.p2p-flowie.com/exchange` (production). * `token` — your API key. It is sent as `Authorization: Bearer {{token}}` on every request (collection-level bearer auth). 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 two 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. ### 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``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``event` * formatenumoptional `json``ubl-xml``cii-xml``auto``raw` `json` (default) — we render UBL. `ubl-xml` / `cii-xml` — provide your own XML in `xml`. `auto` — supply a `file`; the server sniffs the bytes and routes to the right pipeline. `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:…`, `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. * documentDocumentBodyrequired when format=json See schema below. * numberstringrequired * issueDatedaterequired * dueDatedateoptional * currencyISO 4217optional Default `EUR`. * buyerReferencestringoptional Required by many public-sector buyers (e.g. Service Executant / Code Service in FR). * orderReferencestringoptional PO number. * notestringoptional * seller / buyerPartyoptional Overrides the auto-derived seller/buyer. A `Party` object: * namestring * vatNumberstring * addressAddress Billing address — see the [Address](<#address-object>) object. Carried to the party's `billingAddress`. * shippingAddressAddress Same shape as `address`. * contactobject `{ name?, email?, phone?: string }`. The `email` is added to the party's `contacts`. * 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 Payment-means label/code, e.g. `"credit_transfer"`, `"30"`. Folded into the e-invoice's payment instructions. * ibanstring * bicstring * referencestring Remittance / structured communication. Sent as `paymentReferenceNumber`. * discountTermsarray `[{ days: int, percent: number, note?: string }]`. _Accepted but not yet emitted to the e-invoice._ * deliveryobjectoptional Delivery details, e.g. `{ date?: "YYYY-MM-DD", address?: Address }`. _Accepted but not yet emitted to the e-invoice._ * 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`. * unitstring UN/ECE Rec 20 code, e.g. `"HUR"`, `"C62"`. * vatCategorystring UNCL5305 code; defaults to `S`. See [Tax exemption & zero rate](<#tax-exemption>). * itemCodestring * customFieldsobject Keyed by field name or UUID. See [Custom fields & templates](<#custom-fields>). * periodobject `{ from: "YYYY-MM-DD", to: "YYYY-MM-DD" }`. _Accepted but not yet emitted to the e-invoice._ * 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. * 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"`); otherwise the bytes are persisted on the document file API and the response carries `deliveryStatus="stored"`, `fileId`, and `storedFormat`. * 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``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; only `street`, `streetLine2`, `city`, `postalCode` and `country` are carried to the e-invoice (mapped to `street`, `street2`, `city`, `zipCode`, `country`). * streetstring * streetLine2string Second address line (suite, box…). → `street2`. * citystring * postalCodestring → `zipCode`. * countryISO 3166-1 alpha-2 e.g. `"FR"`, `"BE"`. * state / regionstring Accepted; not currently emitted. #### 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. #### 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 — send them as UBL 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). The structured `format=json` body has **no field** for this reason, so a JSON invoice in an exempt category will fail compliance validation. To send an exempt or reverse-charge invoice today, build it as `format=ubl-xml` and put the reason inside `` yourself. `Z` (zero-rated) and `S` need no reason and work fine over JSON. 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``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``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 Same filters as the list endpoint, but accepts a JSON body with compound expressions: `AND`, `OR`, `NOT` trees. Use when a query exceeds URL length limits or you need nested predicates. #### Request body * querystringoptional Free-text query, same semantics as the list `search` param. * filtersobjectoptional Compound predicate tree (`AND` / `OR` / `NOT`) over the same fields as the list filters (direction, type, status, deliveryStatus, issueDate range, amount bounds, companyId). * sortobjectoptional Sort spec, e.g. `{"field": "issueDate", "order": "desc"}`. * limitintegeroptional Page size. Default `20`. * cursorstringoptional Opaque pagination cursor from the previous page. ### 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. * detailenumoptional `basic``full` Default `basic` (directory fields only). `full` enriches each row with access-point / SMP detail — slower, one lookup per result. * limitintegeroptional Max distinct participants to return. Default `20`. ### Lookup Peppol ID GET/v1/directory/{peppol_id} ### Verify recipient POST/v1/directory/verify The **recommended pre-flight check** before every send. Tells you whether the recipient exists, can accept the document type, and returns the access point metadata. #### 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" } [/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} ### Record a payment POST/v1/payments/documents/{documentId}/pay Body: `{"amount": …, "date": "YYYY-MM-DD", "reference": "…"}`. Automatically advances the lifecycle to `partially_paid`, or `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`. ### 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. [code] { "documentId": "doc_abc", "amountDue": 2359.50, "amountPaid": 0.00, "currency": "EUR", "dueDate": "2026-05-25", "status": "unpaid", "iban": "BE68539007547034", "reference": "INV-2026-0417" } [/code] ## AFNOR XP Z12-013 Flowie Exchange is a French **PDP** (Plateforme de Dématérialisation Partenaire) and ships a fully compliant implementation of the AFNOR XP Z12-013 facade. ERPs, OD (Opérateur de Dématérialisation), other PDPs, and the public PPF infrastructure all talk to us in the standardized format below — so swapping us in or out of an existing AFNOR-compliant integration is a base-URL change. XP Z12-013 specification (AFNOR — French e-invoicing reform) The standard defines three contractually-required interfaces that every PDP must publish. Flowie's facade implements all three at the URLs below; identifiers and verbs match the AFNOR _Annexe A_ normative grammar verbatim. * Part 1 — Service de fluxflow-service Submission, retrieval and search of structured flows (invoices, credit notes, status updates) between PDPs and between PDPs and the PPF concentrator. Mounted at `/afnor/flow-service/v1`. * Part 2 — Service d'annuairedirectory-service Lookup and reverse-lookup of recipients keyed by SIREN, SIRET, and routing codes — published once per day by the PPF and queried at runtime by every PDP. Mounted at `/afnor/directory-service/v1`. * Part 3 — Webhooksflow-service/webhooks Subscription model so receiving PDPs and ODs are notified the moment a flow targeting them is processed. **Reference documents** : * [XP Z12-013 — AFNOR boutique (norm reference)]() * [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] ## 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. #### Query parameters * byenumoptional `key``user` * sincedatetimeoptional ISO-8601 lower bound. * untildatetimeoptional ISO-8601 upper bound. [code] { "rows": [ { "apiKeyId": "key_01HY…", "label": "erp-prod", "requests": 1284, "errors": 3, "lastSeenAt": "2026-04-25T10:05:00Z" } ] } [/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 two endpoints build and parse that message. ### Build 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. Returns the normalised `subject`, the 18-field `csv` (header + row) and a `dispatched` flag. Dispatch is off by default, so this is a safe build-and-preview call: nothing is emailed to the counterparty unless dispatch is enabled server-side. #### 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": "submitted", "requestRef": "PORT-2026-0042", "taxpayerSiren": "552100554", "effectiveDate": "2026-06-01" } [/code] [code] { "subject": "[PORTABILITE][REQUEST][SUBMITTED] 552100554 PORT-2026-0042", "csv": "requestRef;state;siren;…\nPORT-2026-0042;SUBMITTED;552100554;…", "dispatched": false } [/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][SUBMITTED] 552100554 PORT-2026-0042" } [/code] [code] { "messageType": "request", "state": "submitted", "requestRef": "PORT-2026-0042", "taxpayerSiren": "552100554", "valid": true } [/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 * streetstring * citystring * postalCodestring (≤20) * regionstring * countryISO 3166-1 α-2required ### 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 ======================================================================== --- source: https://docs.get-flowie.com/reference/data-model.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/reference/document-types.html --- 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. `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_. ======================================================================== # Sandbox guide # Source: https://docs.get-flowie.com/sandbox/index.html ======================================================================== --- source: https://docs.get-flowie.com/sandbox/index.html --- 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`| `flw_test_…` · `flw_plat_test_…` · `flw_wl_test_…` Production| `https://back.p2p-flowie.com`| `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. `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 ======================================================================== --- source: https://docs.get-flowie.com/sandbox/keys.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/playground/index.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/playground/requests.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/reference/webhooks.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/build-with-ai/index.html --- 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 six tags an agent actually needs: `Documents`, `Directory`, `Companies`, `Lifecycle`, `Compliance`, `Partners`. 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`, or `Partners`. The high-leverage ones for agents: Tool| What it does ---|--- `send_document`| Send an e-invoice / credit note / order over Peppol. `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. 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 onboarding (sandbox + OAuth) # Source: https://docs.get-flowie.com/build-with-ai/agent-onboarding.html ======================================================================== --- source: https://docs.get-flowie.com/build-with-ai/agent-onboarding.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/reference/errors.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/guides/index.html --- 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]()** ## 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; 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; 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 same six| `auto`| `file` = PDF / Factur-X| `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 `0040`. _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`, 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`| 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 ======================================================================== --- source: https://docs.get-flowie.com/guides/send-invoice.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/guides/receive-invoices.html --- 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. ======================================================================== # Platform onboarding kit # Source: https://docs.get-flowie.com/guides/onboarding-kit.html ======================================================================== --- source: https://docs.get-flowie.com/guides/onboarding-kit.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/index.html --- 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| Mandatory clearance live 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 corporate live; 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 · large taxpayers from 1 July 2026| Peppol-based CTC launching mid-2026 — first MENA country to adopt the 5-corner model. [🇬🇧 **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 mandatory.")[]( "**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.") []( "**2025-07-01** · Corporate billing software — Veri*Factu obligation begins.") []( "**2026-07-01** · Self-employed — Veri*Factu obligation extended.")[]( "**≥ 2026-Q4** · Large taxpayers \(Crea y Crece\) — B2B e-invoicing mandate \(date pending royal decree\).") []( "**≥ 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** · Large taxpayers \(revenue > AED 50 m\) + government — Phase 1: PINT AE issuance + DRP reporting mandatory.")[]( "**2026-07-31** · Phase 1 taxpayers — Deadline to appoint an Accredited Service Provider.") []( "2027-01-01 · Mid-size taxpayers — Phase 2 onboarding.")[]( "2027-07-01 · All remaining VAT-registered — Phase 3 — universal scope including most free zone entities.") [🇬🇧 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-07-01**| [🇪🇸 Spain]()| Veri*Factu obligation begins. **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 mandatory. **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**| [🇪🇸 Spain]()| Veri*Factu obligation extended. **2026-07-01**| [🇦🇪 United Arab Emirates]()| Phase 1: PINT AE issuance + DRP reporting mandatory. **2026-07-31**| [🇦🇪 United Arab Emirates]()| Deadline to appoint an Accredited Service Provider. **2026-10-01**| [🇯🇵 Japan]()| Transition: input-tax credit on non-qualified invoices drops to 50%. **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. **2028-01-01**| [🇩🇪 Germany]()| Universal send obligation. **2029-10-01**| [🇯🇵 Japan]()| Final transition: input-tax credit on non-qualified invoices drops to 0%. ## 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/fr/index.html --- 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 `0040`. _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. ## 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 (`0040`) 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| `0040` 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-0040-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 (`0040`) 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 0040)]() — 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/fr/lifecycle.html --- 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° `0040`). 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/fr/refusal-rejection.html --- 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. **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. ## 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. * [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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/fr/use-cases.html --- 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 · E-invoicing integration playbook # Source: https://docs.get-flowie.com/compliance/fr/integration.html ======================================================================== --- source: https://docs.get-flowie.com/compliance/fr/integration.html --- 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° `0040`: 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° 0040) ── 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/it/index.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/it/document-types.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/be.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/at.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/bg.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/hr.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/cy.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/cz.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/dk.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/ee.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/fi.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/de.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/gr.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/hu.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/ie.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/lv.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/lt.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/lu.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/mt.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/nl.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/pl.html --- 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-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 * **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(2)** — Polish XML schema, mandatory; 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 mandatory. **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. Flowie's domestic Polish flow: `POST /v1/documents/send` → Flowie translates JSON to FA(2) → 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(2)** XML schema, defined by the Polish Ministry of Finance. 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(2) 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(2) 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(2).| 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(2)? 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. **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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/pt.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/ro.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/sk.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/si.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/es.html --- 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-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 * **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. * **Corporate Veri*Factu live since 1 July 2025** ; self-employed from 1 July 2026. * **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. **2025-07-01**| Corporate billing software| Veri*Factu obligation begins. **2026-07-01**| Self-employed| Veri*Factu obligation extended. **≥ 2026-Q4**| Large taxpayers (Crea y Crece)| B2B e-invoicing mandate (date pending royal decree). **≥ 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. Already live for corporate. **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 from corporate billing software. * 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 (already live for corporate); Crea y Crece is a future B2B e-invoicing mandate (phasing from 2026). They overlap but are separate 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. **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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/se.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/no.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/is.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/li.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/uk.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/compliance/ch.html --- 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. ======================================================================== # Webhook fixtures # Source: https://docs.get-flowie.com/fixtures/index.html ======================================================================== --- source: https://docs.get-flowie.com/fixtures/index.html --- 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 ======================================================================== --- source: https://docs.get-flowie.com/changelog.html --- 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.7.0 — PA-to-PA portability: SIRET import, bulk import & inter-PA channel 2026-08-02 New **`POST /v1/companies/import` — onboard a company for portability migration from just its SIRET.** A taxpayer files a portability request giving only its `siret` (or `siren`); Flowie derives the SIREN, country and Peppol id, resolves the legal name and current PA from the PPF annuaire, then provisions the company on Sovos (via the request's `sovosOrganizationId` or the configured `SOVOS_ORGANIZATION_ID`) or registers it locally as pending, and emits `company.imported` / `company.import.pending`. Supply `sovosOrganizationId` \+ `sovosCompanyId` instead to import an existing Sovos company. New **`POST /v1/companies/import/batch`** imports many companies in one call for bulk portability onboarding — concurrent, idempotent, with per-item `imported`/`failed` results in order (a per-item failure never aborts the batch). New **PA-to-PA portability channel —`POST /v1/portability/messages` and `POST /v1/portability/messages/parse`.** Build the normalised inter-plateforme message (codified subject + 18-field CSV + status code) a portability request must exchange with the losing/gaining PA, and parse inbound messages back into structured fields. The wire format is a best-guess pending the AIFE 15/07 annex; outbound dispatch is gated by `PORTABILITY_DISPATCH_ENABLED` (default off), so the endpoint builds and returns the message without emailing a counterparty. ## v3.6.6 — New document type: purchase request (requisition) 2026-08-02 New **`purchase-request` is now a document `type`.** [`POST /v1/documents/send`]() accepts `type: "purchase-request"` for a **purchase requisition** — the buyer's internal request to authorise a purchase, ahead of the order. It maps to the transaction-documents `PURCHASE_REQUEST` document and takes the same `from`/`to` \+ `document` body as the other order-side types. The [Document & invoice types]() page gains an _Orders, quotes & requisitions_ section covering it alongside `quote`, `purchase-order` and `sales-order` (with a ready example in the tester), and how to chain them via `orderReference`. Localized in French and Italian. ## v3.6.5 — Peppol capabilities are no longer inferred for French companies 2026-08-02 Fix **`GET /v1/directory/{peppol_id}` stops reporting Peppol capabilities a French company may not have.** When a participant was resolved from the PPF annuaire, `documentTypes` was hardcoded to `invoice`/`credit-note` and `smpStatus` was derived from the INSEE administrative state — so any going concern in France was reported as able to receive Peppol invoices. The Peppol Directory is now queried alongside the annuaire: `documentTypes`, `accessPoint`, `registeredSince` and `smpStatus` are returned only when the company is actually on the network, and left empty otherwise. Identity, VAT and the French addressing plan are unchanged. **If you branched on`smpStatus` for French participants, it can now be `null`.** Fix **Peppol participant lookups now reach the directory at all.** The `participant` filter was sent as a bare identifier (`921376265`) where the Peppol Directory requires the fully-qualified form (`iso6523-actorid-upis::0009:921376265`); it answered HTTP 400 and the error was swallowed, so _every_ participant looked unregistered. Registration status and document types are now returned for companies that really are on the network. ## v3.6.4 — French addressing plan on directory lookups 2026-08-02 Fix **`GET /v1/directory/{peppol_id}` now returns the French addressing plan for any French participant.** The `enrichment.platformeAgree` block — the routing platform an invoice must be addressed to, its addressing level and effective dates — was only attached when the participant was already registered with Flowie, so for an ordinary French company looked up straight from the PPF annuaire it was silently absent. It is now returned on both paths, under the same key. Identity and VAT enrichment are unchanged, and the two annuaire calls are made concurrently so the extra data costs no additional latency. ## v3.6.3 — Download the Postman collection 2026-08-02 New **The API Reference now has a[Postman collection]() section.** One-click download of the ready-made collection (every endpoint, pre-filled with a working example body), with import instructions and the two collection variables to set — `baseUrl` and `token` (bearer auth). The `openapi.json` spec is offered alongside for generating your own client. The collection is regenerated on every release, so it always matches the reference. ## v3.6.2 — Try any use case from the docs 2026-08-02 New **Ready-to-send example payloads for every use case.** The [Document & invoice types]() page now has a _Test any use case_ section: an example `POST /v1/documents/send` body for each scenario — standard invoice, prepayment/acompte, corrected, credit & debit notes, self-billing, reverse charge, multi-party, orders, quote and event — each with **Try in Playground** (opens the request builder prefilled, your sandbox key loaded), **Copy JSON** and **Copy curl**. Every example uses the sandbox test identifiers, so it runs as-is. Localized in French and Italian. ## v3.6.1 — Cross-tenant isolation on collection endpoints 2026-07-30 Fix **Collection endpoints now enforce your tenant on a caller-supplied`companyId`.** On `GET /v1/documents`, `POST /v1/documents/search` and `GET /v1/events`, a `companyId` filter is _validated against_ the organization your credentials are bound to instead of being applied as-is; naming an organization you can't access now returns **403`PERMISSION_DENIED`** rather than that tenant's rows. The default (unfiltered) company listing (`GET /v1/companies`) is scoped to your own organization, and `GET /v1/companies/{id}` rejects an id outside your tenant — including one reached via a `vat:`/`peppol:` alias. This closes a cross-tenant read of company records, document metadata and events. Calls that omit `companyId`, or pass your own organization, are unaffected. ## v3.6.0 — Document & invoice types reference 2026-07-28 New **A single page for every document and invoice type.** [Document & invoice types]() lays out the seven `type` values (invoice, credit-note, debit-note, purchase-order, sales-order, quote, event), the four invoice subtypes rendered as the UBL `InvoiceTypeCode` (commercial 380, prepayment 386, corrected 384, self-billed 389), and — new — dedicated deep-dives on **prepayment invoices** (_facture d'acompte_ , UNCL1001 386), **self-billing** (_autofacturation_ , the `selfBilled` flag that flips the buyer/seller roles) and reverse-charge **self-invoicing** (_autofattura_ , Italian TD16–TD29), plus a note on how employee **expenses** map onto the model (inbound invoice vs e-reporting). Reachable from the API Reference menu and sidebar. ## v3.5.2 — Fuller Compliance menu 2026-07-15 Changed The Compliance dropdown's first column now links straight into the overview's key sections — [coverage matrix](), [mandate timeline](), [how Flowie handles each regime](), and [regime types]() — alongside the France and Italy deep-dive columns. ## v3.5.1 — Multi-column menu panels 2026-07-14 Changed **Top-menu panels with several groups now lay out as columns.** Compliance shows _All countries · 🇫🇷 France · 🇮🇹 Italy_ side by side; Guides, Sandbox and Build with AI pair their pages with their journey group. Panels that would overflow the viewport flip to right-aligned automatically. ## v3.5.0 — Journey-shaped menus, deeper compliance navigation 2026-07-14 Navigation now follows the integration journey: integrate fast, test extensively, verify it works, then deep-dive the regulations — with the edge cases one click away. Changed **Deeper top-menu dropdowns.** Guides gains an _Integrate fast_ group (send an invoice, receive, ERP webhooks, order flow, go-live checklist); Sandbox a _Test extensively_ group (test identifiers, recipient simulators, lifecycle & compliance sims, forced errors, time travel); Build with AI a _Ship faster with AI_ group (MCP server, docs for agents, AI tools); and Compliance now lists the full 🇫🇷 France and 🇮🇹 Italy deep-dives — including the [refusal & rejection edge cases]() and [all 45 cas d'usage]() — from every page. Changed **Compliance sidebars nest the deep-dives.** On every country page, the France and Italy entries in the countries list expand with their sub-pages (lifecycle explorer, refusal & rejection, use cases, integration playbook, document types), so the regulation deep-dives are reachable from anywhere in the compliance section — in all three languages. Changed The redundant "Exchange" chip next to the logo was removed — it appeared inconsistently and duplicated the _Docs_ menu entry. ## v3.4.2 — Richer top-menu dropdowns 2026-07-13 Changed **Top-menu dropdowns now tell you what's inside.** Every entry carries a one-line insight under the page name — e.g. _Errors — every error code, with the fix_ , _Sandbox — simulators, test IDs & time travel_ — localized in all three languages, with a refreshed panel design (soft entrance animation, accent highlight on the current page, deeper shadow). Same links, faster orientation. ## v3.4.1 — Sidebar menus aligned with the top menu 2026-07-13 Changed **Every sidebar now leads with its section.** The first sidebar group on Reference, Guides, Build with AI, Sandbox and Playground pages lists the same pages as that section's top-menu dropdown (with the current page highlighted), so the side menu and the top menu never disagree — e.g. the [error catalog]() now shows its Reference siblings (overview, data model, webhooks) instead of a lone page outline. Page outlines are uniformly titled "On this page", matching the compliance deep-dives, and the French/Italian compliance pages' sidebar titles are now translated. Compliance country pages keep their richer country directory as the section group. ## v3.4.0 — Clean docs architecture: section directories + menu dropdowns 2026-07-13 The docs URL tree now mirrors the menu: every section is a directory, every page is directly reachable from the top menu, and the language suffix is unambiguous everywhere. Changed **Every menu section became a directory.** `reference/` ([overview](), [data model](), [errors](), [webhooks]()), `guides/` ([overview](), [onboarding kit]()), `build-with-ai/` ([overview](), [agent onboarding]()), `sandbox/` ([overview](), [API keys]()), `playground/` ([overview](), [request inspector]()) — in all three languages. Every old flat URL (e.g. `reference.html`, `keys.html`) permanently redirects to its new home, preserving query strings and anchors, so existing bookmarks, deep links and API-returned `viewerUrl`s keep working. Changed **France and Italy compliance deep-dives moved to per-country directories.** `compliance/fr-lifecycle.html` → [`compliance/fr/lifecycle.html`](), and likewise the France overview ([`compliance/fr/`]()), [refusal & rejection](), [use cases](), [integration playbook](), the Italy overview ([`compliance/it/`]()) and [document types](). The old flat names read ambiguously next to the `.fr.html`/`.it.html` language suffixes; now the directory is the country and the suffix is the language. Old URLs redirect. New **Top-menu section dropdowns.** Sections that own sub-pages (API Reference, Guides, Compliance, Build with AI, Sandbox, Playground) expose them in a dropdown, so pages like the error catalog, webhook cookbook or API-keys manager are one click from anywhere — no more reliance on buried body links. Opens on hover or keyboard focus, with a caret toggle for touch. Changed Sitemap, search index, `llms.txt`/`llms-full.txt` exports and the OpenAPI description examples all follow the new paths; the sitemap also gained the previously-missing Build with AI and compliance sub-pages. ## v3.3.0 — Update a document's lifecycle by invoice number 2026-07-06 New **Transition a document by its invoice number.** `POST /v1/documents/by-number/{number}/lifecycle` targets a document by the human-readable invoice number instead of Flowie's internal `documentId` — for integration partners (e.g. ERP/iPaaS connectors) that only hold the number. Same request body, auth and response as `POST /v1/documents/{documentId}/lifecycle`, and it runs the identical state-machine validation, tx-docs update, PPF/SDI compliance reporting and `lifecycle.updated` webhook. The number is resolved _scoped to your organization_ , so you can never transition another tenant's document. Because invoice numbers are not unique, resolution is strict: **no match → 404** , **exactly one → the transition is applied** , **more than one → 409** (re-issue against the specific `documentId`). The existing id-based route is unchanged. ## v3.2.1 — France: machine-readable lifecycle referential 2026-07-03 New **The French lifecycle referential is now published as data.** [`/schemas/fr-lifecycle-statuses.json`]() carries all 14 statuses (200–213) — tier, phase, emitter, terminality, motif & amount-block rules, UNTDID 1373 mapping, canonical transitions, and the exact Flowie call or webhook per status — validated by [`/schemas/fr-lifecycle-status.schema.json`]() (JSON Schema draft 2020-12). Versioned with the DGFiP / AFNOR spec revisions it was verified against. See [the lifecycle explorer](). ## v3.2.0 — France: interactive lifecycle reference & integration playbook 2026-07-03 Two new France compliance pages make the docs a full reference for the 2026–2027 reform. New **[Lifecycle explorer]()** — the complete AFNOR XP Z12-012 status referential (codes 200–213) as an interactive, animated state machine: filter by tier (obligatoire / recommandé / libre), play the five canonical scenarios (happy path, dispute, suspension, refusal, platform reject) with a live webhook log, and click any status for its definition, transitions and the exact API call that emits or observes it. Includes the CDAR field guide (MDT-77/105/113/114, MDG-43 amount blocks), the UNTDID 1373 mapping, and a status → API cheat-sheet. New **[Integration playbook]()** — the end-to-end French implementation path: onboarding & annuaire, receiving, sending Factur-X, the buyer/supplier status responsibility matrix, a production-grade webhook handler (idempotent, out-of-order-safe), e-reporting, the sandbox test matrix and a go-live checklist. Changed **[France overview]() corrected & re-tiered.** Deadlines fixed (large & mid-sized companies must _send_ from 1 September 2026, not 2027 — SMEs follow in 2027) and the lifecycle section now reflects the official three-tier classification: 4 obligatoires (200, 210, 212, 213), 5 recommandés (203–206, 211) and 5 coded libres (201, 202, 207–209). The French translation drops a legacy status-code table (302/304/309/40x) that never existed in the official referential. New [`GET /v1/documents/{id}/lifecycle`]() now returns `currentStatusReason` — the failing EN 16931 / CTC-FR schematron rule ids behind a validation-driven status. ## v3.1.11 — Directory-line search returns real entries 2026-06-12 Fix `POST /afnor/directory-service/v1/directory-line/search` was a stub that always returned `totalNumberOfResults: 0`. It now forwards the filter set to the PPF annuaire (`ppf-annuaire`'s `/api/search/ligne-annuaire`) and maps each entry to an AFNOR directory line (`addressingIdentifier`, `routingIdentifier`, `administrativeStatus`, …), so a SIRET that has annuaire lines now returns them. ## v3.1.10 — Original/Converted XML for JSON-created docs 2026-06-12 Fix Retrieving a document's `Original`/`Converted` XML (AFNOR `GET /afnor/flow-service/v1/flows/{id}?docType=Original` and `GET /v1/documents/{id}/xml`) returned `404` "XML not available… created from JSON without a stored XML file" for documents created from structured JSON. When no XML is physically stored, the UBL 2.4 is now rendered on the fly from the document's JSON (via the same converter the "Convert to UBL" flow uses), so the original/converted content is returned. A genuinely empty document still 404s. ## v3.1.9 — Flow search results match the query 2026-06-12 Fix `POST /afnor/flow-service/v1/flows/search` could return flows whose `flowType`/`flowDirection` didn't match the request (e.g. a `CustomerInvoice`/`Out` query surfacing `SupplierInvoice`/`In` rows), and lifecycle (`…LC`) searches returned plain invoices. Results are now hard-filtered to the requested `flowType`/`flowDirection` sets, so the response always matches the query (lifecycle searches return an empty set when no lifecycle flows exist rather than mislabeled invoices). ## v3.1.8 — API-key creation hardening 2026-06-12 Closes privilege-escalation gaps in [`POST /v1/api-keys`](). Keys remain bound to the caller's organization and tier (no `organizationId` in the request). Changed **Scopes are clamped to the caller.** A new key can no longer be granted scopes the caller doesn't hold — requesting an unheld scope (or `*`) now returns `403`. Omitting `scopes` inherits the caller's scopes instead of silently defaulting to `*`. Changed **`rateLimit.requestsPerMinute` is clamped to the tier ceiling**, so a key can't grant itself a higher request rate than its tier allows. Fix `name` must be non-empty and `expiresAt`, if given, must be in the future (a past value now returns `400` instead of minting a dead key). Keys with no `expiresAt` remain permanent. ## v3.1.7 — Company PATCH fix 2026-06-12 Fix [`PATCH /v1/companies/{id}`]() returned `500` when only `capabilities` or `compliance` were patched — the response was built from a detached DB row after the session closed. It is now built inside the session. ## v3.1.6 — AFNOR SIREN search pagination 2026-06-11 Fix **SIREN search pagination.** `POST /afnor/directory-service/v1/siren/search` only fetched `limit` rows then sliced, so pages past the first (`ignore > 0`) came back empty and `totalNumberOfResults` reflected only the fetched window. Pages now resolve correctly with an accurate total. Fix **Bounded`ignore`.** `ignore` was unbounded; a very large value forced an oversized upstream fetch. It is now capped (max 10000) and the upstream fetch is bounded regardless. ## v3.1.5 — Deterministic directory ordering 2026-06-11 Fix Directory search results are now ordered deterministically. The local-registration fallback used a bare `LIMIT` with no `ORDER BY`, so identical [`GET /v1/directory/search`]() calls could return rows in a different order. All AFNOR directory-service lookups are read-only and idempotent. ## v3.1.4 — Directory by SIREN, list direction, recoverable number lookup 2026-06-10 Fix **Directory search by SIREN/SIRET in`q`.** A bare 9-digit SIREN (or 14-digit SIRET) typed into [`GET /v1/directory/search?q=…`]() now routes to the SIREN/SIRET lookup instead of the name search, which never matched a number. The AFNOR endpoints also stop deriving the wrong SIREN from the VAT number. Fix **Per-row`direction` in document listings.** [`GET /v1/documents`]() now stamps each row `incoming`/`outgoing` (previously always `null`). An invalid `direction` now returns `400` instead of silently returning both. Fix **Recoverable lookup by invoice number.** When a number matches more than one document, [`GET /v1/documents/{number}`]() now returns the candidate ids in the `409` body instead of a dead-end error. ## v3.1.3 — Lifecycle history shows real statuses 2026-06-10 Fix [`GET /v1/documents/{id}/lifecycle`]() returned `currentStatus` and every `history[].status` as `"unknown"` (and `setBy` null) for all real documents. The history now reads the upstream audit-log's real fields (`action`, `actionBy`), and `currentStatus`/`allowedTransitions` resolve from the document's authoritative per-party lifecycle status rather than the most recent audit action. ## Docs — Unified navigation 2026-06-04 Documentation-site improvements only — no API surface change. The top navigation is now generated from a single source of truth, so every page (including the per-country compliance pages) carries the same complete, consistent set of links. Docs Top navigation is now consistent across every page. Pages that were missing the **API Keys** or **Playground** links (e.g. [Build with AI](), [agent onboarding]()) now carry the full set, and the per-country [compliance pages]() use the same generated nav. Docs Active-link highlighting now works on the French and Italian pages even with JavaScript disabled (their nav hrefs point at the English asset, so the old runtime filename match never fired). Docs Browser-storage keys used by the docs site (theme, cached tokens, saved playground vars) are now sourced from a single `window.FLW` registry loaded on every page, removing drift between the individual scripts. ## v3.1.2 — Agent handoff link 2026-05-08 Hand a single URL to your LLM and it does the integration. The user pre-approves a scope set bound to their org; the agent redeems the embedded token for an API key in one POST. No PKCE round-trip, no consent UI — the issued key acts on the user's _real_ organization, not a fresh sandbox. New **`POST /v1/oauth/handoff`** (authenticated) — mints a single-use, scope-and-org-bound handoff token and returns a ready-to-paste URL. Default 10-min TTL (60s–60min configurable). Default scopes: `send`, `receive`, `documents.read`, `companies.read`, `stats`. Caller cannot pre-approve scopes their own token doesn't hold. New **`POST /v1/oauth/handoff/exchange`** (no auth) — single-use redemption. Returns an `flw_test_…` (or `flw_live_…` for paid tiers) key bound to the original user's organization, company, and tier. New Home-page widget under ["Building with an AI agent?"]() — copy the anonymous link, or paste an existing API key to generate a personalized handoff URL in-browser. The pasted key never leaves the page. Docs [Agent onboarding]() documents three paths now: handoff (fastest), sandbox bootstrap (no auth), OAuth consent (PKCE). LLM discovery surface ([llms.txt]()) updated. ## v3.1.1 — Belgium HERMES retired 2026-05-04 Belgium's regulator-side reporting hub HERMES was decommissioned by FPS Finance on 2025-12-31 (consultation-only access expired 2026-03-31). Flowie's `HermesAdapter` and the BE branch of the compliance dispatcher have been removed. Belgian invoices are now pure Peppol — the delivery itself is the compliance event. Changed **No`compliance.reported` events fire for Belgian invoices.** If your webhook router branches on `data.platform == "HERMES"`, drop the branch — see [migration guide]() for the full diff. Changed BE-CIUS validation now surfaces synchronously: `POST /v1/documents/send` returns `422` with the BE-CIUS schematron rule code in `error.details[].code` \+ the failing XPath. Replaces the old deferred `compliance.reported.failed` \+ `HER-*` path. Same checks, faster feedback. Deprec `HERMES_REPORT_URL` / `HERMES_REPORT_TOKEN` environment variables are no longer read. `simulateCompliance: "reject_HER_001"` sandbox value is also retired. Historical `compliance_reports` rows with `platform=HERMES` are retained for audit; new ones won't be created. Docs Belgium compliance page ([compliance/be.html]()) rewritten with sourced timeline (2024-02-06 → 2028-01-01), 4-corner Peppol diagram, end-to-end send example, lifecycle table comparing FR/IT/BE, BR-BE-* error catalog, sandbox tests, and HERMES → Peppol migration table. ## v3.1.0 — AI agents & multi-org 2026-05-03 First-class Model Context Protocol surface for AI agents, plus organization switching for JWT users in multiple orgs. New **MCP servers.** `/exchange/mcp` (curated, 34 tools across Documents / Directory / Companies / Lifecycle / Compliance / Partners) and `/exchange/mcp/full` (every documented operation, 94 tools). Same Bearer token as REST, same quotas, same sandbox. [Full guide →]() New `POST /v1/documents/send` accepts `type: "event"` — pure audit-trail records, persisted as documents but never routed over Peppol. Useful for ERP-side notifications you want to keep alongside real invoices. New Organization switching for multi-org JWT users: switch the active organization without re-login. Existing `flw_*` API keys are unaffected (single-tenant by design). Changed MCP transport upgraded from legacy SSE to **streamable-HTTP** (MCP spec `2025-06-18`). Reconfigure existing clients as `"transport": "streamable-http"`. ## v3.0.0 — Unified surface 2026-04-13 First stable cut of the Exchange API. The legacy `/api/…` endpoints continue to work but are deprecated. New Resource-oriented surface under `/v1/`: `companies`, `documents`, `partners`, `webhooks`, `events`, `compliance`, `platform`, `api-keys`, `stats`. New Lifecycle state machine with auto-reporting to PPF (FR), SDI (IT), HERMES (BE). New `Idempotency-Key` is accepted on every `POST` with a 24h TTL. New Cursor-based pagination everywhere (`limit`, `cursor`, `hasMore`). New Platform keys with `X-Flowie-Company` for tenant-scoped calls. New AFNOR XP Z12-013 adapter under `/afnor/flow-service` and `/afnor/directory-service`. New cXML PunchOut callback at `/document/callback`. Deprec All `/api/…` endpoints. Sunset date: 2027-04-01. Mapping table in the [migration guide](). ## v2.9.0 2026-03-28 New `POST /v1/documents/search` accepts compound `AND`/`OR`/`NOT` filter trees. New Webhook deliveries now include `X-Flowie-Attempt` header. Changed Directory verify latency dropped from p95 420ms → 90ms via SMP cache. ## v2.8.0 2026-03-10 New AI tag recommendation: `POST /v1/categorization/objects/tags/auto`. New Structured document view at `GET /v1/documents/{id}/structured` — all scalars flattened, ready for warehouses. Fix VAT normalization now strips all whitespace (was only stripping leading/trailing). ## v2.7.0 2026-02-14 New Company identifier resolution: `vat:` and `peppol:` prefixes accepted in any `{company_id}` / `{partner_id}` path param. New Batch lifecycle update: `POST /v1/documents/lifecycle/batch`, up to 500 per call. Changed HERMES (BE) reporting enabled by default for newly created BE companies. Existing companies untouched. ## v2.6.0 2026-01-22 New Circuit-breaker visibility at `GET /health/readiness`. Per-upstream state. New Webhook secret rotation: `PATCH /v1/webhooks/{id}` with `{"rotateSecret": true}`. Old secret stays valid for 60 minutes. Fix Idempotency cache correctly distinguishes requests differing only in a query param. ## v2.5.0 2025-12-05 New PPF (FR) adapter graduated from beta. Registered PDP status confirmed by DGFiP. New ISO 20022 / SEPA export at `POST /v1/payments/export/iso20022`. ## v2.4.0 2025-10-18 New Events API (`/v1/events`) — durable twin of every webhook, replayable. New Rate-limit headers (`X-RateLimit-*`) added to every response. Changed Free tier rate limit raised from 30 to 60 req/min.