{"openapi":"3.1.0","info":{"title":"Flowie Exchange API","description":"\n## Flowie Exchange API\n\n**The Flowie Exchange API for every persona** — from a solo freelancer to a white-label ISV managing 10,000 companies.\n\nDesign principle: **Stripe Connect for Peppol**. Simple for the simple case, powerful for the complex one.\n\n### Worldwide Coverage\n\nFlowie covers **47 countries** across four continents — Europe (EU-27, EEA, UK, Switzerland),\nthe Middle East (KSA, UAE, Israel, Egypt, Türkiye), Asia (India, Singapore, Malaysia, Thailand,\nVietnam, Japan, South Korea, China), and the Pacific (Australia, New Zealand). The API auto-\ndetects the country from the identifier scheme and enriches data from national registries.\n\n### 4 Personas, 1 API\n\n| Persona | What they need | Example |\n|---------|----------------|---------|\n| **Client** | Send/receive invoices | Accountant using Flowie dashboard |\n| **Agent** | Structured data, batch ops, idempotency | AI workflow, n8n, Make.com |\n| **Integrator** | Connect their app to Peppol for their users | Odoo plugin, WinBooks connector |\n| **White-label** | Run their own branded Peppol service | Accounting firm offering Peppol |\n\n---\n\n### Authentication\n\nAll requests require a Bearer token in the `Authorization` header. **Three authentication methods** are supported — use whichever fits your use case:\n\n#### Method 1: Flowie JWT Token (existing users)\n\nIf you already use the Flowie platform, use your existing JWT token. No extra setup needed.\n\n```\nAuthorization: Bearer eyJhbGciOiJSUzI1NiIs...\n```\n\n- Issued by Flowie's Auth0 tenant\n- Organization resolved from the `_permissions` claim in the token\n- Full access (`*` scope) — permissions are managed at the organization level\n- **Best for**: Dashboard users, existing Flowie apps, internal services\n\n#### Method 2: Exchange API Key (external consumers)\n\nFor programmatic access, generate an API key from the Platform endpoints.\n\n```\nAuthorization: Bearer flw_live_a1b2c3d4e5f6...\n```\n\n**Key types:**\n\n| Type | Prefix | Scope | Use case |\n|------|--------|-------|----------|\n| **Personal** | `flw_live_` | Single company | Client, Agent |\n| **Platform** | `flw_plat_live_` | All managed companies | Integrator |\n| **White-label** | `flw_wl_live_` | Branded instance | White-label partner |\n\n**Scoped permissions** (API keys only):\n\n| Scope | Allows |\n|-------|--------|\n| `send` | Send documents |\n| `receive` | Configure receiving (webhooks, SMP) |\n| `documents.read` | Read documents, download XML/PDF |\n| `documents.search` | Search documents |\n| `documents.write` | Mark read, archive, tag, add notes |\n| `companies.read` | Read company details |\n| `companies.write` | Update company details |\n| `directory` | Search directory, verify recipients |\n| `partners` | Manage trading partners |\n| `payments` | Manage payment methods, terms, and partner sharing |\n| `lifecycle` | Update document lifecycle status |\n| `compliance` | View compliance dashboard and reports |\n| `stats` | Read usage statistics |\n| `platform` | Manage platform (onboard, keys, settings) |\n| `*` | All scopes (default for personal keys) |\n\n**Sandbox**: Use `flw_test_` prefix keys — same endpoints, no real Peppol delivery.\n\nPlatform keys use the `X-Flowie-Company` header to act on behalf of a managed company.\n\n#### Method 3: OAuth 2.0 client credentials (machine-to-machine)\n\nFor a backend that runs unattended — an ERP or D365 connector, a nightly sync, a webhook consumer —\nwith no user to sign in. Flowie issues a client id and secret per integration, backed by a technical\naccount made a member of your organization with a role.\n\n```\nPOST https://login.flowieapp.io/oauth/token\nContent-Type: application/json\n\n{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"...\",\n  \"client_secret\": \"...\",\n  \"audience\": \"https://auth.flowie.me\"\n}\n```\n\n- Token endpoint: `https://login.flowieapp.io/oauth/token` (sandbox / pilot), `https://login.flowie.me/oauth/token` (production)\n- `audience` is `https://auth.flowie.me` — **not** the API base URL. A wrong audience is refused at the token endpoint with `403 access_denied`, which is not a credentials problem\n- The token carries **no organization**: `_permissions` is empty and no org claim is issued. Name the tenant in `X-Flowie-Organization-Id` on **every** call — without it, `403 No organization found in token`; with an organization the technical account is not a member of, `403 Token does not grant access to organization '…'. Available: none.`\n- Cache the token and renew it on `expires_in` (24 h) rather than minting one per call\n- **Best for**: ERP / DCS connectors, unattended server-to-server integrations\n\nFull walkthrough: <https://docs.get-flowie.com/auth.md#machine-to-machine-oauth-20-client-credentials>\n\n#### How it works\n\nAll three return the same result to the API — the caller is identified by their organization ID,\nand endpoints behave identically regardless of which auth method is used.\n\n```\nAuthorization: Bearer <token>\n                      │\n                      ├─ starts with flw_ → API Key (database lookup)\n                      │\n                      └─ otherwise → JWT (Auth0 JWKS verification)\n                                     │\n                                     ├─ user JWT → org from the _permissions claim,\n                                     │             X-Flowie-Organization-Id picks among them\n                                     │\n                                     └─ client_credentials JWT → no org claim,\n                                                   X-Flowie-Organization-Id is the only source\n```\n\n---\n\n### Built-in Compliance\n\nFlowie auto-reports lifecycle changes to government platforms and handles hard\nclearance across **47 jurisdictions** worldwide. Highlights:\n\n**Europe**\n- **France**: PPF (Portail Public de Facturation), Flowie is registered Plateforme Agréée (PA, formerly PDP) number 0064 — mandate Sept 2026\n- **Italy**: SDI (Sistema di Interscambio) — mandatory since 2019\n- **Belgium**: Peppol BIS — B2B mandate live since 1 January 2026 (HERMES decommissioned 2025-12-31)\n- **Germany**: XRechnung / ZUGFeRD — phased B2B 2025–2028 (Wachstumschancengesetz)\n- **Spain**: Veri*Factu + Crea y Crece B2B mandate phasing\n- **Poland**: KSeF mandatory clearance — large taxpayers Feb 2026, all April 2026\n- **Romania**: RO e-Factura mandatory clearance, universal B2B since July 2024\n- **Hungary**: NAV Online Számla 3.0 — universal real-time reporting\n- **Croatia**: Fiscalisation 2.0 — B2B mandate live since January 2026\n\n**Middle East / Africa**\n- **Saudi Arabia**: ZATCA Fatoora clearance, Wave 24 (full register) by 30 June 2026\n- **UAE**: Peppol 5-corner with FTA Data Reporting Platform — Phase 1 large taxpayers from 1 July 2026\n- **Israel**: ITA SHAAM clearance — threshold drops to NIS 5,000 from June 2026\n- **Egypt**: ETA e-invoicing universal clearance + e-receipt for B2C\n- **Türkiye**: GİB e-Fatura + e-Arşiv, universal from January 2026\n\n**Asia / Pacific**\n- **India**: GST IRP-issued IRN — universal above ₹5 cr turnover, 30-day reporting cap above ₹10 cr\n- **Singapore**: Peppol InvoiceNow + GST 5-corner with IRAS — phased through 2031\n- **Malaysia**: LHDN MyInvois clearance — final wave 1 January 2026 (RM 1m floor)\n- **Australia**: Peppol PINT A-NZ via ATO — federal B2G default by Dec 2026\n- **New Zealand**: Peppol PINT A-NZ via MBIE — supplier mandate Jan 2027 (revenue > NZ$33m)\n- **Japan**: Peppol JP PINT + Qualified Invoice System\n- **South Korea**: NTS HomeTax e-Tax invoice — universal corporate clearance since 2011\n- **China**: Fully Digitalized e-fapiao + Golden Tax IV — codified by 2026 VAT Law\n- **Vietnam**: GDT mandatory e-invoice + Decree 70/2025 expansion\n- **Thailand**: Voluntary e-Tax Invoice / e-Receipt\n\nSee `docs/compliance/index.html` for the full coverage map across all 47 countries.\n\n### AFNOR XP Z12-013\n\nEndpoints under `/afnor/` implement the French AFNOR certification standard for PA / PDP interoperability.\nSame backend, different contract — use these for French e-invoicing certification compliance.\n\n### Idempotency\n\nAll `POST` endpoints accept an `Idempotency-Key` header. Same key within 24 hours returns the cached response.\n\n### Pagination\n\nAll list endpoints use cursor-based pagination: `?limit=50&cursor=xxx`\n\n### Rate Limiting\n\nEvery response carries the IETF `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`\nand `RateLimit-Policy` headers, so an agent can self-throttle without waiting for a 429.\n`RateLimit-Reset` is **delta-seconds until the window frees a slot**, not a timestamp.\n\nThe legacy `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` headers are\nstill sent for existing clients; there `X-RateLimit-Reset` is a Unix timestamp.\n\nOn `429`, honour `Retry-After` rather than retrying on a fixed timer.\n\n| Tier | Requests/min |\n|------|-------------|\n| Free | 60 |\n| Starter | 120 |\n| Pro | 300 |\n| Platform | 600 |\n| White-label | 1,200 |\n\n## Versioning and deprecation\n\n`/v1` is current and stable. **Breaking changes ship as a new path version**\n(`/v2`) rather than mutating an existing one, so a client pinned to `/v1` keeps\nworking. Additive changes — new endpoints, new optional request fields, new\nresponse fields and new enum values — land within a version at any time, so\nparse defensively and ignore what you do not recognise.\n\nAn operation or field being retired is marked `deprecated: true` in this\ndocument before it is removed, and the change is recorded in the changelog at\nhttps://docs.get-flowie.com/changelog.md with the replacement named.\n\nFull policy: https://docs.get-flowie.com/deprecation-policy\n","version":"3.0.0"},"paths":{"/health/liveness":{"get":{"tags":["Health"],"summary":"Liveness probe — is the process up?","description":"Trivial liveness probe used by Kubernetes / Cloud Run / load balancers.\n\nReturns immediately without touching any upstream — the only thing a 200\nhere proves is that the FastAPI event loop is still responsive. Never\nreturns non-2xx unless the process is wedged at the network layer.\n\n**Example response (200):**\n```json\n{\"status\": \"ok\"}\n```\n\n**Errors**\n\n| Status | Code | When                                                 |\n|--------|------|------------------------------------------------------|\n| —      | —    | This endpoint does not emit error envelopes.         |","operationId":"liveness_health_liveness_get","responses":{"200":{"description":"Process is up and the HTTP loop is responsive.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Liveness Health Liveness Get"},"example":{"status":"ok"}}}}}}},"/health/readiness":{"get":{"tags":["Health"],"summary":"Readiness probe — should traffic be sent here?","description":"Readiness probe used by orchestrators to decide whether to route traffic.\n\nInspects the in-process circuit breaker for every tracked upstream\ndependency (documents, org-v2, …). If any circuit is **open** the body\nreports ``status=degraded`` and the caller (e.g. Kubernetes) should pull\nthe pod out of rotation until the circuit closes again.\n\n**Example response (200 — all closed):**\n```json\n{\n  \"status\": \"ok\",\n  \"circuits\": {\n    \"documents\": {\"state\": \"closed\", \"failures\": 0, \"cooldown_remaining\": 0},\n    \"org-v2\":    {\"state\": \"closed\", \"failures\": 0, \"cooldown_remaining\": 0}\n  }\n}\n```\n\n**Example response (503 — one open):**\n```json\n{\n  \"status\": \"degraded\",\n  \"circuits\": {\n    \"documents\": {\"state\": \"closed\", \"failures\": 0, \"cooldown_remaining\": 0},\n    \"org-v2\":    {\"state\": \"open\",   \"failures\": 7, \"cooldown_remaining\": 23}\n  }\n}\n```\n\n**Errors**\n\n| Status | Code         | When                                                       |\n|--------|--------------|------------------------------------------------------------|\n| 503    | `degraded`   | One or more upstream circuits are open — pod not ready.    |","operationId":"readiness_health_readiness_get","responses":{"200":{"description":"All tracked upstream circuits are closed — the service is ready to take traffic.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Readiness Health Readiness Get"},"example":{"status":"ok","circuits":{"documents":{"state":"closed","failures":0,"cooldown_remaining":0},"org-v2":{"state":"closed","failures":0,"cooldown_remaining":0}}}}}},"503":{"description":"One or more circuits are open — load balancer should stop sending traffic.","content":{"application/json":{"example":{"status":"degraded","circuits":{"documents":{"state":"closed","failures":0,"cooldown_remaining":0},"org-v2":{"state":"open","failures":7,"cooldown_remaining":23}}}}}}}}},"/health/contracts":{"get":{"tags":["Health"],"summary":"Contract-monitor status across tracked upstream services","description":"Snapshot of the background contract monitor.\n\nA separate worker pings each upstream service on a schedule and records\nthe last response time + any error. This endpoint returns the current\nsnapshot — useful for dashboards and for verifying schema drift was\ndetected. ``status=degraded`` means at least one upstream contract failed\nits last check; consult the ``services`` map for the offending entry.\n\n**Example response (200 — everything healthy):**\n```json\n{\n  \"status\": \"ok\",\n  \"services\": {\n    \"documents\": {\"status\": \"ok\", \"responseMs\": 42,\n                  \"lastCheck\": \"2026-06-17T08:30:12.512000+00:00\", \"error\": null},\n    \"org-v2\":    {\"status\": \"ok\", \"responseMs\": 58,\n                  \"lastCheck\": \"2026-06-17T08:30:12.518000+00:00\", \"error\": null}\n  }\n}\n```\n\n**Example response (503 — one upstream failing):**\n```json\n{\n  \"status\": \"degraded\",\n  \"services\": {\n    \"org-v2\": {\"status\": \"error\", \"responseMs\": 5021,\n               \"lastCheck\": \"2026-06-17T08:30:11.001000+00:00\",\n               \"error\": \"timeout after 5s\"}\n  }\n}\n```\n\n**Errors**\n\n| Status | Code         | When                                                          |\n|--------|--------------|---------------------------------------------------------------|\n| 503    | `degraded`   | At least one tracked contract failed its last health check.   |","operationId":"contract_status_health_contracts_get","responses":{"200":{"description":"Background contract monitor reports every tracked upstream contract as healthy.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Contract Status Health Contracts Get"},"example":{"status":"ok","services":{"documents":{"status":"ok","responseMs":42,"lastCheck":"2026-06-17T08:30:12.512000+00:00"},"org-v2":{"status":"ok","responseMs":58,"lastCheck":"2026-06-17T08:30:12.518000+00:00"}}}}}},"503":{"description":"At least one upstream contract is failing (schema drift, 5xx, timeout).","content":{"application/json":{"example":{"status":"degraded","services":{"documents":{"status":"ok","responseMs":41,"lastCheck":"2026-06-17T08:30:12.512000+00:00"},"org-v2":{"status":"error","responseMs":5021,"lastCheck":"2026-06-17T08:30:11.001000+00:00","error":"timeout after 5s"}}}}}}}}},"/v1/companies":{"post":{"tags":["Companies"],"summary":"Create (or find-and-link) a company from a VAT number","description":"Create a company in the caller's Flowie organization.\n\nFlowie first asks org-v2 to find-or-create an organization by `vatNumber`; if the\nupstream registry already knows the company, the returned profile is enriched and\nlinked rather than duplicated. The Peppol ID is derived from the VAT prefix (ICD\nscheme code) and the local registration row is created with `status=\"active\"` on\n`POST /{id}/register`.\n\n**Example request:**\n```http\nPOST /v1/companies\nContent-Type: application/json\n\n{\n  \"vatNumber\": \"FR86797978996\",\n  \"name\": \"Acme SAS\",\n  \"address\": {\n    \"street\": \"123 Rue de la Paix\",\n    \"city\": \"Paris\",\n    \"postalCode\": \"75001\",\n    \"country\": \"FR\"\n  }\n}\n```\n\n**Example response (201):**\n```json\n{\n  \"id\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n  \"name\": \"Acme SAS\",\n  \"country\": \"FR\",\n  \"vatNumber\": \"FR86797978996\",\n  \"peppolId\": \"0009:797978996\",\n  \"status\": \"active\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | VAT malformed, unsupported country prefix.                    |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `companies:write`.                                  |\n| 422    | `validation_error`     | Body failed schema validation.                                |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx response.                           |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / timed out.                               |","operationId":"create_company_v1_companies_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyCreate"}}},"description":"`CompanyCreate` — payload to create (or find-and-link) a company from a VAT number."},"responses":{"201":{"description":"Company created — or an existing org-v2 record matched by VAT was linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse"},"example":{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","slug":"hozelock-exel","legalName":"HOZELOCK EXEL","country":"FR","vatNumber":"FR02779658772","taxNumber":"FR02779658772","registrationNumber":"77965877200024","legalAddressId":"019a6e5b-3b4c-7000-a460-a09181acc257","address":{"street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD","city":"ARNAS","postalCode":"69400","country":"FR"},"isPublic":false,"dataProviderType":"Whitepages","dataProviderSyncDate":"2025-11-10T15:21:04.068319Z","peppolId":"0009:02779658772","status":"active","smpRegistered":true,"createdAt":"2025-11-10T15:21:03.581980Z","updatedAt":"2026-03-06T09:08:26.855434Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Companies"],"summary":"List companies the caller can manage","description":"List the companies the caller's API key can act on.\n\nFor JWT auth, this is every org in the user's `_permissions` claim that has a\nFlowie row; for API-key auth, it is the single org the key is bound to. Pass the\n`cursor` returned by the previous page to walk results. Set `include_address=false`\nto skip the N parallel address resolves and get a faster, lighter list.\n\n**Example request:**\n```http\nGET /v1/companies?country=FR&limit=20&include_address=true\n```\n\n**Example response (200):**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n      \"name\": \"HOZELOCK EXEL\",\n      \"country\": \"FR\",\n      \"vatNumber\": \"FR02779658772\",\n      \"peppolId\": \"0009:02779658772\",\n      \"status\": \"active\"\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Bad cursor / unsupported filter combination.                  |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `companies:read`.                                   |\n| 422    | `validation_error`     | Query failed schema validation.                               |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx response.                           |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / timed out.                               |","operationId":"list_companies_v1_companies_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"description":"ISO 3166-1 alpha-2 country code, for example `FR`."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"description":"Restrict to documents in the given lifecycle status."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"},"description":"Free-text search term."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"},"description":"Opaque cursor from the previous page. Paginate with this rather than raising `limit` to avoid paging."},{"name":"include_address","in":"query","required":false,"schema":{"type":"boolean","description":"Resolve each row's legalAddressId in parallel (adds N round-trips). Set false for a faster, lighter list.","default":true,"title":"Include Address"},"description":"Resolve each row's legalAddressId in parallel (adds N round-trips). Set false for a faster, lighter list."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Paginated companies the API key is allowed to see.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_CompanyResponse_"},"example":{"data":[{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","slug":"hozelock-exel","legalName":"HOZELOCK EXEL","country":"FR","vatNumber":"FR02779658772","taxNumber":"FR02779658772","registrationNumber":"77965877200024","legalAddressId":"019a6e5b-3b4c-7000-a460-a09181acc257","address":{"street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD","city":"ARNAS","postalCode":"69400","country":"FR"},"isPublic":false,"dataProviderType":"Whitepages","dataProviderSyncDate":"2025-11-10T15:21:04.068319Z","peppolId":"0009:02779658772","status":"active","smpRegistered":true,"createdAt":"2025-11-10T15:21:03.581980Z","updatedAt":"2026-03-06T09:08:26.855434Z"}],"hasMore":false}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/import":{"post":{"tags":["Companies"],"summary":"Onboard a company for portability migration (SIRET-first)","description":"Onboard a company for portability migration — keyed on the taxpayer's SIRET.\n\nIn the portal flow the taxpayer files a request giving **only its SIRET**.\nFlowie derives the SIREN, country (FR) and Peppol id (`0009:<siren>`),\nresolves the legal name (and current PA) from the PPF annuaire, then attaches\nthe company to its compliance backend:\n\n- **provision** on Sovos when a Sovos org is known (request field or the\n  configured `SOVOS_ORGANIZATION_ID`),\n- **import** an existing Sovos company when a `sovosCompanyId` is supplied,\n- or register **locally as pending** when no Sovos org is known (never\n  hard-fails). Emits `company.imported` / `company.import.pending`.\n\n**Example request (portal — SIRET only):**\n```http\nPOST /v1/companies/import\nContent-Type: application/json\n\n{ \"siret\": \"92137626500018\" }\n```\n\n**Example request (import an existing Sovos company):**\n```json\n{\n  \"sovosOrganizationId\": \"SOVOS-ORG-1\",\n  \"sovosCompanyId\": \"SOVOS-CMP-42\",\n  \"companyName\": \"HOZELOCK EXEL\",\n  \"countryCode\": \"FR\"\n}\n```\n\n**Example response (201):**\n```json\n{\n  \"id\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n  \"name\": \"FLOWIE\",\n  \"country\": \"FR\",\n  \"peppolId\": \"0009:921376265\",\n  \"status\": \"active\",\n  \"smpRegistered\": true\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | No `siret`/`siren` and no `sovosCompanyId`.                   |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 422    | `validation_error`     | Body failed schema validation.                                |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Sovos / org-v2 returned a non-2xx or unexpected response.     |\n| 503    | `upstream_unavailable` | Sovos integration not configured / unreachable.               |","operationId":"import_company_v1_companies_import_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyImportRequest"}}},"description":"`CompanyImportRequest` — payload to onboard a company for portability migration (SIRET-first)."},"responses":{"201":{"description":"Company onboarded (imported or provisioned), linked in org-v2 and its Peppol registration set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse"},"example":{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","slug":"hozelock-exel","legalName":"HOZELOCK EXEL","country":"FR","vatNumber":"FR02779658772","taxNumber":"FR02779658772","registrationNumber":"77965877200024","legalAddressId":"019a6e5b-3b4c-7000-a460-a09181acc257","address":{"street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD","city":"ARNAS","postalCode":"69400","country":"FR"},"isPublic":false,"dataProviderType":"Whitepages","dataProviderSyncDate":"2025-11-10T15:21:04.068319Z","peppolId":"0009:02779658772","status":"active","smpRegistered":true,"createdAt":"2025-11-10T15:21:03.581980Z","updatedAt":"2026-03-06T09:08:26.855434Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/import/batch":{"post":{"tags":["Companies"],"summary":"Import many Sovos companies in one call (bulk portability migration)","description":"Import a list of existing Sovos companies (bulk onboarding for portability).\n\nEach item is a `CompanyImportRequest`; items are imported concurrently\n(bounded) and idempotently. A per-item failure is reported in that item's\nresult row instead of failing the whole call, so a partial batch still\nonboards every company it can.\n\nNote: the Sovos API has no \"list unimported companies\" endpoint, so the\ncaller supplies the `sovosCompanyId`s to import (e.g. from a portability\nwork-list).\n\n**Example request:**\n```http\nPOST /v1/companies/import/batch\nContent-Type: application/json\n\n{\n  \"items\": [\n    {\"sovosOrganizationId\": \"SOVOS-ORG-1\", \"sovosCompanyId\": \"CMP-1\", \"companyName\": \"ACME\", \"countryCode\": \"FR\"},\n    {\"sovosOrganizationId\": \"SOVOS-ORG-1\", \"sovosCompanyId\": \"CMP-2\", \"companyName\": \"BETA\", \"countryCode\": \"FR\"}\n  ]\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"results\": [\n    {\"index\": 0, \"sovosCompanyId\": \"CMP-1\", \"status\": \"imported\", \"companyId\": \"019a…\", \"peppolId\": \"0009:…\"},\n    {\"index\": 1, \"sovosCompanyId\": \"CMP-2\", \"status\": \"failed\", \"error\": \"Sovos import returned no taxId\"}\n  ],\n  \"imported\": 1,\n  \"failed\": 1\n}\n```","operationId":"import_companies_batch_v1_companies_import_batch_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyImportBatchRequest"}}},"description":"`CompanyImportBatchRequest` — payload to import many Sovos companies in one call (bulk portability migration)."},"responses":{"200":{"description":"Per-item import results (order preserved); per-item failures do not abort the batch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyImportBatchResponse"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/resolve":{"get":{"tags":["Companies"],"summary":"Resolve (and lazily create) a company from VAT / SIREN","description":"Resolve a company from VAT or registration number, creating it in Flowie if absent.\n\nUsed by partner-onboarding flows that want a single-call \"find-or-create\" against\nthe public registries (PPF annuaire for FR, BCE for BE, etc.). At least one of\n`vatNumber` / `registrationNumber` must be provided alongside `countryCode`.\n\n**Example request:**\n```http\nGET /v1/companies/resolve?countryCode=FR&vatNumber=FR26921376265\n```\n\n**Example response (200):**\n```json\n{\n  \"id\": \"019a6e5b-3960-7000-ab88-153aed5a6241\",\n  \"name\": \"FLOWIE\",\n  \"country\": \"FR\",\n  \"vatNumber\": \"FR26921376265\",\n  \"registrationNumber\": \"921376265\",\n  \"peppolId\": \"0009:921376265\",\n  \"status\": \"active\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Neither identifier given, unsupported country code.           |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `companies:write`.                                  |\n| 422    | `validation_error`     | Query failed schema validation.                               |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Registry / org-v2 returned a non-2xx response.                |\n| 503    | `upstream_unavailable` | Registry / org-v2 unreachable / timed out.                    |","operationId":"resolve_company_v1_companies_resolve_get","parameters":[{"name":"countryCode","in":"query","required":true,"schema":{"type":"string","description":"ISO 3166-1 alpha-2 country code (e.g. FR, BE, DE).","title":"Countrycode"},"description":"ISO 3166-1 alpha-2 country code (e.g. FR, BE, DE)."},{"name":"vatNumber","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"VAT number — with or without the country prefix.","title":"Vatnumber"},"description":"VAT number — with or without the country prefix."},{"name":"registrationNumber","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"National registration number (e.g. French SIREN, Belgian CBE).","title":"Registrationnumber"},"description":"National registration number (e.g. French SIREN, Belgian CBE)."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Resolved (or newly created) company profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse"},"example":{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","slug":"hozelock-exel","legalName":"HOZELOCK EXEL","country":"FR","vatNumber":"FR02779658772","taxNumber":"FR02779658772","registrationNumber":"77965877200024","legalAddressId":"019a6e5b-3b4c-7000-a460-a09181acc257","address":{"street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD","city":"ARNAS","postalCode":"69400","country":"FR"},"isPublic":false,"dataProviderType":"Whitepages","dataProviderSyncDate":"2025-11-10T15:21:04.068319Z","peppolId":"0009:02779658772","status":"active","smpRegistered":true,"createdAt":"2025-11-10T15:21:03.581980Z","updatedAt":"2026-03-06T09:08:26.855434Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/search":{"get":{"tags":["Companies"],"summary":"Autocomplete search in public registries (not yet in Flowie)","description":"Autocomplete search in public registries for companies not yet in Flowie.\n\nHits the country-appropriate registry (PPF annuaire for FR, etc.) and returns\nlightweight hits suitable for an autocomplete dropdown. Use `/resolve` once the\nuser picks a row to materialize the company in Flowie.\n\n**Example request:**\n```http\nGET /v1/companies/search?q=Flowie&countryCode=FR&limit=5\n```\n\n**Example response (200):**\n```json\n[\n  {\n    \"name\": \"FLOWIE\",\n    \"vatNumber\": \"FR26921376265\",\n    \"registrationNumber\": \"921376265\",\n    \"country\": \"FR\",\n    \"address\": {\"city\": \"PARIS\", \"postalCode\": \"75008\", \"country\": \"FR\"},\n    \"source\": \"ppf-annuaire\"\n  }\n]\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | `countryCode` not supported by any indexed registry.          |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `companies:read`.                                   |\n| 422    | `validation_error`     | Query failed schema validation.                               |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Registry returned a non-2xx response.                         |\n| 503    | `upstream_unavailable` | Registry unreachable / timed out.                             |","operationId":"search_companies_v1_companies_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","description":"Free-text query — matched as a company name in the upstream registry.","title":"Q"},"description":"Free-text query — matched as a company name in the upstream registry."},{"name":"countryCode","in":"query","required":true,"schema":{"type":"string","description":"ISO 3166-1 alpha-2 country code — required by the upstream registry index","title":"Countrycode"},"description":"ISO 3166-1 alpha-2 country code — required by the upstream registry index"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of results to return (1–100).","default":10,"title":"Limit"},"description":"Maximum number of results to return (1–100)."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Up to `limit` registry hits, ordered by registry relevance.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Search Companies V1 Companies Search Get"},"example":[{"name":"FLOWIE","vatNumber":"FR26921376265","registrationNumber":"921376265","country":"FR","address":{"city":"PARIS","postalCode":"75008","country":"FR"},"source":"ppf-annuaire"},{"name":"FLOWIE SOLUTIONS","vatNumber":"FR40303265045","registrationNumber":"303265045","country":"FR","address":{"city":"PARIS","postalCode":"75008","country":"FR"},"source":"ppf-annuaire"}]}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/join-requests":{"get":{"tags":["Companies"],"summary":"List the current user's pending join requests","description":"List the calling user's outbound join requests in `pending` state.\n\nOnly meaningful for JWT auth — API keys are already bound to an organization.\nUse the per-company `/{id}/join-requests/{request_id}/accept|reject` routes (as\nan org admin) to act on inbound requests.\n\n**Example request:**\n```http\nGET /v1/companies/join-requests\n```\n\n**Example response (200):**\n```json\n[\n  {\n    \"id\": \"jr_01HX2K8...\",\n    \"companyId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n    \"companyName\": \"HOZELOCK EXEL\",\n    \"status\": \"pending\",\n    \"createdAt\": \"2026-06-10T08:11:00Z\"\n  }\n]\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | API key auth (no user identity attached).                     |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx response.                           |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / timed out.                               |","operationId":"list_join_requests_v1_companies_join_requests_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Pending join requests issued by the calling user.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Join Requests V1 Companies Join Requests Get"},"example":[{"id":"jr_01HX2K8...","companyId":"019a6e5b-3960-7000-ab88-153aed5a6240","companyName":"HOZELOCK EXEL","requesterUserId":"user_01HX...","requesterEmail":"alice@example.com","status":"pending","createdAt":"2026-06-10T08:11:00Z"}]}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/{company_id}":{"get":{"tags":["Companies"],"summary":"Get a company by id, `vat:<VAT>`, or `peppol:<scheme:id>`","description":"Fetch a company by its Flowie id, or by `vat:<VAT>` / `peppol:<scheme:id>` alias.\n\nThe path parameter accepts three forms — a UUID/slug, `vat:FR26921376265`, or\n`peppol:0009:921376265`. The service joins the org-v2 organization record with\nthe local `PeppolRegistration` to surface `peppolId`, `status`, `smpRegistered`,\ncapabilities and stats in a single payload.\n\n**Example request:**\n```http\nGET /v1/companies/peppol:0009:921376265\n```\n\n**Example response (200):** see the example body above.\n\n**Example error (404):**\n```json\n{\n  \"type\": \"not_found\",\n  \"code\": \"company_not_found\",\n  \"message\": \"No company matches 'peppol:0009:921376265' in this organization.\",\n  \"details\": [],\n  \"requestId\": \"req_01HX...\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Identifier prefix malformed.                                  |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `companies:read`.                                   |\n| 404    | `company_not_found`    | No matching company in org-v2 or local registrations.         |\n| 422    | `validation_error`     | Path validation failed.                                       |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx response.                           |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / timed out.                               |","operationId":"get_company_v1_companies__company_id__get","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"},"description":"Identifier of the company."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Full company profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse"},"example":{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","slug":"hozelock-exel","legalName":"HOZELOCK EXEL","country":"FR","vatNumber":"FR02779658772","taxNumber":"FR02779658772","registrationNumber":"77965877200024","legalAddressId":"019a6e5b-3b4c-7000-a460-a09181acc257","address":{"street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD","city":"ARNAS","postalCode":"69400","country":"FR"},"isPublic":false,"dataProviderType":"Whitepages","dataProviderSyncDate":"2025-11-10T15:21:04.068319Z","peppolId":"0009:02779658772","status":"active","smpRegistered":true,"createdAt":"2025-11-10T15:21:03.581980Z","updatedAt":"2026-03-06T09:08:26.855434Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company not found in org-v2 nor in local Peppol registrations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Companies"],"summary":"Update a company (partial / JSON merge-patch semantics)","description":"Partially update a company. Only fields present in the body are written.\n\nNested dict fields (`capabilities`, `settings`, `compliance`, `metadata`) are\nshallow-merged with the existing value; pass an explicit `null` to clear a\nnested key. Pure org-v2 fields are forwarded to org-v2; Peppol-specific fields\nare persisted on the local `PeppolRegistration`.\n\n**Example request:**\n```http\nPATCH /v1/companies/019a6e5b-3960-7000-ab88-153aed5a6240\nContent-Type: application/json\n\n{\n  \"name\": \"HOZELOCK EXEL (renamed)\",\n  \"metadata\": {\"erpId\": \"SAP-001\"}\n}\n```\n\n**Example response (200):** the updated company — see body above.\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Malformed field value.                                        |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `companies:write`.                                  |\n| 404    | `company_not_found`    | No matching company.                                          |\n| 422    | `validation_error`     | Body failed schema validation.                                |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx response.                           |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / timed out.                               |","operationId":"update_company_v1_companies__company_id__patch","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"},"description":"Identifier of the company."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyUpdate"}}},"description":"`CompanyUpdate` — payload to update a company (partial / JSON merge-patch semantics)."},"responses":{"200":{"description":"Updated company profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse"},"example":{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL (renamed)","slug":"hozelock-exel","legalName":"HOZELOCK EXEL","country":"FR","vatNumber":"FR02779658772","taxNumber":"FR02779658772","registrationNumber":"77965877200024","legalAddressId":"019a6e5b-3b4c-7000-a460-a09181acc257","address":{"street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD","city":"ARNAS","postalCode":"69400","country":"FR"},"isPublic":false,"dataProviderType":"Whitepages","dataProviderSyncDate":"2025-11-10T15:21:04.068319Z","peppolId":"0009:02779658772","status":"active","smpRegistered":true,"createdAt":"2025-11-10T15:21:03.581980Z","updatedAt":"2026-03-06T09:08:26.855434Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company not found in org-v2 nor in local Peppol registrations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Companies"],"summary":"Deregister a company from Peppol","description":"Deregister a company from Peppol — marks the local registration inactive and\ntears down the Sovos managed connection (where applicable).\n\nReturns `204 No Content` on success. The org-v2 organization record is preserved\n(it is not Flowie-owned) — only the Peppol-side registration is removed.\n\n**Example request:**\n```http\nDELETE /v1/companies/019a6e5b-3960-7000-ab88-153aed5a6240\n```\n\n**Errors** (body follows `ErrorResponse` except for 204):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 204    | —                      | Company deregistered (no body).                               |\n| 400    | `invalid_request`      | Identifier prefix malformed.                                  |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `companies:write`.                                  |\n| 404    | `company_not_found`    | No matching company.                                          |\n| 422    | `validation_error`     | Path validation failed.                                       |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Sovos / org-v2 returned a non-2xx response.                   |\n| 503    | `upstream_unavailable` | Sovos / org-v2 unreachable / timed out.                       |","operationId":"deregister_company_v1_companies__company_id__delete","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"},"description":"Identifier of the company."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"204":{"description":"Company deregistered from Peppol (no body)."},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/{company_id}/register":{"post":{"tags":["Companies"],"summary":"Deploy a company on Peppol (via Sovos) and activate registration","description":"Deploy a company on Peppol (via Sovos) and activate its registration so it can send.\n\nIdempotent: for an org already provisioned on Sovos this just re-syncs and\nactivates the local registration; for a new org it provisions the Sovos\ncustomer config + managed connection (needs a genuine `sovosOrganizationId`).\n\n**Example request — re-activate existing registration:**\n```http\nPOST /v1/companies/019a6e5b-3960-7000-ab88-153aed5a6240/register\nContent-Type: application/json\n\n{}\n```\n\n**Example request — first-time provision:**\n```http\nPOST /v1/companies/019a6e5b-3960-7000-ab88-153aed5a6240/register\nContent-Type: application/json\n\n{\n  \"sovosOrganizationId\": \"sovos-org-12345\",\n  \"sovosProduct\": \"compliancenetwork\",\n  \"mode\": \"managed\"\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"id\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n  \"peppolId\": \"0009:02779658772\",\n  \"status\": \"active\",\n  \"smpRegistered\": true,\n  \"smpRegisteredAt\": \"2026-06-17T09:00:00Z\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | `sovosOrganizationId` missing for a new provision.            |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `companies:write`.                                  |\n| 404    | `company_not_found`    | No matching company.                                          |\n| 422    | `validation_error`     | Body failed schema validation.                                |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Sovos / SMP returned a non-2xx response.                      |\n| 503    | `upstream_unavailable` | Sovos / SMP unreachable / timed out.                          |","operationId":"register_company_v1_companies__company_id__register_post","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"},"description":"Identifier of the company."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/CompanyRegisterRequest"},{"type":"null"}],"title":"Body"}}},"description":"Payload to deploy a company on Peppol (via Sovos) and activate registration."},"responses":{"200":{"description":"Company provisioned and registration activated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse"},"example":{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","slug":"hozelock-exel","legalName":"HOZELOCK EXEL","country":"FR","vatNumber":"FR02779658772","taxNumber":"FR02779658772","registrationNumber":"77965877200024","legalAddressId":"019a6e5b-3b4c-7000-a460-a09181acc257","address":{"street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD","city":"ARNAS","postalCode":"69400","country":"FR"},"isPublic":false,"dataProviderType":"Whitepages","dataProviderSyncDate":"2025-11-10T15:21:04.068319Z","peppolId":"0009:02779658772","status":"active","smpRegistered":true,"createdAt":"2025-11-10T15:21:03.581980Z","updatedAt":"2026-03-06T09:08:26.855434Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company not found in org-v2 nor in local Peppol registrations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/{company_id}/join":{"post":{"tags":["Companies"],"summary":"Request to join a company (as the calling user)","description":"Request to join a Flowie company as the calling user.\n\nRequires JWT (user-bound) auth — API keys are already bound to a single org and\ncannot file join requests. An org admin then accepts or rejects via the\n`/{id}/join-requests/{request_id}/accept|reject` routes.\n\n**Example request:**\n```http\nPOST /v1/companies/019a6e5b-3960-7000-ab88-153aed5a6240/join\n```\n\n**Example response (201):**\n```json\n{\n  \"id\": \"jr_01HX2K8...\",\n  \"companyId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n  \"status\": \"pending\",\n  \"createdAt\": \"2026-06-17T09:00:00Z\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Identifier prefix malformed.                                  |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | API key auth (no user identity attached).                     |\n| 404    | `company_not_found`    | No matching company.                                          |\n| 409    | `conflict`             | User already a member, or has a pending request.              |\n| 422    | `validation_error`     | Path validation failed.                                       |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx response.                           |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / timed out.                               |","operationId":"request_join_v1_companies__company_id__join_post","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"},"description":"Identifier of the company."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"201":{"description":"Join request created and queued for an org admin's decision.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Request Join V1 Companies  Company Id  Join Post"},"example":{"id":"jr_01HX2K8...","companyId":"019a6e5b-3960-7000-ab88-153aed5a6240","status":"pending","createdAt":"2026-06-17T09:00:00Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company or join request not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — request already pending / already a member / already resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/{company_id}/join-requests/{request_id}/accept":{"post":{"tags":["Companies"],"summary":"Accept a pending join request (org admin)","description":"Accept a pending join request for a company. Requires admin scope in the target org.\n\nOn success the requesting user is granted membership in org-v2 with the default\nmember role; the join request transitions to `status=\"accepted\"`.\n\n**Example request:**\n```http\nPOST /v1/companies/019a6e5b-3960-7000-ab88-153aed5a6240/join-requests/jr_01HX2K8.../accept\n```\n\n**Example response (200):**\n```json\n{\n  \"id\": \"jr_01HX2K8...\",\n  \"companyId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n  \"status\": \"accepted\",\n  \"decidedAt\": \"2026-06-12T09:15:00Z\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Malformed ids.                                                |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Caller is not an org admin.                                   |\n| 404    | `not_found`            | Company or join request not found.                            |\n| 409    | `conflict`             | Request already resolved.                                     |\n| 422    | `validation_error`     | Path validation failed.                                       |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx response.                           |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / timed out.                               |","operationId":"accept_join_request_v1_companies__company_id__join_requests__request_id__accept_post","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"},"description":"Identifier of the company."},{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"},"description":"Identifier of the request, as returned in the `X-Request-Id` response header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Join request accepted; requester is now a member.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Accept Join Request V1 Companies  Company Id  Join Requests  Request Id  Accept Post"},"example":{"id":"jr_01HX2K8...","companyId":"019a6e5b-3960-7000-ab88-153aed5a6240","status":"accepted","decidedAt":"2026-06-12T09:15:00Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company or join request not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — request already pending / already a member / already resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/companies/{company_id}/join-requests/{request_id}/reject":{"post":{"tags":["Companies"],"summary":"Reject a pending join request (org admin)","description":"Reject a pending join request for a company. Requires admin scope in the target org.\n\nThe join request transitions to `status=\"rejected\"`; the requester is *not*\ngranted membership and may not re-request until the rejection is cleared.\n\n**Example request:**\n```http\nPOST /v1/companies/019a6e5b-3960-7000-ab88-153aed5a6240/join-requests/jr_01HX2K8.../reject\n```\n\n**Example response (200):**\n```json\n{\n  \"id\": \"jr_01HX2K8...\",\n  \"companyId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n  \"status\": \"rejected\",\n  \"decidedAt\": \"2026-06-12T09:15:00Z\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Malformed ids.                                                |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Caller is not an org admin.                                   |\n| 404    | `not_found`            | Company or join request not found.                            |\n| 409    | `conflict`             | Request already resolved.                                     |\n| 422    | `validation_error`     | Path validation failed.                                       |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx response.                           |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / timed out.                               |","operationId":"reject_join_request_v1_companies__company_id__join_requests__request_id__reject_post","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"},"description":"Identifier of the company."},{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"},"description":"Identifier of the request, as returned in the `X-Request-Id` response header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Join request rejected.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reject Join Request V1 Companies  Company Id  Join Requests  Request Id  Reject Post"},"example":{"id":"jr_01HX2K8...","companyId":"019a6e5b-3960-7000-ab88-153aed5a6240","status":"rejected","decidedAt":"2026-06-12T09:15:00Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2 / Sovos / Peppol Directory) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Company or join request not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — request already pending / already a member / already resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/send":{"post":{"tags":["Documents"],"summary":"Send a document (invoice, credit-note, order, quote, event) over Peppol","description":"Send a document over Peppol (or record an event).\n\nTwo request modes are supported, selected by whether the URL has `type=…`:\n\n1. **Structured JSON mode** (default) — POST a JSON body matching\n   `SendDocumentRequest`. The server picks the right pipeline based on\n   `format`: `json` builds UBL from structured fields, `ubl-xml` /\n   `cii-xml` validates a raw XML body, `auto` sniffs `file.content` magic\n   bytes, and `raw` stores the file as-is without Peppol routing. A\n   Factur-X PDF or a CII sent as an invoice, credit note or debit note\n   (`auto`, or `cii-xml`) is read into a structured document: number,\n   parties, lines and totals from the CII, the type from its BT-3, the\n   CII kept as the original. Send `raw` to archive one without reading it.\n   A `cii-xml` payload we cannot read is refused with `422` rather than\n   recorded as an empty document.\n2. **Raw-body mode** — pass `type`/`from`/`contentType`/`filename` as\n   query parameters and put the raw ERP payload (UBL/CII XML, PDF,\n   image, proprietary file) in the request body verbatim. The server\n   wraps it into a `FileAttachment` and delegates to the same pipeline.\n\nPass an `Idempotency-Key` header (any opaque string ≤ 255 chars) to\ndeduplicate retries: subsequent requests with the same key return the\ncached response without re-sending. Retries are deduplicated on the\npayload itself even without the header, so an ERP that resends an\nunchanged invoice never mints a second document for it.\n\n**Two success codes.** `201 Created` means a document was created.\n`200 OK` means this call created nothing — the payload had already been\nsent and the existing document is returned as-is, with the original\n`sentAt`. A `200` also carries `Idempotent-Replayed: true`, so a client\ncan branch on the header alone without diffing the body.\n\n---\n\n**Example request — JSON mode (structured invoice):**\n```json\n{\n  \"type\": \"invoice\",\n  \"format\": \"json\",\n  \"from\": \"0009:FR86797978996\",\n  \"to\": \"0208:0123456789\",\n  \"document\": {\n    \"number\": \"INV-2026-0042\",\n    \"issueDate\": \"2026-04-15\",\n    \"currency\": \"EUR\",\n    \"lines\": [\n      {\"description\": \"Consulting services\", \"quantity\": 10.0, \"unitPrice\": 150.00, \"vatRate\": 21.0}\n    ]\n  }\n}\n```\n\n**Example request — raw-body mode (UBL XML upload):**\n```\nPOST /v1/documents/send?type=invoice&from=0009:FR86797978996&contentType=application/xml&filename=INV-2026-0042.xml\nContent-Type: application/xml\n\n<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\">…</Invoice>\n```\n\n**Example response (201):**\n```json\n{\n  \"id\": \"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6\",\n  \"status\": \"sent\",\n  \"type\": \"INVOICE\",\n  \"number\": \"INV-2026-0042\",\n  \"from\": {\"peppolId\": \"0009:FR86797978996\", \"name\": \"FLOWIE\", \"companyId\": \"comp_abc123\"},\n  \"to\": {\"peppolId\": \"0208:0123456789\", \"name\": \"ACME BELGIUM\"},\n  \"totals\": {\"net\": 1500.00, \"vat\": 315.00, \"gross\": 1815.00, \"currency\": \"EUR\"},\n  \"deliveryStatus\": \"delivered\",\n  \"sentAt\": \"2026-04-15T10:32:18.421Z\",\n  \"fileId\": null,\n  \"storedFormat\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | Malformed JSON body, unknown `type`, unresolvable `from`/`to`.    |\n| 400    | `validation_error`  | Required field missing (`document.lines` for an invoice, etc.).   |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | `from` resolves to an organization the key doesn't own.           |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | tx-docs / documents / converter returned a non-2xx response.      |\n| 503    | `upstream_unavailable` | Upstream service unreachable / timed out.                      |","operationId":"send_document_v1_documents_send_post","parameters":[{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"description":"Document type, for example `invoice` or `credit-note`."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From"},"description":"Start of the date range to filter on, inclusive."},{"name":"contentType","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contenttype"},"description":"Raw-body mode only: media type of the body being posted, for example `application/xml`."},{"name":"filename","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"},"description":"Raw-body mode only: filename to record for the posted document."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"},"description":"Client-generated key making a retry safe. Writes are not idempotent by default: replaying a send without this header delivers the document twice."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"201":{"description":"Document created and accepted for delivery. In sandbox mode the response is synthesized synchronously; in live mode `deliveryStatus` may still be `pending` until the Peppol access point confirms the MLR.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendDocumentResponse"},"example":{"id":"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6","status":"sent","type":"INVOICE","number":"INV-2026-0042","from":{"peppolId":"0009:FR86797978996","name":"FLOWIE","companyId":"comp_abc123"},"to":{"peppolId":"0208:0123456789","name":"ACME BELGIUM"},"totals":{"net":1500.0,"vat":315.0,"gross":1815.0,"currency":"EUR"},"deliveryStatus":"delivered","sentAt":"2026-04-15T10:32:18.421Z"}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"200":{"description":"**Replay — nothing was created.** The same payload (or the same `Idempotency-Key`) was already sent, so the existing document is returned unchanged, `sentAt` still pointing at the original transmission. The response also carries `Idempotent-Replayed: true`. Treat this as success: the document is on the platform, it was just not sent twice.","content":{"application/json":{"example":{"id":"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6","status":"sent","type":"INVOICE","number":"INV-2026-0042","from":{"peppolId":"0009:FR86797978996","name":"FLOWIE","companyId":"comp_abc123"},"to":{"peppolId":"0208:0123456789","name":"ACME BELGIUM"},"totals":{"net":1500.0,"vat":315.0,"gross":1815.0,"currency":"EUR"},"deliveryStatus":"delivered","sentAt":"2026-04-15T10:32:18.421Z"},"schema":{"$ref":"#/components/schemas/SendDocumentResponse"}}}}},"requestBody":{"description":"Structured JSON mode (default). Omit this body and instead pass `type`/`from`/`contentType`/`filename` as query parameters to use raw-body mode, where the request body is the native ERP payload (UBL/CII XML, PDF, image, proprietary file) verbatim.","required":false,"content":{"application/json":{"schema":{"type":"object","required":["type","from"],"properties":{"type":{"type":"string","enum":["invoice","credit-note","debit-note","purchase-order","purchase-request","sales-order","quote","goods-receipt","event"]},"format":{"type":"string","enum":["json","ubl-xml","cii-xml","auto","raw"],"description":"Defaults to json."},"from":{"type":"string","description":"Sender company. Bare Peppol id (0208:0123456789) or peppol:… / vat:… / comp_… / org:… — normalised to the canonical Peppol id before delivery."},"to":{"type":["string","null"],"description":"Recipient. A Peppol id (0208:… / peppol:…) used as-is, or any resolvable identifier: vat:, siren:/siret:, duns:, gln:, lei:, eori:, email:, domain:, name:, org:/id: (or their bare forms). Non-Peppol ids are resolved (and provisioned if never seen) to a routable participant. Required unless type=event."},"document":{"$ref":"#/components/schemas/DocumentBody"},"xml":{"type":["string","null"]},"file":{"$ref":"#/components/schemas/FileAttachment"},"selfBilled":{"type":"boolean","description":"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 UNCL1001 389. Only valid for type=invoice. Default false."},"documentSubtype":{"type":["string","null"],"description":"UNCL1001 document subtype, rendered as the UBL InvoiceTypeCode (BT-3). For invoice sub-kinds beyond the plain commercial invoice (380): PREPAYMENT_INVOICE (386 - facture d'acompte), CORRECTED_INVOICE (384 - facture rectificative), SELF_BILLED_INVOICE (389). Accepts the CAPITAL_SNAKE_CASE name or the raw code. Only for type=invoice."}}}}}}}},"/v1/documents/send/batch":{"post":{"tags":["Documents"],"summary":"Send many documents in a single call","description":"Send up to N documents in one HTTP round-trip. Each item is processed\nindependently; a 4xx on one item does not abort the rest — its error is\nsurfaced inline under `results[i].error` and the outer response is still\n200. Each item may carry its own `idempotencyKey` for safe retries.\n\n---\n\n**Example request:**\n```json\n{\n  \"documents\": [\n    {\n      \"idempotencyKey\": \"batch-1-item-1\",\n      \"type\": \"invoice\",\n      \"from\": \"0009:FR86797978996\",\n      \"to\": \"0208:0123456789\",\n      \"document\": {\n        \"number\": \"INV-2026-0042\",\n        \"issueDate\": \"2026-04-15\",\n        \"currency\": \"EUR\",\n        \"lines\": [{\"description\": \"Consulting\", \"quantity\": 10, \"unitPrice\": 150.0, \"vatRate\": 21.0}]\n      }\n    },\n    {\n      \"type\": \"credit-note\",\n      \"from\": \"0009:FR86797978996\",\n      \"to\": \"0208:0123456789\",\n      \"document\": {\"number\": \"CN-2026-0001\", \"issueDate\": \"2026-04-15\", \"currency\": \"EUR\"}\n    }\n  ]\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"results\": [\n    {\"id\": \"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6\", \"status\": \"sent\", \"deliveryStatus\": \"delivered\", \"number\": \"INV-2026-0042\"},\n    {\"status\": \"error\", \"error\": {\"code\": 400, \"detail\": \"credit-note requires 'lines'\"}, \"type\": \"credit-note\"}\n  ],\n  \"summary\": {\"total\": 2, \"succeeded\": 1, \"failed\": 1}\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | `documents` empty or malformed at the envelope level.             |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | A `from` resolves to an organization the key doesn't own.         |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | Upstream tx-docs / documents service returned a non-2xx.          |\n| 503    | `upstream_unavailable` | Upstream service unreachable / timed out.                      |","operationId":"batch_send_v1_documents_send_batch_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchSendRequest"}}},"description":"`BatchSendRequest` — payload to send many documents in a single call."},"responses":{"200":{"description":"Per-document results plus an aggregate summary. Individual failures do not abort the batch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchSendResponse"},"example":{"results":[{"id":"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6","status":"sent","deliveryStatus":"delivered","number":"INV-2026-0042"},{"status":"error","error":{"code":400,"detail":"'to' is required for type=invoice"},"type":"invoice"}],"summary":{"total":2,"succeeded":1,"failed":1}}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/validate":{"post":{"tags":["Documents"],"summary":"Validate a document body without sending it","description":"Dry-run validate a document without persisting or sending it. For\n`format=ubl-xml` the payload is forwarded to the einvoice-validator\nservice (Schematron + EN16931 + national rules). For structured JSON\nonly minimal sanity checks run today (document body present, at least\none line) — full UBL generation + validation happens at send time.\n\nA `valid=false` response is still a 200 — clients should look at the\n`errors` array, not the HTTP status, to decide whether to proceed.\n\n---\n\n**Example request:**\n```json\n{\n  \"type\": \"invoice\",\n  \"format\": \"ubl-xml\",\n  \"from\": \"0009:FR86797978996\",\n  \"to\": \"0208:0123456789\",\n  \"xml\": \"<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\">…</Invoice>\"\n}\n```\n\n**Example response — valid (200):**\n```json\n{\"valid\": true, \"errors\": [], \"warnings\": [], \"preview\": null}\n```\n\n**Example response — invalid (200):**\n```json\n{\n  \"valid\": false,\n  \"errors\": [\n    {\"field\": \"/Invoice/AccountingSupplierParty\", \"rule\": \"BR-CO-26\", \"message\": \"Seller party is required\"}\n  ],\n  \"warnings\": [\n    {\"field\": \"/Invoice/PaymentMeans\", \"rule\": \"PEPPOL-EN16931-R061\", \"message\": \"IBAN should be uppercase\"}\n  ],\n  \"preview\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | Malformed body or unsupported `format`.                           |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | `from` resolves to an org the key doesn't own.                    |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | einvoice-validator returned a non-2xx response.                   |\n| 503    | `upstream_unavailable` | einvoice-validator unreachable / timed out.                    |","operationId":"validate_document_v1_documents_validate_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateRequest"}}},"description":"`ValidateRequest` — payload to validate a document body without sending it."},"responses":{"200":{"description":"Validation result. `valid=false` does NOT make this a 4xx — inspect `errors`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateResponse"},"example":{"valid":false,"errors":[{"field":"document.lines","rule":"min_items","message":"at least one line required"}],"warnings":[]}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents":{"get":{"tags":["Documents"],"summary":"List documents with filters and cursor pagination","description":"List documents the caller can see, with optional filters.\n\nFilters compose with AND semantics: `direction=incoming`, `type=INVOICE`,\n`deliveryStatus=delivered`, `status` (exact `lifecycleStatus`), `from`/`to`\n(issue-date range), `amountMin`/`amountMax`, `companyId`, and a free-text\n`search` against document number / sender / receiver name. Cursor-based\npagination — pass the `cursor` from the previous page to fetch the next one.\n\n`status` matches the document's exact `lifecycleStatus`, which is org-specific\nand may be localized — so prefer `deliveryStatus` for network delivery state.\n(`status=delivered` / `status=failed` are treated as `deliveryStatus`.)\n\n---\n\n**Example request — delivered incoming invoices:**\n```\nGET /v1/documents?direction=incoming&type=INVOICE&deliveryStatus=delivered&limit=20\n```\n\n**Example response (200):**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6\",\n      \"type\": \"INVOICE\",\n      \"direction\": \"outgoing\",\n      \"number\": \"INV-2026-0042\",\n      \"issueDate\": \"2026-04-15\",\n      \"dueDate\": \"2026-05-15\",\n      \"currency\": \"EUR\",\n      \"grossAmount\": 1815.00,\n      \"sender\": {\"peppolId\": \"0009:FR86797978996\", \"name\": \"FLOWIE\"},\n      \"receiver\": {\"peppolId\": \"0208:0123456789\", \"name\": \"ACME BELGIUM\"},\n      \"status\": \"sent\",\n      \"deliveryStatus\": \"delivered\",\n      \"receivedAt\": null\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | `cursor` malformed, `amountMin > amountMax`, unknown `type`.      |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | `companyId` belongs to another org.                               |\n| 422    | `validation_error`  | Query failed schema validation (non-numeric `limit`, etc.).       |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | tx-docs returned a non-2xx response.                              |\n| 503    | `upstream_unavailable` | tx-docs unreachable / timed out.                               |","operationId":"list_documents_v1_documents_get","parameters":[{"name":"direction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"},"description":"Restrict to documents sent or received (`inbound` / `outbound`)."},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"description":"Document type, for example `invoice` or `credit-note`."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Lifecycle status — matched against the document's exact `lifecycleStatus`, which is **org-specific and may be localized** (e.g. `draft`, `sent`, `Reçue par la plateforme`). The delivery-state values `delivered` / `failed` are routed to `deliveryStatus` instead. For network delivery state, prefer the `deliveryStatus` filter.","title":"Status"},"description":"Lifecycle status — matched against the document's exact `lifecycleStatus`, which is **org-specific and may be localized** (e.g. `draft`, `sent`, `Reçue par la plateforme`). The delivery-state values `delivered` / `failed` are routed to `deliveryStatus` instead. For network delivery state, prefer the `deliveryStatus` filter."},{"name":"deliveryStatus","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Peppol delivery state: `pending`, `delivered`, `failed`, or `rejected`.","title":"Deliverystatus"},"description":"Peppol delivery state: `pending`, `delivered`, `failed`, or `rejected`."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From"},"description":"Start of the date range to filter on, inclusive."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"},"description":"End of the date range to filter on, inclusive."},{"name":"amountMin","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amountmin"},"description":"Only documents whose total is greater than or equal to this amount."},{"name":"amountMax","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amountmax"},"description":"Only documents whose total is less than or equal to this amount."},{"name":"companyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"},"description":"Restrict results to one company, which is how you scope to a single counterparty."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"},"description":"Free-text search term."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"},"description":"Opaque cursor from the previous page. Paginate with this rather than raising `limit` to avoid paging."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Page of documents matching the filter set, ordered newest-first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_DocumentListItem_"},"example":{"data":[{"id":"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6","type":"INVOICE","direction":"outgoing","number":"INV-2026-0042","issueDate":"2026-04-15","dueDate":"2026-05-15","currency":"EUR","grossAmount":1815.0,"sender":{"peppolId":"0009:FR86797978996","name":"FLOWIE"},"receiver":{"peppolId":"0208:0123456789","name":"ACME BELGIUM"},"status":"sent","deliveryStatus":"delivered"}],"hasMore":false}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/search":{"post":{"tags":["Documents"],"summary":"Search documents with a structured filter body (POST variant of GET /documents)","description":"Search documents using a structured body instead of query parameters —\nuseful when filter combinations get long enough to bump into URL-length\nlimits, or when callers want to express nested boolean `filters` without\nURL-encoding them. Same index, scoping, and paginated response shape as\n`GET /documents`.\n\n`filters` is a predicate tree: each leaf is `{\"<field>\": <match>}` where the\nmatch is a bare scalar (exact), a bare list (any-of) or an operator object\n(`{\"$gte\": …}`), and leaves nest under `$and` / `$or`. The operators are\n`$eq` `$ne` `$lt` `$lte` `$gt` `$gte` `$contains` `$notContains` `$in`\n`$notIn` `$null` `$notNull` — there is no `$not`. Fields are the **stored** names\n(`documentType`, `issuedAt`, `totalAmountDue`, `lifecycleStatus`, …), not\nthe friendly list query params. `companyId` is special-cased as a tenant\nselector (validated against the caller's token), not a column filter.\n`sort` is a `{field: \"asc\"|\"desc\"}` map (or a `\"field:direction\"` string).\n\n---\n\n**Example request:**\n```json\n{\n  \"query\": \"ACME\",\n  \"filters\": {\n    \"$and\": [\n      {\"documentType\": {\"$eq\": \"INVOICE\"}},\n      {\"issuedAt\": {\"$gte\": \"2026-01-01\", \"$lte\": \"2026-06-30\"}},\n      {\"$or\": [\n        {\"lifecycleStatus\": {\"$eq\": \"disputed\"}},\n        {\"totalAmountDue\": {\"$gte\": 10000}}\n      ]}\n    ]\n  },\n  \"sort\": {\"issuedAt\": \"desc\"},\n  \"limit\": 50\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6\",\n      \"type\": \"INVOICE\",\n      \"direction\": \"outgoing\",\n      \"number\": \"INV-2026-0042\",\n      \"issueDate\": \"2026-04-15\",\n      \"dueDate\": \"2026-05-15\",\n      \"currency\": \"EUR\",\n      \"grossAmount\": 1815.00,\n      \"sender\": {\"peppolId\": \"0009:FR86797978996\", \"name\": \"FLOWIE\"},\n      \"receiver\": {\"peppolId\": \"0208:0123456789\", \"name\": \"ACME BELGIUM\"},\n      \"status\": \"sent\",\n      \"deliveryStatus\": \"delivered\",\n      \"receivedAt\": null\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | Malformed `filters` / `sort`, unknown field.                      |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | `companyId` filter belongs to another org.                        |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | tx-docs returned a non-2xx response.                              |\n| 503    | `upstream_unavailable` | tx-docs unreachable / timed out.                               |","operationId":"search_documents_v1_documents_search_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}},"description":"`SearchRequest` — payload to search documents with a structured filter body (POST variant of GET /documents)."},"responses":{"200":{"description":"Page of documents matching the search body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_DocumentListItem_"},"example":{"data":[{"id":"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6","type":"INVOICE","direction":"outgoing","number":"INV-2026-0042","issueDate":"2026-04-15","dueDate":"2026-05-15","currency":"EUR","grossAmount":1815.0,"sender":{"peppolId":"0009:FR86797978996","name":"FLOWIE"},"receiver":{"peppolId":"0208:0123456789","name":"ACME BELGIUM"},"status":"sent","deliveryStatus":"delivered"}],"hasMore":false}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/{document_id}":{"get":{"tags":["Documents"],"summary":"Get the full nested representation of a single document","description":"Return the complete document representation: header + nested\n`document` body (lines, totals, payment, etc.) + status + delivery\nmetadata. This is the canonical \"give me everything about doc X\" call\n— for a flat agent-friendly shape use `/{document_id}/structured`.\n\n---\n\n**Example response (200):**\n```json\n{\n  \"id\": \"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6\",\n  \"type\": \"INVOICE\",\n  \"direction\": \"outgoing\",\n  \"number\": \"INV-2026-0042\",\n  \"issueDate\": \"2026-04-15\",\n  \"dueDate\": \"2026-05-15\",\n  \"currency\": \"EUR\",\n  \"grossAmount\": 1815.00,\n  \"netAmount\": 1500.00,\n  \"vatAmount\": 315.00,\n  \"sender\": {\"peppolId\": \"0009:FR86797978996\", \"name\": \"FLOWIE\"},\n  \"receiver\": {\"peppolId\": \"0208:0123456789\", \"name\": \"ACME BELGIUM\"},\n  \"status\": \"sent\",\n  \"deliveryStatus\": \"delivered\",\n  \"lifecycleStatus\": \"approved\",\n  \"sentAt\": \"2026-04-15T10:32:18.421Z\",\n  \"createdAt\": \"2026-04-15T10:32:00.000Z\",\n  \"updatedAt\": \"2026-04-15T10:32:18.421Z\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Document belongs to a different organization.                     |\n| 404    | `not_found`         | No document with that id.                                         |\n| 422    | `validation_error`  | Path id failed schema validation.                                 |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | tx-docs returned a non-2xx response.                              |\n| 503    | `upstream_unavailable` | tx-docs unreachable / timed out.                               |","operationId":"get_document_v1_documents__document_id__get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Full document, including the nested `document` body and metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentResponse"},"example":{"id":"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6","type":"INVOICE","direction":"outgoing","number":"INV-2026-0042","issueDate":"2026-04-15","dueDate":"2026-05-15","currency":"EUR","grossAmount":1815.0,"netAmount":1500.0,"vatAmount":315.0,"sender":{"peppolId":"0009:FR86797978996","name":"FLOWIE"},"receiver":{"peppolId":"0208:0123456789","name":"ACME BELGIUM"},"status":"sent","deliveryStatus":"delivered","lifecycleStatus":"approved","sentAt":"2026-04-15T10:32:18.421Z","createdAt":"2026-04-15T10:32:00.000Z","updatedAt":"2026-04-15T10:32:18.421Z"}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/{document_id}/xml":{"get":{"tags":["Documents"],"summary":"Download the canonical UBL XML for a document","description":"Download the canonical UBL XML representation of a document — what\nactually traveled over Peppol. For inbound documents this is the\nreceived payload; for outbound it's what we generated and submitted.\nResponse media type is `application/xml`.\n\n---\n\n**Example request:**\n```\nGET /v1/documents/doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6/xml\nAccept: application/xml\n```\n\n**Example response (200, body):**\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\">\n  <cbc:ID>INV-2026-0042</cbc:ID>\n  <cbc:IssueDate>2026-04-15</cbc:IssueDate>\n  …\n</Invoice>\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Document belongs to a different organization.                     |\n| 404    | `not_found`         | No document with that id, or no XML payload stored.               |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | documents service returned a non-2xx response.                    |\n| 503    | `upstream_unavailable` | documents service unreachable / timed out.                     |","operationId":"get_document_xml_v1_documents__document_id__xml_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Raw UBL XML bytes.","content":{"application/json":{"schema":{}},"application/xml":{"example":"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\">\n  <cbc:ID>INV-2026-0042</cbc:ID>\n  <cbc:IssueDate>2026-04-15</cbc:IssueDate>\n  …\n</Invoice>","schema":{"type":"string"}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/{document_id}/pdf":{"get":{"tags":["Documents"],"summary":"Download a PDF rendering of a document","description":"Download a PDF rendering of the document. For documents that\nalready had a PDF attached at send time (PDF/A-3 with embedded UBL)\nthe original PDF is returned verbatim; otherwise the document-\nconversion service renders one on the fly from the UBL XML. Response\nmedia type is `application/pdf`.\n\n---\n\n**Example request:**\n```\nGET /v1/documents/doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6/pdf\nAccept: application/pdf\n```\n\nReturns PDF bytes in the response body.\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Document belongs to a different organization.                     |\n| 404    | `not_found`         | No document with that id, or no renderable payload.               |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | document-conversion service returned a non-2xx.                   |\n| 503    | `upstream_unavailable` | document-conversion service unreachable / timed out.           |","operationId":"get_document_pdf_v1_documents__document_id__pdf_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"PDF bytes (`application/pdf`).","content":{"application/json":{"schema":{}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/{document_id}/structured":{"get":{"tags":["Documents"],"summary":"Get a flat agent-friendly view of a single document","description":"Return a flat, primitives-only view of a document — everything an\nLLM agent or a tabular UI needs to render or reason about a doc, with\nno nested objects. Use this when you don't need the full UBL line\nbreakdown.\n\n---\n\n**Example response (200):** see the `responses` block above.\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Document belongs to a different organization.                     |\n| 404    | `not_found`         | No document with that id.                                         |\n| 422    | `validation_error`  | Path id failed schema validation.                                 |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | tx-docs returned a non-2xx response.                              |\n| 503    | `upstream_unavailable` | tx-docs unreachable / timed out.                               |","operationId":"get_document_structured_v1_documents__document_id__structured_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Flat, primitives-only representation — ideal for LLM tool-use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StructuredDocumentResponse"},"example":{"id":"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6","type":"INVOICE","direction":"outgoing","number":"INV-2026-0042","issueDate":"2026-04-15","dueDate":"2026-05-15","currency":"EUR","grossAmount":1815.0,"netAmount":1500.0,"vatAmount":315.0,"status":"sent","lifecycleStatus":"approved","deliveryStatus":"delivered","senderPeppolId":"0009:FR86797978996","senderName":"FLOWIE","senderVatNumber":"FR26921376265","receiverPeppolId":"0208:0123456789","receiverName":"ACME BELGIUM","receiverVatNumber":"BE0123456789","buyerReference":"PO-2026-0815","orderReference":"ORD-2026-0042","paymentIban":"FR7630006000011234567890189","paymentReference":"INV-2026-0042","note":"Payment due in 30 days","sentAt":"2026-04-15T10:32:18.421Z","createdAt":"2026-04-15T10:32:00.000Z","updatedAt":"2026-04-15T10:32:18.421Z"}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/{document_id}/actions":{"post":{"tags":["Documents"],"summary":"Perform an inbox-style action on a document (tag, archive, assign, link, …)","description":"Apply a side-effectful action to a document — mark-read / mark-unread,\narchive / unarchive, tag / untag, assign / unassign, add-note, link.\nEach action maps to an upstream call (tags service, inbox service,\nchat service for notes, or a tx-docs relationship link).\n\n---\n\n**Example request — tag a document:**\n```json\n{\"action\": \"tag\", \"tag\": \"needs-review\"}\n```\n\n**Example request — assign to a user:**\n```json\n{\"action\": \"assign\", \"userId\": \"usr_5f2a8c\"}\n```\n\n**Example request — link to a related document:**\n```json\n{\"action\": \"link\", \"relatedDocumentId\": \"doc_71b3c8e4d9a2f5c8\"}\n```\n\n**Example response (200):** see `GET /v1/documents/{document_id}`.\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | Unknown `action`, or required field missing (e.g. `tag` for tag). |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Document belongs to a different organization.                     |\n| 404    | `not_found`         | No document with that id, or `relatedDocumentId` not found.       |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected server error.                                          |\n| 502    | `upstream_error`    | tags / inbox / chat / tx-docs returned a non-2xx response.        |\n| 503    | `upstream_unavailable` | Upstream service unreachable / timed out.                      |","operationId":"perform_action_v1_documents__document_id__actions_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionRequest"}}},"description":"`ActionRequest` — payload to perform an inbox-style action on a document (tag, archive, assign, link, …)."},"responses":{"200":{"description":"Document after the action was applied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentResponse"},"example":{"id":"doc_01HF8K3JD9V2M8X7Q9N3R4T5Y6","type":"INVOICE","direction":"outgoing","number":"INV-2026-0042","issueDate":"2026-04-15","dueDate":"2026-05-15","currency":"EUR","grossAmount":1815.0,"netAmount":1500.0,"vatAmount":315.0,"sender":{"peppolId":"0009:FR86797978996","name":"FLOWIE"},"receiver":{"peppolId":"0208:0123456789","name":"ACME BELGIUM"},"status":"sent","deliveryStatus":"delivered","lifecycleStatus":"approved","sentAt":"2026-04-15T10:32:18.421Z","createdAt":"2026-04-15T10:32:00.000Z","updatedAt":"2026-04-15T10:32:18.421Z"}}}},"400":{"description":"Invalid request body or query — malformed JSON, unknown document `type`, unresolvable `from`/`to` identifier, conflicting filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document or sender belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (tx-docs, validator, converter, inbox) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/{document_id}/lifecycle":{"get":{"tags":["Lifecycle"],"summary":"Read a document's lifecycle history and current status","description":"Return the lifecycle audit log for a document — every status the\ndocument has been in, who set it, when, with optional notes — plus the\ncurrent status, the set of legal next transitions, and any compliance\nreporting metadata (e.g. PPF acknowledgement for FR, SDI receipt for IT).\n\nThe state machine is:\n  `draft` / `received` → `under_review` → `approved` / `rejected`,\n  `approved` → `partially_paid` → `paid`, `approved` → `disputed`,\n  `disputed` → `approved` / `rejected`. Terminal states are `paid` and\n  `rejected`.\n\n---\n\n**Example response (200):**\n```json\n{\n  \"documentId\": \"doc_test001\",\n  \"currentStatus\": \"approved\",\n  \"currentStatusAt\": \"2026-04-15T10:32:18.421Z\",\n  \"compliance\": {},\n  \"history\": [\n    {\"status\": \"received\", \"at\": \"2026-04-14T09:12:03.100Z\", \"setBy\": \"system\", \"note\": null, \"reported\": false, \"reportedTo\": null, \"reportedAt\": null},\n    {\"status\": \"under_review\", \"at\": \"2026-04-14T14:08:45.220Z\", \"setBy\": \"usr_5f2a\", \"note\": \"Awaiting PO match\", \"reported\": false, \"reportedTo\": null, \"reportedAt\": null},\n    {\"status\": \"approved\", \"at\": \"2026-04-15T10:32:18.421Z\", \"setBy\": \"usr_5f2a\", \"note\": \"Invoice verified against PO\", \"reported\": false, \"reportedTo\": null, \"reportedAt\": null}\n  ],\n  \"allowedTransitions\": [\"partially_paid\", \"paid\", \"disputed\"]\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Document belongs to a different organization.                     |\n| 404    | `not_found`         | No document with that id.                                         |\n| 422    | `validation_error`  | Path id failed schema validation.                                 |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 502    | `upstream_error`    | tx-docs returned a non-2xx response when fetching the audit log.  |\n| 503    | `upstream_unavailable` | tx-docs unreachable / timed out.                               |","operationId":"get_lifecycle_v1_documents__document_id__lifecycle_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Full lifecycle history + current state machine status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleHistoryResponse"},"example":{"documentId":"doc_test001","currentStatus":"approved","currentStatusAt":"2026-04-15T10:32:18.421Z","compliance":{},"history":[{"status":"received","at":"2026-04-14T09:12:03.100Z","setBy":"system","reported":false},{"status":"under_review","at":"2026-04-14T14:08:45.220Z","setBy":"usr_5f2a","note":"Awaiting PO match","reported":false},{"status":"approved","at":"2026-04-15T10:32:18.421Z","setBy":"usr_5f2a","note":"Invoice verified against PO","reported":false}],"allowedTransitions":["partially_paid","paid","disputed"]}}}},"400":{"description":"Invalid request — unknown `status`, illegal transition from the current state, or missing required payment fields for `status=paid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / path failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream transaction-documents service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream transaction-documents service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Lifecycle"],"summary":"Transition a document to a new lifecycle status","description":"Move a document to a new status. The target must be a legal successor\nof the current status in the finite-state machine\n(`ALLOWED_TRANSITIONS`) — illegal transitions return 400. For\nFR and IT documents the status change is also reported to the national\nCTC platform (PPF / SDI) and the upstream acknowledgement is surfaced\nin the response under `compliance`.\n\nPayment fields (`paymentDate`, `paymentAmount`, `paymentCurrency`,\n`paymentReference`) are required when `status=paid` or\n`status=partially_paid`.\n\nA coded reason (`reasonCode`) is required for `rejected` and `disputed`:\nuse one of the 14 official Peppol status reason codes (OPStatusReason) —\n`NON`, `REF`, `LEG`, `REC`, `QUA`, `DEL`, `PRI`, `QTY`, `ITM`, `PAY`,\n`UNR`, `FIN`, `PPD`, `OTH` — documented with their meanings at\n<https://docs.get-flowie.com/reference/#reason-codes>. On the French DGFiP\nleg the code is forwarded verbatim as MDT-113 (AFNOR XP Z12-012 motif),\nand `reasonCode=\"suspended\"` with `status=disputed` transmits\n208 Suspendue instead of 207.\n\n**Prioritize on hold over refusing directly.** `rejected` is terminal —\nthe supplier must issue a corrective invoice. If the disagreement may\nstill be resolved, send the reversible statuses first: `disputed`\n(contest) or `disputed` + `reasonCode=\"suspended\"` (on hold pending\ndocuments); both keep the invoice alive and resolve back to approval.\nWhichever you send, make the reason actionable: the code names the\nproblem, the free-text `reason` says exactly what is missing or wrong\n(line, expected value) so the supplier can fix it first time.\n\n---\n\n**Example request — approve an invoice:**\n```json\n{\n  \"status\": \"approved\",\n  \"note\": \"Invoice verified against PO\"\n}\n```\n\n**Example request — reject with a coded reason:**\n```json\n{\n  \"status\": \"rejected\",\n  \"reasonCode\": \"PRI\",\n  \"reason\": \"Unit price on line 3 does not match the quote\"\n}\n```\n\n**Example request — mark as paid:**\n```json\n{\n  \"status\": \"paid\",\n  \"paymentDate\": \"2026-04-15\",\n  \"paymentAmount\": 1815.00,\n  \"paymentCurrency\": \"EUR\",\n  \"paymentReference\": \"PAY-2026-0042\"\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"documentId\": \"doc_test001\",\n  \"previousStatus\": \"received\",\n  \"currentStatus\": \"approved\",\n  \"updatedAt\": \"2026-04-15T10:32:18.421Z\",\n  \"compliance\": {},\n  \"allowedTransitions\": [\"partially_paid\", \"paid\", \"disputed\"]\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_transition`| Target `status` is not in `ALLOWED_TRANSITIONS` from the current. |\n| 400    | `validation_error`  | Required payment field missing for `paid`/`partially_paid`.       |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Document belongs to a different organization.                     |\n| 404    | `not_found`         | No document with that id.                                         |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 502    | `upstream_error`    | tx-docs or compliance platform (PPF/SDI) returned an error.       |\n| 503    | `upstream_unavailable` | tx-docs or compliance platform unreachable / timed out.        |","operationId":"update_lifecycle_v1_documents__document_id__lifecycle_post","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleUpdateRequest"}}},"description":"`LifecycleUpdateRequest` — payload to transition a document to a new lifecycle status."},"responses":{"200":{"description":"Status updated successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleUpdateResponse"},"example":{"documentId":"doc_test001","previousStatus":"received","currentStatus":"approved","updatedAt":"2026-04-15T10:32:18.421Z","compliance":{},"allowedTransitions":["partially_paid","paid","disputed"]}}}},"400":{"description":"Invalid request — unknown `status`, illegal transition from the current state, or missing required payment fields for `status=paid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / path failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream transaction-documents service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream transaction-documents service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/by-number/{number}/lifecycle":{"post":{"tags":["Lifecycle"],"summary":"Transition a document to a new lifecycle status, targeted by invoice number","description":"Move a document to a new status, targeting it by its **invoice number**\ninstead of Flowie's internal `documentId`.\n\nIntegration partners frequently only hold the human-readable invoice number\n(the value on the invoice), not our internal id. This route resolves the\nnumber to exactly one document **scoped to the caller's organization**, then\napplies the *same* transition as `POST /documents/{documentId}/lifecycle` —\nidentical finite-state-machine validation (`ALLOWED_TRANSITIONS`), the same\ntx-docs update, the same PPF/SDI compliance reporting for FR/IT documents,\nand the same `lifecycle.updated` webhook. The request body and success\nresponse are identical to the id-based route.\n\nBecause invoice numbers are **not unique** (the same number can exist as a\nsale and a purchase, or across periods), resolution is strict:\n\n- **0 matches → 404** — no document with that number that your organization\n  is a party on.\n- **exactly 1 match → 200** — the transition is applied.\n- **more than 1 match → 409** — ambiguous; re-issue the call against\n  `POST /documents/{documentId}/lifecycle` with the specific `documentId`.\n\nPayment fields (`paymentDate`, `paymentAmount`, `paymentCurrency`,\n`paymentReference`) are required when `status=paid` or\n`status=partially_paid`, exactly as on the id-based route. Likewise a\n`reasonCode` is required for `rejected` / `disputed` — the 14 official\nPeppol status reason codes are documented at\n<https://docs.get-flowie.com/reference/#reason-codes>.\n\n---\n\n**Example request — approve invoice `INV-2026-0042`:**\n```\nPOST /v1/documents/by-number/INV-2026-0042/lifecycle\n```\n```json\n{\n  \"status\": \"approved\",\n  \"note\": \"Invoice verified against PO\"\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"documentId\": \"doc_test001\",\n  \"previousStatus\": \"received\",\n  \"currentStatus\": \"approved\",\n  \"updatedAt\": \"2026-04-15T10:32:18.421Z\",\n  \"compliance\": {},\n  \"allowedTransitions\": [\"partially_paid\", \"paid\", \"disputed\"]\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_transition`| Target `status` is not in `ALLOWED_TRANSITIONS` from the current. |\n| 400    | `validation_error`  | Required payment field missing for `paid`/`partially_paid`.       |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 404    | `not_found`         | No document with that invoice number in your organization.        |\n| 409    | `conflict`          | Invoice number matches multiple documents — target by documentId. |\n| 422    | `validation_error`  | Body / path failed schema validation (legacy handler).            |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 502    | `upstream_error`    | tx-docs or compliance platform (PPF/SDI) returned an error.       |\n| 503    | `upstream_unavailable` | tx-docs or compliance platform unreachable / timed out.        |","operationId":"update_lifecycle_by_number_v1_documents_by_number__number__lifecycle_post","parameters":[{"name":"number","in":"path","required":true,"schema":{"type":"string","title":"Number"},"description":"The document's own number, as printed on it."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleUpdateRequest"}}},"description":"`LifecycleUpdateRequest` — payload to transition a document to a new lifecycle status, targeted by invoice number."},"responses":{"200":{"description":"Status updated successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LifecycleUpdateResponse"},"example":{"documentId":"doc_test001","previousStatus":"received","currentStatus":"approved","updatedAt":"2026-04-15T10:32:18.421Z","compliance":{},"allowedTransitions":["partially_paid","paid","disputed"]}}}},"400":{"description":"Invalid request — unknown `status`, illegal transition from the current state, or missing required payment fields for `status=paid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / path failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream transaction-documents service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream transaction-documents service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No document with that invoice number in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Multiple documents match this invoice number — target by documentId instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/documents/lifecycle/batch":{"post":{"tags":["Lifecycle"],"summary":"Transition many documents in one call","description":"Apply many lifecycle updates in one HTTP round-trip. Each entry in\n`updates` is shaped like the body of `POST /documents/{id}/lifecycle`\nplus a top-level `documentId`. Updates are applied sequentially; a\nfailed transition on one item does not abort the rest.\n\n---\n\n**Example request:**\n```json\n{\n  \"updates\": [\n    {\n      \"documentId\": \"doc_test001\",\n      \"status\": \"approved\",\n      \"note\": \"Invoice verified against PO\"\n    },\n    {\n      \"documentId\": \"doc_test002\",\n      \"status\": \"paid\",\n      \"paymentDate\": \"2026-04-15\",\n      \"paymentAmount\": 4200.00,\n      \"paymentCurrency\": \"EUR\",\n      \"paymentReference\": \"PAY-2026-0043\"\n    }\n  ]\n}\n```\n\n**Example response (200):**\n```json\n[\n  {\n    \"documentId\": \"doc_test001\",\n    \"previousStatus\": \"received\",\n    \"currentStatus\": \"approved\",\n    \"updatedAt\": \"2026-04-15T10:32:18.421Z\",\n    \"compliance\": {},\n    \"allowedTransitions\": [\"partially_paid\", \"paid\", \"disputed\"]\n  },\n  {\n    \"documentId\": \"doc_test002\",\n    \"previousStatus\": \"approved\",\n    \"currentStatus\": \"paid\",\n    \"updatedAt\": \"2026-04-15T10:33:01.117Z\",\n    \"compliance\": {},\n    \"allowedTransitions\": []\n  }\n]\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | `updates` empty or contains malformed entries.                    |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | One of the documents belongs to a different organization.         |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 502    | `upstream_error`    | tx-docs or compliance platform returned an error.                 |\n| 503    | `upstream_unavailable` | tx-docs or compliance platform unreachable / timed out.        |","operationId":"batch_lifecycle_update_v1_documents_lifecycle_batch_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchLifecycleUpdateRequest"}}},"description":"`BatchLifecycleUpdateRequest` — payload to transition many documents in one call."},"responses":{"200":{"description":"List of per-document lifecycle update results, in the same order as the request. Errors on individual items are surfaced inline; the overall call still succeeds.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LifecycleUpdateResponse"},"title":"Response Batch Lifecycle Update V1 Documents Lifecycle Batch Post"},"example":[{"documentId":"doc_test001","previousStatus":"received","currentStatus":"approved","updatedAt":"2026-04-15T10:32:18.421Z","compliance":{},"allowedTransitions":["partially_paid","paid","disputed"]},{"documentId":"doc_test002","previousStatus":"approved","currentStatus":"paid","updatedAt":"2026-04-15T10:33:01.117Z","compliance":{},"allowedTransitions":[]}]}}},"400":{"description":"Invalid request — unknown `status`, illegal transition from the current state, or missing required payment fields for `status=paid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Document belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / path failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream transaction-documents service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream transaction-documents service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/compliance/status":{"get":{"tags":["Compliance"],"summary":"Per-company compliance posture across registered jurisdictions","description":"Return a compliance snapshot for every company the calling API key owns.\n\nFor each Peppol registration scoped to the caller's organisation, this rolls up the\ncounts of `compliance_report` rows by status (`synced`, `pending`, `failed`, `rejected`)\nand derives an overall `status` per company:\n\n- `compliant` — at least one synced report, no failures / rejections.\n- `pending` — only pending reports, none failed.\n- `non_compliant` — any failed or rejected report.\n- otherwise the raw registration status (e.g. `awaiting_smp`).\n\nUseful as a single-pane dashboard for finance / ops to see which entities are at risk\nacross the regimes the org reports to (PPF, SDI, Peppol-BIS, XRechnung, KSeF, ZATCA, …).\n\n---\n\n**Example request:**\n```\nGET /v1/compliance/status?country=FR\nAuthorization: Bearer flw_live_***\n```\n\n**Example response (200):**\n```json\n{\n  \"companies\": [\n    {\n      \"companyId\": \"comp_abc123\",\n      \"peppolId\": \"0009:921376265\",\n      \"country\": \"FR\",\n      \"status\": \"compliant\",\n      \"smpRegistered\": true,\n      \"smpRegisteredAt\": \"2025-02-10T14:23:11Z\",\n      \"compliance\": {\"synced\": 142, \"pending\": 3, \"failed\": 0, \"rejected\": 0},\n      \"lastCheckedAt\": \"2026-06-17T08:01:42Z\"\n    }\n  ]\n}\n```\n\n**Example response (401):**\n```json\n{\n  \"type\": \"about:blank\",\n  \"code\": \"unauthorized\",\n  \"message\": \"Missing or invalid API key.\",\n  \"details\": [],\n  \"requestId\": \"req_01HZX...\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Unknown country code, malformed `companyId`, conflicting filters.|\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header or API key.            |\n| 403    | `forbidden`           | Key valid but lacks `compliance:read` scope.                    |\n| 422    | `validation_error`    | Query schema validation failed (e.g. `country` not 2 letters).  |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Aggregation failed unexpectedly.                                |\n| 502    | `upstream_error`      | A backing e-invoicing platform returned a non-2xx response.     |\n| 503    | `upstream_unavailable`| Backing compliance platform unreachable / timed out.            |","operationId":"get_compliance_status_v1_compliance_status_get","parameters":[{"name":"companyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict the result to a single company / Flowie organisation. Slug-style id, e.g. `comp_abc123`.","examples":["comp_abc123"],"title":"Companyid"},"description":"Restrict the result to a single company / Flowie organisation. Slug-style id, e.g. `comp_abc123`."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Za-z]{2}$"},{"type":"null"}],"description":"ISO 3166-1 alpha-2 country code (uppercase). Filters both the companies surfaced and the compliance reports counted per jurisdiction. Examples: `FR` (PPF), `IT` (SDI), `BE` (Peppol-BIS), `DE` (XRechnung), `PL` (KSeF), `RO` (e-Factura), `SA` (ZATCA), `IN` (GST-IRP).","examples":["FR","IT","BE","DE","PL"],"title":"Country"},"description":"ISO 3166-1 alpha-2 country code (uppercase). Filters both the companies surfaced and the compliance reports counted per jurisdiction. Examples: `FR` (PPF), `IT` (SDI), `BE` (Peppol-BIS), `DE` (XRechnung), `PL` (KSeF), `RO` (e-Factura), `SA` (ZATCA), `IN` (GST-IRP)."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceStatusResponse"},"example":{"companies":[{"companyId":"comp_abc123","vatNumber":"FR26921376265","peppolId":"0009:921376265","name":"FLOWIE","country":"FR","status":"compliant","smpRegistered":true,"smpRegisteredAt":"2025-02-10T14:23:11Z","compliance":{"synced":142,"pending":3,"failed":0,"rejected":0},"lastCheckedAt":"2026-06-17T08:01:42Z"},{"companyId":"comp_xyz789","vatNumber":"IT01234567890","peppolId":"0211:it01234567890","name":"ACME SRL","country":"IT","status":"non_compliant","smpRegistered":true,"smpRegisteredAt":"2024-09-01T10:00:00Z","compliance":{"synced":87,"pending":1,"failed":2,"rejected":1},"lastCheckedAt":"2026-06-17T07:50:11Z"}]}}}},"400":{"description":"Invalid query parameters (e.g. unknown country code, malformed date).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while aggregating compliance state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream e-invoicing platform (PPF, SDI, KSeF, ZATCA, …) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream compliance platform unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/compliance/reports":{"get":{"tags":["Compliance"],"summary":"List individual compliance reports (per document) emitted to e-invoicing platforms","description":"List per-document compliance reports filed with external e-invoicing platforms.\n\nEach `ComplianceReport` row represents one submission of a document to one\njurisdictional platform (PPF, SDI, KSeF, ZATCA, e-Factura, …). Records are returned in\nreverse-chronological order on `reportedAt`. Use the `cursor` returned in the previous\npage's last record id to walk pages.\n\nTypical use cases: finance reconciliation (\"which invoices have we successfully reported\nto SDI this month?\"), error triage (\"show me all `failed` reports in PL last week\"), and\naudit trails.\n\n---\n\n**Example request:**\n```\nGET /v1/compliance/reports?country=FR&status=failed&from=2026-06-01T00:00:00Z&limit=50\nAuthorization: Bearer flw_live_***\n```\n\n**Example response (200):**\n```json\n{\n  \"records\": [\n    {\n      \"documentId\": \"doc_01HZXFAILED9876543210\",\n      \"type\": \"invoice\",\n      \"number\": \"INV-2026-0043\",\n      \"issueDate\": \"2026-06-12\",\n      \"status\": \"failed\",\n      \"reportedAt\": \"2026-06-12T09:18:02Z\",\n      \"reportedTo\": \"PPF\",\n      \"platformResponse\": {\"code\": \"PPF-422\", \"message\": \"schema invalid\"},\n      \"error\": \"PPF rejected: missing UBL CustomizationID\"\n    }\n  ]\n}\n```\n\n**Example response (422):**\n```json\n{\n  \"type\": \"about:blank\",\n  \"code\": \"validation_error\",\n  \"message\": \"country must be exactly 2 letters\",\n  \"details\": [{\"field\": \"country\", \"rule\": \"max_length\", \"message\": \"ensure this value has at most 2 characters\"}],\n  \"requestId\": \"req_01HZX...\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Bad date format, unknown country, invalid `status` value.       |\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `compliance:read` scope.                              |\n| 422    | `validation_error`    | Query schema validation failed.                                 |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Query failed unexpectedly.                                      |\n| 502    | `upstream_error`      | Backing platform returned a non-2xx response.                   |\n| 503    | `upstream_unavailable`| Backing platform unreachable / timed out.                       |","operationId":"list_compliance_reports_v1_compliance_reports_get","parameters":[{"name":"companyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict to a single company / org id, e.g. `comp_abc123`.","examples":["comp_abc123"],"title":"Companyid"},"description":"Restrict to a single company / org id, e.g. `comp_abc123`."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Za-z]{2}$"},{"type":"null"}],"description":"ISO 3166-1 alpha-2 country code of the reporting jurisdiction.","examples":["FR","IT","PL","SA"],"title":"Country"},"description":"ISO 3166-1 alpha-2 country code of the reporting jurisdiction."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by report status: `pending`, `synced`, `failed`, `rejected`.","examples":["synced","failed"],"title":"Status"},"description":"Filter by report status: `pending`, `synced`, `failed`, `rejected`."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 lower bound on `reportedAt` (inclusive). Example: `2026-01-01T00:00:00Z`.","examples":["2026-01-01T00:00:00Z"],"title":"From"},"description":"ISO 8601 lower bound on `reportedAt` (inclusive). Example: `2026-01-01T00:00:00Z`."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 upper bound on `reportedAt` (inclusive). Example: `2026-06-30T23:59:59Z`.","examples":["2026-06-30T23:59:59Z"],"title":"To"},"description":"ISO 8601 upper bound on `reportedAt` (inclusive). Example: `2026-06-30T23:59:59Z`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of records to return (1–100).","default":20,"title":"Limit"},"description":"Maximum number of records to return (1–100)."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor — pass the last record's id from the previous page.","examples":["rpt_01HZXABCDEF0123456789"],"title":"Cursor"},"description":"Opaque pagination cursor — pass the last record's id from the previous page."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceReportResponse"},"example":{"records":[{"documentId":"doc_01HZXABCDEF0123456789","type":"invoice","number":"INV-2026-0042","issueDate":"2026-06-12","status":"synced","reportedAt":"2026-06-12T09:15:33Z","reportedTo":"PPF","platformResponse":{"ackId":"PPF-ACK-7741","receivedAt":"2026-06-12T09:15:34Z"}},{"documentId":"doc_01HZXFAILED9876543210","type":"invoice","number":"INV-2026-0043","issueDate":"2026-06-12","status":"failed","reportedAt":"2026-06-12T09:18:02Z","reportedTo":"SDI","platformResponse":{"code":"00400","message":"Invalid VAT"},"error":"SDI rejected: invalid recipient VAT"}]}}}},"400":{"description":"Invalid query parameters (e.g. unknown country code, malformed date).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while aggregating compliance state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream e-invoicing platform (PPF, SDI, KSeF, ZATCA, …) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream compliance platform unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/directory/search":{"get":{"tags":["Directory"],"summary":"Search the Peppol directory and the French PPF annuaire","description":"Search the Peppol Directory + French PPF annuaire.\n\nResolution order:\n  1. **PPF annuaire** (French companies) — exact SIREN/SIRET lookup via `vatNumber` or\n     `q`, then a name search when `country=FR`.\n  2. **Peppol Directory** (international) — `directory.peppol.eu` with the standard\n     `q / participant / country / city / zip / naics / doctype` filters.\n  3. **Local registrations** fallback (Flowie-managed Peppol participants) — only when\n     neither `q` nor `vatNumber` is supplied, to avoid leaking unrelated rows.\n\nYou must supply at least one search criterion (`q`, `vatNumber`, or `country`), and a\nfree-text `q` must be scoped by `country` (a bare SIREN/SIRET or `vatNumber` is exempt).\nResults are collapsed to one row per legal entity unless `includeSubEntities=true`.\n\n---\n\n**Example — search by name (Peppol Directory):**\n```\nGET /v1/directory/search?q=Vodafone&country=GB&limit=5\n```\n```json\n{\n  \"data\": [\n    {\n      \"peppolId\": \"9932:gb569953277\",\n      \"name\": \"VODAFONE GROUP SERVICES LIMITED\",\n      \"country\": \"GB\",\n      \"city\": null,\n      \"postalCode\": null,\n      \"vatNumber\": null,\n      \"registeredSince\": \"2022-02-10T00:00:00\",\n      \"documentTypes\": [\"invoice\", \"credit-note\"],\n      \"accessPoint\": null\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n**Example — French SIREN lookup (PPF annuaire):**\n```\nGET /v1/directory/search?q=921376265&country=FR\n```\n```json\n{\n  \"data\": [\n    {\n      \"peppolId\": \"0009:921376265\",\n      \"name\": \"FLOWIE\",\n      \"country\": \"FR\",\n      \"city\": \"PARIS\",\n      \"postalCode\": \"75008\",\n      \"vatNumber\": \"FR26921376265\",\n      \"registeredSince\": null,\n      \"documentTypes\": [\"invoice\", \"credit-note\"],\n      \"accessPoint\": \"PDP-0001\"\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n**Example — filter by sector (NACE):**\n```\nGET /v1/directory/search?country=FR&naceCodes=62.01&limit=10\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                          | When                                                              |\n|--------|-------------------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`             | Malformed VAT, unknown country code, conflicting filters.         |\n| 401    | `unauthorized`                | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`                   | Key valid but lacks `directory:read`, or wrong org.               |\n| 422    | `validation_error`            | Query schema validation failed (e.g. `country` not 2 letters).    |\n| 429    | `rate_limited`                | Too many requests — retry after `Retry-After` seconds.            |\n| 502    | `upstream_error`              | Peppol Directory or ppf-annuaire returned a non-2xx response.     |\n| 503    | `upstream_unavailable`        | Peppol Directory or ppf-annuaire unreachable / timed out.         |","operationId":"search_directory_v1_directory_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text query. Matched as a **company / business name** against the Peppol Directory; for French results the PPF annuaire name index is also queried. A bare number is treated as a **registration number**, not a name: with no `country` (or `country=FR`) it is routed to the French SIREN/SIRET lookup and must be 9 or 14 digits; with any other `country` it is looked up as that country's national register number (a Dutch KvK, a Belgian CBE — no length rule applies), falling back to the name index if it matches nothing. Whitespace, dots and dashes inside the value are stripped before the digit test. One of `q` or `vatNumber` is required.","examples":["Vodafone","FLOWIE","921376265","92137626500017"],"title":"Q"},"description":"Free-text query. Matched as a **company / business name** against the Peppol Directory; for French results the PPF annuaire name index is also queried. A bare number is treated as a **registration number**, not a name: with no `country` (or `country=FR`) it is routed to the French SIREN/SIRET lookup and must be 9 or 14 digits; with any other `country` it is looked up as that country's national register number (a Dutch KvK, a Belgian CBE — no length rule applies), falling back to the name index if it matches nothing. Whitespace, dots and dashes inside the value are stripped before the digit test. One of `q` or `vatNumber` is required."},{"name":"vatNumber","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"EU VAT identification number, optionally prefixed by the 2-letter ISO 3166-1 country code (e.g. `FR26921376265`, `BE0123456789`). For French VATs the SIREN is extracted (trailing 9 digits) and matched against the PPF annuaire; a VAT carrying a foreign prefix is never read as a SIREN. `vatNumber` is a **filter**: every returned row, from any source, is checked to actually carry the requested identifier, and a row that does not is dropped. A VAT that matches nothing therefore yields an empty list, never an unrelated company. Note the directory exposes no cross-scheme identifiers, so a company listed under its national number is not findable by its VAT. One of `q` or `vatNumber` is required.","examples":["FR26921376265","BE0123456789"],"title":"Vatnumber"},"description":"EU VAT identification number, optionally prefixed by the 2-letter ISO 3166-1 country code (e.g. `FR26921376265`, `BE0123456789`). For French VATs the SIREN is extracted (trailing 9 digits) and matched against the PPF annuaire; a VAT carrying a foreign prefix is never read as a SIREN. `vatNumber` is a **filter**: every returned row, from any source, is checked to actually carry the requested identifier, and a row that does not is dropped. A VAT that matches nothing therefore yields an empty list, never an unrelated company. Note the directory exposes no cross-scheme identifiers, so a company listed under its national number is not findable by its VAT. One of `q` or `vatNumber` is required."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Za-z]{2}$"},{"type":"null"}],"description":"**ISO 3166-1 alpha-2** country code (uppercase, 2 letters — `FR`, `BE`, `DE`, `GB`, …). Selects the directory backend: `FR` triggers the PPF annuaire path, everything else hits the Peppol Directory only. It also selects which national identifier scheme a bare number in `q` is looked up under, and which length rules apply, so it is never ignored. **Required when searching by a free-text `q`** (a bare SIREN/SIRET or a `vatNumber` already carries its country, so it is exempt).","examples":["FR","BE","GB"],"title":"Country"},"description":"**ISO 3166-1 alpha-2** country code (uppercase, 2 letters — `FR`, `BE`, `DE`, `GB`, …). Selects the directory backend: `FR` triggers the PPF annuaire path, everything else hits the Peppol Directory only. It also selects which national identifier scheme a bare number in `q` is looked up under, and which length rules apply, so it is never ignored. **Required when searching by a free-text `q`** (a bare SIREN/SIRET or a `vatNumber` already carries its country, so it is exempt)."},{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"City name — forwarded as-is to the Peppol Directory `city` filter.","examples":["Paris"],"title":"City"},"description":"City name — forwarded as-is to the Peppol Directory `city` filter."},{"name":"postalCode","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Postal / ZIP code — forwarded to the Peppol Directory `zip` filter.","examples":["75001"],"title":"Postalcode"},"description":"Postal / ZIP code — forwarded to the Peppol Directory `zip` filter."},{"name":"naceCodes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"**NACE** = *Nomenclature statistique des Activités économiques dans la Communauté Européenne* — the EU statistical classification of economic activities (Regulation (EC) No 1893/2006). Pass one or more dot-formatted codes (e.g. `62.01` = *Computer programming activities*, `47.11` = *Retail sale in non-specialised stores*). Multiple codes are comma-separated. Forwarded to the Peppol Directory `naics` filter.","examples":["62.01","62.01,62.02"],"title":"Nacecodes"},"description":"**NACE** = *Nomenclature statistique des Activités économiques dans la Communauté Européenne* — the EU statistical classification of economic activities (Regulation (EC) No 1893/2006). Pass one or more dot-formatted codes (e.g. `62.01` = *Computer programming activities*, `47.11` = *Retail sale in non-specialised stores*). Multiple codes are comma-separated. Forwarded to the Peppol Directory `naics` filter."},{"name":"documentTypes","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated list of Peppol document-type identifiers / UBL URNs that the recipient must support (e.g. `invoice`, `credit-note`, or a full `busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##…` URN). Forwarded to the Peppol Directory `doctype` filter.","examples":["invoice","invoice,credit-note"],"title":"Documenttypes"},"description":"Comma-separated list of Peppol document-type identifiers / UBL URNs that the recipient must support (e.g. `invoice`, `credit-note`, or a full `busdox-docid-qns::urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##…` URN). Forwarded to the Peppol Directory `doctype` filter."},{"name":"includeSubEntities","in":"query","required":false,"schema":{"type":"boolean","description":"When `false` (default) results are collapsed to **one row per legal entity** — the directory lists each company once per identifier scheme (national + VAT). Set `true` to return every identifier-scheme / establishment row, e.g. when you need the exact routable participant ID.","default":false,"title":"Includesubentities"},"description":"When `false` (default) results are collapsed to **one row per legal entity** — the directory lists each company once per identifier scheme (national + VAT). Set `true` to return every identifier-scheme / establishment row, e.g. when you need the exact routable participant ID."},{"name":"detail","in":"query","required":false,"schema":{"type":"string","pattern":"^(basic|full)$","description":"`basic` (default) returns directory fields only. `full` enriches each row with access-point / SMP detail — slower, as it issues one lookup per result.","examples":["basic","full"],"default":"basic","title":"Detail"},"description":"`basic` (default) returns directory fields only. `full` enriches each row with access-point / SMP detail — slower, as it issues one lookup per result."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of distinct results to return (1–100).","default":20,"title":"Limit"},"description":"Maximum number of distinct results to return (1–100)."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_DirectorySearchResult_"}}}},"400":{"description":"Invalid query parameters (e.g. malformed VAT, unknown country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/directory/{peppol_id}":{"get":{"tags":["Directory"],"summary":"Look up a single Peppol participant by ID","description":"Resolve a Peppol ID (`<scheme>:<identifier>`, e.g. `0009:921376265` for a French\nSIREN, `0208:0123456789` for a Belgian CBE) against local registrations → PPF annuaire\n(French only) → Peppol Directory. Returns 404 if no source has the participant.","operationId":"lookup_peppol_id_v1_directory__peppol_id__get","parameters":[{"name":"peppol_id","in":"path","required":true,"schema":{"type":"string","title":"Peppol Id"},"description":"Peppol participant identifier, for example `0208:0000000001`."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryLookupResponse"}}}},"400":{"description":"Invalid query parameters (e.g. malformed VAT, unknown country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Peppol participant not found in any backing directory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/directory/verify":{"post":{"tags":["Directory"],"summary":"Verify a Peppol recipient can receive a given document type","description":"Check that `peppolId` is on the Peppol network AND that a participant advertises\n`documentType`, matched as a whole name against what the directory publishes.\n\nBeing listed in a national company register is not being reachable: a French company\nthe PPF annuaire knows but the network does not answers `exists: false`. For a French\ncompany ID (`0009:{siren}`) the answer covers its addressing lines, and `acceptedBy`\nnames the ones that accept this document — the company ID itself is not an address.","operationId":"verify_recipient_v1_directory_verify_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyRequest"}}},"description":"`VerifyRequest` — payload to verify a Peppol recipient can receive a given document type."},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}},"400":{"description":"Invalid query parameters (e.g. malformed VAT, unknown country code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/partners":{"post":{"tags":["Partners"],"summary":"Create a partner (supplier / buyer / both)","description":"Register a new partner (trading counterparty) in the caller's organization.\n\nA partner is a counterparty you exchange invoices / documents with through Peppol.\nAt least one of `peppolId` or `vatNumber` must be supplied — Flowie derives the\nother where possible. Sending an `Idempotency-Key` header replays the original\nresponse if the same key is reused within the retention window.\n\n**Example request:**\n```http\nPOST /v1/partners\nIdempotency-Key: 0e8f1d4c-1c0e-4f8e-9a3a-2b9d11e2f5b7\nContent-Type: application/json\n\n{\n  \"peppolId\": \"0009:BE0123456789\",\n  \"vatNumber\": \"BE0123456789\",\n  \"role\": \"supplier\",\n  \"contactName\": \"Jean Dupont\",\n  \"contactEmail\": \"jean@example.com\",\n  \"tags\": [\"preferred\"]\n}\n```\n\n**Example response (201):**\n```json\n{\n  \"id\": \"65b3f2f4492c6a505518fe0b\",\n  \"peppolId\": \"0009:921376265\",\n  \"name\": \"FLOWIE\",\n  \"vatNumber\": \"FR32921376265\",\n  \"country\": \"FR\",\n  \"role\": \"supplier\",\n  \"contactName\": null,\n  \"contactEmail\": null,\n  \"peppolStatus\": null,\n  \"tags\": []\n}\n```\n\n`id` is the partner's organization id — pass it to `GET`, `PATCH` or `DELETE\n/v1/partners/{id}`.\n\nThe response reports what Flowie stored, not what you sent. `contactName`, `contactEmail`,\n`defaults`, `tags` and `metadata` are accepted and not yet persisted anywhere, so they come\nback empty rather than echoed; `peppolId` and `vatNumber` are used to identify the partner\nand do survive.\n\n**Example error (400 — no identifier):**\n```json\n{\n  \"type\": \"invalid_request\",\n  \"code\": \"missing_identifier\",\n  \"message\": \"At least one of peppolId or vatNumber must be provided\",\n  \"details\": [],\n  \"requestId\": \"req_01HX...\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                 | When                                                              |\n|--------|----------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`    | Neither `peppolId` nor `vatNumber` given, or VAT malformed.       |\n| 401    | `unauthorized`       | Missing / invalid API key.                                        |\n| 403    | `forbidden`          | Key lacks `partners:write`.                                       |\n| 422    | `validation_error`   | Body failed schema validation (e.g. bad email pattern).           |\n| 429    | `rate_limited`       | Too many requests — retry after `Retry-After` seconds.            |\n| 502    | `upstream_error`     | org-v2 returned a non-2xx response.                         |\n| 503    | `upstream_unavailable` | org-v2 unreachable or timed out.                          |","operationId":"create_partner_v1_partners_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"},"description":"Client-generated key making a retry safe. Writes are not idempotent by default: replaying a send without this header delivers the document twice."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerCreate"}}},"description":"`PartnerCreate` — payload to create a partner (supplier / buyer / both)."},"responses":{"201":{"description":"Partner created (or replayed via Idempotency-Key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerResponse"},"example":{"id":"65b3f2f4492c6a505518fe0b","peppolId":"0009:921376265","name":"FLOWIE","vatNumber":"FR32921376265","country":"FR","role":"supplier","tags":[],"enrichment":{"source":"PPF Annuaire","legalForm":"SAS, société par actions simplifiée","workforce":"11","naf":"62.01Z","nafLabel":"Programmation informatique"},"metadata":{"registration":{"legalName":"FLOWIE","countryCode":"FR"}},"createdAt":"2026-01-12T10:23:11Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing identifier).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Partners"],"summary":"List partners","description":"List the caller's partners.\n\nRows come from the organizations service, deduped on the counterparty organization id so a\ncompany holding both a supplier and a buyer partnership appears once. French partners are\nenriched from the PPF annuaire, which can override `name` with the official denomination.\n\n`stats` is no longer populated: it carried Book of Relations' `lastAction`, which the\norganizations service does not record (RAC-10659).","operationId":"list_partners_v1_partners_get","parameters":[{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"description":"Filter partners by their role, for example `supplier` or `buyer`."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"},"description":"Free-text search term."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"description":"ISO 3166-1 alpha-2 country code, for example `FR`."},{"name":"tags","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tags"},"description":"Filter partners by tag."},{"name":"hasActivity","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Hasactivity"},"description":"Filter to partners that do or do not have recorded activity."},{"name":"peppolStatus","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolstatus"},"description":"Filter partners by Peppol registration status, for example `active`."},{"name":"sortBy","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sortby"},"description":"Field to sort the results by."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"},"description":"Sort direction, ascending or descending."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"},"description":"Opaque cursor from the previous page. Paginate with this rather than raising `limit` to avoid paging."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Deduped paginated list of the caller's partners.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_PartnerListItem_"},"example":{"data":[{"id":"65b3f2f4492c6a505518fe0b","peppolId":"0009:921376265","name":"FLOWIE","country":"FR","role":"supplier","tags":[]},{"id":"64edd35bdd74306a85f11cd3","name":"Acme BVBA","country":"BE","role":"buyer","tags":[]}],"hasMore":false}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing identifier).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/partners/by-account-number":{"get":{"tags":["Partners"],"summary":"Resolve a partner by an internal account number (custom field)","description":"Reverse lookup: find the partner (org) linked to a customer account whose\ninternal account number is stored as a custom field.\n\nThe `value` is matched against custom-field rows scoped to your organization;\nthe matched object is then resolved to the partner. Returns 404 when no\npartner matches.","operationId":"get_partner_by_account_number_v1_partners_by_account_number_get","parameters":[{"name":"value","in":"query","required":true,"schema":{"type":"string","description":"The exact custom-field value to match, e.g. an internal customer/account number.","title":"Value"},"description":"The exact custom-field value to match, e.g. an internal customer/account number."},{"name":"field","in":"query","required":false,"schema":{"type":"string","description":"Name of the custom-field definition holding the account number.","default":"Numéro de compte interne","title":"Field"},"description":"Name of the custom-field definition holding the account number."},{"name":"entityType","in":"query","required":false,"schema":{"type":"string","description":"Entity type the custom field is attached to.","default":"PARTNERSHIP","title":"Entitytype"},"description":"Entity type the custom field is attached to."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"The partner whose custom field matches the value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerResponse"},"example":{"id":"65b3f2f4492c6a505518fe0b","peppolId":"0009:921376265","name":"FLOWIE","vatNumber":"FR32921376265","country":"FR","role":"supplier","tags":[],"enrichment":{"source":"PPF Annuaire","legalForm":"SAS, société par actions simplifiée","workforce":"11","naf":"62.01Z","nafLabel":"Programmation informatique"},"metadata":{"registration":{"legalName":"FLOWIE","countryCode":"FR"}},"createdAt":"2026-01-12T10:23:11Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing identifier).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Partner not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/partners/{partner_id}/invoices":{"get":{"tags":["Partners"],"summary":"List invoices exchanged with a partner","description":"List every invoice exchanged between your organization and this partner\n(the partner as seller or payer). Scoped to your org by the platform.","operationId":"list_partner_invoices_v1_partners__partner_id__invoices_get","parameters":[{"name":"partner_id","in":"path","required":true,"schema":{"type":"string","title":"Partner Id"},"description":"Identifier of the partner."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor from the previous page.","title":"Cursor"},"description":"Opaque pagination cursor from the previous page."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Invoices between your org and this partner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_DocumentListItem_"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing identifier).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/partners/{partner_id}":{"get":{"tags":["Partners"],"summary":"Get a partner by id, `vat:<VAT>`, or `peppol:<scheme:id>`","description":"Fetch a partner by its Flowie id, or by `vat:<VAT>` / `peppol:<scheme:id>` alias.\n\nThe path parameter accepts three forms — the partner's organization id as returned by\n`POST /v1/partners` and the list, `vat:BE0123456789`, or `peppol:0009:BE0123456789`. An id is\nlooked up directly; the two prefixed forms are resolved through partner autocomplete on the\ncounterparty's identifiers first.\n\n**Example request:**\n```http\nGET /v1/partners/vat:BE0123456789\n```\n\n**Example response (200):** see the example body above.\n\n**Example error (404):**\n```json\n{\n  \"type\": \"not_found\",\n  \"code\": \"partner_not_found\",\n  \"message\": \"No partner matches 'vat:BE0123456789' in this organization.\",\n  \"details\": [],\n  \"requestId\": \"req_01HX...\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Identifier prefix malformed.                                  |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `partners:read`.                                    |\n| 404    | `partner_not_found`    | No matching partner.                         |\n| 422    | `validation_error`     | Path validation failed.                                       |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | org-v2 returned a non-2xx response.                     |\n| 503    | `upstream_unavailable` | org-v2 unreachable or timed out.                        |","operationId":"get_partner_v1_partners__partner_id__get","parameters":[{"name":"partner_id","in":"path","required":true,"schema":{"type":"string","title":"Partner Id"},"description":"Identifier of the partner."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Full partner profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerResponse"},"example":{"id":"65b3f2f4492c6a505518fe0b","peppolId":"0009:921376265","name":"FLOWIE","vatNumber":"FR32921376265","country":"FR","role":"supplier","tags":[],"enrichment":{"source":"PPF Annuaire","legalForm":"SAS, société par actions simplifiée","workforce":"11","naf":"62.01Z","nafLabel":"Programmation informatique"},"metadata":{"registration":{"legalName":"FLOWIE","countryCode":"FR"}},"createdAt":"2026-01-12T10:23:11Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing identifier).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Partner not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Partners"],"summary":"Update a partner (partial / JSON merge-patch semantics)","description":"Partially update a partner. Only fields present in the body are touched.\n\nNested dict fields (`defaults`, `metadata`) are shallow-merged with the existing\nvalue; pass an explicit `null` to clear a field.\n\n**Not yet persisted.** Flowie stores none of these fields on a partner today, so a `200`\nhere means the partner exists and was addressed — not that anything changed. The response\nis the partner as stored, so you can see what did. Tracked in RAC-10552.\n\n`{id}` is the partner's organization id, as returned by `GET`, `POST` and the list. A partner\nyou both buy from and sell to holds two relationships behind that one id; the body applies to\nboth, since it describes the partner rather than a direction.\n\n**Example request:**\n```http\nPATCH /v1/partners/65b3f2f4492c6a505518fe0b\nContent-Type: application/json\n\n{\n  \"tags\": [\"preferred\", \"vip\"],\n  \"contactEmail\": \"billing@acme.example\"\n}\n```\n\n**Example response (200):** the updated partner — see body above.\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 400    | `invalid_request`      | Malformed field value (e.g. bad email).                       |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `partners:write`.                                   |\n| 404    | `partner_not_found`    | No matching partner.                                          |\n| 422    | `validation_error`     | Body failed schema validation.                                |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | org-v2 returned a non-2xx response.                     |\n| 503    | `upstream_unavailable` | org-v2 unreachable or timed out.                        |","operationId":"update_partner_v1_partners__partner_id__patch","parameters":[{"name":"partner_id","in":"path","required":true,"schema":{"type":"string","title":"Partner Id"},"description":"Identifier of the partner."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerUpdate"}}},"description":"`PartnerUpdate` — payload to update a partner (partial / JSON merge-patch semantics)."},"responses":{"200":{"description":"Updated partner profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerResponse"},"example":{"id":"65b3f2f4492c6a505518fe0b","peppolId":"0009:921376265","name":"FLOWIE","vatNumber":"FR32921376265","country":"FR","role":"supplier","tags":["preferred","vip"],"enrichment":{"source":"PPF Annuaire","legalForm":"SAS, société par actions simplifiée","workforce":"11","naf":"62.01Z","nafLabel":"Programmation informatique"},"metadata":{"registration":{"legalName":"FLOWIE","countryCode":"FR"}},"createdAt":"2026-01-12T10:23:11Z"}}}},"400":{"description":"Malformed request (e.g. invalid VAT, missing identifier).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Partner not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Partners"],"summary":"Delete a partner","description":"Permanently delete a partner from the caller's organization.\n\nReturns `204 No Content` on success. Already-deleted partners return `404`\nso the operation is *not* idempotent — callers should treat a 404 after a\nsuccessful prior delete as a no-op.\n\n`{id}` is the partner's organization id, as returned by `GET`, `POST` and the list. A partner\nyou both buy from and sell to holds two relationships behind that one id; both are removed, so\nthe partner leaves the list rather than losing one side of itself.\n\n**Example request:**\n```http\nDELETE /v1/partners/65b3f2f4492c6a505518fe0b\n```\n\n**Errors** (body follows `ErrorResponse` except for 204):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 204    | —                      | Partner deleted (no body).                                    |\n| 400    | `invalid_request`      | Identifier prefix malformed.                                  |\n| 401    | `unauthorized`         | Missing / invalid API key.                                    |\n| 403    | `forbidden`            | Key lacks `partners:write`.                                   |\n| 404    | `partner_not_found`    | No matching partner.                                          |\n| 422    | `validation_error`     | Path validation failed.                                       |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | org-v2 returned a non-2xx response.                     |\n| 503    | `upstream_unavailable` | org-v2 unreachable or timed out.                        |","operationId":"delete_partner_v1_partners__partner_id__delete","parameters":[{"name":"partner_id","in":"path","required":true,"schema":{"type":"string","title":"Partner Id"},"description":"Identifier of the partner."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"204":{"description":"Partner deleted (no body)."},"400":{"description":"Malformed request (e.g. invalid VAT, missing identifier).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (org-v2) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (org-v2) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Partner not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/purchase-orders/{purchase_order_id}/invoices":{"get":{"tags":["Purchase Orders"],"summary":"List invoices linked to a purchase order","description":"List every invoice linked to the given purchase order. Scoped to your\norganization by the platform (you must be a party on the documents).","operationId":"list_purchase_order_invoices_v1_purchase_orders__purchase_order_id__invoices_get","parameters":[{"name":"purchase_order_id","in":"path","required":true,"schema":{"type":"string","title":"Purchase Order Id"},"description":"Identifier of the purchase order."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor from the previous page.","title":"Cursor"},"description":"Opaque pagination cursor from the previous page."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Invoices linked to this purchase order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_DocumentListItem_"}}}},"400":{"description":"Malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks the required scope for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream (transaction-documents) returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream (transaction-documents) unavailable or timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks":{"post":{"tags":["Webhooks"],"summary":"Register a new webhook endpoint","description":"Register an HTTPS endpoint to receive event notifications.\n\nThe webhook is bound to the caller's organization (and optionally a single\n`companyId`). If `secret` is omitted, the server generates a 64-hex-character\nsigning secret and uses it for HMAC-SHA256 signatures via the\n`X-Flowie-Signature` header on each delivery. The secret is NEVER returned\nin any response — store it client-side at create time if you want it.\n\n---\n\n**Example request:**\n```json\n{\n  \"url\": \"https://example.com/webhooks/peppol\",\n  \"events\": [\"document.received\", \"document.delivered\"],\n  \"secret\": \"whsec_abc123\"\n}\n```\n\n**Example response (201):**\n```json\n{\n  \"id\": \"wh_01HF8K3JD9V2M8X7Q9N3R4T5Y6\",\n  \"url\": \"https://example.com/webhooks/peppol\",\n  \"events\": [\"document.received\", \"document.delivered\"],\n  \"status\": \"active\",\n  \"companyId\": \"comp_abc123\",\n  \"createdAt\": \"2026-04-15T10:32:18Z\",\n  \"lastDeliveredAt\": null,\n  \"failureCount\": 0\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `validation_error`  | `url` missing, not http(s), `events` list empty.                  |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Key lacks the `webhooks.write` scope.                             |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected error while persisting the webhook.                    |","operationId":"create_webhook_v1_webhooks_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreate"}}},"description":"`WebhookCreate` — payload to register a new webhook endpoint."},"responses":{"201":{"description":"Webhook successfully registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"},"example":{"id":"wh_01HF8K3JD9V2M8X7Q9N3R4T5Y6","url":"https://example.com/webhooks/peppol","events":["document.received","document.delivered"],"status":"active","companyId":"comp_abc123","createdAt":"2026-04-15T10:32:18Z","failureCount":0}}}},"400":{"description":"Validation error (e.g. missing `url`, empty `events`, non-http(s) URL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key lacks the required `webhooks.read` / `webhooks.write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while persisting the webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Webhooks"],"summary":"List webhooks registered for the caller's organization","description":"List all webhooks the API key can see.\n\nFor a personal/platform key bound to a single company, results are\nscoped to that company. For a white-label / org-wide key, every webhook\nin the organization is returned — pass `companyId` to narrow the view.\n\n---\n\n**Example response (200):**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"wh_01HF8K3JD9V2M8X7Q9N3R4T5Y6\",\n      \"url\": \"https://example.com/webhooks/peppol\",\n      \"events\": [\"document.received\", \"document.delivered\"],\n      \"status\": \"active\",\n      \"companyId\": \"comp_abc123\",\n      \"createdAt\": \"2026-04-15T10:32:18Z\",\n      \"lastDeliveredAt\": \"2026-04-16T08:11:02Z\",\n      \"failureCount\": 0\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `validation_error`  | Query parameter is malformed.                                     |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Key lacks the `webhooks.read` scope.                              |\n| 422    | `validation_error`  | Query failed schema validation (legacy handler).                  |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected error while reading webhooks from the DB.              |","operationId":"list_webhooks_v1_webhooks_get","parameters":[{"name":"companyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"},"description":"Restrict results to one company, which is how you scope to a single counterparty."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Page of registered webhooks (currently un-paginated — `hasMore` is always `false`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_WebhookResponse_"},"example":{"data":[{"id":"wh_01HF8K3JD9V2M8X7Q9N3R4T5Y6","url":"https://example.com/webhooks/peppol","events":["document.received","document.delivered"],"status":"active","companyId":"comp_abc123","createdAt":"2026-04-15T10:32:18Z","failureCount":0}],"hasMore":false}}}},"400":{"description":"Validation error (e.g. missing `url`, empty `events`, non-http(s) URL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key lacks the required `webhooks.read` / `webhooks.write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while persisting the webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/webhooks/{webhook_id}":{"patch":{"tags":["Webhooks"],"summary":"Update a webhook's URL, event list, or signing secret","description":"Partially update a webhook. Only the fields provided in the body are\nchanged. Pass `rotateSecret: true` to mint a new 64-hex-character signing\nsecret — the new secret is generated server-side and is NOT returned, so\nout-of-band sharing is required to keep verifying signatures.\n\n---\n\n**Example request:**\n```json\n{\n  \"events\": [\"document.received\", \"document.delivered\", \"document.failed\"],\n  \"rotateSecret\": true\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"id\": \"wh_01HF8K3JD9V2M8X7Q9N3R4T5Y6\",\n  \"url\": \"https://example.com/webhooks/peppol\",\n  \"events\": [\"document.received\", \"document.delivered\", \"document.failed\"],\n  \"status\": \"active\",\n  \"companyId\": \"comp_abc123\",\n  \"createdAt\": \"2026-04-15T10:32:18Z\",\n  \"lastDeliveredAt\": \"2026-04-16T08:11:02Z\",\n  \"failureCount\": 0\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `validation_error`  | Invalid URL or event list.                                        |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Key lacks the `webhooks.write` scope.                             |\n| 404    | `not_found`         | No webhook with that id in the caller's organization.             |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected error while persisting the change.                     |","operationId":"update_webhook_v1_webhooks__webhook_id__patch","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"},"description":"Identifier of the webhook subscription."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookUpdate"}}},"description":"`WebhookUpdate` — payload to update a webhook's URL, event list, or signing secret."},"responses":{"200":{"description":"Webhook successfully updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"},"example":{"id":"wh_01HF8K3JD9V2M8X7Q9N3R4T5Y6","url":"https://example.com/webhooks/peppol","events":["document.received","document.delivered"],"status":"active","companyId":"comp_abc123","createdAt":"2026-04-15T10:32:18Z","failureCount":0}}}},"400":{"description":"Validation error (e.g. missing `url`, empty `events`, non-http(s) URL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key lacks the required `webhooks.read` / `webhooks.write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while persisting the webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found, or belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook subscription","description":"Permanently delete a webhook. Idempotent against re-delete: a second\ncall against the same id returns 404. In-flight retries for prior deliveries\nare NOT cancelled — they continue until their backoff schedule is exhausted.\n\n---\n\n**Example request:**\n```\nDELETE /v1/webhooks/wh_01HF8K3JD9V2M8X7Q9N3R4T5Y6\n```\n\nReturns `204 No Content` on success.\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Key lacks the `webhooks.write` scope.                             |\n| 404    | `not_found`         | No webhook with that id in the caller's organization.             |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Unexpected error while deleting from the DB.                      |","operationId":"delete_webhook_v1_webhooks__webhook_id__delete","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"},"description":"Identifier of the webhook subscription."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"204":{"description":"Webhook deleted. No response body."},"400":{"description":"Validation error (e.g. missing `url`, empty `events`, non-http(s) URL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key lacks the required `webhooks.read` / `webhooks.write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while persisting the webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found, or belongs to a different organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/events":{"get":{"tags":["Events"],"summary":"List unacknowledged events for the caller's organization","description":"List events the caller has not yet acknowledged.\n\nEvents represent observable things that happened to your documents\n(`document.received`, `document.sent`, `document.delivered`,\n`document.failed`, `lifecycle.updated`, …) — they are the persistent\ncounterpart to webhook deliveries, available to clients who poll\ninstead of (or in addition to) subscribing to webhooks.\n\nCursor-based pagination over `(createdAt, id)` — pass the `cursor` from\na previous page to fetch the next one. `hasMore=false` means you've\nreached the end. Personal/platform keys see only their bound company;\norg-wide keys default to their own organization and may narrow with\n`companyId` — but only to a company/organization the key can access. A\n`companyId` outside the caller's tenant returns `403`, never another\ntenant's events.\n\n---\n\n**Example response (200):**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"evt_5e8c9a1f3b7d4a2c9e1f8b3d7a2c9e1f\",\n      \"type\": \"document.received\",\n      \"createdAt\": \"2026-04-15T10:32:18.421Z\",\n      \"data\": {\n        \"documentId\": \"doc_test001\",\n        \"direction\": \"incoming\",\n        \"number\": \"INV-2026-0042\",\n        \"from\": \"0009:FR86797978996\",\n        \"to\": \"0208:0123456789\"\n      }\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | `cursor` refers to an event that no longer exists.                |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Key has no read access to the requested company.                  |\n| 422    | `validation_error`  | Query failed schema validation (e.g. non-int `limit`).            |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | DB error while reading events.                                    |","operationId":"list_events_v1_events_get","parameters":[{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"description":"Document type, for example `invoice` or `credit-note`."},{"name":"companyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"},"description":"Restrict results to one company, which is how you scope to a single counterparty."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"},"description":"Opaque cursor from the previous page. Paginate with this rather than raising `limit` to avoid paging."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Page of unacknowledged events, ordered oldest-first by (createdAt, id).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_EventResponse_"},"example":{"data":[{"id":"evt_5e8c9a1f3b7d4a2c9e1f8b3d7a2c9e1f","type":"document.received","createdAt":"2026-04-15T10:32:18.421Z","data":{"documentId":"doc_test001","direction":"incoming","number":"INV-2026-0042","from":"0009:FR86797978996","to":"0208:0123456789"}}],"hasMore":false}}}},"400":{"description":"Invalid query parameters (e.g. malformed `cursor`, unknown `type`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key cannot access this event / company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while reading the event store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/events/{event_id}/ack":{"post":{"tags":["Events"],"summary":"Acknowledge a single event so it stops appearing in /v1/events","description":"Mark a single event as acknowledged. Acknowledged events no longer\nappear in `GET /v1/events`. The call is idempotent — acknowledging\nan already-acknowledged event is a no-op success.\n\n---\n\n**Example request:**\n```\nPOST /v1/events/evt_5e8c9a1f3b7d4a2c9e1f8b3d7a2c9e1f/ack\n```\n\nReturns `204 No Content` on success.\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Event belongs to a different company than the key allows.         |\n| 404    | `not_found`         | No event with that id in the store.                               |\n| 422    | `validation_error`  | Path id failed schema validation.                                 |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | DB error while updating the event row.                            |","operationId":"ack_event_v1_events__event_id__ack_post","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Identifier of the event."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"204":{"description":"Event acknowledged. No response body."},"400":{"description":"Invalid query parameters (e.g. malformed `cursor`, unknown `type`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key cannot access this event / company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while reading the event store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Event id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/events/ack":{"post":{"tags":["Events"],"summary":"Acknowledge a batch of events in a single call","description":"Acknowledge many events at once. All ids must belong to the caller's\norganization (and bound company if the key is scoped) — the call is\nrejected as a whole if any id is missing or forbidden.\n\n---\n\n**Example request:**\n```json\n{\n  \"eventIds\": [\n    \"evt_5e8c9a1f3b7d4a2c9e1f8b3d7a2c9e1f\",\n    \"evt_71b3c8e4d9a2f5c8e1b3d7a2c9e1f8b3\"\n  ]\n}\n```\n\nReturns `204 No Content` on success.\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`   | `eventIds` empty or malformed.                                    |\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | At least one event belongs to a different company.                |\n| 404    | `not_found`         | At least one event id does not exist.                             |\n| 422    | `validation_error`  | Body failed schema validation (legacy handler).                   |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | DB error while updating events.                                   |","operationId":"batch_ack_events_v1_events_ack_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AckRequest"}}},"description":"`AckRequest` — payload to acknowledge a batch of events in a single call."},"responses":{"204":{"description":"All listed events acknowledged. No response body."},"400":{"description":"Invalid query parameters (e.g. malformed `cursor`, unknown `type`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key cannot access this event / company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while reading the event store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Event id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/events/{event_id}/replay":{"post":{"tags":["Events"],"summary":"Re-deliver a previously emitted event to subscribed webhooks","description":"Re-deliver a previously emitted event to all matching webhooks.\n\nUse this to recover from a downstream outage on your side: the original\nJSON payload is preserved byte-for-byte; only the `X-Flowie-Delivery`\nid and the `X-Flowie-Signature` timestamp differ from the first\ndelivery, so HMAC signature verification still succeeds with your\nexisting webhook secret. Returns a summary\n`{eventId, found, matched, delivered, deliveryId}` describing how many\nsubscribed endpoints were attempted and how many accepted the call.\n\n---\n\n**Example response (200):**\n```json\n{\n  \"eventId\": \"evt_5e8c9a1f3b7d4a2c9e1f8b3d7a2c9e1f\",\n  \"found\": true,\n  \"matched\": 2,\n  \"delivered\": 2,\n  \"deliveryId\": \"dlv_01HF8K3JD9V2M8X7Q9N3R4T5Y6\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                | When                                                              |\n|--------|---------------------|-------------------------------------------------------------------|\n| 401    | `unauthorized`      | Missing / invalid `Authorization` header or API key.              |\n| 403    | `forbidden`         | Event belongs to a different company than the key allows.         |\n| 404    | `not_found`         | Event id does not exist (or was purged).                          |\n| 422    | `validation_error`  | Path id failed schema validation.                                 |\n| 429    | `rate_limited`      | Too many requests — retry after `Retry-After` seconds.            |\n| 500    | `internal_error`    | Failure while dispatching to webhooks.                            |","operationId":"replay_event_v1_events__event_id__replay_post","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"},"description":"Identifier of the event."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Replay attempt summary.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Replay Event V1 Events  Event Id  Replay Post"},"example":{"eventId":"evt_5e8c9a1f3b7d4a2c9e1f8b3d7a2c9e1f","found":true,"matched":2,"delivered":2,"deliveryId":"dlv_01HF8K3JD9V2M8X7Q9N3R4T5Y6"}}}},"400":{"description":"Invalid query parameters (e.g. malformed `cursor`, unknown `type`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key cannot access this event / company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while reading the event store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Event id not found in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/stats":{"get":{"tags":["Stats"],"summary":"Usage, quota, and rate-limit stats for the calling API key","description":"Return aggregated usage and limits for the API key's organisation, for a given month.\n\nCombines three pieces of information that consumers need on a single dashboard:\n\n1. **Quota** — documents-sent + documents-received counted against the tier limit\n   (from `QUOTAS`). `unlimited: true` is returned for enterprise tiers without a cap.\n2. **Rate limit** — requests-per-minute ceiling enforced on this key (the key-level\n   override wins over the tier default from `RATE_LIMITS`).\n3. **Documents** — granular counts of sent / received / failed / total for the period.\n\nUseful for billing dashboards, in-app meters, and as the data source for the\n`usage.quota_warning` webhook event.\n\n---\n\n**Example request:**\n```\nGET /v1/stats?period=2026-06\nAuthorization: Bearer flw_live_***\n```\n\n**Example response (200):**\n```json\n{\n  \"period\": {\"value\": \"2026-06\", \"start\": \"2026-06-01T00:00:00Z\"},\n  \"quota\": {\"used\": 1247, \"limit\": 10000, \"unlimited\": false, \"percentUsed\": 12.5},\n  \"rateLimit\": {\"requestsPerMinute\": 600},\n  \"documents\": {\"sent\": 812, \"received\": 435, \"failed\": 4, \"total\": 1247},\n  \"byType\": null,\n  \"byCountry\": null,\n  \"partners\": null\n}\n```\n\n**Example response (200, enterprise tier):**\n```json\n{\n  \"period\": {\"value\": \"2026-06\", \"start\": \"2026-06-01T00:00:00Z\"},\n  \"quota\": {\"used\": 184230, \"limit\": null, \"unlimited\": true, \"percentUsed\": null},\n  \"rateLimit\": {\"requestsPerMinute\": 6000},\n  \"documents\": {\"sent\": 120410, \"received\": 63820, \"failed\": 12, \"total\": 184230},\n  \"byType\": null,\n  \"byCountry\": null,\n  \"partners\": null\n}\n```\n\n**Example response (422):**\n```json\n{\n  \"type\": \"about:blank\",\n  \"code\": \"validation_error\",\n  \"message\": \"period must match ^\\d{4}-\\d{2}$\",\n  \"details\": [{\"field\": \"period\", \"rule\": \"pattern\", \"message\": \"string does not match pattern\"}],\n  \"requestId\": \"req_01HZX...\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Period not parseable as `YYYY-MM`, or unknown `companyId`.      |\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header or API key.            |\n| 403    | `forbidden`           | Key valid but lacks `stats:read` (or wrong org).                |\n| 422    | `validation_error`    | Query schema validation failed.                                 |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Aggregation failed unexpectedly.                                |","operationId":"get_stats_v1_stats_get","parameters":[{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}$"},{"type":"null"}],"description":"Monthly billing period in `YYYY-MM` form (UTC). Defaults to the current month when omitted. Historical periods are accepted as long as `usage_record` rows exist.","examples":["2026-06","2026-05"],"title":"Period"},"description":"Monthly billing period in `YYYY-MM` form (UTC). Defaults to the current month when omitted. Historical periods are accepted as long as `usage_record` rows exist."},{"name":"companyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional company / organisation scope. When supplied alongside an API key bound to a specific company, restricts the aggregation to that company's usage rows. Slug-style id.","examples":["comp_abc123"],"title":"Companyid"},"description":"Optional company / organisation scope. When supplied alongside an API key bound to a specific company, restricts the aggregation to that company's usage rows. Slug-style id."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsResponse"},"example":{"period":{"value":"2026-06","start":"2026-06-01T00:00:00Z"},"quota":{"used":1247,"limit":10000,"unlimited":false,"percentUsed":12.5},"rateLimit":{"requestsPerMinute":600},"documents":{"sent":812,"received":435,"failed":4,"total":1247}}}}},"400":{"description":"Invalid query parameters (e.g. malformed `period`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while aggregating usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/platform/companies":{"post":{"tags":["Platform"],"summary":"Onboard a managed company (create org + optional webhook + key)","description":"One-call onboarding for a company the platform manages on behalf of an end\ncustomer. Creates the company in org-v2, optionally provisions a webhook,\nand optionally mints an API key bound to that company — all atomically from\nthe caller's point of view. Requires a `platform` or `white_label` key with\nthe `companies.write` scope.\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/platform/companies \\\n  -H \"Authorization: Bearer flw_plat_live_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"vatNumber\": \"FR02779658772\",\n    \"name\": \"HOZELOCK EXEL\",\n    \"receiveDocuments\": true,\n    \"autoVerify\": false,\n    \"webhook\": {\n      \"url\": \"https://customer.example.com/webhooks/peppol\",\n      \"events\": [\"document.received\", \"document.delivered\"]\n    },\n    \"apiKey\": {\n      \"name\": \"HOZELOCK production key\",\n      \"scopes\": [\"documents.read\", \"documents.write\"],\n      \"tier\": \"starter\"\n    }\n  }'\n```\n\n**Example 201 response:**\n```json\n{\n  \"company\": {\n    \"id\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n    \"vatNumber\": \"FR02779658772\",\n    \"name\": \"HOZELOCK EXEL\",\n    \"country\": \"FR\",\n    \"peppolId\": \"0009:02779658772\",\n    \"status\": \"active\",\n    \"smpRegistered\": true\n  },\n  \"apiKey\": {\n    \"id\": \"key_8f2c41a3b6e84d119f5a3c2b9a7d6e1f\",\n    \"key\": \"flw_live_8f2c41a3b6e84d119f5a3c2b9a7d6e1f7a2c5b8d3e9f0a1b\",\n    \"keyPrefix\": \"flw_live_8f2c\"\n  },\n  \"webhook\": {\n    \"id\": \"wh_abc123\",\n    \"url\": \"https://customer.example.com/webhooks/peppol\",\n    \"events\": [\"document.received\", \"document.delivered\"],\n    \"status\": \"active\"\n  }\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                 | When                                                                |\n|--------|----------------------|---------------------------------------------------------------------|\n| 400    | `invalid_request`    | Malformed `vatNumber`, blank `apiKey.name`, etc.                    |\n| 401    | `unauthorized`       | Missing / invalid `Authorization` header.                           |\n| 403    | `forbidden_key_type` | Caller is not a `platform` / `white_label` key, or missing scope.   |\n| 422    | `validation_error`   | Body failed Pydantic validation.                                    |\n| 429    | `rate_limited`       | Too many requests — retry after `Retry-After`.                      |\n| 502    | `upstream_error`     | org-v2 rejected the create call.                                    |\n| 503    | `upstream_unavailable` | org-v2 unreachable / timed out.                                   |","operationId":"onboard_company_v1_platform_companies_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformOnboardRequest"}}},"description":"`PlatformOnboardRequest` — payload to onboard a managed company (create org + optional webhook + key)."},"responses":{"201":{"description":"Managed company created. The optional `apiKey.key` field is the only chance to capture the secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformOnboardResponse"},"example":{"company":{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","vatNumber":"FR02779658772","country":"FR","peppolId":"0009:02779658772","status":"active","smpRegistered":true,"createdAt":"2026-06-17T09:14:22.157894Z"},"apiKey":{"id":"key_8f2c41a3b6e84d119f5a3c2b9a7d6e1f","key":"flw_live_8f2c41a3b6e84d119f5a3c2b9a7d6e1f7a2c5b8d3e9f0a1b","keyPrefix":"flw_live_8f2c"},"webhook":{"id":"wh_abc123","url":"https://customer.example.com/webhooks/peppol","events":["document.received","document.delivered"],"status":"active","failureCount":0}}}}},"400":{"description":"Invalid request payload (e.g. malformed VAT, missing required field, conflicting filters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a `platform` or `white_label` key, or the targeted `companyId` is not a company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (org-v2 / Sovos) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unreachable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Platform"],"summary":"List the companies managed by this platform","description":"Return every company in this platform's org-v2 ownership graph (its child\norganizations). Requires a `platform` or `white_label` key with the\n`companies.read` scope. In sandbox mode an empty page is returned — sandbox\nplatform tenants don't have real child orgs in org-v2.\n\n---\n\n**Example response:**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n      \"name\": \"HOZELOCK EXEL\",\n      \"vatNumber\": \"FR02779658772\",\n      \"country\": \"FR\",\n      \"peppolId\": \"0009:02779658772\",\n      \"status\": \"active\",\n      \"stats\": {\"documentsSent\": 142, \"documentsReceived\": 87}\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                       |\n|--------|------------------------|------------------------------------------------------------|\n| 401    | `unauthorized`         | Missing / invalid `Authorization` header.                  |\n| 403    | `forbidden_key_type`   | Caller is not a `platform` / `white_label` key.            |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After`.             |\n| 502    | `upstream_error`       | org-v2 returned a non-2xx response.                        |\n| 503    | `upstream_unavailable` | org-v2 unreachable / timed out.                            |","operationId":"list_managed_companies_v1_platform_companies_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Page of managed (org-v2 child) companies. Empty list in sandbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_CompanyListItem_"},"example":{"data":[{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","vatNumber":"FR02779658772","country":"FR","peppolId":"0009:02779658772","status":"active","stats":{"documentsSent":142,"documentsReceived":87}}],"hasMore":false}}}},"400":{"description":"Invalid request payload (e.g. malformed VAT, missing required field, conflicting filters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a `platform` or `white_label` key, or the targeted `companyId` is not a company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (org-v2 / Sovos) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unreachable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/platform/api-keys":{"post":{"tags":["Platform"],"summary":"Mint an API key for a managed company","description":"Mint an API key on behalf of a managed company. Requires a `platform` or\n`white_label` key, the `api_keys.write` scope, and a `companyId` belonging\nto a company this platform manages — otherwise returns `403`.\n\nFor keys on your *own* organization, use the self-service endpoint\n`POST /v1/api-keys` instead.\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/platform/api-keys \\\n  -H \"Authorization: Bearer flw_plat_live_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"companyId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n    \"name\": \"HOZELOCK production key\",\n    \"scopes\": [\"documents.read\", \"documents.write\"]\n  }'\n```\n\n**Example 201 response:**\n```json\n{\n  \"id\": \"key_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e\",\n  \"key\": \"flw_live_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e9c2a4b6d8f1e3a5c\",\n  \"keyPrefix\": \"flw_live_3b81\",\n  \"name\": \"HOZELOCK production key\",\n  \"scopes\": [\"documents.read\", \"documents.write\"],\n  \"companyId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n  \"createdAt\": \"2026-06-17T09:14:22.157894Z\",\n  \"expiresAt\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                              |\n|--------|-----------------------|-------------------------------------------------------------------|\n| 400    | `invalid_request`     | Missing `companyId` (required for platform keys), past `expiresAt`. |\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header.                         |\n| 403    | `forbidden_key_type`  | Caller is not a `platform` / `white_label` key.                   |\n| 403    | `company_unmanaged`   | `companyId` is not one of this platform's managed companies.      |\n| 403    | `scope_escalation`    | Requested scopes exceed the caller's own scopes.                  |\n| 422    | `validation_error`    | Body failed Pydantic validation (e.g. blank `name`).              |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After`.                    |","operationId":"create_api_key_v1_platform_api_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateRequest"}}},"description":"`ApiKeyCreateRequest` — payload to mint an API key for a managed company."},"responses":{"201":{"description":"Key minted. The `key` field is the full secret — store it now, it will not be shown again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateResponse"},"example":{"id":"key_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e","key":"flw_live_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e9c2a4b6d8f1e3a5c","keyPrefix":"flw_live_3b81","name":"HOZELOCK production key","scopes":["documents.read","documents.write"],"companyId":"019a6e5b-3960-7000-ab88-153aed5a6240","createdAt":"2026-06-17T09:14:22.157894Z"}}}},"400":{"description":"Invalid request payload (e.g. malformed VAT, missing required field, conflicting filters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a `platform` or `white_label` key, or the targeted `companyId` is not a company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (org-v2 / Sovos) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unreachable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Platform"],"summary":"List API keys issued to managed companies","description":"List the API keys currently issued to companies this platform manages\n(i.e. keys carrying a `companyId`). Requires a `platform` or `white_label`\nkey with the `api_keys.read` scope. Pass `companyId` to scope down to one\nmanaged tenant. For keys on your own org, use `GET /v1/api-keys`.\n\n---\n\n**Example response:**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"key_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e\",\n      \"keyPrefix\": \"flw_live_3b81\",\n      \"name\": \"HOZELOCK production key\",\n      \"scopes\": [\"documents.read\", \"documents.write\"],\n      \"companyId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n      \"createdAt\": \"2026-04-12T08:00:00Z\",\n      \"expiresAt\": null\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                  |\n|--------|-----------------------|-------------------------------------------------------|\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header.             |\n| 403    | `forbidden_key_type`  | Caller is not a `platform` / `white_label` key.       |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After`.        |","operationId":"list_api_keys_v1_platform_api_keys_get","parameters":[{"name":"companyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter results to a single managed company id.","examples":["019a6e5b-3960-7000-ab88-153aed5a6240"],"title":"Companyid"},"description":"Filter results to a single managed company id."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Non-revoked keys belonging to managed companies (filter via `companyId`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_ApiKeyResponse_"},"example":{"data":[{"id":"key_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e","keyPrefix":"flw_live_3b81","name":"HOZELOCK production key","scopes":["documents.read","documents.write"],"companyId":"019a6e5b-3960-7000-ab88-153aed5a6240","createdAt":"2026-04-12T08:00:00Z"}],"hasMore":false}}}},"400":{"description":"Invalid request payload (e.g. malformed VAT, missing required field, conflicting filters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a `platform` or `white_label` key, or the targeted `companyId` is not a company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (org-v2 / Sovos) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unreachable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/platform/api-keys/{key_id}":{"delete":{"tags":["Platform"],"summary":"Revoke a managed-company API key","description":"Revoke a key previously issued under one of the platform's managed\ncompanies. Requires a `platform` or `white_label` key. Revocation is a\nsoft-delete: the key row is kept (audit trail) but `is_revoked=true` —\nfurther auth attempts return `401 unauthorized`.\n\n---\n\n**Example request:**\n```bash\ncurl -X DELETE https://api.flowie.fr/v1/platform/api-keys/key_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e \\\n  -H \"Authorization: Bearer flw_plat_live_…\"\n```\n\nReturns `204 No Content` — no body.\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                  |\n|--------|-----------------------|-------------------------------------------------------|\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header.             |\n| 403    | `forbidden_key_type`  | Caller is not a `platform` / `white_label` key.       |\n| 404    | `not_found`           | Key id does not exist under this platform's org.      |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After`.        |","operationId":"revoke_api_key_v1_platform_api_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"},"description":"Identifier of the API key."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"204":{"description":"Key revoked — subsequent requests using it will return 401."},"400":{"description":"Invalid request payload (e.g. malformed VAT, missing required field, conflicting filters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a `platform` or `white_label` key, or the targeted `companyId` is not a company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (org-v2 / Sovos) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unreachable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No API key with that id exists under the platform's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/platform/usage":{"get":{"tags":["Platform"],"summary":"Aggregate platform usage for a billing period","description":"Return aggregated document / API counters for the platform's organization\nover a billing period. Requires a `platform` or `white_label` key with the\n`usage.read` scope. Use `groupBy=company` to surface a per-managed-company\nbreakdown (handy for fan-out billing).\n\n---\n\n**Example request:**\n```\nGET /v1/platform/usage?period=2026-06&groupBy=company\n```\n\n**Example response:**\n```json\n{\n  \"period\": {\"value\": \"2026-06\"},\n  \"total\": {\n    \"documentsSent\": 5421,\n    \"documentsReceived\": 3187,\n    \"documentsFailed\": 12,\n    \"apiCalls\": 184230\n  },\n  \"byCompany\": [\n    {\n      \"apiKeyId\": \"key_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e\",\n      \"documentsSent\": 2103,\n      \"documentsReceived\": 1542,\n      \"documentsFailed\": 4,\n      \"apiCalls\": 71204\n    }\n  ]\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                  |\n|--------|-----------------------|-------------------------------------------------------|\n| 400    | `invalid_request`     | `period` not in `YYYY-MM` form.                       |\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header.             |\n| 403    | `forbidden_key_type`  | Caller is not a `platform` / `white_label` key.       |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After`.        |","operationId":"get_usage_v1_platform_usage_get","parameters":[{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Billing period in `YYYY-MM` form. Defaults to the current calendar month (UTC).","examples":["2026-06"],"title":"Period"},"description":"Billing period in `YYYY-MM` form. Defaults to the current calendar month (UTC)."},{"name":"groupBy","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Set to `company` to break totals down per managed company (returned in `byCompany`).","examples":["company"],"title":"Groupby"},"description":"Set to `company` to break totals down per managed company (returned in `byCompany`)."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Aggregated counters; `byCompany` populated only when `groupBy=company`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformUsageResponse"},"example":{"period":{"value":"2026-06"},"total":{"documentsSent":5421,"documentsReceived":3187,"documentsFailed":12,"apiCalls":184230},"byCompany":[{"apiKeyId":"key_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e","documentsSent":2103,"documentsReceived":1542,"documentsFailed":4,"apiCalls":71204},{"apiKeyId":"key_8f2c41a3b6e84d119f5a3c2b9a7d6e1f","documentsSent":3318,"documentsReceived":1645,"documentsFailed":8,"apiCalls":113026}]}}}},"400":{"description":"Invalid request payload (e.g. malformed VAT, missing required field, conflicting filters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a `platform` or `white_label` key, or the targeted `companyId` is not a company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (org-v2 / Sovos) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unreachable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/platform/events":{"get":{"tags":["Platform"],"summary":"Stream every event across the platform's managed companies","description":"Return the unified event stream for *every* company managed by this\nplatform — useful as a fallback when an integrator's webhook receiver is\ndown, or for nightly reconciliation jobs. Requires a `platform` or\n`white_label` key with the `events.read` scope.\n\nDefaults to the most recent 100 events. Use the `cursor` returned in the\nresponse to paginate older entries (cursor-based pagination, opaque token).\n\n---\n\n**Example response:**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"evt_01HFTQ4M5XW8C2A3B4D5E6F7G8\",\n      \"type\": \"document.delivered\",\n      \"createdAt\": \"2026-06-17T09:14:22.157894Z\",\n      \"data\": {\n        \"documentId\": \"doc_01HFTQ4M5XW8C2A3B4D5E6F7G8\",\n        \"companyId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n        \"recipientPeppolId\": \"0208:0123456789\"\n      }\n    }\n  ],\n  \"hasMore\": true,\n  \"cursor\": \"evt_01HFTQ4M5XW8C2A3B4D5E6F7G8\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                  |\n|--------|-----------------------|-------------------------------------------------------|\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header.             |\n| 403    | `forbidden_key_type`  | Caller is not a `platform` / `white_label` key.       |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After`.        |","operationId":"get_platform_events_v1_platform_events_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Page of events emitted across every company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_EventResponse_"},"example":{"data":[{"id":"evt_01HFTQ4M5XW8C2A3B4D5E6F7G8","type":"document.delivered","createdAt":"2026-06-17T09:14:22.157894Z","data":{"documentId":"doc_01HFTQ4M5XW8C2A3B4D5E6F7G8","companyId":"019a6e5b-3960-7000-ab88-153aed5a6240","recipientPeppolId":"0208:0123456789"}}],"hasMore":true,"cursor":"evt_01HFTQ4M5XW8C2A3B4D5E6F7G8"}}}},"400":{"description":"Invalid request payload (e.g. malformed VAT, missing required field, conflicting filters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a `platform` or `white_label` key, or the targeted `companyId` is not a company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (org-v2 / Sovos) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unreachable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/platform/settings":{"patch":{"tags":["Platform"],"summary":"Update platform branding, defaults, and custom domain","description":"Persist platform-level configuration — branding (logo, colors, email\nfrom-name), per-company defaults (tier, webhook events), and an optional\ncustom CNAME for the white-label dashboard. Requires a `platform` or\n`white_label` key with the `settings.write` scope.\n\n`branding` and `defaults` are **merged** with whatever is already stored —\nkeys you omit are preserved. `customDomain` is a straight replace.\nIn sandbox the body is echoed back without persistence.\n\n---\n\n**Example request:**\n```bash\ncurl -X PATCH https://api.flowie.fr/v1/platform/settings \\\n  -H \"Authorization: Bearer flw_plat_live_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"branding\": {\"primaryColor\": \"#1A73E8\"},\n    \"defaults\": {\"tier\": \"starter\"},\n    \"customDomain\": \"peppol.acme-platform.com\"\n  }'\n```\n\n**Example response:**\n```json\n{\n  \"branding\": {\n    \"logoUrl\": \"https://cdn.example.com/logo.svg\",\n    \"primaryColor\": \"#1A73E8\"\n  },\n  \"defaults\": {\"tier\": \"starter\"},\n  \"customDomain\": \"peppol.acme-platform.com\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                  |\n|--------|-----------------------|-------------------------------------------------------|\n| 400    | `invalid_request`     | Malformed `customDomain` (not a hostname).            |\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header.             |\n| 403    | `forbidden_key_type`  | Caller is not a `platform` / `white_label` key.       |\n| 422    | `validation_error`    | Body failed Pydantic validation.                      |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After`.        |\n| 500    | `internal_error`      | Database error while writing `PlatformConfig`.        |","operationId":"update_platform_settings_v1_platform_settings_patch","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSettingsUpdate"}}},"description":"`PlatformSettingsUpdate` — payload to update platform branding, defaults, and custom domain."},"responses":{"200":{"description":"Merged settings — branding/defaults are merged with the existing row, `customDomain` replaces.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformSettingsUpdate"},"example":{"branding":{"logoUrl":"https://cdn.example.com/logo.svg","primaryColor":"#1A73E8","emailFromName":"Acme Platform"},"defaults":{"tier":"starter","webhook":{"events":["document.received","document.delivered"]}},"customDomain":"peppol.acme-platform.com"}}}},"400":{"description":"Invalid request payload (e.g. malformed VAT, missing required field, conflicting filters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller is not a `platform` or `white_label` key, or the targeted `companyId` is not a company managed by this platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream service (org-v2 / Sovos) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream service unreachable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/api-keys":{"post":{"tags":["API Keys"],"summary":"Create an API key for your own organization","description":"Mint a new API key bound to the caller's organization and tier.\n\nAvailable to any authenticated principal (JWT user or another API key).\nThe new key inherits the caller's organization and tier, and is **scope-clamped**\nto the caller's own scopes — a key holding `documents.read` cannot mint a\nkey with `documents.write`. Pass `expiresAt` to issue a short-lived key\n(must be in the future); pass `rateLimit.requestsPerMinute` to lower the\nkey's rate limit below the tier ceiling.\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/api-keys \\\n  -H \"Authorization: Bearer flw_live_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"CI deploy key\",\n    \"scopes\": [\"documents.read\", \"documents.write\"],\n    \"expiresAt\": \"2027-06-17T09:14:22Z\"\n  }'\n```\n\n**Example 201 response:**\n```json\n{\n  \"id\": \"key_8f2c41a3b6e84d119f5a3c2b9a7d6e1f\",\n  \"key\": \"flw_live_8f2c41a3b6e84d119f5a3c2b9a7d6e1f7a2c5b8d3e9f0a1b\",\n  \"keyPrefix\": \"flw_live_8f2c\",\n  \"name\": \"CI deploy key\",\n  \"scopes\": [\"documents.read\", \"documents.write\"],\n  \"companyId\": null,\n  \"createdAt\": \"2026-06-17T09:14:22.157894Z\",\n  \"expiresAt\": \"2027-06-17T09:14:22.157894Z\"\n}\n```\n\n**Example 403 response — scope escalation blocked:**\n```json\n{\n  \"type\": \"forbidden\",\n  \"code\": \"scope_escalation\",\n  \"message\": \"Cannot grant scopes the caller does not hold: ['documents.write']\",\n  \"details\": [],\n  \"requestId\": \"req_01HF…\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                                 |\n|--------|-----------------------|----------------------------------------------------------------------|\n| 400    | `invalid_request`     | `expiresAt` in the past or malformed `rateLimit`.                    |\n| 401    | `unauthorized`        | Missing / invalid `Authorization` header.                            |\n| 403    | `scope_escalation`    | Requested scopes exceed the caller's own scopes.                     |\n| 422    | `validation_error`    | Body failed Pydantic validation (e.g. blank `name`).                 |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.               |\n| 500    | `internal_error`      | Persistence layer error while saving the new key.                    |","operationId":"create_api_key_v1_api_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateRequest"}}},"description":"`ApiKeyCreateRequest` — payload to create an API key for your own organization."},"responses":{"201":{"description":"Key minted. The `key` field is the full secret — store it now, it will not be shown again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateResponse"},"example":{"id":"key_8f2c41a3b6e84d119f5a3c2b9a7d6e1f","key":"flw_live_8f2c41a3b6e84d119f5a3c2b9a7d6e1f7a2c5b8d3e9f0a1b","keyPrefix":"flw_live_8f2c","name":"CI deploy key","scopes":["documents.read","documents.write"],"createdAt":"2026-06-17T09:14:22.157894Z","expiresAt":"2027-06-17T09:14:22.157894Z"}}}},"400":{"description":"Invalid request — e.g. `expiresAt` set to a past timestamp, or `rateLimit.requestsPerMinute` not a positive integer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller tried to grant scopes it does not itself hold. New keys are scope-clamped to the caller's own scopes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while persisting the key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["API Keys"],"summary":"List API keys for your own organization","description":"List every non-revoked API key in the caller's organization.\n\nReturns the keys' metadata only — the raw secret is **never** returned\nafter creation. The `keyPrefix` (first 12–16 characters) is enough to\ncorrelate a logged request back to a specific key for audit/rotation\nworkflows.\n\n---\n\n**Example response:**\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"key_8f2c41a3b6e84d119f5a3c2b9a7d6e1f\",\n      \"keyPrefix\": \"flw_live_8f2c\",\n      \"name\": \"CI deploy key\",\n      \"scopes\": [\"documents.read\", \"documents.write\"],\n      \"companyId\": null,\n      \"createdAt\": \"2026-06-17T09:14:22.157894Z\",\n      \"expiresAt\": \"2027-06-17T09:14:22.157894Z\"\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code              | When                                                  |\n|--------|-------------------|-------------------------------------------------------|\n| 401    | `unauthorized`    | Missing / invalid `Authorization` header.             |\n| 403    | `forbidden`       | Key valid but lacks read access to its own org.       |\n| 429    | `rate_limited`    | Too many requests — retry after `Retry-After`.        |\n| 500    | `internal_error`  | Database error while reading the key table.           |","operationId":"list_api_keys_v1_api_keys_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"List of non-revoked keys belonging to the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedResponse_ApiKeyResponse_"},"example":{"data":[{"id":"key_8f2c41a3b6e84d119f5a3c2b9a7d6e1f","keyPrefix":"flw_live_8f2c","name":"CI deploy key","scopes":["documents.read","documents.write"],"createdAt":"2026-06-17T09:14:22.157894Z","expiresAt":"2027-06-17T09:14:22.157894Z"},{"id":"key_3b81e0d2f74a4c9b8e1f6a5d2c8b9a3e","keyPrefix":"flw_test_3b81e0d2","name":"Local dev","scopes":["*"],"companyId":"comp_abc123","createdAt":"2026-05-02T11:08:45.000000Z"}],"hasMore":false}}}},"400":{"description":"Invalid request — e.g. `expiresAt` set to a past timestamp, or `rateLimit.requestsPerMinute` not a positive integer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller tried to grant scopes it does not itself hold. New keys are scope-clamped to the caller's own scopes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while persisting the key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/api-keys/{key_id}":{"delete":{"tags":["API Keys"],"summary":"Revoke an API key by id","description":"Revoke an API key permanently. The key remains in the database (audit\ntrail) but is marked `is_revoked=true`, so any future request presenting\nits secret will fail authentication with `401 unauthorized`.\n\nRevocation is **idempotent** in sandbox/test-mode: revoking a key that\ndoes not exist returns 204 (so test runs that recreate keys don't flake).\nIn live mode, an unknown `key_id` returns `404 not_found`.\n\n---\n\n**Example request:**\n```bash\ncurl -X DELETE https://api.flowie.fr/v1/api-keys/key_8f2c41a3b6e84d119f5a3c2b9a7d6e1f \\\n  -H \"Authorization: Bearer flw_live_…\"\n```\n\nReturns `204 No Content` on success — no body.\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code              | When                                                  |\n|--------|-------------------|-------------------------------------------------------|\n| 401    | `unauthorized`    | Missing / invalid `Authorization` header.             |\n| 403    | `forbidden`       | Caller cannot revoke keys it doesn't own.             |\n| 404    | `not_found`       | No key with that id in the caller's org (live mode).  |\n| 429    | `rate_limited`    | Too many requests — retry after `Retry-After`.        |\n| 500    | `internal_error`  | Database error while updating the key row.            |","operationId":"revoke_api_key_v1_api_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"},"description":"Identifier of the API key."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"204":{"description":"Key revoked — subsequent requests using it will return 401."},"400":{"description":"Invalid request — e.g. `expiresAt` set to a past timestamp, or `rateLimit.requestsPerMinute` not a positive integer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Caller tried to grant scopes it does not itself hold. New keys are scope-clamped to the caller's own scopes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while persisting the key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No API key with that id exists in the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/categorization/groups":{"get":{"tags":["Categorization"],"summary":"List available tag groups (UNSPSC, Accounting, Custom, …)","description":"List the tag groups available to the calling organisation.\n\nA *tag group* is a category schema — e.g. `UNSPSC` (United Nations Standard Products\nand Services Code, used for procurement classification), `Accounting` (revenue / COGS\n/ opex buckets), or a user-defined `Custom` group. Each group hosts a flat list of\n`Tag` values that can then be assigned to documents, partners, suppliers, etc.\n\nDelegates to the internal Flowie tag service (`TAGS_URL`). Sandbox API keys get a\nsynthetic three-group stub so the contract is exercisable without depending on the\nlive service.\n\n---\n\n**Example request:**\n```\nGET /v1/categorization/groups\nAuthorization: Bearer flw_live_***\n```\n\n**Example response (200):**\n```json\n[\n  {\"id\": \"grp_unspsc\",     \"name\": \"UNSPSC\",     \"code\": \"unspsc\",     \"kind\": \"system\"},\n  {\"id\": \"grp_accounting\", \"name\": \"Accounting\", \"code\": \"accounting\", \"kind\": \"system\"},\n  {\"id\": \"grp_custom\",     \"name\": \"Custom\",     \"code\": \"custom\",     \"kind\": \"user\"}\n]\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Malformed request.                                              |\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `categorization:read` scope.                          |\n| 422    | `validation_error`    | Request failed schema validation.                               |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure while contacting the tag service.              |\n| 502    | `upstream_error`      | Upstream tag service returned a non-2xx response.               |\n| 503    | `upstream_unavailable`| Upstream tag service unreachable / timed out.                   |","operationId":"list_groups_v1_categorization_groups_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"code":{"type":"string"},"kind":{"type":"string"}}}},"example":[{"id":"grp_unspsc","name":"UNSPSC","code":"unspsc","kind":"system"},{"id":"grp_accounting","name":"Accounting","code":"accounting","kind":"system"},{"id":"grp_custom","name":"Custom","code":"custom","kind":"user"}]}}},"400":{"description":"Malformed request body or path parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream tag service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream tag service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/categorization/groups/{group_id}/tags":{"get":{"tags":["Categorization"],"summary":"List the tags (category values) inside a group","description":"List every tag belonging to a given group.\n\n`group_id` is the id returned by `GET /v1/categorization/groups` (e.g.\n`grp_unspsc`). An empty list is returned for groups that simply have no tags yet —\nno 404 — matching the upstream tag-service contract.\n\n---\n\n**Example request:**\n```\nGET /v1/categorization/groups/grp_unspsc/tags\nAuthorization: Bearer flw_live_***\n```\n\n**Example response (200):**\n```json\n[\n  {\"id\": \"tag_servprof\", \"tagGroupId\": \"grp_unspsc\", \"code\": \"80101504\", \"label\": \"Professional services\"},\n  {\"id\": \"tag_software\", \"tagGroupId\": \"grp_unspsc\", \"code\": \"43232300\", \"label\": \"Software\"}\n]\n```\n\n**Example response (200, empty group):**\n```json\n[]\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Malformed `group_id`.                                           |\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `categorization:read` scope.                          |\n| 404    | `not_found`           | Group does not exist for this org.                              |\n| 422    | `validation_error`    | Path / query failed schema validation.                          |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure.                                               |\n| 502    | `upstream_error`      | Upstream tag service returned a non-2xx response.               |\n| 503    | `upstream_unavailable`| Upstream tag service unreachable / timed out.                   |","operationId":"list_group_tags_v1_categorization_groups__group_id__tags_get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"},"description":"Identifier of the group."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"tagGroupId":{"type":"string"},"code":{"type":"string"},"label":{"type":"string"}}}},"example":[{"id":"tag_servprof","tagGroupId":"grp_unspsc","code":"80101504","label":"Professional services"},{"id":"tag_software","tagGroupId":"grp_unspsc","code":"43232300","label":"Software"}]}}},"400":{"description":"Malformed request body or path parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream tag service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream tag service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Group, tag, or object not found in the tag service.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/categorization/objects/{object_id}/tags":{"get":{"tags":["Categorization"],"summary":"Get all tags assigned to a document, partner, or supplier","description":"List every tag currently assigned to a single object.\n\n`object_id` can be any Flowie object id — a document (`doc_…`), partner (`prt_…`),\nsupplier, etc. The response is a flat list with the originating group id so the\ncaller can render breadcrumbs (group → tag) without a second round-trip.\n\n---\n\n**Example request:**\n```\nGET /v1/categorization/objects/doc_01HZXABCDEF0123456789/tags\nAuthorization: Bearer flw_live_***\n```\n\n**Example response (200):**\n```json\n[\n  {\n    \"id\": \"oat_01HZXTAGGED012345\",\n    \"objectId\": \"doc_01HZXABCDEF0123456789\",\n    \"objectType\": \"document\",\n    \"tagId\": \"tag_servprof\",\n    \"tagGroupId\": \"grp_unspsc\",\n    \"label\": \"Professional services\",\n    \"assignedAt\": \"2026-06-10T12:34:56Z\"\n  }\n]\n```\n\n**Example response (200, sandbox / no tags):**\n```json\n[]\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Malformed `object_id`.                                          |\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `categorization:read` scope.                          |\n| 404    | `not_found`           | Object not known to the tag service.                            |\n| 422    | `validation_error`    | Path validation failed.                                         |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure.                                               |\n| 502    | `upstream_error`      | Upstream tag service returned a non-2xx response.               |\n| 503    | `upstream_unavailable`| Upstream tag service unreachable / timed out.                   |","operationId":"get_object_tags_v1_categorization_objects__object_id__tags_get","parameters":[{"name":"object_id","in":"path","required":true,"schema":{"type":"string","title":"Object Id"},"description":"Identifier of the object the operation acts on."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"objectId":{"type":"string"},"objectType":{"type":"string"},"tagId":{"type":"string"},"tagGroupId":{"type":"string"},"label":{"type":"string"},"assignedAt":{"type":"string"}}}},"example":[{"id":"oat_01HZXTAGGED012345","objectId":"doc_01HZXABCDEF0123456789","objectType":"document","tagId":"tag_servprof","tagGroupId":"grp_unspsc","label":"Professional services","assignedAt":"2026-06-10T12:34:56Z"}]}}},"400":{"description":"Malformed request body or path parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream tag service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream tag service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Group, tag, or object not found in the tag service.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"tags":["Categorization"],"summary":"Assign a tag to an object","description":"Assign a single tag to an object (document, partner, supplier, …).\n\nThe request body must be a JSON object containing:\n\n- `tagId` (required) — id of the tag to attach (must already exist in the org).\n- `objectType` (required) — one of `document`, `partner`, `supplier`, `company`.\n\nReturns the newly created `ObjectAssignedTag` record (`oat_…`).\n\n---\n\n**Example request:**\n```\nPOST /v1/categorization/objects/doc_01HZXABCDEF0123456789/tags\nAuthorization: Bearer flw_live_***\nContent-Type: application/json\n\n{\n  \"tagId\": \"tag_servprof\",\n  \"objectType\": \"document\"\n}\n```\n\n**Example response (201):**\n```json\n{\n  \"id\": \"oat_01HZXNEWASSIGN0001\",\n  \"objectId\": \"doc_01HZXABCDEF0123456789\",\n  \"objectType\": \"document\",\n  \"tagId\": \"tag_servprof\",\n  \"assignedAt\": \"2026-06-17T09:21:00Z\"\n}\n```\n\n**Example response (422):**\n```json\n{\n  \"type\": \"about:blank\",\n  \"code\": \"validation_error\",\n  \"message\": \"body must be a JSON object\",\n  \"details\": [{\"field\": \"body\", \"rule\": \"type\", \"message\": \"Input should be a valid dictionary\"}],\n  \"requestId\": \"req_01HZX...\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Missing `tagId` / `objectType`, or unknown values.              |\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `categorization:write` scope.                         |\n| 404    | `not_found`           | Tag or object does not exist.                                   |\n| 409    | `conflict`            | The tag is already assigned to this object.                     |\n| 422    | `validation_error`    | Body failed schema validation (e.g. body not a JSON object).    |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure.                                               |\n| 502    | `upstream_error`      | Upstream tag service returned a non-2xx response.               |\n| 503    | `upstream_unavailable`| Upstream tag service unreachable / timed out.                   |","operationId":"assign_tag_v1_categorization_objects__object_id__tags_post","parameters":[{"name":"object_id","in":"path","required":true,"schema":{"type":"string","title":"Object Id"},"description":"Identifier of the object the operation acts on."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"examples":[{"tagId":"tag_servprof","objectType":"document"},{"tagId":"tag_revenue","objectType":"supplier"}],"title":"Body"}}},"description":"Payload to assign a tag to an object."},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"objectId":{"type":"string"},"objectType":{"type":"string"},"tagId":{"type":"string"},"assignedAt":{"type":"string"}}},"example":{"id":"oat_01HZXNEWASSIGN0001","objectId":"doc_01HZXABCDEF0123456789","objectType":"document","tagId":"tag_servprof","assignedAt":"2026-06-17T09:21:00Z"}}}},"400":{"description":"Malformed request body or path parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream tag service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream tag service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Group, tag, or object not found in the tag service.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The same tag is already assigned to this object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/categorization/objects/{object_id}/tags/{tag_id}":{"delete":{"tags":["Categorization"],"summary":"Remove a tag from an object","description":"Remove a single tag from an object. Idempotent for sandbox keys (no-op).\n\nReturns `204 No Content` on success — no response body.\n\n---\n\n**Example request:**\n```\nDELETE /v1/categorization/objects/doc_01HZXABCDEF0123456789/tags/tag_servprof\nAuthorization: Bearer flw_live_***\n```\n\n**Example response (204):** *(empty body)*\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `categorization:write` scope.                         |\n| 404    | `not_found`           | No such assignment.                                             |\n| 422    | `validation_error`    | Path validation failed.                                         |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure.                                               |\n| 502    | `upstream_error`      | Upstream tag service returned a non-2xx response.               |\n| 503    | `upstream_unavailable`| Upstream tag service unreachable / timed out.                   |","operationId":"remove_tag_v1_categorization_objects__object_id__tags__tag_id__delete","parameters":[{"name":"object_id","in":"path","required":true,"schema":{"type":"string","title":"Object Id"},"description":"Identifier of the object the operation acts on."},{"name":"tag_id","in":"path","required":true,"schema":{"type":"string","title":"Tag Id"},"description":"Identifier of the tag."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"204":{"description":"Tag assignment removed. No content."},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Assignment not found (object or tag never linked).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Path failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream tag service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream tag service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/categorization/objects/tags/auto":{"post":{"tags":["Categorization"],"summary":"Get AI-recommended tags for an object","description":"Ask the categorization engine for likely tag assignments.\n\nThe body must be a JSON object naming the target:\n\n- `objectId` (required) — Flowie id of the document / partner / supplier to score.\n- `objectType` (optional, defaults to `document`).\n\nReturns an ordered list of `suggestions`, each with a `tagId`, a `label`, a\n`confidence` in `[0.0, 1.0]`, and a free-text `reasoning` field describing why the\nengine selected that tag. The caller decides whether to auto-apply or surface them\nfor human confirmation.\n\n---\n\n**Example request:**\n```\nPOST /v1/categorization/objects/tags/auto\nAuthorization: Bearer flw_live_***\nContent-Type: application/json\n\n{\"objectId\": \"doc_01HZXABCDEF0123456789\", \"objectType\": \"document\"}\n```\n\n**Example response (200):**\n```json\n{\n  \"objectId\": \"doc_01HZXABCDEF0123456789\",\n  \"objectType\": \"document\",\n  \"suggestions\": [\n    {\"tagId\": \"tag_servprof\", \"label\": \"Professional services\", \"confidence\": 0.92, \"reasoning\": \"Line items mention 'consulting'.\"},\n    {\"tagId\": \"tag_revenue\",  \"label\": \"Revenue\",               \"confidence\": 0.78, \"reasoning\": \"Outgoing invoice.\"}\n  ]\n}\n```\n\n**Example response (400, body not a JSON object):**\n```json\n{\n  \"type\": \"about:blank\",\n  \"code\": \"invalid_request\",\n  \"message\": \"request body must be a JSON object\",\n  \"details\": [],\n  \"requestId\": \"req_01HZX...\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Body not a JSON object, or unknown `objectId`.                  |\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `categorization:read` scope.                          |\n| 422    | `validation_error`    | Body failed schema validation.                                  |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure.                                               |\n| 502    | `upstream_error`      | Upstream tag service returned a non-2xx response.               |\n| 503    | `upstream_unavailable`| Upstream tag service unreachable / timed out.                   |","operationId":"auto_recommend_v1_categorization_objects_tags_auto_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"examples":[{"objectId":"doc_01HZXABCDEF0123456789","objectType":"document"},{"objectId":"prt_01HZXPARTNER0000001","objectType":"partner"}],"title":"Body"}}},"description":"Payload to get AI-recommended tags for an object."},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"objectId":{"type":"string"},"objectType":{"type":"string"},"suggestions":{"type":"array","items":{"type":"object","properties":{"tagId":{"type":"string"},"label":{"type":"string"},"confidence":{"type":"number"},"reasoning":{"type":"string"}}}}}},"example":{"objectId":"doc_01HZXABCDEF0123456789","objectType":"document","suggestions":[{"tagId":"tag_servprof","label":"Professional services","confidence":0.92,"reasoning":"Line items mention 'consulting' and 'advisory'."},{"tagId":"tag_revenue","label":"Revenue","confidence":0.78,"reasoning":"Outgoing invoice booked against revenue account."}]}}}},"400":{"description":"Malformed request body or path parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but lacks permission for this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream tag service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream tag service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/payments/documents/{documentId}":{"get":{"tags":["Payments"],"summary":"Get payment information for a document (invoice / PO)","description":"Return the payment ledger for a single document (typically an invoice or PO).\n\nThe response includes every individual `Payment` record linked to the document, the\n`totalPaid` so far, the remaining `balanceDue` (when the document amount is known),\nand the document's `currency`. Sandbox API keys receive an empty ledger.\n\n---\n\n**Example request:**\n```\nGET /v1/payments/documents/doc_01HZXABCDEF0123456789\nAuthorization: Bearer flw_live_***\n```\n\n**Example response (200):**\n```json\n{\n  \"objectId\": \"doc_01HZXABCDEF0123456789\",\n  \"objectType\": \"document\",\n  \"organizationId\": \"comp_abc123\",\n  \"payments\": [\n    {\n      \"id\": \"pay_01HZXPAID0000001\",\n      \"amount\": 1200.00,\n      \"currency\": \"EUR\",\n      \"paidAt\": \"2026-06-10T13:42:11Z\",\n      \"method\": \"SEPA\",\n      \"reference\": \"INV-2026-0042\",\n      \"status\": \"recorded\"\n    }\n  ],\n  \"balanceDue\": 0.00,\n  \"totalPaid\": 1200.00,\n  \"currency\": \"EUR\"\n}\n```\n\n**Example response (404):**\n```json\n{\n  \"type\": \"about:blank\",\n  \"code\": \"not_found\",\n  \"message\": \"Document doc_01HZXABCDEF0123456789 not found\",\n  \"details\": [],\n  \"requestId\": \"req_01HZX...\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Malformed `documentId`.                                         |\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `payments` scope.                                     |\n| 404    | `not_found`           | Document does not exist for this org.                           |\n| 422    | `validation_error`    | Path validation failed.                                         |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure.                                               |\n| 502    | `upstream_error`      | Upstream payment service returned a non-2xx response.           |\n| 503    | `upstream_unavailable`| Upstream payment service unreachable / timed out.               |","operationId":"get_document_payments_v1_payments_documents__documentId__get","parameters":[{"name":"documentId","in":"path","required":true,"schema":{"type":"string","title":"Documentid"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"objectId":{"type":"string"},"objectType":{"type":"string"},"organizationId":{"type":"string"},"payments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"paidAt":{"type":"string"},"method":{"type":"string"},"reference":{"type":"string"},"status":{"type":"string"}}}},"balanceDue":{"type":"number"},"totalPaid":{"type":"number"},"currency":{"type":"string"}}},"example":{"objectId":"doc_01HZXABCDEF0123456789","objectType":"document","organizationId":"comp_abc123","payments":[{"id":"pay_01HZXPAID0000001","amount":1200.0,"currency":"EUR","paidAt":"2026-06-10T13:42:11Z","method":"SEPA","reference":"INV-2026-0042","status":"recorded"}],"balanceDue":0.0,"totalPaid":1200.0,"currency":"EUR"}}}},"400":{"description":"Malformed request body or path parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the `payments` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream payment service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream payment service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document / payment object not found for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/payments/documents/{documentId}/pay":{"post":{"tags":["Payments"],"summary":"Record a payment against a document","description":"Record a payment made against a document (invoice or PO).\n\nBody fields:\n\n- `amount` (required) — numeric amount paid (use a positive number).\n- `date` (optional) — `YYYY-MM-DD` (or an ISO 8601 timestamp). `paidAt` is\n  accepted as an alias.\n- `reference` (optional) — free-form external reference (e.g. bank txn id).\n- `currency` (optional, default `EUR`) — ISO 4217 code.\n- `method` (optional) — `SEPA`, `card`, `cheque`, `cash`, `wire`, `other`.\n- `note` (optional) — free-text comment stored on the payment.\n\nReturns the created `Payment` record. The document's `totalPaid` and `balanceDue`\nare recomputed on the next `GET /v1/payments/documents/{documentId}`.\n\nThe document lifecycle is advanced to `partially_paid`, or `paid` once the\nrecorded payments cover `totalAmountDue`. This is best-effort: the payment is\nrecorded regardless, and `lifecycleStatus` in the response is `null` when the\ntransition was not permitted from the document's current status (only\n`approved`, `partially_paid` and `disputed` can move to a paid state — see\n`GET /v1/documents/{documentId}/lifecycle` → `allowedTransitions`).\n\n---\n\n**Example request:**\n```\nPOST /v1/payments/documents/doc_01HZXABCDEF0123456789/pay\nAuthorization: Bearer flw_live_***\nContent-Type: application/json\n\n{\n  \"amount\": 1200.00,\n  \"currency\": \"EUR\",\n  \"method\": \"SEPA\",\n  \"paidAt\": \"2026-06-17T09:30:00Z\",\n  \"reference\": \"INV-2026-0042\"\n}\n```\n\n**Example response (201):**\n```json\n{\n  \"id\": \"pay_01HZXNEW000000001\",\n  \"objectId\": \"doc_01HZXABCDEF0123456789\",\n  \"objectType\": \"document\",\n  \"organizationId\": \"comp_abc123\",\n  \"amount\": 1200.00,\n  \"currency\": \"EUR\",\n  \"paidAt\": \"2026-06-17T09:30:00Z\",\n  \"method\": \"SEPA\",\n  \"reference\": \"INV-2026-0042\",\n  \"status\": \"recorded\",\n  \"lifecycleStatus\": \"paid\",\n  \"createdAt\": \"2026-06-17T09:30:00Z\"\n}\n```\n\n**Example response (422):**\n```json\n{\n  \"type\": \"about:blank\",\n  \"code\": \"validation_error\",\n  \"message\": \"amount must be a positive number\",\n  \"details\": [{\"field\": \"amount\", \"rule\": \"gt\", \"message\": \"ensure this value is greater than 0\"}],\n  \"requestId\": \"req_01HZX...\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Missing `amount`, unknown `method`, malformed `paidAt`.         |\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `payments` scope.                                     |\n| 404    | `not_found`           | Document does not exist for this org.                           |\n| 409    | `conflict`            | Duplicate payment (idempotency clash / reference re-used).      |\n| 422    | `validation_error`    | Body schema validation failed.                                  |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure.                                               |\n| 502    | `upstream_error`      | Upstream payment service returned a non-2xx response.           |\n| 503    | `upstream_unavailable`| Upstream payment service unreachable / timed out.               |","operationId":"record_document_payment_v1_payments_documents__documentId__pay_post","parameters":[{"name":"documentId","in":"path","required":true,"schema":{"type":"string","title":"Documentid"},"description":"Identifier of the document."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"examples":[{"amount":1200.0,"date":"2026-06-17","reference":"INV-2026-0042"},{"amount":500.0,"date":"2026-06-17","currency":"USD","method":"card","reference":"PO-2026-0099"}],"title":"Body"}}},"description":"Payload to record a payment against a document."},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"objectId":{"type":"string"},"objectType":{"type":"string"},"organizationId":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"paidAt":{"type":"string"},"method":{"type":"string"},"reference":{"type":"string"},"status":{"type":"string"},"lifecycleStatus":{"type":"string"},"createdAt":{"type":"string"}}},"example":{"id":"pay_01HZXNEW000000001","objectId":"doc_01HZXABCDEF0123456789","objectType":"document","organizationId":"comp_abc123","amount":1200.0,"currency":"EUR","paidAt":"2026-06-17T09:30:00Z","method":"SEPA","reference":"INV-2026-0042","status":"recorded","lifecycleStatus":"paid","createdAt":"2026-06-17T09:30:00Z"}}}},"400":{"description":"Malformed request body or path parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the `payments` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream payment service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream payment service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Document / payment object not found for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A payment with the same idempotency key / reference already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/payments/export/iso20022":{"post":{"tags":["Payments"],"summary":"Generate an ISO 20022 (SEPA pain.001) payment file","description":"Generate an ISO 20022 (`pain.001.001.09`) SEPA Credit Transfer Initiation file.\n\nBundles one or more documents into a single payment file the caller can hand to its\nbank. The returned `content` is base64-encoded XML — decode it before saving / upload.\n\nBody fields:\n\n- `documentIds` (required) — list of document ids to include. Each must have a\n  registered creditor bank account and a non-zero outstanding balance.\n- `currency` (optional, default `EUR`) — ISO 4217 currency the bundle is settled in.\n- `executionDate` (optional) — ISO 8601 date on which the bank should execute the\n  payments. Defaults to next business day.\n\nSandbox keys receive a minimal placeholder pain.001 with `transactionCount: 0`.\n\n---\n\n**Example request:**\n```\nPOST /v1/payments/export/iso20022\nAuthorization: Bearer flw_live_***\nContent-Type: application/json\n\n{\n  \"documentIds\": [\"doc_01HZXABCDEF0123456789\", \"doc_01HZXABCDEF0123456790\"],\n  \"currency\": \"EUR\",\n  \"executionDate\": \"2026-06-18\"\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"messageId\": \"MSG-2026-06-17-0001\",\n  \"organizationId\": \"comp_abc123\",\n  \"format\": \"pain.001.001.09\",\n  \"filename\": \"MSG-2026-06-17-0001.xml\",\n  \"content\": \"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4K…\",\n  \"contentType\": \"application/xml\",\n  \"transactionCount\": 2,\n  \"totalAmount\": 2400.00,\n  \"currency\": \"EUR\",\n  \"generatedAt\": \"2026-06-17T09:35:14Z\"\n}\n```\n\n**Example response (400):**\n```json\n{\n  \"type\": \"about:blank\",\n  \"code\": \"invalid_request\",\n  \"message\": \"documentIds must contain at least one document\",\n  \"details\": [{\"field\": \"documentIds\", \"rule\": \"min_items\", \"message\": \"ensure this list has at least 1 items\"}],\n  \"requestId\": \"req_01HZX...\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                  | When                                                            |\n|--------|-----------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`     | Empty `documentIds`, malformed `executionDate`, unknown currency.|\n| 401    | `unauthorized`        | Missing / invalid API key.                                      |\n| 403    | `forbidden`           | Key lacks `payments` scope.                                     |\n| 422    | `validation_error`    | Body schema validation failed.                                  |\n| 429    | `rate_limited`        | Too many requests — retry after `Retry-After` seconds.          |\n| 500    | `internal_error`      | Internal failure.                                               |\n| 502    | `upstream_error`      | Upstream payment service returned a non-2xx response.           |\n| 503    | `upstream_unavailable`| Upstream payment service unreachable / timed out.               |","operationId":"export_iso20022_v1_payments_export_iso20022_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"examples":[{"documentIds":["doc_01HZXABCDEF0123456789","doc_01HZXABCDEF0123456790","doc_01HZXABCDEF0123456791"],"currency":"EUR","executionDate":"2026-06-18"}],"title":"Body"}}},"description":"Payload to generate an ISO 20022 (SEPA pain.001) payment file."},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"messageId":{"type":"string"},"organizationId":{"type":"string"},"format":{"type":"string"},"filename":{"type":"string"},"content":{"type":"string"},"contentType":{"type":"string"},"transactionCount":{"type":"integer"},"totalAmount":{"type":"number"},"currency":{"type":"string"},"generatedAt":{"type":"string"}}},"example":{"messageId":"MSG-2026-06-17-0001","organizationId":"comp_abc123","format":"pain.001.001.09","filename":"MSG-2026-06-17-0001.xml","content":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4K…(base64)…","contentType":"application/xml","transactionCount":3,"totalAmount":4525.5,"currency":"EUR","generatedAt":"2026-06-17T09:35:14Z"}}}},"400":{"description":"Malformed request body or path parameter.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key valid but lacks the `payments` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body / query failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream payment service returned an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream payment service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/me":{"get":{"tags":["Identity"],"summary":"Get the caller identity + accessible organizations","description":"Return the caller's identity plus the organizations they can act as.\n\nThis is the first call most integrations make — the docs auth widget reads it\nto render the organization switcher, and clients use it to verify their API key\nis valid and to discover the org id to send in `X-Flowie-Organization-Id` on\nsubsequent requests.\n\nFor **JWT** auth, `organizationIds` is every org in the user's `_permissions`\nclaim. For **API-key** auth, it is the single org the key is bound to (no\nswitching). Org-v2 enrichment is best-effort and bounded — on partial outage\nthe array may contain id-only entries (`name` / `country` / `vatNumber` null).\n\n**Example request:**\n```http\nGET /v1/me\nAuthorization: Bearer flw_live_…\n```\n\n**Example response (200):**\n```json\n{\n  \"authMethod\": \"api_key\",\n  \"userId\": null,\n  \"email\": null,\n  \"keyType\": \"personal\",\n  \"organizationId\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n  \"organizationIds\": [\"019a6e5b-3960-7000-ab88-153aed5a6240\"],\n  \"organizations\": [\n    {\n      \"id\": \"019a6e5b-3960-7000-ab88-153aed5a6240\",\n      \"name\": \"HOZELOCK EXEL\",\n      \"country\": \"FR\",\n      \"vatNumber\": \"FR02779658772\"\n    }\n  ],\n  \"scopes\": [\"*\"],\n  \"isTestMode\": false\n}\n```\n\n**Example error (401 — bad key):**\n```json\n{\n  \"type\": \"unauthorized\",\n  \"code\": \"invalid_api_key\",\n  \"message\": \"API key is missing or invalid.\",\n  \"details\": [],\n  \"requestId\": \"req_01HX...\"\n}\n```\n\n**Errors** (body follows `ErrorResponse`):\n\n| Status | Code                   | When                                                          |\n|--------|------------------------|---------------------------------------------------------------|\n| 401    | `unauthorized`         | Missing / invalid API key or bearer token.                    |\n| 403    | `forbidden`            | Key type not permitted on this endpoint.                      |\n| 429    | `rate_limited`         | Too many requests.                                            |\n| 502    | `upstream_error`       | Org-v2 returned a non-2xx during enrichment (rare — bounded). |\n| 503    | `upstream_unavailable` | Org-v2 unreachable / enrichment budget exhausted.             |","operationId":"get_me_v1_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Caller identity + the orgs they can switch to (enrichment is best-effort).","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Me V1 Me Get"},"example":{"authMethod":"api_key","keyType":"personal","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","organizationIds":["019a6e5b-3960-7000-ab88-153aed5a6240","019a6e5b-3960-7000-ab88-153aed5a6241"],"organizations":[{"id":"019a6e5b-3960-7000-ab88-153aed5a6240","name":"HOZELOCK EXEL","country":"FR","vatNumber":"FR02779658772"},{"id":"019a6e5b-3960-7000-ab88-153aed5a6241","name":"FLOWIE","country":"FR","vatNumber":"FR26921376265"}],"scopes":["*"],"isTestMode":false}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Token valid but its key type is not allowed on this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream org-v2 returned an error during enrichment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream org-v2 unavailable / timed out (enrichment is best-effort; the endpoint may still return id-only entries on partial outage).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/afnor/flow-service/v1/healthcheck":{"get":{"tags":["AFNOR Flow Service"],"summary":"Health check","description":"Liveness probe required by AFNOR XP Z12-013 v1.3.0 § 5 (PDP→PA reachability check).\n\nReturns a static `{\"status\": \"ok\"}` payload so partner PDPs (DCSnet, Symtrax, …) can\nverify the Flow-Service facade is reachable before exercising the actual `/v1/flows/*`\nor `/v1/webhooks/*` routes. No authentication required, no upstream calls made.\n\n**Example response (200):**\n```json\n{\"status\": \"ok\"}\n```\n\n**Errors**\n\n| Status | Code                   | When                          |\n|--------|------------------------|-------------------------------|\n| 503    | `SERVICE_UNAVAILABLE`  | Process not yet ready / dying. |","operationId":"healthcheck_afnor_flow_service_v1_healthcheck_get","responses":{"200":{"description":"Service is live.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Healthcheck Afnor Flow Service V1 Healthcheck Get"},"example":{"status":"ok"}}}},"503":{"description":"Service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/flow-service/v1/flows":{"post":{"tags":["AFNOR Flow Service"],"summary":"Submit a flow (createFlow)","description":"Submit a document flow for asynchronous PA-side processing — AFNOR XP Z12-013 v1.3.0\n§ 6.2 `createFlow`. The PA performs syntactic + semantic checks, attaches the\n`processingRule`, persists the artifact, and (when the recipient is reachable) hands\nthe flow over to the destination PDP. The 202 response carries the PA-assigned\n`flowId` and a timestamp — final delivery status arrives later via the\n`document.sent.*` webhook.\n\n**Multipart parts**\n\n- `flowInfo` (required) — JSON-serialised `FlowInfo` object (`name`, `flowSyntax`,\n  `trackingId`, `processingRule`, `flowProfile`, `sha256`).\n- `file` (optional) — binary document body. UTF-8 XML for `UBL` / `CII` / `CDAR` /\n  `FRR`, PDF/A-3 for `Factur-X`. Omit only for control flows.\n\n**Example `flowInfo` part:**\n```json\n{\n  \"name\": \"FA-2026-000123\",\n  \"flowSyntax\": \"UBL\",\n  \"trackingId\": \"TRK-2026-000123\",\n  \"processingRule\": \"B2B\",\n  \"flowProfile\": \"CIUS\",\n  \"sha256\": \"9b74c9897bac770ffc029102a200c5de7afbab8e1f0f1e6d39e6a8b3c0d1e2f3\"\n}\n```\n\n**Example response (202 Accepted):**\n```json\n{\n  \"flowId\": \"e89d4676-eb0c-4aa0-a8aa-3c519a2ed145\",\n  \"submittedAt\": \"2026-06-17T09:15:32.512Z\",\n  \"name\": \"FA-2026-000123\",\n  \"flowSyntax\": \"UBL\",\n  \"trackingId\": \"TRK-2026-000123\",\n  \"processingRule\": \"B2B\",\n  \"flowProfile\": \"CIUS\",\n  \"sha256\": \"9b74c9897bac770ffc029102a200c5de7afbab8e1f0f1e6d39e6a8b3c0d1e2f3\"\n}\n```\n\n**Example error (400 — bad `flowInfo` JSON):**\n```json\n{\n  \"errorCode\": \"BAD_REQUEST\",\n  \"errorMessage\": \"Invalid flowInfo JSON: Expecting property name enclosed in double quotes\",\n  \"type\": \"about:blank\"\n}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                          |\n|--------|-------------------------|---------------------------------------------------------------|\n| 400    | `BAD_REQUEST`       | Bad `flowInfo` JSON, unknown `flowSyntax`, bad SHA-256 hex.   |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                                    |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Flow-Service scope or wrong `Organization-Id`.      |\n| 413    | `FILE_SIZE_EXCEEDED`     | `file` exceeds the 5 MiB inline ceiling.                      |\n| 415    | `UNSUPPORTED_MEDIA_TYPE`     | `file` content does not match declared `flowSyntax`.          |\n| 422    | `UNPROCESSABLE_ENTITY`      | `FlowInfo` schema validation failed (e.g. `name` >255 chars). |\n| 429    | `TOO_MANY_REQUESTS`          | PDP submission quota exhausted.                               |\n| 502    | `UPSTREAM_ERROR`        | `document_service.send_document` rejected the payload.        |\n| 503    | `SERVICE_UNAVAILABLE`  | tx-doc store or signing service unreachable.                  |","operationId":"create_flow_afnor_flow_service_v1_flows_post","parameters":[{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"Request-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request-Id"},"description":"Client-supplied correlation id, echoed back on the response."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_flow_afnor_flow_service_v1_flows_post"}}},"description":"`Body_create_flow_afnor_flow_service_v1_flows_post` — payload to submit a flow (createFlow)."},"responses":{"202":{"description":"Flow accepted for asynchronous processing. The work is not finished when this returns.\n\nPoll `GET /afnor/flow-service/v1/flows/{flowId}` using the `flowId` in the body — the `Location` response header carries that exact URL, so follow it rather than assembling it yourself.\n\nHonour `Retry-After` if present and back off between polls; a flow typically settles in seconds but a national platform can take longer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FullFlowInfo"},"example":{"flowId":"e89d4676-eb0c-4aa0-a8aa-3c519a2ed145","submittedAt":"2026-06-17T09:15:32.512Z","name":"FA-2026-000123","flowSyntax":"UBL","trackingId":"TRK-2026-000123","processingRule":"B2B","flowProfile":"CIUS","sha256":"9b74c9897bac770ffc029102a200c5de7afbab8e1f0f1e6d39e6a8b3c0d1e2f3"}}},"headers":{"Location":{"description":"URL of the submitted flow — poll this to follow the outcome.","schema":{"type":"string","format":"uri-reference"}}}},"400":{"description":"Malformed request body (e.g. invalid `flowInfo` JSON, unknown `docType`, bad SHA-256).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key valid but lacks the AFNOR Flow-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body failed AFNOR schema validation (FlowInfo, SearchFlowFilters, WebhookParams…).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream service (document_service / webhook_service / tx-doc) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"413":{"description":"Uploaded `file` exceeds the 5 MiB inline ceiling.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"415":{"description":"Unsupported `flowSyntax` for the supplied content (e.g. PDF when CII is declared).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/flow-service/v1/flows/search":{"post":{"tags":["AFNOR Flow Service"],"summary":"Search flows (searchFlows)","description":"Page through flows matching the AFNOR filter set — XP Z12-013 v1.3.0 § 6.3\n`searchFlows`. Filters are AND-combined across keys; arrays within a single key are\nOR-combined. Results are returned in `updatedAt` ascending order.\n\n**Pagination.** Pass `limit` (max 100) to size the page. When more rows remain the\nresponse carries an opaque `nextCursor`; re-send the identical request with\n`cursor` set to that value to fetch the next page. The cursor is stateless\n(base64 of an offset) — the last page omits `nextCursor` entirely. Keep `where`\nstable across a pagination run.\n\nBehavior intentionally hard-filters on `flowType` / `flowDirection` after mapping\nto the internal `document_service.list_documents` shape — the upstream filter is\nbest-effort and `flowType` ↔ document-type is many-to-one. Without the post-filter\na `CustomerInvoice/Out` query could surface `SupplierInvoice/In` rows (the bug\nDCSnet reported on 2026-06-08), and a lifecycle (`…LC`) query would leak plain\ninvoices instead of returning an empty page.\n\n**Example request:**\n```json\n{\n  \"limit\": 25,\n  \"where\": {\n    \"updatedAfter\": \"2026-06-01T00:00:00Z\",\n    \"processingRule\": [\"B2B\", \"NotApplicable\"],\n    \"flowType\": [\"CustomerInvoice\"],\n    \"flowDirection\": [\"Out\"],\n    \"ackStatus\": \"Ok\"\n  }\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"limit\": 25,\n  \"filters\": {\n    \"flowType\": [\"CustomerInvoice\"],\n    \"flowDirection\": [\"Out\"],\n    \"processingRule\": [\"B2B\", \"NotApplicable\"],\n    \"ackStatus\": \"Ok\"\n  },\n  \"results\": [\n    {\n      \"flowId\": \"e89d4676-eb0c-4aa0-a8aa-3c519a2ed145\",\n      \"submittedAt\": \"2026-06-17T09:15:32.512Z\",\n      \"name\": \"FA-2026-000123\",\n      \"flowSyntax\": \"UBL\",\n      \"trackingId\": \"TRK-2026-000123\",\n      \"processingRule\": \"B2B\",\n      \"flowProfile\": \"CIUS\",\n      \"updatedAt\": \"2026-06-17T09:17:01.004Z\",\n      \"flowType\": \"CustomerInvoice\",\n      \"processingRuleSource\": \"Computed\",\n      \"flowDirection\": \"Out\",\n      \"acknowledgement\": {\"status\": \"Ok\", \"details\": []}\n    }\n  ]\n}\n```\n\n**Example response (200 — lifecycle query returns empty for non-LC corpora):**\n```json\n{\"limit\": 25, \"filters\": {\"flowType\": [\"CustomerInvoiceLC\"]}, \"results\": []}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                            |\n|--------|-------------------------|-----------------------------------------------------------------|\n| 400    | `BAD_REQUEST`       | Filter combination not honored by the upstream document store.  |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                                      |\n| 403    | `FORBIDDEN_ACCESS`             | `Organization-Id` not visible to the calling key.               |\n| 422    | `UNPROCESSABLE_ENTITY`      | `SearchFlowParams` failed schema validation (bad enum, dates).  |\n| 429    | `TOO_MANY_REQUESTS`          | Search quota exhausted.                                         |\n| 502    | `UPSTREAM_ERROR`        | `document_service.list_documents` raised.                       |\n| 503    | `SERVICE_UNAVAILABLE`  | Document store unreachable / timed out.                         |","operationId":"search_flows_afnor_flow_service_v1_flows_search_post","parameters":[{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchFlowParams"}}},"description":"`SearchFlowParams` — payload to search flows (searchFlows)."},"responses":{"200":{"description":"Page of matching flows (ordered by `updatedAt` ascending).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchFlowResponse"},"example":{"limit":25,"nextCursor":"eyJvZmZzZXQiOiAyNX0=","filters":{"flowType":["CustomerInvoice"],"flowDirection":["Out"],"processingRule":["B2B"]},"results":[{"flowId":"e89d4676-eb0c-4aa0-a8aa-3c519a2ed145","submittedAt":"2026-06-17T09:15:32.512Z","name":"FA-2026-000123","flowSyntax":"UBL","trackingId":"TRK-2026-000123","processingRule":"B2B","flowProfile":"CIUS","updatedAt":"2026-06-17T09:17:01.004Z","flowType":"CustomerInvoice","processingRuleSource":"Computed","flowDirection":"Out","acknowledgement":{"status":"Ok"}}]}}}},"400":{"description":"Malformed request body (e.g. invalid `flowInfo` JSON, unknown `docType`, bad SHA-256).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key valid but lacks the AFNOR Flow-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body failed AFNOR schema validation (FlowInfo, SearchFlowFilters, WebhookParams…).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream service (document_service / webhook_service / tx-doc) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/flow-service/v1/flows/{flow_id}":{"get":{"tags":["AFNOR Flow Service"],"summary":"Download a flow (getFlow)","description":"Retrieve a single flow by its AFNOR `flowId` — XP Z12-013 v1.3.0 § 6.4 `getFlow`.\nThe `docType` query parameter selects the rendering, matching the AFNOR spec:\n\n- `Metadata` — JSON `Flow` object (default), useful for polling ack status.\n- `Original` — raw bytes of the originally received XML (UBL preferred, CII fallback).\n- `Converted` — the other syntax, lazily generated if the flow was JSON-only on intake.\n- `ReadableView` — the human-readable PDF derived from the XML.\n\nThe `Organization-Id` header is forwarded to the document service so partner tokens\n(e.g. DCSnet's PDP key accessing a Reynolds-owned flow) resolve against the AFNOR\norganisation, not the API key's home org.\n\n**Example response (`docType=Metadata`, 200):**\n```json\n{\n  \"flowId\": \"e89d4676-eb0c-4aa0-a8aa-3c519a2ed145\",\n  \"submittedAt\": \"2026-06-17T09:15:32.512Z\",\n  \"name\": \"FA-2026-000123\",\n  \"flowSyntax\": \"UBL\",\n  \"trackingId\": \"TRK-2026-000123\",\n  \"processingRule\": \"B2B\",\n  \"flowProfile\": \"CIUS\",\n  \"updatedAt\": \"2026-06-17T09:17:01.004Z\",\n  \"flowType\": \"CustomerInvoice\",\n  \"processingRuleSource\": \"Computed\",\n  \"flowDirection\": \"Out\",\n  \"acknowledgement\": {\"status\": \"Ok\", \"details\": []}\n}\n```\n\n**Example response (`docType=Original`, 200):**\n```xml\n<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\">\n  <cbc:ID>FA-2026-000123</cbc:ID>\n  <cbc:IssueDate>2026-06-17</cbc:IssueDate>\n</Invoice>\n```\n\n**Example error (404):**\n```json\n{\"errorCode\": \"404\", \"errorMessage\": \"Flow e89d4676-... not found\"}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                              |\n|--------|-------------------------|-------------------------------------------------------------------|\n| 400    | `BAD_REQUEST`       | `docType` not one of `Metadata/Original/ReadableView/Converted`.  |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                                        |\n| 403    | `FORBIDDEN_ACCESS`             | `Organization-Id` not authorised for this flow.                   |\n| 404    | `NOT_FOUND`             | Flow ID unknown or not visible to caller.                         |\n| 422    | `UNPROCESSABLE_ENTITY`      | `flow_id` is not a UUID-shaped string.                            |\n| 429    | `TOO_MANY_REQUESTS`          | Download quota exhausted.                                         |\n| 502    | `UPSTREAM_ERROR`        | `document_service` failed to fetch XML / generate PDF.            |\n| 503    | `SERVICE_UNAVAILABLE`  | Document store unreachable.                                       |","operationId":"get_flow_afnor_flow_service_v1_flows__flow_id__get","parameters":[{"name":"flow_id","in":"path","required":true,"schema":{"type":"string","title":"Flow Id"},"description":"Identifier of the flow."},{"name":"docType","in":"query","required":false,"schema":{"type":"string","description":"Metadata | Original | ReadableView | Converted","default":"Metadata","title":"Doctype"},"description":"Metadata | Original | ReadableView | Converted"},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"responses":{"200":{"description":"Flow content. The response body is JSON for `docType=Metadata`, raw XML for `Original` / `Converted` (UBL or CII), and PDF bytes for `ReadableView`.","content":{"application/json":{"schema":{"type":"object","properties":{"flowId":{"type":"string"},"submittedAt":{"type":"string"},"name":{"type":"string"},"flowSyntax":{"type":"string"},"trackingId":{"type":"string"},"processingRule":{"type":"string"},"flowProfile":{"type":"string"},"updatedAt":{"type":"string"},"flowType":{"type":"string"},"processingRuleSource":{"type":"string"},"flowDirection":{"type":"string"},"acknowledgement":{"type":"object","properties":{"status":{"type":"string"},"details":{"type":"array","items":{}}}}}},"example":{"flowId":"e89d4676-eb0c-4aa0-a8aa-3c519a2ed145","submittedAt":"2026-06-17T09:15:32.512Z","name":"FA-2026-000123","flowSyntax":"UBL","trackingId":"TRK-2026-000123","processingRule":"B2B","flowProfile":"CIUS","updatedAt":"2026-06-17T09:17:01.004Z","flowType":"CustomerInvoice","processingRuleSource":"Computed","flowDirection":"Out","acknowledgement":{"status":"Ok","details":[]}}},"application/xml":{"example":"<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\"><cbc:ID>FA-2026-000123</cbc:ID></Invoice>","schema":{"type":"string"}},"application/pdf":{"example":"<binary PDF bytes>","schema":{"type":"string","format":"binary"}}}},"400":{"description":"Malformed request body (e.g. invalid `flowInfo` JSON, unknown `docType`, bad SHA-256).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key valid but lacks the AFNOR Flow-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body failed AFNOR schema validation (FlowInfo, SearchFlowFilters, WebhookParams…).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream service (document_service / webhook_service / tx-doc) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"404":{"description":"Requested flow / webhook does not exist or is not visible to the calling organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/flow-service/v1/webhooks":{"get":{"tags":["AFNOR Flow Service"],"summary":"List webhooks (listWebhooks)","description":"List the webhook subscriptions registered against the caller's organisation —\nXP Z12-013 v1.3.0 § 6.5 `listWebhooks`. The result is scoped by the `Organization-Id`\nheader; internal Flowie event types are mapped back to AFNOR `metadata`\n(`flowDirection` / `ackStatus`) on the way out.\n\n**Example response (200):**\n```json\n{\n  \"count\": 1,\n  \"webhooks\": [\n    {\n      \"webhookId\": \"wh_01HZX0F6N1B3KQAM7VV9G8T0YT\",\n      \"callbackUrl\": \"https://partner.example.com/afnor/callback\",\n      \"flowTypes\": [\"CustomerInvoice\"],\n      \"flowDirection\": \"Out\",\n      \"ackStatus\": \"Ok\"\n    }\n  ]\n}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                |\n|--------|-------------------------|-----------------------------------------------------|\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                          |\n| 403    | `FORBIDDEN_ACCESS`             | `Organization-Id` not visible to the calling key.   |\n| 429    | `TOO_MANY_REQUESTS`          | Listing quota exhausted.                            |\n| 502    | `UPSTREAM_ERROR`        | `webhook_service.list_webhooks` raised.             |\n| 503    | `SERVICE_UNAVAILABLE`  | Webhook store unreachable.                          |","operationId":"list_webhooks_afnor_flow_service_v1_webhooks_get","parameters":[{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"responses":{"200":{"description":"All webhook subscriptions visible to the caller (filtered by `Organization-Id`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorWebhookList"},"example":{"count":1,"webhooks":[{"webhookId":"wh_01HZX0F6N1B3KQAM7VV9G8T0YT","callbackUrl":"https://partner.example.com/afnor/callback","flowTypes":["CustomerInvoice"],"flowDirection":"Out","ackStatus":"Ok"}]}}}},"400":{"description":"Malformed request body (e.g. invalid `flowInfo` JSON, unknown `docType`, bad SHA-256).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key valid but lacks the AFNOR Flow-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body failed AFNOR schema validation (FlowInfo, SearchFlowFilters, WebhookParams…).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream service (document_service / webhook_service / tx-doc) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}},"post":{"tags":["AFNOR Flow Service"],"summary":"Create webhook (createWebhook)","description":"Subscribe a partner callback URL to AFNOR Flow-Service events — XP Z12-013 v1.3.0\n§ 6.6 `createWebhook`. The supplied `metadata` (`flowType` / `flowDirection` /\n`ackStatus`) is translated to internal event names like `document.sent.ok` or\n`document.received.error`; subsequent flow state transitions matching that filter\nfire a POST to `callback.url` carrying a `Flow` payload.\n\n**Example request (AFNOR swagger shape — source of truth):**\n```json\n{\n  \"callbackUrl\": \"https://partner.example.com/afnor/callback\",\n  \"flowTypes\": [\"CustomerInvoice\"],\n  \"flowDirection\": \"Out\",\n  \"ackStatus\": \"Ok\"\n}\n```\n\nThe legacy nested body `{callback: {url}, metadata: {…}}` is still accepted\nfor backward compatibility, but the response always follows the swagger\n`WebhookIdParam` shape below.\n\n**Example response (201):**\n```json\n{\n  \"webhookId\": \"wh_01HZX0F6N1B3KQAM7VV9G8T0YT\",\n  \"signingKey\": \"b3f1c0a9d4e5...secret-hex...\",\n  \"createdAt\": \"2026-06-17T09:15:32.512Z\"\n}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                          |\n|--------|-------------------------|---------------------------------------------------------------|\n| 400    | `BAD_REQUEST`       | `callback.url` is not https, or `metadata.flowDirection`      |\n|        |                         | / `ackStatus` outside the AFNOR enum.                         |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                                    |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks `webhook:write` scope.                              |\n| 409    | `CONFLICT`              | Duplicate subscription (same url + metadata) already active.  |\n| 422    | `UNPROCESSABLE_ENTITY`      | `WebhookParams` failed schema validation.                     |\n| 429    | `TOO_MANY_REQUESTS`          | Webhook creation quota exhausted.                             |\n| 502    | `UPSTREAM_ERROR`        | `webhook_service.create_webhook` raised.                      |\n| 503    | `SERVICE_UNAVAILABLE`  | Webhook store unreachable.                                    |","operationId":"create_webhook_afnor_flow_service_v1_webhooks_post","parameters":[{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookParams"}}},"description":"`WebhookParams` — payload to create webhook (createWebhook)."},"responses":{"201":{"description":"Webhook subscription created — returns the id + signing key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorWebhookCreated"},"example":{"webhookId":"wh_01HZX0F6N1B3KQAM7VV9G8T0YT","signingKey":"b3f1c0a9d4e5...secret-hex...","createdAt":"2026-06-17T09:15:32.512Z"}}}},"400":{"description":"Malformed request body (e.g. invalid `flowInfo` JSON, unknown `docType`, bad SHA-256).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key valid but lacks the AFNOR Flow-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body failed AFNOR schema validation (FlowInfo, SearchFlowFilters, WebhookParams…).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream service (document_service / webhook_service / tx-doc) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"409":{"description":"An identical (url + metadata) subscription already exists for this org.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/flow-service/v1/webhooks/{webhook_uid}":{"get":{"tags":["AFNOR Flow Service"],"summary":"Get webhook detail (getWebhook)","description":"Fetch a single webhook subscription by its `webhookId` — XP Z12-013 v1.3.0 § 6.7\n`getWebhook`. Implemented as a filter over `webhook_service.list_webhooks` for the\nsame `Organization-Id`, so 404 is returned both when the ID does not exist and when\nit does but belongs to another org (no cross-tenant leak).\n\n**Example response (200):**\n```json\n{\n  \"webhookId\": \"wh_01HZX0F6N1B3KQAM7VV9G8T0YT\",\n  \"callbackUrl\": \"https://partner.example.com/afnor/callback\",\n  \"flowTypes\": [\"CustomerInvoice\"],\n  \"flowDirection\": \"Out\",\n  \"ackStatus\": \"Ok\"\n}\n```\n\n**Example error (404):**\n```json\n{\"errorCode\": \"404\", \"errorMessage\": \"Webhook wh_... not found\"}\n```\n\n**Errors**\n\n| Status | Code                    | When                                              |\n|--------|-------------------------|---------------------------------------------------|\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                        |\n| 403    | `FORBIDDEN_ACCESS`             | `Organization-Id` not authorised.                 |\n| 404    | `NOT_FOUND`             | Webhook ID unknown or owned by another org.       |\n| 422    | `UNPROCESSABLE_ENTITY`      | `webhook_uid` failed path validation.             |\n| 429    | `TOO_MANY_REQUESTS`          | Listing quota exhausted.                          |\n| 502    | `UPSTREAM_ERROR`        | Webhook store returned a non-2xx response.        |\n| 503    | `SERVICE_UNAVAILABLE`  | Webhook store unreachable.                        |","operationId":"get_webhook_afnor_flow_service_v1_webhooks__webhook_uid__get","parameters":[{"name":"webhook_uid","in":"path","required":true,"schema":{"type":"string","title":"Webhook Uid"},"description":"Unique identifier of the webhook subscription."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"responses":{"200":{"description":"Webhook details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorWebhookItem"},"example":{"webhookId":"wh_01HZX0F6N1B3KQAM7VV9G8T0YT","callbackUrl":"https://partner.example.com/afnor/callback","flowTypes":["CustomerInvoice"],"flowDirection":"Out","ackStatus":"Ok"}}}},"400":{"description":"Malformed request body (e.g. invalid `flowInfo` JSON, unknown `docType`, bad SHA-256).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key valid but lacks the AFNOR Flow-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body failed AFNOR schema validation (FlowInfo, SearchFlowFilters, WebhookParams…).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream service (document_service / webhook_service / tx-doc) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"404":{"description":"Requested flow / webhook does not exist or is not visible to the calling organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}},"patch":{"tags":["AFNOR Flow Service"],"summary":"Update webhook technical params (updateWebhook)","description":"Patch the *technical* callback parameters of an existing subscription — XP Z12-013\nv1.3.0 § 6.8 `updateWebhook`. Only `headers`, `authentication` and `signature` may be\nrotated; AFNOR explicitly forbids mutating `metadata` (flowType / flowDirection /\nackStatus) since that would silently change which events the partner receives.\n\nReturns **204 No Content** on success. Internal upstream errors are deliberately\nswallowed so the contract always honours the technical-only spec — partners can\nsafely retry the same PATCH idempotently.\n\n**Example request:**\n```json\n{\n  \"authentication\": {\"type\": \"bearer\", \"token\": \"new-secret\"},\n  \"headers\": [{\"name\": \"X-Partner-Key\", \"value\": \"rotated\"}]\n}\n```\n\n**Errors**\n\n| Status | Code                    | When                                              |\n|--------|-------------------------|---------------------------------------------------|\n| 400    | `BAD_REQUEST`       | Body attempts to mutate `metadata`.               |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                        |\n| 403    | `FORBIDDEN_ACCESS`             | `Organization-Id` not authorised.                 |\n| 404    | `NOT_FOUND`             | Webhook ID unknown.                               |\n| 422    | `UNPROCESSABLE_ENTITY`      | `WebhookPatchPayload` failed schema validation.   |\n| 429    | `TOO_MANY_REQUESTS`          | Update quota exhausted.                           |\n| 502    | `UPSTREAM_ERROR`        | Webhook store returned a non-2xx response.        |\n| 503    | `SERVICE_UNAVAILABLE`  | Webhook store unreachable.                        |","operationId":"update_webhook_afnor_flow_service_v1_webhooks__webhook_uid__patch","parameters":[{"name":"webhook_uid","in":"path","required":true,"schema":{"type":"string","title":"Webhook Uid"},"description":"Unique identifier of the webhook subscription."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPatchPayload"}}},"description":"`WebhookPatchPayload` — payload to update webhook technical params (updateWebhook)."},"responses":{"204":{"description":"Technical parameters updated — no response body."},"400":{"description":"Malformed request body (e.g. invalid `flowInfo` JSON, unknown `docType`, bad SHA-256).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key valid but lacks the AFNOR Flow-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body failed AFNOR schema validation (FlowInfo, SearchFlowFilters, WebhookParams…).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream service (document_service / webhook_service / tx-doc) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"404":{"description":"Requested flow / webhook does not exist or is not visible to the calling organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}},"delete":{"tags":["AFNOR Flow Service"],"summary":"Delete webhook (deleteWebhook)","description":"Unsubscribe a webhook — XP Z12-013 v1.3.0 § 6.9 `deleteWebhook`. The subscription is\nremoved synchronously; in-flight deliveries already on the retry queue are aborted.\nReturns **204 No Content** on success.\n\n**Errors**\n\n| Status | Code                    | When                                              |\n|--------|-------------------------|---------------------------------------------------|\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                        |\n| 403    | `FORBIDDEN_ACCESS`             | Webhook belongs to another organisation.          |\n| 404    | `NOT_FOUND`             | Webhook ID unknown or already deleted.            |\n| 422    | `UNPROCESSABLE_ENTITY`      | `webhook_uid` failed path validation.             |\n| 429    | `TOO_MANY_REQUESTS`          | Deletion quota exhausted.                         |\n| 502    | `UPSTREAM_ERROR`        | Webhook store returned a non-2xx response.        |\n| 503    | `SERVICE_UNAVAILABLE`  | Webhook store unreachable.                        |","operationId":"delete_webhook_afnor_flow_service_v1_webhooks__webhook_uid__delete","parameters":[{"name":"webhook_uid","in":"path","required":true,"schema":{"type":"string","title":"Webhook Uid"},"description":"Unique identifier of the webhook subscription."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"responses":{"204":{"description":"Subscription deleted — no further events will fire on this callback."},"400":{"description":"Malformed request body (e.g. invalid `flowInfo` JSON, unknown `docType`, bad SHA-256).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key valid but lacks the AFNOR Flow-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body failed AFNOR schema validation (FlowInfo, SearchFlowFilters, WebhookParams…).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream service (document_service / webhook_service / tx-doc) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream service unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"404":{"description":"Requested flow / webhook does not exist or is not visible to the calling organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/healthcheck":{"get":{"tags":["AFNOR Directory Service"],"summary":"Health check","description":"Liveness probe required by AFNOR XP Z12-013 v1.3.0 § 7 (Directory-Service reachability).\n\nReturns a static `{\"status\": \"ok\"}` so partner PDPs can verify the Directory-Service\nfacade is reachable before exercising the SIREN / SIRET / routing-code endpoints.\nNo auth required; no upstream calls performed.\n\n**Example response (200):**\n```json\n{\"status\": \"ok\"}\n```\n\n**Errors**\n\n| Status | Code                   | When                              |\n|--------|------------------------|-----------------------------------|\n| 503    | `SERVICE_UNAVAILABLE`  | Process not yet ready / dying.    |","operationId":"healthcheck_afnor_directory_service_v1_healthcheck_get","responses":{"200":{"description":"Service is live.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Healthcheck Afnor Directory Service V1 Healthcheck Get"},"example":{"status":"ok"}}}},"503":{"description":"Service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/siren/search":{"post":{"tags":["AFNOR Directory Service"],"summary":"Search companies by SIREN (searchCompanyBySiren)","description":"Search French legal units by SIREN / business name — AFNOR XP Z12-013 v1.3.0 § 7.2\n`searchCompanyBySiren`. The route maps the AFNOR `filters` block to a Peppol\nDirectory query (`country=FR`, plus SIREN-as-`q` and the derived French VAT). When\nboth `siren` and `businessName` filters are supplied the SIREN takes precedence and\nthe name acts as a post-fetch substring filter.\n\nPagination uses `ignore` (offset) + `limit`. The handler fetches a larger window\nupstream so deep pages don't return empty incorrectly and `totalNumberOfResults`\nreflects the full match set.\n\n**Example request — exact SIREN:**\n```json\n{\n  \"filters\": {\"siren\": {\"op\": \"strict\", \"value\": \"921376265\"}},\n  \"limit\": 50,\n  \"ignore\": 0\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"search\": {\"filters\": {\"siren\": {\"op\": \"strict\", \"value\": \"921376265\"}}, \"limit\": 50, \"ignore\": 0},\n  \"totalNumberOfResults\": 1,\n  \"results\": [\n    {\n      \"siren\": \"921376265\",\n      \"businessName\": \"FLOWIE\",\n      \"entityType\": \"PrivateVatRegistered\",\n      \"administrativeStatus\": \"A\",\n      \"instructions\": {\"isSalesProspectingForbidden\": False}\n    }\n  ]\n}\n```\n\n**Example response (200 — empty result for unknown SIREN):**\n```json\n{\"search\": {\"filters\": {\"siren\": {\"value\": \"000000000\"}}}, \"totalNumberOfResults\": 0, \"results\": []}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                       |\n|--------|-------------------------|------------------------------------------------------------|\n| 400    | `BAD_REQUEST`       | SIREN not 9 digits, unsupported filter operator.           |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                                 |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Directory-Service scope.                         |\n| 422    | `UNPROCESSABLE_ENTITY`      | `SirenSearchRequest` failed schema validation.             |\n| 429    | `TOO_MANY_REQUESTS`          | Search quota exhausted.                                    |\n| 502    | `UPSTREAM_ERROR`        | Peppol Directory / ppf-annuaire returned non-2xx.          |\n| 503    | `SERVICE_UNAVAILABLE`  | Peppol Directory / ppf-annuaire unreachable.               |","operationId":"search_siren_afnor_directory_service_v1_siren_search_post","parameters":[{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SirenSearchRequest"}}},"description":"`SirenSearchRequest` — payload to search companies by SIREN (searchCompanyBySiren)."},"responses":{"200":{"description":"Page of legal units matching the AFNOR filter set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SirenSearchResponse"},"example":{"search":{"filters":{"siren":{"op":"strict","value":"921376265"}},"limit":50,"ignore":0},"totalNumberOfResults":1,"results":[{"siren":"921376265","businessName":"FLOWIE","entityType":"PrivateVatRegistered","administrativeStatus":"A","instructions":{"isSalesProspectingForbidden":false}}]}}}},"400":{"description":"Invalid filter (e.g. SIREN not 9 digits, SIRET not 14 digits, unknown country).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key lacks the AFNOR Directory-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body / path / query failed AFNOR schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire / PPF) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/siren/code-insee:{siren}":{"get":{"tags":["AFNOR Directory Service"],"summary":"Get company by SIREN (getCompanyBySiren)","description":"Look up a single French legal unit by its 9-digit SIREN — AFNOR XP Z12-013 v1.3.0\n§ 7.3 `getCompanyBySiren`. The path parameter follows the AFNOR `code-insee:{siren}`\ntemplate; `fields` lets the caller project a subset of the response keys\n(`siren`, `businessName`, `entityType`, `administrativeStatus`, `instructions`,\n`peppolId`, `country`, `city`, `postalCode`).\n\n**Example request:**\n```\nGET /v1/siren/code-insee:921376265?fields=siren&fields=businessName\n```\n\n**Example response (200):**\n```json\n{\n  \"siren\": \"921376265\",\n  \"businessName\": \"FLOWIE\",\n  \"entityType\": \"PrivateVatRegistered\",\n  \"administrativeStatus\": \"A\",\n  \"instructions\": {\"isSalesProspectingForbidden\": false},\n  \"peppolId\": \"0009:921376265\",\n  \"country\": \"FR\",\n  \"city\": \"PARIS\",\n  \"postalCode\": \"75008\"\n}\n```\n\n**Example error (404):**\n```json\n{\"errorCode\": \"404\", \"errorMessage\": \"Company with SIREN 000000000 not found\"}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                |\n|--------|-------------------------|-----------------------------------------------------|\n| 400    | `BAD_REQUEST`       | SIREN is not 9 digits.                              |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                          |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Directory-Service scope.                  |\n| 404    | `NOT_FOUND`             | SIREN not present in any backing directory.         |\n| 422    | `UNPROCESSABLE_ENTITY`      | Path parameter failed pattern validation.           |\n| 429    | `TOO_MANY_REQUESTS`          | Lookup quota exhausted.                             |\n| 502    | `UPSTREAM_ERROR`        | Peppol Directory / ppf-annuaire returned non-2xx.   |\n| 503    | `SERVICE_UNAVAILABLE`  | Peppol Directory / ppf-annuaire unreachable.        |","operationId":"get_siren_afnor_directory_service_v1_siren_code_insee__siren__get","parameters":[{"name":"siren","in":"path","required":true,"schema":{"type":"string","pattern":"^([0-9]{9})$","description":"SIREN (9 digits)","title":"Siren"},"description":"SIREN (9 digits)"},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Fields"},"description":"Project the response down to a subset of keys. Repeatable: `?fields=siren&fields=businessName`."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."}],"responses":{"200":{"description":"Legal unit detail (`legalUnitPayloadHistory` shape, plus Peppol extension fields).","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Siren Afnor Directory Service V1 Siren Code Insee  Siren  Get"},"example":{"siren":"921376265","businessName":"FLOWIE","entityType":"PrivateVatRegistered","administrativeStatus":"A","instructions":{"isSalesProspectingForbidden":false},"peppolId":"0009:921376265","country":"FR","city":"PARIS","postalCode":"75008"}}}},"400":{"description":"Invalid filter (e.g. SIREN not 9 digits, SIRET not 14 digits, unknown country).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key lacks the AFNOR Directory-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body / path / query failed AFNOR schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire / PPF) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"404":{"description":"Requested SIREN / SIRET / routing code does not exist in the backing directory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/siret/search":{"post":{"tags":["AFNOR Directory Service"],"summary":"Search establishments by SIRET (searchFacilityBySiret)","description":"Search French establishments by SIRET / SIREN / name — AFNOR XP Z12-013 v1.3.0 § 7.4\n`searchFacilityBySiret`. The handler picks the most specific filter available and\ndispatches to ppf-annuaire accordingly:\n\n1. Exact SIRET (14 digits) → `/api/siret/{siret}` — single establishment lookup.\n2. SIREN (9 digits) → `/api/siren/{siren}` — synthetic SIRET (`siren + 00001`).\n3. Name → `/api/search/name?q={name}` — synthetic SIRET per match.\n4. No usable filter → empty result.\n\nThe `administrativeStatus` field follows INSEE conventions (`A` = actif, `F` = fermé).\n\n**Example request — exact SIRET:**\n```json\n{\n  \"filters\": {\"siret\": {\"op\": \"strict\", \"value\": \"75297877500027\"}},\n  \"limit\": 50,\n  \"ignore\": 0\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"search\": {\"filters\": {\"siret\": {\"op\": \"strict\", \"value\": \"75297877500027\"}}, \"limit\": 50, \"ignore\": 0},\n  \"totalNumberOfResults\": 1,\n  \"results\": [\n    {\n      \"siret\": \"75297877500027\",\n      \"siren\": \"752978775\",\n      \"name\": \"FLOWIE SAS\",\n      \"facilityType\": \"P\",\n      \"administrativeStatus\": \"A\",\n      \"address\": {\n        \"addressLines\": [\"10 RUE DE LA PAIX\"],\n        \"postalCode\": \"75002\",\n        \"locality\": \"PARIS\",\n        \"country\": \"FR\"\n      },\n      \"siretInstructions\": {\"isSalesProspectingForbidden\": false}\n    }\n  ]\n}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                  |\n|--------|-------------------------|-------------------------------------------------------|\n| 400    | `BAD_REQUEST`       | SIRET not 14 digits, SIREN not 9 digits.              |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                            |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Directory-Service scope.                    |\n| 422    | `UNPROCESSABLE_ENTITY`      | `SiretSearchRequest` failed schema validation.        |\n| 429    | `TOO_MANY_REQUESTS`          | Search quota exhausted.                               |\n| 502    | `UPSTREAM_ERROR`        | ppf-annuaire returned non-2xx.                        |\n| 503    | `SERVICE_UNAVAILABLE`  | ppf-annuaire unreachable.                             |","operationId":"search_siret_afnor_directory_service_v1_siret_search_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiretSearchRequest"}}},"description":"`SiretSearchRequest` — payload to search establishments by SIRET (searchFacilityBySiret)."},"responses":{"200":{"description":"Page of establishments matching the AFNOR filter set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiretSearchResponse"},"example":{"search":{"filters":{"siret":{"op":"strict","value":"75297877500027"}},"limit":50,"ignore":0},"totalNumberOfResults":1,"results":[{"siret":"75297877500027","siren":"752978775","name":"FLOWIE SAS","facilityType":"P","administrativeStatus":"A","address":{"addressLines":["10 RUE DE LA PAIX"],"postalCode":"75002","locality":"PARIS","country":"FR"},"siretInstructions":{"isSalesProspectingForbidden":false}}]}}}},"400":{"description":"Invalid filter (e.g. SIREN not 9 digits, SIRET not 14 digits, unknown country).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key lacks the AFNOR Directory-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body / path / query failed AFNOR schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire / PPF) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/siret/code-insee:{siret}":{"get":{"tags":["AFNOR Directory Service"],"summary":"Get establishment by SIRET (getFacilityBySiret)","description":"Look up a single French establishment by its 14-digit SIRET — AFNOR XP Z12-013\nv1.3.0 § 7.5 `getFacilityBySiret`. The handler tries `/api/siret/{siret}` first; on\n404 it falls back to the parent SIREN (`/api/siren/{siret[:9]}`) and reuses that\ndata. In sandbox / test mode a synthetic establishment is returned so partners can\nrehearse the contract without depending on a real INSEE entry.\n\n`fields` projects the response to a subset of the AFNOR keys.\n\n**Example response (200):**\n```json\n{\n  \"siret\": \"75297877500027\",\n  \"siren\": \"752978775\",\n  \"name\": \"FLOWIE SAS\",\n  \"facilityType\": \"P\",\n  \"administrativeStatus\": \"A\",\n  \"address\": {\n    \"addressLines\": [\"10 RUE DE LA PAIX\"],\n    \"postalCode\": \"75002\",\n    \"locality\": \"PARIS\",\n    \"country\": \"FR\"\n  },\n  \"siretInstructions\": {\"isSalesProspectingForbidden\": false}\n}\n```\n\n**Example error (404):**\n```json\n{\"errorCode\": \"404\", \"errorMessage\": \"Establishment with SIRET 75297877599999 not found\"}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                |\n|--------|-------------------------|-----------------------------------------------------|\n| 400    | `BAD_REQUEST`       | SIRET is not 14 digits.                             |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                          |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Directory-Service scope.                  |\n| 404    | `NOT_FOUND`             | SIRET (and parent SIREN) not in ppf-annuaire.       |\n| 422    | `UNPROCESSABLE_ENTITY`      | Path parameter failed pattern validation.           |\n| 429    | `TOO_MANY_REQUESTS`          | Lookup quota exhausted.                             |\n| 502    | `UPSTREAM_ERROR`        | ppf-annuaire returned non-2xx.                      |\n| 503    | `SERVICE_UNAVAILABLE`  | ppf-annuaire unreachable.                           |","operationId":"get_siret_afnor_directory_service_v1_siret_code_insee__siret__get","parameters":[{"name":"siret","in":"path","required":true,"schema":{"type":"string","pattern":"^([0-9]{14})$","description":"SIRET (14 digits)","title":"Siret"},"description":"SIRET (14 digits)"},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Fields"},"description":"Project the response down to a subset of keys. Repeatable: `?fields=siren&fields=businessName`."},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Include"},"description":"Embed related blocks in each result. Repeatable, and accepts `siret` and `siren`."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Establishment detail (`facilityPayloadHistory` shape).","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Siret Afnor Directory Service V1 Siret Code Insee  Siret  Get"},"example":{"siret":"75297877500027","siren":"752978775","name":"FLOWIE SAS","facilityType":"P","administrativeStatus":"A","address":{"addressLines":["10 RUE DE LA PAIX"],"postalCode":"75002","locality":"PARIS","country":"FR"},"siretInstructions":{"isSalesProspectingForbidden":false}}}}},"400":{"description":"Invalid filter (e.g. SIREN not 9 digits, SIRET not 14 digits, unknown country).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key lacks the AFNOR Directory-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body / path / query failed AFNOR schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire / PPF) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"404":{"description":"Requested SIREN / SIRET / routing code does not exist in the backing directory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/routing-code/search":{"post":{"tags":["AFNOR Directory Service"],"summary":"Search routing codes (searchRoutingCode)","description":"List the active routing codes published for a company — AFNOR XP Z12-013 v1.3.0\n§ 7.6 `searchRoutingCode`. The handler extracts the SIRET filter, derives the parent\nSIREN (first 9 digits), and queries ppf-annuaire's PA routing-plan endpoint\n(`/api/pa/{siren}`). Each routing plan entry is mapped to an AFNOR routing-code\nrecord (`routingIdentifier`, `routingCodeName`, `administrativeStatus`, `address`);\npass `include=[\"siret\",\"siren\"]` to embed those identifiers back into each row.\n\n**Example request:**\n```json\n{\n  \"filters\": {\"siret\": {\"op\": \"strict\", \"value\": \"75297877500027\"}},\n  \"include\": [\"siret\", \"siren\"],\n  \"limit\": 50\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"search\": {\"filters\": {\"siret\": {\"op\": \"strict\", \"value\": \"75297877500027\"}}, \"include\": [\"siret\", \"siren\"], \"limit\": 50},\n  \"totalNumberOfResults\": 1,\n  \"results\": [\n    {\n      \"routingIdentifier\": \"PDP-DCSNET\",\n      \"routingCodeName\": \"DCSnet PDP\",\n      \"administrativeStatus\": \"A\",\n      \"address\": {\n        \"addressLines\": [\"10 RUE DE LA PAIX\"],\n        \"postalCode\": \"75002\",\n        \"locality\": \"PARIS\",\n        \"country\": \"FR\"\n      },\n      \"siret\": \"75297877500027\",\n      \"siren\": \"752978775\"\n    }\n  ]\n}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                  |\n|--------|-------------------------|-------------------------------------------------------|\n| 400    | `BAD_REQUEST`       | SIRET shorter than 9 digits.                          |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                            |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Directory-Service scope.                    |\n| 422    | `UNPROCESSABLE_ENTITY`      | `RoutingCodeSearchRequest` failed schema validation.  |\n| 429    | `TOO_MANY_REQUESTS`          | Search quota exhausted.                               |\n| 502    | `UPSTREAM_ERROR`        | ppf-annuaire PA endpoint returned non-2xx.            |\n| 503    | `SERVICE_UNAVAILABLE`  | ppf-annuaire PA endpoint unreachable.                 |","operationId":"search_routing_code_afnor_directory_service_v1_routing_code_search_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingCodeSearchRequest"}}},"description":"`RoutingCodeSearchRequest` — payload to search routing codes (searchRoutingCode)."},"responses":{"200":{"description":"Routing plan entries for the company derived from the supplied SIRET.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingCodeSearchResponse"},"example":{"search":{"filters":{"siret":{"op":"strict","value":"75297877500027"}},"limit":50},"totalNumberOfResults":1,"results":[{"routingIdentifier":"PDP-DCSNET","routingCodeName":"DCSnet PDP","administrativeStatus":"A","address":{"addressLines":["10 RUE DE LA PAIX"],"postalCode":"75002","locality":"PARIS","country":"FR"}}]}}}},"400":{"description":"Invalid filter (e.g. SIREN not 9 digits, SIRET not 14 digits, unknown country).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key lacks the AFNOR Directory-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body / path / query failed AFNOR schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire / PPF) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/routing-code/siret:{siret}/code:{routing_identifier}":{"get":{"tags":["AFNOR Directory Service"],"summary":"Get routing code (getRoutingCodeBySiretAndId)","description":"Resolve a specific routing code for a company — AFNOR XP Z12-013 v1.3.0 § 7.7\n`getRoutingCodeBySiretAndId`. The handler fetches the full PA routing plan via\nppf-annuaire (`/api/pa/{siren}` where `siren = siret[:9]`), then filters down to the\nentry whose `routingIdentifier` matches the path parameter. In sandbox / test mode\na synthetic Flowie Sandbox Access Point is synthesised so any (siret, routing_id)\npair round-trips successfully.\n\n**Example request:**\n```\nGET /v1/routing-code/siret:75297877500027/code:PDP-DCSNET?include=siret\n```\n\n**Example response (200):**\n```json\n{\n  \"routingIdentifier\": \"PDP-DCSNET\",\n  \"routingCodeName\": \"DCSnet PDP\",\n  \"administrativeStatus\": \"A\",\n  \"address\": {\n    \"addressLines\": [\"10 RUE DE LA PAIX\"],\n    \"postalCode\": \"75002\",\n    \"locality\": \"PARIS\",\n    \"country\": \"FR\"\n  },\n  \"siret\": \"75297877500027\"\n}\n```\n\n**Example error (404 — non-sandbox key, unknown identifier):**\n```json\n{\"errorCode\": \"404\", \"errorMessage\": \"Routing code UNKNOWN-PA not found for SIRET 75297877500027\"}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                |\n|--------|-------------------------|-----------------------------------------------------|\n| 400    | `BAD_REQUEST`       | Path parameters malformed.                          |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                          |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Directory-Service scope.                  |\n| 404    | `NOT_FOUND`             | No routing plan for SIRET, or identifier unknown.   |\n| 422    | `UNPROCESSABLE_ENTITY`      | Path / query failed schema validation.              |\n| 429    | `TOO_MANY_REQUESTS`          | Lookup quota exhausted.                             |\n| 502    | `UPSTREAM_ERROR`        | ppf-annuaire PA endpoint returned non-2xx.          |\n| 503    | `SERVICE_UNAVAILABLE`  | ppf-annuaire PA endpoint unreachable.               |","operationId":"get_routing_code_afnor_directory_service_v1_routing_code_siret__siret__code__routing_identifier__get","parameters":[{"name":"siret","in":"path","required":true,"schema":{"type":"string","description":"SIRET (14 digits)","title":"Siret"},"description":"SIRET (14 digits)"},{"name":"routing_identifier","in":"path","required":true,"schema":{"type":"string","description":"Routing identifier (max 100 chars)","title":"Routing Identifier"},"description":"Routing identifier (max 100 chars)"},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Include"},"description":"Embed related blocks in each result. Repeatable, and accepts `siret` and `siren`."},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Fields"},"description":"Project the response down to a subset of keys. Repeatable: `?fields=siren&fields=businessName`."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Routing code detail matching `siret` + `routing_identifier`.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Routing Code Afnor Directory Service V1 Routing Code Siret  Siret  Code  Routing Identifier  Get"},"example":{"routingIdentifier":"PDP-DCSNET","routingCodeName":"DCSnet PDP","administrativeStatus":"A","address":{"addressLines":["10 RUE DE LA PAIX"],"postalCode":"75002","locality":"PARIS","country":"FR"}}}}},"400":{"description":"Invalid filter (e.g. SIREN not 9 digits, SIRET not 14 digits, unknown country).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key lacks the AFNOR Directory-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body / path / query failed AFNOR schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire / PPF) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"404":{"description":"Requested SIREN / SIRET / routing code does not exist in the backing directory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/directory-line/search":{"post":{"tags":["AFNOR Directory Service"],"summary":"Search directory lines (searchDirectoryLine)","description":"Search directory lines (reception points) for a company — AFNOR XP Z12-013 v1.3.0\n§ 7.8 `searchDirectoryLine`. The AFNOR filter set (`filters` / `fields` / `ignore`)\nis forwarded to ppf-annuaire's `/api/search/ligne-annuaire`, which proxies the\nFrench PPF `/ligne-annuaire/recherche` endpoint. Each returned `ligne` is mapped to\nthe AFNOR directory-line shape (`addressingIdentifier`, `routingIdentifier`, `siret`,\n`administrativeStatus`, `recipientManagement`, `address`).\n\n`ignore` is forwarded server-side so each page is fetched at the source instead of\nover-fetching and slicing locally. `fields` projects the response keys after mapping.\n\n**Example request:**\n```json\n{\n  \"filters\": {\"siret\": {\"op\": \"strict\", \"value\": \"75297877500027\"}},\n  \"sorting\": [{\"field\": \"addressingIdentifier\", \"order\": \"ascending\"}],\n  \"fields\": [\"addressingIdentifier\", \"routingIdentifier\"],\n  \"limit\": 100,\n  \"ignore\": 0\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"search\": {\"filters\": {\"siret\": {\"op\": \"strict\", \"value\": \"75297877500027\"}}, \"limit\": 100, \"ignore\": 0},\n  \"totalNumberOfResults\": 1,\n  \"results\": [\n    {\n      \"addressingIdentifier\": \"75297877500027_001\",\n      \"directoryLineStatus\": \"Enabled\",\n      \"siren\": \"752978775\",\n      \"siret\": \"75297877500027\",\n      \"routingIdentifier\": \"PDP-DCSNET\"\n    }\n  ]\n}\n```\n\n**Example response (200 — empty result):**\n```json\n{\"search\": {\"filters\": {}}, \"totalNumberOfResults\": 0, \"results\": []}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                  |\n|--------|-------------------------|-------------------------------------------------------|\n| 400    | `BAD_REQUEST`       | Filter values not recognised by PPF (malformed SIRET).|\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                            |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Directory-Service scope.                    |\n| 422    | `UNPROCESSABLE_ENTITY`      | `DirectoryLineSearchRequest` failed schema validation.|\n| 429    | `TOO_MANY_REQUESTS`          | Search quota exhausted.                               |\n| 502    | `UPSTREAM_ERROR`        | ppf-annuaire / PPF returned non-2xx.                  |\n| 503    | `SERVICE_UNAVAILABLE`  | ppf-annuaire / PPF unreachable.                       |","operationId":"search_directory_line_afnor_directory_service_v1_directory_line_search_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryLineSearchRequest"}}},"description":"`DirectoryLineSearchRequest` — payload to search directory lines (searchDirectoryLine)."},"responses":{"200":{"description":"Directory lines (reception points / `lignes annuaire`) for the company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryLineSearchResponse"},"example":{"search":{"filters":{"siret":{"op":"strict","value":"75297877500027"}},"limit":100,"ignore":0},"totalNumberOfResults":1,"results":[{"addressingIdentifier":"75297877500027_001","directoryLineStatus":"Enabled","siren":"752978775","siret":"75297877500027","routingIdentifier":"PDP-DCSNET","platformType":"WK"}]}}}},"400":{"description":"Invalid filter (e.g. SIREN not 9 digits, SIRET not 14 digits, unknown country).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key lacks the AFNOR Directory-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body / path / query failed AFNOR schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire / PPF) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/afnor/directory-service/v1/directory-line/code:{addressing_identifier}":{"get":{"tags":["AFNOR Directory Service"],"summary":"Get directory line by id (getDirectoryLineById)","description":"Resolve a single directory line by its addressing identifier — AFNOR XP Z12-013\nv1.3.0 § 7.9 `getDirectoryLineById`. Backed by the same ppf-annuaire\n`/api/search/ligne-annuaire` (PISTE `/ligne-annuaire/recherche`) endpoint as\n`searchDirectoryLine`, filtered on the addressing identifier and reduced to the\nexact match. In sandbox / test mode a synthetic line is returned so partners can\nrehearse the contract.\n\n**Example request:**\n```\nGET /v1/directory-line/code:752978775_75297877500027_100003\n```\n\n**Example error (404):**\n```json\n{\"errorCode\": \"404\", \"errorMessage\": \"Directory line ... not found\"}\n```\n\n**Errors**\n\n| Status | Code                    | When                                                |\n|--------|-------------------------|-----------------------------------------------------|\n| 400    | `BAD_REQUEST`       | Addressing identifier malformed.                    |\n| 401    | `MISSING_TOKEN`          | Missing / invalid API key.                          |\n| 403    | `FORBIDDEN_ACCESS`             | Key lacks Directory-Service scope.                  |\n| 404    | `NOT_FOUND`             | No directory line for this addressing identifier.   |\n| 422    | `UNPROCESSABLE_ENTITY`      | Path parameter failed schema validation.            |\n| 429    | `TOO_MANY_REQUESTS`          | Lookup quota exhausted.                             |\n| 502    | `UPSTREAM_ERROR`        | ppf-annuaire / PPF returned non-2xx.                |\n| 503    | `SERVICE_UNAVAILABLE`  | ppf-annuaire / PPF unreachable.                     |","operationId":"get_directory_line_afnor_directory_service_v1_directory_line_code__addressing_identifier__get","parameters":[{"name":"addressing_identifier","in":"path","required":true,"schema":{"type":"string","maxLength":125,"description":"Addressing identifier","title":"Addressing Identifier"},"description":"Addressing identifier"},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Include"},"description":"Embed related blocks in each result. Repeatable, and accepts `siret` and `siren`."},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Fields"},"description":"Project the response down to a subset of keys. Repeatable: `?fields=siren&fields=businessName`."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Directory line detail (`directoryLinePayloadLegalUnitFacilityRoutingCode` shape).","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Directory Line Afnor Directory Service V1 Directory Line Code  Addressing Identifier  Get"},"example":{"addressingIdentifier":"752978775_75297877500027_100003","directoryLineStatus":"Enabled","siren":"752978775","siret":"75297877500027","routingIdentifier":"100003","platformType":"WK"}}}},"400":{"description":"Invalid filter (e.g. SIREN not 9 digits, SIRET not 14 digits, unknown country).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"403":{"description":"API key lacks the AFNOR Directory-Service scope, or `Organization-Id` not authorized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"422":{"description":"Request body / path / query failed AFNOR schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"429":{"description":"Per-PDP rate limit exceeded — retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"502":{"description":"Upstream directory (Peppol Directory / ppf-annuaire / PPF) returned a non-2xx response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"503":{"description":"Upstream directory unavailable / timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}},"404":{"description":"Requested SIREN / SIRET / routing code does not exist in the backing directory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AfnorError"}}}}}}},"/document/callback":{"post":{"tags":["Document Callbacks"],"summary":"cXML PunchOutOrderMessage callback (BrowserFormPost)","description":"Receive a PunchOutOrderMessage from a supplier after catalog checkout.\n\nThis endpoint is called by the supplier's system (e.g. Lyreco) when\nthe user finishes shopping. The cXML body contains the cart items, the\nuploaded document is sent to the OCR pipeline which parses cXML and\ncreates a purchase request inside the buyer's Flowie organization.\n\n**PUBLIC endpoint — no API key / bearer token required.** Authentication\nis performed against the cXML body: a ``<SharedSecret>`` under\n``<Header>`` is matched against the supplier whitelist; if no shared\nsecret is present we fall back to matching the ``<From/Credential>`` or\n``<Sender/Credential>`` domain+identity (e.g. ``DUNS / 465585586`` for\nLyreco Spain). The ``<BuyerCookie>`` (a Flowie-issued UUID embedded in\nthe Setup Request as ``flowie:{org_id}:{thread_id?}:{nonce}``) provides\nthe second factor binding the request to a specific organization.\n\nThe request body is the raw cXML. Content-types accepted:\n\n* ``application/xml`` / ``text/xml`` — raw cXML.\n* ``application/x-www-form-urlencoded`` with field ``cxml-urlencoded``\n  (URL-encoded cXML) or ``cxml-base64`` — the BrowserFormPost envelope\n  (cXML 1.2.060 §6.1.2). Both are unwrapped before parsing.\n\nThe response is intentionally HTML (not the cXML ``<Response>`` body\nthe spec describes): BrowserFormPost makes the user's **browser** the\nHTTP client, so we return a meta-refresh page that lands the user back\nin their originating AI chat thread / org dashboard. Suppliers only\ncare that we return 2xx.\n\n**Example request:**\n```\nPOST /document/callback HTTP/1.1\nContent-Type: application/xml\n```\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE cXML SYSTEM \"http://xml.cxml.org/schemas/cXML/1.2.044/cXML.dtd\">\n<cXML payloadID=\"1718614200@flowie.fr\" timestamp=\"2026-06-17T08:30:00+00:00\">\n  <Header>\n    <From><Credential domain=\"DUNS\"><Identity>465585586</Identity></Credential></From>\n    <To><Credential domain=\"DUNS\"><Identity>FLOWIE</Identity></Credential></To>\n    <Sender>\n      <Credential domain=\"DUNS\">\n        <Identity>465585586</Identity>\n        <SharedSecret>PWD465585586</SharedSecret>\n      </Credential>\n      <UserAgent>Lyreco PunchOut</UserAgent>\n    </Sender>\n  </Header>\n  <Message>\n    <PunchOutOrderMessage>\n      <BuyerCookie>flowie:019c76b6-5ad7-7000-b52b-61a9ad3a47c8:thr_01H8YQ:nonce</BuyerCookie>\n      <PunchOutOrderMessageHeader operationAllowed=\"create\">\n        <Total><Money currency=\"EUR\">123.45</Money></Total>\n      </PunchOutOrderMessageHeader>\n      <ItemIn quantity=\"2\">…</ItemIn>\n    </PunchOutOrderMessage>\n  </Message>\n</cXML>\n```\n\n**Example response (200 — happy path):**\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta http-equiv=\"refresh\" content=\"0; url=https://app.flowie.me/acme/ai/chat/thr_01H8YQ\">\n    <title>Redirecting to Flowie…</title>\n  </head>\n  <body><h1>Cart received — returning to your conversation…</h1></body>\n</html>\n```\n\n**Example response (200 — upstream upload failed):**\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cXML payloadID=\"...@flowie.fr\" timestamp=\"2026-06-17T08:30:00+00:00\">\n  <Response><Status code=\"500\" text=\"Processing error: …\"/></Response>\n</cXML>\n```\n\n**Errors**\n\n| Status | Code                 | When                                                                          |\n|--------|----------------------|-------------------------------------------------------------------------------|\n| 400    | `invalid_request`    | Empty body, or `<cXML>` failed to parse (`lxml.XMLSyntaxError`).              |\n| 401    | `invalid_credentials`| No matching `<SharedSecret>` and supplier-identity fallback also failed.      |","operationId":"receive_punchout_cart_document_callback_post","responses":{"200":{"description":"Cart accepted (or upload errored — failures still return 200 with a cXML <Status code=\"500\"/> body so the supplier doesn't retry-loop).","content":{"application/json":{"schema":{}},"text/html":{"example":"<!DOCTYPE html><html lang=\"en\"><head><meta http-equiv=\"refresh\" content=\"0; url=https://app.flowie.me/acme/ai/chat/thr_01H8YQ\"><title>Redirecting to Flowie…</title></head><body><div class=\"card\"><h1>Cart received — returning to your conversation…</h1></div></body></html>","schema":{"type":"string"}},"text/xml":{"example":"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cXML payloadID=\"…@flowie.fr\" timestamp=\"2026-06-17T08:30:00+00:00\">\n  <Response><Status code=\"500\" text=\"Processing error: …\"/></Response>\n</cXML>","schema":{"type":"string"}}}},"400":{"description":"Empty body or malformed cXML.","content":{"application/json":{"example":{"detail":"Invalid XML"}}}},"401":{"description":"SharedSecret missing/unknown and supplier identity fallback failed.","content":{"application/json":{"example":{"detail":"Invalid SharedSecret"}}}}}}},"/document/oci-callback":{"get":{"tags":["Document Callbacks"],"summary":"OCI cart callback (GET variant — supplier auto-submit)","description":"Receive an OCI cart return.\n\nAuth model: the supplier-facing HOOK_URL must contain a ``flowie_cookie``\nquery parameter (or a ``flowie_cookie`` form field) carrying the agents-hub\nBuyerCookie ``flowie:{org_id}:{thread_id_or_empty}:{nonce}``. We use it to\nroute the cart to the right org and to redirect the user back into their\nchat thread, identical to the cXML callback's behaviour.\n\n**PUBLIC endpoint — no API key / bearer token required.** The\n``flowie_cookie`` is the authentication factor; without it the request\nis rejected with 400.\n\nBody is parsed as an OCI form: each ``NEW_ITEM-<FIELD>[n]`` field is one\nattribute of one cart line. Mandatory per-line fields are\n``DESCRIPTION`` / ``QUANTITY`` / ``PRICE`` / ``CURRENCY``; everything\nelse (UNIT, VENDORMAT, MATNR, MATGROUP, CONTRACT, SERVICE…) is optional.\n\n**Example request:**\n```\nPOST /document/oci-callback HTTP/1.1\nContent-Type: application/x-www-form-urlencoded\n\nflowie_cookie=flowie:019c76b6-5ad7-7000-b52b-61a9ad3a47c8:thr_01H8YQ:nonce\n&NEW_ITEM-DESCRIPTION[1]=A4+Copy+paper+80gsm\n&NEW_ITEM-QUANTITY[1]=10\n&NEW_ITEM-UNIT[1]=BX\n&NEW_ITEM-PRICE[1]=24.50\n&NEW_ITEM-PRICEUNIT[1]=1\n&NEW_ITEM-CURRENCY[1]=EUR\n&NEW_ITEM-VENDORMAT[1]=LYR-A4-80\n```\n\n**Example response (200):**\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head><meta http-equiv=\"refresh\" content=\"0; url=https://app.flowie.me/acme/ai/chat/thr_01H8YQ\"></head>\n  <body><h1>Cart received — returning to your conversation…</h1></body>\n</html>\n```\n\n**Example response (400 — missing cookie):**\n```json\n{\"detail\": \"Missing or invalid flowie_cookie\"}\n```\n\n**Errors**\n\n| Status | Code               | When                                                            |\n|--------|--------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`  | `flowie_cookie` missing or org segment unparseable.             |","operationId":"receive_oci_cart_document_oci_callback_get","responses":{"200":{"description":"Cart received and uploaded — browser is redirected back to the originating Flowie chat / dashboard.","content":{"application/json":{"schema":{}},"text/html":{"example":"<!DOCTYPE html><html lang=\"en\"><head><meta http-equiv=\"refresh\" content=\"0; url=https://app.flowie.me/acme/ai/chat/thr_01H8YQ\"><title>Redirecting to Flowie…</title></head><body><div class=\"card\"><h1>Cart received — returning to your conversation…</h1></div></body></html>","schema":{"type":"string"}}}},"400":{"description":"Missing or invalid `flowie_cookie` (no org context).","content":{"application/json":{"example":{"detail":"Missing or invalid flowie_cookie"}}}}}},"post":{"tags":["Document Callbacks"],"summary":"OCI cart callback (Mercateo / Conrad / SAP-style suppliers)","description":"Receive an OCI cart return.\n\nAuth model: the supplier-facing HOOK_URL must contain a ``flowie_cookie``\nquery parameter (or a ``flowie_cookie`` form field) carrying the agents-hub\nBuyerCookie ``flowie:{org_id}:{thread_id_or_empty}:{nonce}``. We use it to\nroute the cart to the right org and to redirect the user back into their\nchat thread, identical to the cXML callback's behaviour.\n\n**PUBLIC endpoint — no API key / bearer token required.** The\n``flowie_cookie`` is the authentication factor; without it the request\nis rejected with 400.\n\nBody is parsed as an OCI form: each ``NEW_ITEM-<FIELD>[n]`` field is one\nattribute of one cart line. Mandatory per-line fields are\n``DESCRIPTION`` / ``QUANTITY`` / ``PRICE`` / ``CURRENCY``; everything\nelse (UNIT, VENDORMAT, MATNR, MATGROUP, CONTRACT, SERVICE…) is optional.\n\n**Example request:**\n```\nPOST /document/oci-callback HTTP/1.1\nContent-Type: application/x-www-form-urlencoded\n\nflowie_cookie=flowie:019c76b6-5ad7-7000-b52b-61a9ad3a47c8:thr_01H8YQ:nonce\n&NEW_ITEM-DESCRIPTION[1]=A4+Copy+paper+80gsm\n&NEW_ITEM-QUANTITY[1]=10\n&NEW_ITEM-UNIT[1]=BX\n&NEW_ITEM-PRICE[1]=24.50\n&NEW_ITEM-PRICEUNIT[1]=1\n&NEW_ITEM-CURRENCY[1]=EUR\n&NEW_ITEM-VENDORMAT[1]=LYR-A4-80\n```\n\n**Example response (200):**\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head><meta http-equiv=\"refresh\" content=\"0; url=https://app.flowie.me/acme/ai/chat/thr_01H8YQ\"></head>\n  <body><h1>Cart received — returning to your conversation…</h1></body>\n</html>\n```\n\n**Example response (400 — missing cookie):**\n```json\n{\"detail\": \"Missing or invalid flowie_cookie\"}\n```\n\n**Errors**\n\n| Status | Code               | When                                                            |\n|--------|--------------------|-----------------------------------------------------------------|\n| 400    | `invalid_request`  | `flowie_cookie` missing or org segment unparseable.             |","operationId":"receive_oci_cart_document_oci_callback_post","responses":{"200":{"description":"Cart received and uploaded — browser is redirected back to the originating Flowie chat / dashboard.","content":{"application/json":{"schema":{}},"text/html":{"example":"<!DOCTYPE html><html lang=\"en\"><head><meta http-equiv=\"refresh\" content=\"0; url=https://app.flowie.me/acme/ai/chat/thr_01H8YQ\"><title>Redirecting to Flowie…</title></head><body><div class=\"card\"><h1>Cart received — returning to your conversation…</h1></div></body></html>","schema":{"type":"string"}}}},"400":{"description":"Missing or invalid `flowie_cookie` (no org context).","content":{"application/json":{"example":{"detail":"Missing or invalid flowie_cookie"}}}}}}},"/v1/oauth/scopes":{"get":{"tags":["OAuth"],"summary":"Public scope catalogue (no auth required)","description":"Return every grantable scope with a human-readable description.\n\nPublic — no auth required. Agents call this once at boot to render an\nhonest scope-selection UI to the user before kicking off the consent\nflow at ``POST /v1/oauth/authorize``.\n\n**Example request:**\n```\nGET /v1/oauth/scopes\n```\n\n**Example response (200):**\n```json\n{\n  \"scopes\": [\n    {\"scope\": \"send\", \"label\": \"Send documents\",\n     \"description\": \"Issue invoices, credit notes, orders over Peppol.\"},\n    {\"scope\": \"documents.read\", \"label\": \"Read documents\",\n     \"description\": \"List, search, download XML / PDF / structured views of documents.\"}\n  ]\n}\n```\n\n**Errors**\n\n| Status | Code                   | When                                                  |\n|--------|------------------------|-------------------------------------------------------|\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.|\n| 503    | `upstream_unavailable` | Upstream service unavailable.                         |","operationId":"get_scope_catalogue_v1_oauth_scopes_get","responses":{"200":{"description":"The full list of grantable scopes plus human-readable labels.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScopeCatalogueResponse"},"example":{"scopes":[{"scope":"send","label":"Send documents","description":"Issue invoices, credit notes, orders over Peppol."},{"scope":"receive","label":"Receive documents","description":"Configure inbound Peppol delivery + webhooks + SMP registration."},{"scope":"documents.read","label":"Read documents","description":"List, search, download XML / PDF / structured views of documents."}]}}}},"429":{"description":"Rate limit exceeded (sandbox bootstrap is capped at 120/IP/hr).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream sandbox / api-keys service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/oauth/authorize":{"post":{"tags":["OAuth"],"summary":"Step 1 of the consent flow — register intent, get a consent URL","description":"Step 1 of the consent flow. Returns the URL the agent shows the user.\n\nThe agent supplies its display name, the scopes it wants, and a PKCE\nchallenge (SHA-256 of a verifier it keeps secret). The user opens\n``consent_url`` in their browser, clicks Approve, and the server hands\nthem a short-lived code that the agent later exchanges via\n``POST /v1/oauth/token``.\n\n**Example request:**\n```json\nPOST /v1/oauth/authorize\n{\n  \"client_name\": \"Claude Desktop · Acme\",\n  \"scopes\": [\"send\", \"documents.read\"],\n  \"redirect_uri\": \"urn:ietf:wg:oauth:2.0:oob\",\n  \"code_challenge\": \"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM\",\n  \"code_challenge_method\": \"S256\",\n  \"state\": \"xyz-session-binding\"\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"request_id\": \"cnsnt_019a6e5b3b4c7000a460a09181acc257\",\n  \"consent_url\": \"https://exchange.flowie.fr/oauth/consent?req=cnsnt_…\",\n  \"expires_in\": 600\n}\n```\n\n**Errors**\n\n| Status | Code                   | When                                                       |\n|--------|------------------------|------------------------------------------------------------|\n| 400    | `invalid_request`      | Unknown scope, redirect URI not allowed, or bad PKCE.      |\n| 422    | `validation_error`     | Body failed schema validation.                             |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.     |\n| 503    | `upstream_unavailable` | Upstream sandbox / api-keys service unavailable.           |","operationId":"authorize_v1_oauth_authorize_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizeRequest"}}},"required":true,"description":"`AuthorizeRequest` — payload to step 1 of the consent flow — register intent, get a consent URL."},"responses":{"200":{"description":"Consent request created — show `consent_url` to the user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizeResponse"},"example":{"request_id":"cnsnt_019a6e5b3b4c7000a460a09181acc257","consent_url":"https://exchange.flowie.fr/oauth/consent?req=cnsnt_019a6e5b3b4c7000a460a09181acc257","expires_in":600}}}},"400":{"description":"Malformed request (e.g. unknown scope, invalid PKCE verifier, expired code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (sandbox bootstrap is capped at 120/IP/hr).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream sandbox / api-keys service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/oauth/token":{"post":{"tags":["OAuth"],"summary":"Exchange the consent auth code (with PKCE) for an API key","description":"Final step of the consent flow — exchange the auth code for an API key.\n\nThe server hashes ``code_verifier`` with SHA-256 and checks it against the\n``code_challenge`` recorded during ``/authorize``. The code is one-time\nand expires 5 minutes after consent.\n\n**Example request:**\n```json\nPOST /v1/oauth/token\n{\n  \"grant_type\": \"authorization_code\",\n  \"code\": \"code_01H8YQXM2V7K5N3R4T6W8Z1A2B\",\n  \"code_verifier\": \"dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\"\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"access_token\": \"flw_test_01H8YQXM2V7K5N3R4T6W8Z1B9CdEfGhIjKlMnOpQrStUv\",\n  \"token_type\": \"Bearer\",\n  \"scopes\": [\"send\", \"documents.read\"],\n  \"expires_in\": 604800,\n  \"company_id\": \"cmp_01H8YQXM2V7K5N3R4T6W8Z1A2B\",\n  \"organization_id\": \"org_sbx_01H8YQXM2V7K5N3R4T6W8Z1A2B\"\n}\n```\n\n**Errors**\n\n| Status | Code                   | When                                                                  |\n|--------|------------------------|-----------------------------------------------------------------------|\n| 400    | `invalid_grant`        | Code unknown / already redeemed / expired, or PKCE verifier mismatch. |\n| 422    | `validation_error`     | Body failed schema validation.                                        |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.                |\n| 503    | `upstream_unavailable` | Upstream api-keys service unavailable.                                |","operationId":"token_v1_oauth_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}},"required":true,"description":"`TokenRequest` — payload to exchange the consent auth code (with PKCE) for an API key."},"responses":{"200":{"description":"Code validated — the API key is issued and bound to the consent's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"},"example":{"access_token":"flw_test_01H8YQXM2V7K5N3R4T6W8Z1B9CdEfGhIjKlMnOpQrStUv","token_type":"Bearer","scopes":["send","documents.read"],"expires_in":604800,"company_id":"cmp_01H8YQXM2V7K5N3R4T6W8Z1A2B","organization_id":"org_sbx_01H8YQXM2V7K5N3R4T6W8Z1A2B"}}}},"400":{"description":"Malformed request (e.g. unknown scope, invalid PKCE verifier, expired code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (sandbox bootstrap is capped at 120/IP/hr).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream sandbox / api-keys service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/oauth/handoff":{"post":{"tags":["OAuth"],"summary":"Generate a pre-approved, single-use link to share with an AI agent","description":"Generate a single-use, pre-approved link to share with an AI agent.\n\nThe agent reads the URL, calls ``POST /v1/oauth/handoff/exchange`` with\nthe embedded token, and gets a scoped API key bound to **your**\norganization. Skips the consent dance for agents you already trust.\n\nScope rule: you can only pre-approve scopes your own token already\nholds. Live keys (``mode=live``) require a paid tier.\n\n**Example request:**\n```json\nPOST /v1/oauth/handoff\nAuthorization: Bearer flw_live_…\n{\n  \"scopes\": [\"send\", \"documents.read\"],\n  \"label\": \"Claude Desktop — June onboarding\",\n  \"ttl_seconds\": 600,\n  \"mode\": \"test\"\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"handoff_token\": \"hnd_01H8YQXM2V7K5N3R4T6W8Z1A2B3C4D5E6F\",\n  \"handoff_url\": \"https://exchange.flowie.fr/oauth/handoff?token=hnd_…\",\n  \"expires_at\": \"2026-06-17T08:30:00+00:00\",\n  \"scopes\": [\"send\", \"documents.read\"],\n  \"organization_id\": \"019c76b2-9c94-7000-8cb6-ef104afb6093\"\n}\n```\n\n**Errors**\n\n| Status | Code                   | When                                                                  |\n|--------|------------------------|-----------------------------------------------------------------------|\n| 400    | `invalid_request`      | Requested a scope the caller doesn't hold, or live mode on free tier. |\n| 401    | `unauthorized`         | Missing / invalid `Authorization` header or API key.                  |\n| 403    | `forbidden`            | Key valid but cannot grant handoffs.                                  |\n| 422    | `validation_error`     | Body failed schema validation.                                        |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.                |\n| 503    | `upstream_unavailable` | Upstream api-keys service unavailable.                                |","operationId":"create_handoff_v1_oauth_handoff_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandoffCreateRequest"}}},"description":"`HandoffCreateRequest` — payload to generate a pre-approved, single-use link to share with an AI agent."},"responses":{"200":{"description":"Handoff URL minted — share it with the agent; it expires after `ttl_seconds`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandoffCreateResponse"},"example":{"handoff_token":"hnd_01H8YQXM2V7K5N3R4T6W8Z1A2B3C4D5E6F","handoff_url":"https://exchange.flowie.fr/oauth/handoff?token=hnd_01H8YQXM2V7K5N3R4T6W8Z1A2B3C4D5E6F","expires_at":"2026-06-17T08:30:00+00:00","scopes":["send","receive","documents.read","companies.read","stats"],"organization_id":"019c76b2-9c94-7000-8cb6-ef104afb6093"}}}},"400":{"description":"Malformed request (e.g. scope not held by caller, live mode but free tier).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but the requested scope is forbidden for it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream api-keys service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/oauth/handoff/sandbox":{"post":{"tags":["OAuth"],"summary":"Anonymous sandbox handoff — bootstrap a fresh org + mint a handoff token","description":"Anonymous handoff: bootstraps a fresh sandbox + mints a handoff token.\n\nNo auth required. Subject to the same 120/IP/hr rate limit as\n``POST /v1/sandbox/bootstrap``. Designed for the docs home page to\nauto-mint a personalized URL on every visit so an LLM that fetches\nthe URL can redeem the embedded token straight away.\n\nThe redeemed key is bound to a brand-new sandbox organization\n(``org_sbx_…``) — never to a real production tenant.\n\n**Example request:**\n```\nPOST /v1/oauth/handoff/sandbox\n```\n\n**Example response (200):**\n```json\n{\n  \"handoff_token\": \"hnd_01H8YQXM2V7K5N3R4T6W8Z1A2B3C4D5E6F\",\n  \"handoff_url\": \"https://exchange.flowie.fr/oauth/handoff?token=hnd_…\",\n  \"expires_at\": \"2026-06-17T08:30:00+00:00\",\n  \"scopes\": [\"send\", \"receive\", \"documents.read\", \"companies.read\", \"stats\"],\n  \"organization_id\": \"org_sbx_01H8YQXM2V7K5N3R4T6W8Z1A2B\"\n}\n```\n\n**Example response (429 — IP cap reached):**\n```json\n{\n  \"type\": \"rate_limit_error\",\n  \"code\": \"BOOTSTRAP_RATE_LIMITED\",\n  \"message\": \"Sandbox handoff is limited to 120 calls per IP per hour (shared with /v1/sandbox/bootstrap). Retry in 1734s.\",\n  \"retryAfter\": 1734\n}\n```\n\n**Errors**\n\n| Status | Code                       | When                                                       |\n|--------|----------------------------|------------------------------------------------------------|\n| 400    | `invalid_request`          | Malformed request (rare for this endpoint).                |\n| 429    | `BOOTSTRAP_RATE_LIMITED`   | 120/IP/hr cap reached — honour `Retry-After`.              |\n| 503    | `upstream_unavailable`     | Sandbox bootstrap upstream unavailable.                    |","operationId":"create_sandbox_handoff_v1_oauth_handoff_sandbox_post","responses":{"200":{"description":"A new sandbox org was provisioned and a single-use handoff URL was issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandoffCreateResponse"},"example":{"handoff_token":"hnd_01H8YQXM2V7K5N3R4T6W8Z1A2B3C4D5E6F","handoff_url":"https://exchange.flowie.fr/oauth/handoff?token=hnd_01H8YQXM2V7K5N3R4T6W8Z1A2B3C4D5E6F","expires_at":"2026-06-17T08:30:00+00:00","scopes":["send","receive","documents.read","companies.read","stats"],"organization_id":"org_sbx_01H8YQXM2V7K5N3R4T6W8Z1A2B"}}}},"400":{"description":"Malformed request (e.g. unknown scope, invalid PKCE verifier, expired code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (sandbox bootstrap is capped at 120/IP/hr).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream sandbox / api-keys service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/oauth/handoff/exchange":{"post":{"tags":["OAuth"],"summary":"Redeem a handoff token for an API key (single-use, no auth)","description":"Redeem a handoff token for an API key. Single-use, no other auth.\n\nTokens minted by ``POST /v1/oauth/handoff`` (or the anonymous\n``/handoff/sandbox`` variant) can be redeemed exactly once. A second\nattempt returns ``400 invalid_grant``.\n\n**Example request:**\n```json\nPOST /v1/oauth/handoff/exchange\n{\n  \"handoff_token\": \"hnd_01H8YQXM2V7K5N3R4T6W8Z1A2B3C4D5E6F\"\n}\n```\n\n**Example response (200):**\n```json\n{\n  \"access_token\": \"flw_test_01H8YQXM2V7K5N3R4T6W8Z1B9CdEfGhIjKlMnOpQrStUv\",\n  \"token_type\": \"Bearer\",\n  \"scopes\": [\"send\", \"receive\", \"documents.read\", \"companies.read\", \"stats\"],\n  \"expires_in\": 604800,\n  \"company_id\": \"cmp_01H8YQXM2V7K5N3R4T6W8Z1A2B\",\n  \"organization_id\": \"019c76b2-9c94-7000-8cb6-ef104afb6093\"\n}\n```\n\n**Errors**\n\n| Status | Code                   | When                                                       |\n|--------|------------------------|------------------------------------------------------------|\n| 400    | `invalid_grant`        | Token unknown / already redeemed / expired.                |\n| 422    | `validation_error`     | Body failed schema validation.                             |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.     |\n| 503    | `upstream_unavailable` | Upstream api-keys service unavailable.                     |","operationId":"exchange_handoff_v1_oauth_handoff_exchange_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandoffExchangeRequest"}}},"required":true,"description":"`HandoffExchangeRequest` — payload to redeem a handoff token for an API key (single-use, no auth)."},"responses":{"200":{"description":"Token redeemed — the issued API key is returned and tied to the handoff's org.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"},"example":{"access_token":"flw_test_01H8YQXM2V7K5N3R4T6W8Z1B9CdEfGhIjKlMnOpQrStUv","token_type":"Bearer","scopes":["send","documents.read"],"expires_in":604800,"company_id":"cmp_01H8YQXM2V7K5N3R4T6W8Z1A2B","organization_id":"org_sbx_01H8YQXM2V7K5N3R4T6W8Z1A2B"}}}},"400":{"description":"Malformed request (e.g. unknown scope, invalid PKCE verifier, expired code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded (sandbox bootstrap is capped at 120/IP/hr).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Upstream sandbox / api-keys service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/sandbox/bootstrap":{"post":{"tags":["Sandbox"],"summary":"Self-serve a fresh sandbox API key + starter company (no auth)","description":"Issue a fresh sandbox API key, a starter sandbox company, and a Peppol\ntest identifier — all in one call. **No authentication required.**\n\nDesigned for the \"I just landed on the docs and want to try the API\"\ncase. The returned `apiKey` is the full secret — store it now, it will\nnot be shown again. Bootstrapped keys expire after 7 days; create a\nlong-lived one from the dashboard later. Rate-limited per IP (default\n120 calls / hour). Set `keyType=platform` or `keyType=white_label` to\nmint a key that satisfies the platform-key gate in sandbox.\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/sandbox/bootstrap \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"label\": \"quickstart\", \"email\": \"dev@example.com\", \"keyType\": \"personal\"}'\n```\n\n**Example 200 response:**\n```json\n{\n  \"organizationId\": \"org_sbx_4f8a1c2d3e7b9a5c6d8e0f1a2b3c\",\n  \"apiKey\": \"flw_test_4f8a1c2d3e7b9a5c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a\",\n  \"keyPrefix\": \"flw_test_4f8a1c2d\",\n  \"keyType\": \"personal\",\n  \"company\": {\n    \"id\": \"comp_sbx_4f8a1c2d3e7b9a5c6d8e\",\n    \"peppolId\": \"0208:0000000001\",\n    \"vatNumber\": \"BE0000000001\",\n    \"name\": \"Sandbox Test BVBA\",\n    \"country\": \"BE\",\n    \"status\": \"active\"\n  },\n  \"expiresAt\": \"2026-06-24T09:14:22.157894+00:00\",\n  \"nextSteps\": [\"Save the apiKey now — it will not be shown again.\", \"…\"],\n  \"docs\": {\"playground\": \"https://docs.get-flowie.com/playground/index.html\"}\n}\n```\n\n**Example 429 response — per-IP cap hit:**\n```json\n{\n  \"type\": \"rate_limit_error\",\n  \"code\": \"BOOTSTRAP_RATE_LIMITED\",\n  \"message\": \"Bootstrap is limited to 120 keys per IP per hour. Retry in 1843s, or contact developers@flowie.fr if you need more.\",\n  \"retryAfter\": 1843\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code                       | When                                                                |\n|--------|----------------------------|---------------------------------------------------------------------|\n| 422    | `validation_error`         | Body failed Pydantic validation (e.g. invalid `keyType`).           |\n| 429    | `BOOTSTRAP_RATE_LIMITED`   | Per-IP cap exhausted — `Retry-After` seconds before next attempt.   |\n| 500    | `internal_error`           | Persistence layer error while issuing the key.                      |","operationId":"bootstrap_v1_sandbox_bootstrap_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxBootstrapRequest","default":{"label":"quickstart","keyType":"personal"}}}},"description":"`SandboxBootstrapRequest` — payload to self-serve a fresh sandbox API key + starter company (no auth)."},"responses":{"200":{"description":"Sandbox key issued. The `apiKey` field is the only chance to capture the secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxBootstrapResponse"},"example":{"organizationId":"org_sbx_4f8a1c2d3e7b9a5c6d8e0f1a2b3c","apiKey":"flw_test_4f8a1c2d3e7b9a5c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a","keyPrefix":"flw_test_4f8a1c2d","keyType":"personal","company":{"id":"comp_sbx_4f8a1c2d3e7b9a5c6d8e","peppolId":"0208:0000000001","vatNumber":"BE0000000001","name":"Sandbox Test BVBA","country":"BE","status":"active"},"expiresAt":"2026-06-24T09:14:22.157894+00:00","nextSteps":["Save the apiKey now — it will not be shown again.","Open the playground at https://docs.get-flowie.com/playground/index.html","Try POST /v1/directory/verify with peppolId=\"0208:TEST_OK\".","Then POST /v1/documents/send to that recipient.","Listen for document.delivered on a webhook (use ngrok for local)."],"docs":{"playground":"https://docs.get-flowie.com/playground/index.html","reference":"https://docs.get-flowie.com/reference/index.html","sandbox":"https://docs.get-flowie.com/sandbox/index.html","fixtures":"https://docs.get-flowie.com/fixtures/"}}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Per-IP bootstrap cap exhausted (default 120 / hour). `Retry-After` header set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while issuing the sandbox key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/sandbox/reset":{"post":{"tags":["Sandbox"],"summary":"Wipe sandbox events / idempotency cache for the caller","description":"Wipe events and / or idempotency cache for the caller's organization.\n\nRequires a **test-mode** API key. The literal string `\"yes\"` in\n`confirm` is mandatory to prevent accidental wipes mid-debug. Use\n`scope` to narrow the blast radius:\n\n* `all`         — events + idempotency + pending scheduled events (default)\n* `events`      — only the event stream\n* `idempotency` — only entries tied to the calling key\n* `documents`   — (reserved) no-op today; counter returns 0\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/sandbox/reset \\\n  -H \"Authorization: Bearer flw_test_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"confirm\": \"yes\", \"scope\": \"all\"}'\n```\n\n**Example response:**\n```json\n{\n  \"deletedDocuments\": 0,\n  \"deletedEvents\": 17,\n  \"deletedIdempotencyEntries\": 42,\n  \"organizationId\": \"org_sbx_4f8a1c2d3e7b9a5c6d8e0f1a2b3c\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code               | When                                                       |\n|--------|--------------------|------------------------------------------------------------|\n| 400    | `invalid_request`  | `scope` is not one of `all` / `events` / `idempotency`.    |\n| 401    | `unauthorized`     | Missing / invalid `Authorization` header.                  |\n| 403    | `SANDBOX_ONLY`     | Caller is using a live-mode key.                           |\n| 422    | `validation_error` | Body missing `confirm: \"yes\"`.                             |\n| 429    | `rate_limited`     | Too many requests — retry after `Retry-After`.             |","operationId":"reset_sandbox_v1_sandbox_reset_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxResetRequest"}}},"description":"`SandboxResetRequest` — payload to wipe sandbox events / idempotency cache for the caller."},"responses":{"200":{"description":"Sandbox state wiped — counters reflect how many rows were deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxResetResponse"},"example":{"deletedDocuments":0,"deletedEvents":17,"deletedIdempotencyEntries":42,"organizationId":"org_sbx_4f8a1c2d3e7b9a5c6d8e0f1a2b3c"}}}},"400":{"description":"Invalid request payload (e.g. unparsable `by` duration, unknown `scope`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Endpoint only callable with a test-mode (`flw_test_…`) API key — live keys are rejected with code `SANDBOX_ONLY`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while mutating sandbox state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/sandbox/rate-limit/exhaust":{"post":{"tags":["Sandbox"],"summary":"Force the caller's org into a 429-loop for N seconds","description":"Arm a rate-limit override that makes every subsequent request from the\ncaller's sandbox org return `429` for `durationSeconds` (max 3600).\nRequires a **test-mode** API key.\n\nUseful for verifying your retry / exponential-backoff implementation\nunder realistic conditions — including responses that honor the\n`Retry-After` header.\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/sandbox/rate-limit/exhaust \\\n  -H \"Authorization: Bearer flw_test_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"durationSeconds\": 60}'\n```\n\n**Example response:**\n```json\n{\n  \"organizationId\": \"org_sbx_4f8a1c2d3e7b9a5c6d8e0f1a2b3c\",\n  \"durationSeconds\": 60,\n  \"expiresAt\": \"2026-06-17T09:15:22.157894+00:00\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code               | When                                                       |\n|--------|--------------------|------------------------------------------------------------|\n| 401    | `unauthorized`     | Missing / invalid `Authorization` header.                  |\n| 403    | `SANDBOX_ONLY`     | Caller is using a live-mode key.                           |\n| 422    | `validation_error` | `durationSeconds` outside [1, 3600].                       |\n| 429    | `rate_limited`     | Too many requests — retry after `Retry-After`.             |","operationId":"exhaust_rate_limit_v1_sandbox_rate_limit_exhaust_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRateLimitExhaustRequest"}}},"description":"`SandboxRateLimitExhaustRequest` — payload to force the caller's org into a 429-loop for N seconds."},"responses":{"200":{"description":"Override armed — subsequent requests return 429 until `expiresAt`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxRateLimitExhaustResponse"},"example":{"organizationId":"org_sbx_4f8a1c2d3e7b9a5c6d8e0f1a2b3c","durationSeconds":60,"expiresAt":"2026-06-17T09:15:22.157894+00:00"}}}},"400":{"description":"Invalid request payload (e.g. unparsable `by` duration, unknown `scope`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Endpoint only callable with a test-mode (`flw_test_…`) API key — live keys are rejected with code `SANDBOX_ONLY`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while mutating sandbox state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/sandbox/clock/advance":{"post":{"tags":["Sandbox"],"summary":"Fast-forward a sandbox company's virtual clock","description":"Advance a single sandbox company's virtual clock by `by`. Requires a\n**test-mode** API key.\n\nAccepts compact units: `s|m|h|d|w|y` (e.g. `\"60d\"`, `\"2h\"`, `\"1y\"`).\nThe offset is **per-company** — other tenants in the same sandbox org\nare unaffected. Pending scheduled events whose virtual fire-time has\nnow passed are woken on the next scheduler tick (~1s).\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/sandbox/clock/advance \\\n  -H \"Authorization: Bearer flw_test_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"companyId\": \"comp_sbx_4f8a1c2d3e7b9a5c6d8e\", \"by\": \"60d\"}'\n```\n\n**Example response:**\n```json\n{\n  \"companyId\": \"comp_sbx_4f8a1c2d3e7b9a5c6d8e\",\n  \"offsetSeconds\": 5184000,\n  \"virtualNow\": \"2026-08-16T09:14:22.157894+00:00\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code               | When                                                       |\n|--------|--------------------|------------------------------------------------------------|\n| 400    | `invalid_request`  | `by` could not be parsed (missing unit, unknown unit, negative). |\n| 401    | `unauthorized`     | Missing / invalid `Authorization` header.                  |\n| 403    | `SANDBOX_ONLY`     | Caller is using a live-mode key.                           |\n| 422    | `validation_error` | Body missing `companyId` or `by`.                          |\n| 429    | `rate_limited`     | Too many requests — retry after `Retry-After`.             |","operationId":"advance_clock_v1_sandbox_clock_advance_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxClockAdvanceRequest"}}},"description":"`SandboxClockAdvanceRequest` — payload to fast-forward a sandbox company's virtual clock."},"responses":{"200":{"description":"Clock advanced; `virtualNow` reflects wall-clock + cumulative offset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxClockResponse"},"example":{"companyId":"comp_sbx_4f8a1c2d3e7b9a5c6d8e","offsetSeconds":5184000,"virtualNow":"2026-08-16T09:14:22.157894+00:00"}}}},"400":{"description":"Invalid request payload (e.g. unparsable `by` duration, unknown `scope`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Endpoint only callable with a test-mode (`flw_test_…`) API key — live keys are rejected with code `SANDBOX_ONLY`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while mutating sandbox state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/sandbox/clock/reset":{"post":{"tags":["Sandbox"],"summary":"Snap a sandbox company's virtual clock back to now","description":"Snap a sandbox company's virtual clock back to wall-clock now (offset\nbecomes 0). Requires a **test-mode** API key. Use this between scenarios\nto start each test from a known time anchor.\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/sandbox/clock/reset \\\n  -H \"Authorization: Bearer flw_test_…\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"companyId\": \"comp_sbx_4f8a1c2d3e7b9a5c6d8e\"}'\n```\n\n**Example response:**\n```json\n{\n  \"companyId\": \"comp_sbx_4f8a1c2d3e7b9a5c6d8e\",\n  \"offsetSeconds\": 0,\n  \"virtualNow\": \"2026-06-17T09:14:22.157894+00:00\"\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code               | When                                                       |\n|--------|--------------------|------------------------------------------------------------|\n| 401    | `unauthorized`     | Missing / invalid `Authorization` header.                  |\n| 403    | `SANDBOX_ONLY`     | Caller is using a live-mode key.                           |\n| 422    | `validation_error` | Body missing `companyId`.                                  |\n| 429    | `rate_limited`     | Too many requests — retry after `Retry-After`.             |","operationId":"reset_clock_v1_sandbox_clock_reset_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxClockResetRequest"}}},"description":"`SandboxClockResetRequest` — payload to snap a sandbox company's virtual clock back to now."},"responses":{"200":{"description":"Offset cleared; `virtualNow` equals wall-clock now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxClockResponse"},"example":{"companyId":"comp_sbx_4f8a1c2d3e7b9a5c6d8e","offsetSeconds":0,"virtualNow":"2026-06-17T09:14:22.157894+00:00"}}}},"400":{"description":"Invalid request payload (e.g. unparsable `by` duration, unknown `scope`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Endpoint only callable with a test-mode (`flw_test_…`) API key — live keys are rejected with code `SANDBOX_ONLY`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while mutating sandbox state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/sandbox/idempotency/flush":{"post":{"tags":["Sandbox"],"summary":"Drop every idempotency entry stored for the calling key","description":"Drop every idempotency-cache entry stored against the calling key.\nRequires a **test-mode** API key.\n\nLets you re-run the exact same request with the same `Idempotency-Key`\nheader and get a fresh execution rather than a cached replay — handy\nwhen iterating on payload shape or stubbing webhooks.\n\n---\n\n**Example request:**\n```bash\ncurl -X POST https://api.flowie.fr/v1/sandbox/idempotency/flush \\\n  -H \"Authorization: Bearer flw_test_…\"\n```\n\n**Example response:**\n```json\n{\n  \"organizationId\": \"org_sbx_4f8a1c2d3e7b9a5c6d8e0f1a2b3c\",\n  \"deletedEntries\": 27\n}\n```\n\n---\n\n**Errors** (response body follows `ErrorResponse`):\n\n| Status | Code               | When                                                       |\n|--------|--------------------|------------------------------------------------------------|\n| 401    | `unauthorized`     | Missing / invalid `Authorization` header.                  |\n| 403    | `SANDBOX_ONLY`     | Caller is using a live-mode key.                           |\n| 429    | `rate_limited`     | Too many requests — retry after `Retry-After`.             |\n| 500    | `internal_error`   | Database error while deleting cache entries.               |","operationId":"flush_idempotency_v1_sandbox_idempotency_flush_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Idempotency cache cleared; `deletedEntries` reports how many rows were removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxIdempotencyFlushResponse"},"example":{"organizationId":"org_sbx_4f8a1c2d3e7b9a5c6d8e0f1a2b3c","deletedEntries":27}}}},"400":{"description":"Invalid request payload (e.g. unparsable `by` duration, unknown `scope`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Endpoint only callable with a test-mode (`flw_test_…`) API key — live keys are rejected with code `SANDBOX_ONLY`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request body failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Unexpected server error while mutating sandbox state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/requests":{"get":{"tags":["Requests"],"summary":"List captured request logs for your organization","description":"List captured request logs for your organization (newest first).\n\nCaptures every mutation (POST/PUT/PATCH/DELETE) and every error; successful\nGETs only when ``REQUEST_LOG_ALL`` is enabled server-side. Filter by ``apiKeyId``\nor ``userId`` to see all queries done by a given key / user. Returns\nlightweight summaries — fetch a single ``request_id`` for the full\nrequest/response.\n\n**Example request:**\n```\nGET /v1/requests?method=POST&status=502&limit=20\nAuthorization: Bearer flw_live_…\n```\n\n**Example response (200):**\n```json\n{\n  \"data\": [\n    {\n      \"requestId\": \"019a6e5b-3b4c-7000-a460-a09181acc257\",\n      \"organizationId\": \"019c76b2-9c94-7000-8cb6-ef104afb6093\",\n      \"apiKeyId\": \"ak_01H8YQXM2V7K5N3R4T6W8Z1B9C\",\n      \"userId\": null,\n      \"userEmail\": null,\n      \"method\": \"POST\",\n      \"path\": \"/exchange/v1/documents/send\",\n      \"statusCode\": 502,\n      \"durationMs\": 543,\n      \"createdAt\": \"2026-06-17T08:24:11.214000+00:00\"\n    }\n  ],\n  \"hasMore\": false,\n  \"cursor\": null\n}\n```\n\n**Errors**\n\n| Status | Code                   | When                                                       |\n|--------|------------------------|------------------------------------------------------------|\n| 400    | `invalid_request`      | Unparseable ISO datetime on `since` / `until`.             |\n| 401    | `unauthorized`         | Missing / invalid `Authorization` header or API key.       |\n| 403    | `forbidden`            | Key valid but cross-org access denied.                     |\n| 422    | `validation_error`     | `limit` out of range or wrong type.                        |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.     |\n| 503    | `upstream_unavailable` | Request-log database unavailable.                          |","operationId":"list_requests_v1_requests_get","parameters":[{"name":"method","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Method"},"description":"Filter by HTTP method, for example `POST`."},{"name":"path","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Substring match on the request path","title":"Path"},"description":"Substring match on the request path"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Exact HTTP status code","title":"Status"},"description":"Exact HTTP status code"},{"name":"apiKeyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only queries made by this API key id (for JWT auth this is the OAuth client id)","title":"Apikeyid"},"description":"Only queries made by this API key id (for JWT auth this is the OAuth client id)"},{"name":"userId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only queries made by this user id (JWT auth)","title":"Userid"},"description":"Only queries made by this user id (JWT auth)"},{"name":"organizationId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only queries owned by this organization id","title":"Organizationid"},"description":"Only queries owned by this organization id"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO datetime — logs created at/after","title":"Since"},"description":"ISO datetime — logs created at/after"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO datetime — logs created before","title":"Until"},"description":"ISO datetime — logs created before"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"},"description":"Opaque cursor from the previous page. Paginate with this rather than raising `limit` to avoid paging."},{"name":"unattributed","in":"query","required":false,"schema":{"type":"boolean","description":"Platform operators only: return the calls that belong to NO organization instead of your own. These are requests refused before auth resolved a tenant — an expired or malformed token, a machine-to-machine token naming no org. Requires an allow-listed admin API key; a user token gets 403.","default":false,"title":"Unattributed"},"description":"Platform operators only: return the calls that belong to NO organization instead of your own. These are requests refused before auth resolved a tenant — an expired or malformed token, a machine-to-machine token naming no org. Requires an allow-listed admin API key; a user token gets 403."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Page of recent request-log summaries (newest first).","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Requests V1 Requests Get"},"example":{"data":[{"requestId":"019a6e5b-3b4c-7000-a460-a09181acc257","organizationId":"019c76b2-9c94-7000-8cb6-ef104afb6093","apiKeyId":"ak_01H8YQXM2V7K5N3R4T6W8Z1B9C","method":"POST","path":"/exchange/v1/documents/send","statusCode":502,"durationMs":543,"createdAt":"2026-06-17T08:24:11.214000+00:00"},{"requestId":"019a6e5b-7f12-7000-b820-91c2ad0918aa","organizationId":"019c76b2-9c94-7000-8cb6-ef104afb6093","apiKeyId":"jwt","userId":"usr_01H8YQXM2V7K5N3R4T6W8Z1A2B","userEmail":"yann@flowie.fr","method":"PATCH","path":"/exchange/v1/documents/doc_01H8…/lifecycle","statusCode":200,"durationMs":87,"createdAt":"2026-06-17T08:23:55.001000+00:00"}],"hasMore":false,"total":2}}}},"400":{"description":"Bad query parameter (e.g. unparseable ISO datetime on `since`/`until`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but the targeted log belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Query parameters failed schema validation (e.g. `limit` out of range).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Request log database unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/requests/summary":{"get":{"tags":["Requests"],"summary":"Per-API-key (or per-user) usage rollup","description":"Per-API-key (or per-user) usage rollup for your organization.\n\nPowers an \"API activity\" view: who called the API, how many times, how many\nerrors, and when they were last seen — without paging through every log.\nUse ``by=user`` to group by JWT user (and surface their email) instead of\nby API-key id.\n\nGrouped by API key it also reports rate-limit pressure:\n``throttledRequests`` (429s — exact, since errors are always captured),\n``peakRequestsPerMinute`` (the busiest minute in the window — a *lower\nbound* unless the service runs with ``REQUEST_LOG_ALL``, because successful\nGETs spend budget without being logged) and ``rateLimitPerMinute`` /\n``rateLimitSource``, the budget that key is actually allowed and where it\ncame from (``tier``, ``key``, ``org-override``, ``client-override``).\n\n**Example request:**\n```\nGET /v1/requests/summary?by=user&since=2026-06-01T00:00:00Z\nAuthorization: Bearer flw_live_…\n```\n\n**Example response (200, grouped by API key):**\n```json\n{\n  \"by\": \"apiKey\",\n  \"data\": [\n    {\n      \"apiKeyId\": \"ak_01H8YQXM2V7K5N3R4T6W8Z1B9C\",\n      \"totalRequests\": 4218,\n      \"errorRequests\": 17,\n      \"throttledRequests\": 7,\n      \"peakRequestsPerMinute\": 412,\n      \"rateLimitPerMinute\": 600,\n      \"rateLimitSource\": \"tier\",\n      \"lastRequestAt\": \"2026-06-17T08:24:11.214000+00:00\"\n    }\n  ]\n}\n```\n\n**Example response (200, grouped by user):**\n```json\n{\n  \"by\": \"user\",\n  \"data\": [\n    {\n      \"userId\": \"usr_01H8YQXM2V7K5N3R4T6W8Z1A2B\",\n      \"userEmail\": \"yann@flowie.fr\",\n      \"totalRequests\": 311,\n      \"errorRequests\": 2,\n      \"lastRequestAt\": \"2026-06-17T08:23:55.001000+00:00\"\n    }\n  ]\n}\n```\n\n**Errors**\n\n| Status | Code                   | When                                                       |\n|--------|------------------------|------------------------------------------------------------|\n| 400    | `invalid_request`      | Unparseable ISO datetime on `since` / `until`.             |\n| 401    | `unauthorized`         | Missing / invalid `Authorization` header or API key.       |\n| 403    | `forbidden`            | Key valid but lacks access to organization logs.           |\n| 422    | `validation_error`     | `by` not one of `apiKey`/`user`.                           |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.     |\n| 503    | `upstream_unavailable` | Request-log database unavailable.                          |","operationId":"usage_summary_v1_requests_summary_get","parameters":[{"name":"by","in":"query","required":false,"schema":{"type":"string","description":"Group by 'apiKey', 'user', or 'org'","default":"apiKey","title":"By"},"description":"Group by 'apiKey', 'user', or 'org'"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO datetime — logs created at/after","title":"Since"},"description":"ISO datetime — logs created at/after"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO datetime — logs created before","title":"Until"},"description":"ISO datetime — logs created before"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Aggregated counts per API key (or per user) for the requested window.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Usage Summary V1 Requests Summary Get"},"example":{"by":"apiKey","data":[{"apiKeyId":"ak_01H8YQXM2V7K5N3R4T6W8Z1B9C","totalRequests":4218,"errorRequests":17,"throttledRequests":7,"peakRequestsPerMinute":412,"rateLimitPerMinute":600,"rateLimitSource":"tier","lastRequestAt":"2026-06-17T08:24:11.214000+00:00"},{"apiKeyId":"jwt","totalRequests":311,"errorRequests":2,"throttledRequests":0,"peakRequestsPerMinute":44,"lastRequestAt":"2026-06-17T08:23:55.001000+00:00"}]}}}},"400":{"description":"Bad query parameter (e.g. unparseable ISO datetime on `since`/`until`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but the targeted log belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Query parameters failed schema validation (e.g. `limit` out of range).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Request log database unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/requests/timeseries":{"get":{"tags":["Requests"],"summary":"Request volume over time","description":"Request volume bucketed over time, so a spike has a shape instead of a number.\n\nThe rollup at `/summary` answers \"how much, in total\". This answers \"when\" —\nthe series a chart draws against the caller's per-minute budget, with the\nerrors and the limiter's own refusals broken out.\n\nLike every count derived from the request log, `total` is a **lower bound**\nunless the service runs with `REQUEST_LOG_ALL`: successful GETs are not\ncaptured, though they do spend the rate-limit budget. `errors` and\n`throttled` are exact, because every 4xx/5xx is captured. For the true\nrequest rate including successful reads, use the Prometheus counter\n`exchange_rate_limit_requests_total`, which is recorded in the limiter itself.\n\n**Errors**\n\n| Status | Code                   | When                                                       |\n|--------|------------------------|------------------------------------------------------------|\n| 400    | `invalid_request`      | Unparseable ISO datetime, or an unknown `bucket`.          |\n| 401    | `unauthorized`         | Missing / invalid `Authorization` header or API key.       |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.     |\n| 503    | `upstream_unavailable` | Request-log database unavailable.                          |","operationId":"requests_timeseries_v1_requests_timeseries_get","parameters":[{"name":"bucket","in":"query","required":false,"schema":{"type":"string","description":"Bucket width: 'minute' or 'hour'","default":"minute","title":"Bucket"},"description":"Bucket width: 'minute' or 'hour'"},{"name":"apiKeyId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only this API key / client id","title":"Apikeyid"},"description":"Only this API key / client id"},{"name":"organizationId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only this organization","title":"Organizationid"},"description":"Only this organization"},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO datetime — at/after","title":"Since"},"description":"ISO datetime — at/after"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO datetime — before","title":"Until"},"description":"ISO datetime — before"},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Per-bucket counts over the requested window.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Requests Timeseries V1 Requests Timeseries Get"},"example":{"bucket":"minute","data":[{"ts":"2026-08-28 21:14","total":96,"errors":2,"throttled":0,"p95DurationMs":210},{"ts":"2026-08-28 21:15","total":412,"errors":31,"throttled":29,"p95DurationMs":940}]}}}},"400":{"description":"Bad query parameter (e.g. unparseable ISO datetime on `since`/`until`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but the targeted log belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Query parameters failed schema validation (e.g. `limit` out of range).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Request log database unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/requests/{request_id}":{"get":{"tags":["Requests"],"summary":"Fetch a single captured request/response envelope by X-Request-Id","description":"Return a captured 4xx/5xx request envelope.\n\nAll secrets are redacted at capture time, so the body returned here is\nalways safe to share — paste a ``requestId`` into a Slack channel or\nSentry comment and your team can pull the same view. In sandbox mode an\nunknown ``request_id`` returns a synthesised envelope (``sandbox: true``)\nso the contract can be exercised without first triggering a real failure.\n\n**Example request:**\n```\nGET /v1/requests/019a6e5b-3b4c-7000-a460-a09181acc257\nAuthorization: Bearer flw_live_…\n```\n\n**Example response (200):**\n```json\n{\n  \"requestId\": \"019a6e5b-3b4c-7000-a460-a09181acc257\",\n  \"organizationId\": \"019c76b2-9c94-7000-8cb6-ef104afb6093\",\n  \"apiKeyId\": \"ak_01H8YQXM2V7K5N3R4T6W8Z1B9C\",\n  \"method\": \"POST\",\n  \"path\": \"/exchange/v1/documents/send\",\n  \"queryString\": null,\n  \"request\": {\n    \"headers\": {\n      \"content-type\": \"application/json\",\n      \"authorization\": \"Bearer flw_live_…(redacted)\"\n    },\n    \"body\": {\"type\": \"invoice\", \"from\": \"vat:FR26921376265\", \"to\": \"0208:0123456789\"}\n  },\n  \"response\": {\n    \"statusCode\": 502,\n    \"headers\": {\"content-type\": \"application/json\"},\n    \"body\": {\"type\": \"upstream_error\", \"code\": \"upstream_error\",\n             \"message\": \"Upstream Peppol AP refused the document\",\n             \"requestId\": \"019a6e5b-3b4c-7000-a460-a09181acc257\"}\n  },\n  \"durationMs\": 543,\n  \"userAgent\": \"flowie-python/1.4.2\",\n  \"clientIp\": \"203.0.113.42\",\n  \"createdAt\": \"2026-06-17T08:24:11.214000+00:00\",\n  \"viewerUrl\": \"https://docs.get-flowie.com/playground/requests.html?id=019a6e5b-…\"\n}\n```\n\n**Example response (404):**\n```json\n{\n  \"type\": \"not_found\",\n  \"code\": \"not_found\",\n  \"message\": \"No request log for 019a6e5b-3b4c-7000-a460-a09181acc257\",\n  \"requestId\": \"019a6e5b-7f12-7000-b820-91c2ad0918aa\"\n}\n```\n\n**Errors**\n\n| Status | Code                   | When                                                                  |\n|--------|------------------------|-----------------------------------------------------------------------|\n| 401    | `unauthorized`         | Missing / invalid `Authorization` header or API key.                  |\n| 403    | `forbidden`            | The log exists but belongs to a different organization.               |\n| 404    | `not_found`            | No request log captured for this id (non-sandbox keys only).          |\n| 429    | `rate_limited`         | Too many requests — retry after `Retry-After` seconds.                |\n| 503    | `upstream_unavailable` | Request-log database unavailable.                                     |","operationId":"get_request_v1_requests__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"},"description":"Identifier of the request, as returned in the `X-Request-Id` response header."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Full captured envelope, with secrets redacted at capture time.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Request V1 Requests  Request Id  Get"},"example":{"requestId":"019a6e5b-3b4c-7000-a460-a09181acc257","organizationId":"019c76b2-9c94-7000-8cb6-ef104afb6093","apiKeyId":"ak_01H8YQXM2V7K5N3R4T6W8Z1B9C","method":"POST","path":"/exchange/v1/documents/send","request":{"headers":{"content-type":"application/json","authorization":"Bearer flw_live_…(redacted)","x-request-id":"019a6e5b-3b4c-7000-a460-a09181acc257"},"body":{"type":"invoice","from":"vat:FR26921376265","to":"0208:0123456789","documentNumber":"INV-2026-00042"}},"response":{"statusCode":502,"headers":{"content-type":"application/json"},"body":{"type":"upstream_error","code":"upstream_error","message":"Upstream Peppol AP refused the document","requestId":"019a6e5b-3b4c-7000-a460-a09181acc257"}},"durationMs":543,"userAgent":"flowie-python/1.4.2","clientIp":"203.0.113.42","createdAt":"2026-06-17T08:24:11.214000+00:00","viewerUrl":"https://docs.get-flowie.com/playground/requests.html?id=019a6e5b-3b4c-7000-a460-a09181acc257"}}}},"400":{"description":"Bad query parameter (e.g. unparseable ISO datetime on `since`/`until`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"API key is valid but the targeted log belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Query parameters failed schema validation (e.g. `limit` out of range).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Request log database unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No request log captured for this `request_id` (and not in sandbox mode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/tools/fr/ubl/scenarios":{"get":{"tags":["UBL Generator"],"summary":"List every French e-invoice business case, described in business terms","description":"The complete referential — every case, with its business narrative.\n\nNo API key needed: this is a catalogue, not your data. Start here, pick a\nscenario id, then call `POST /v1/tools/fr/ubl/generate` with it.","operationId":"list_scenarios_v1_tools_fr_ubl_scenarios_get","parameters":[{"name":"theme","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by theme, e.g. `Acompte & paiement échelonné`.","title":"Theme"},"description":"Filter by theme, e.g. `Acompte & paiement échelonné`."},{"name":"family","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`data`, `third-party`, `lifecycle` or `foundation`.","title":"Family"},"description":"`data`, `third-party`, `lifecycle` or `foundation`."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`e-invoicing` or `e-reporting`.","title":"Channel"},"description":"`e-invoicing` or `e-reporting`."},{"name":"case","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"XP Z12-014 case number, e.g. `20` or `19b`.","title":"Case"},"description":"XP Z12-014 case number, e.g. `20` or `19b`."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search across the title, the story and the French case title.","title":"Q"},"description":"Free-text search across the title, the story and the French case title."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScenarioListResponse"}}}},"400":{"description":"Request body failed schema validation (`INVALID_REQUEST`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown scenario id — the response lists the closest matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tools/fr/ubl/scenarios/{scenario_id}":{"get":{"tags":["UBL Generator"],"summary":"Read one business case in full","description":"The story, the rule that makes it its own case, the trap, and the BT\nfields that carry it — without generating anything.","operationId":"get_scenario_v1_tools_fr_ubl_scenarios__scenario_id__get","parameters":[{"name":"scenario_id","in":"path","required":true,"schema":{"type":"string","title":"Scenario Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScenarioOut"}}}},"400":{"description":"Request body failed schema validation (`INVALID_REQUEST`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown scenario id — the response lists the closest matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tools/fr/ubl/business-terms":{"get":{"tags":["UBL Generator"],"summary":"List every EN 16931 business term, with what France requires of it","description":"The whole semantic model on one list — 30 groups, 164 terms.\n\nEvery other surface names business terms one at a time. This is the list\nthey all point at: what each BT is, where it lives in UBL 2.1, whether\nFrance makes it mandatory (and under which `BR-FR-*` rule), and which\nfield of `POST /v1/documents/send` carries it.\n\nNo API key needed: this is a referential, not your data.","operationId":"list_business_terms_v1_tools_fr_ubl_business_terms_get","parameters":[{"name":"group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one business group, e.g. `BG-23`.","title":"Group"},"description":"Filter to one business group, e.g. `BG-23`."},{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`document` or `line`.","title":"Scope"},"description":"`document` or `line`."},{"name":"fr","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`mandatory`, `conditional`, `restricted`, `optional` or `unused`.","title":"Fr"},"description":"`mandatory`, `conditional`, `restricted`, `optional` or `unused`."},{"name":"mapped","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true` for the terms `POST /v1/documents/send` has a field for.","title":"Mapped"},"description":"`true` for the terms `POST /v1/documents/send` has a field for."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text search across the id, both names, the UBL path and the French note.","title":"Q"},"description":"Free-text search across the id, both names, the UBL path and the French note."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessTermListResponse"}}}},"400":{"description":"Request body failed schema validation (`INVALID_REQUEST`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown scenario id — the response lists the closest matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tools/fr/ubl/business-terms/{term_id}":{"get":{"tags":["UBL Generator"],"summary":"Read one business term","description":"One BT or BG by id — `BT-121`, `BG-23`, `BT-29-1`.","operationId":"get_business_term_v1_tools_fr_ubl_business_terms__term_id__get","parameters":[{"name":"term_id","in":"path","required":true,"schema":{"type":"string","title":"Term Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessTermOut"}}}},"400":{"description":"Request body failed schema validation (`INVALID_REQUEST`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown scenario id — the response lists the closest matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tools/fr/ubl/generate":{"post":{"tags":["UBL Generator"],"summary":"Generate the UBL for a business case, with your own parties if you like","description":"Render one scenario as EN 16931 UBL 2.1, plus the send call for it.\n\nOverride the invoice number, the dates, the currency and either party to\nmake the sample look like your own data. The identifier *schemes* stay as\nthe catalogue set them, so an override cannot produce a party whose SIRET\nand SIREN disagree (BR-FR-09).\n\nNo API key needed — the response is a pure function of the request.","operationId":"generate_v1_tools_fr_ubl_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRequest"}}},"required":true,"description":"`GenerateRequest` — payload to generate the UBL for a business case, with your own parties if you like."},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"400":{"description":"Request body failed schema validation (`INVALID_REQUEST`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown scenario id — the response lists the closest matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tools/fr/ubl/simple":{"post":{"tags":["UBL Generator"],"summary":"Generate a compliant French invoice from your own data — no scenario id","description":"Your parties, your lines. The reform's part is inferred and explained.\n\nThe catalogue answers \"what does *this* business case look like?\". This\nanswers the question people ask first — \"here is my data, make it legal\" —\nwhich nine out of ten French invoices need and no *cas d'usage* covers,\nbecause they are just a company billing another company.\n\nFrom the line kinds and the VAT regime it derives the type code (BT-3), the\n*cadre de facturation* (BT-23), the tax point (BT-8), the VAT category with\nthe exemption reason EN 16931 demands, the three legal mentions of\nBR-FR-05 and the BAR regime note. Each one comes back in `inferred` with\nthe rule that forced it, so you can see what was added on your behalf\nrather than trusting it.\n\nTwo fields cannot be guessed and are asked for: each party's **SIRET**,\nbecause the routing address and the legal identifier both derive from it,\nand the seller's **legal form and share capital** (BT-33), which France\nmakes mandatory and nobody else knows.\n\nNo API key needed — the response is a pure function of the request.","operationId":"generate_simple_v1_tools_fr_ubl_simple_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleGenerateRequest"}}},"required":true,"description":"`SimpleGenerateRequest` — payload to generate a compliant French invoice from your own data — no scenario id."},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleGenerateResponse"}}}},"400":{"description":"Request body failed schema validation (`INVALID_REQUEST`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown scenario id — the response lists the closest matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tools/fr/ubl/generate-and-validate":{"post":{"tags":["UBL Generator"],"summary":"Generate the UBL and run it past the official French schematrons","description":"Generate a case and prove it: XSD, then the EN 16931 profile\nschematron, then the French BR-FR rules, then the complementary field\nchecks the CIUS-FR adds on top.\n\nUse it as the reference answer when your own ERP's output for the same\ncase is rejected: generate the scenario, validate both, and diff.\n\nOne scenario is expected to come back **invalid** — the B2C restaurant\nreceipt (`uc-28-restaurant-receipts`). A consumer has no SIREN and no\nrouting address, so a B2C receipt cannot be an e-invoice; it belongs to\ne-reporting. Its `scenario.validatesAsEInvoice` is `false` to say so.","operationId":"generate_and_validate_v1_tools_fr_ubl_generate_and_validate_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateAndValidateRequest"}}},"description":"`GenerateAndValidateRequest` — payload to generate the UBL and run it past the official French schematrons."},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateAndValidateResponse"}}}},"400":{"description":"Request body failed schema validation (`INVALID_REQUEST`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown scenario id — the response lists the closest matches.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded — see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"The validator service is unreachable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/messages":{"post":{"tags":["Portability"],"summary":"Send a normalised inter-PA portability message (subject + 18-field CSV)","description":"Assemble the AIFE inter-PA message, email it to the counterparty PA, and log it.\n\n**Addressing.** Pass `to` explicitly, or name the counterparty (`losingPaName`\n/ `losingPaId`, or `gainingPa*` when you are the losing PA) and let the\nserver resolve it against the registry of registered Plateformes Agréées —\nthe platform's dedicated portability address when it published one, its\nDGFiP `courriel de contact` otherwise. The response's `recipientSource`\nrecords which of those happened.\n\n**Dispatch** is gated by `PORTABILITY_DISPATCH_ENABLED` (default OFF) and by\nthe SMTP configuration, and never happens for a sandbox key. When it does\nnot happen the message is still built and logged, with `reason` explaining\nwhy — so a dry environment produces exactly the same audit trail minus the\nemail.\n\n**Following it.** The response carries the log `id`; list them at\n`GET /v1/portability/messages` or fetch one back with its CSV and hash at\n`GET /v1/portability/messages/{id}`.\n\n**Wire format is a best guess** pending the AIFE 15/07 annex; the exact\nsubject grammar, CSV columns and status codes are centralised in\n`app/services/portability/channel.py`.","operationId":"send_portability_message_v1_portability_messages_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityMessageRequest"}}},"description":"`PortabilityMessageRequest` — payload to send a normalised inter-PA portability message (subject + 18-field CSV)."},"responses":{"200":{"description":"Message built and logged. `dispatched` says whether it was emailed; `reason` says why not.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityMessageResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Portability"],"summary":"List the inter-PA portability messages sent from your organization","description":"Every inter-PA message your organization has produced, newest first.\n\nThis is the follow-up surface for an open port: filter by `requestRef` to\nreplay one exchange end to end, or by `dispatched=false` to find the\nmessages that never left (relay down, no recipient resolved, kill-switch\noff) and need re-sending.","operationId":"list_portability_messages_v1_portability_messages_get","parameters":[{"name":"requestRef","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact portability request reference.","title":"Requestref"},"description":"Exact portability request reference."},{"name":"siren","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Taxpayer SIREN (9 digits).","title":"Siren"},"description":"Taxpayer SIREN (9 digits)."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Internal FSM state (`received`, `accepted`, …).","title":"State"},"description":"Internal FSM state (`received`, `accepted`, …)."},{"name":"messageType","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`REQUEST` / `ACK` / `DECISION` / `COMPLETION`.","title":"Messagetype"},"description":"`REQUEST` / `ACK` / `DECISION` / `COMPLETION`."},{"name":"dispatched","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only messages that were (or were not) emailed.","title":"Dispatched"},"description":"Only messages that were (or were not) emailed."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"},"description":"Opaque cursor from the previous page. Paginate with this rather than raising `limit` to avoid paging."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Logged messages, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityMessageListResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/messages/{message_id}":{"get":{"tags":["Portability"],"summary":"Fetch one logged portability message (CSV, hash, annuaire snapshot)","description":"The proof bundle for one message: the exact CSV row that left, its\nSHA-256, the recipient and how it was resolved, the SMTP message id, and the\nPPF annuaire's answer for the taxpayer at the moment we sent.","operationId":"get_portability_message_v1_portability_messages__message_id__get","parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","title":"Message Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityMessageLog"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The message belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No portability message with this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/companies":{"get":{"tags":["Portability"],"summary":"Suggest companies by name, so nobody has to look up their own tax ID","description":"Type a name, pick the company.\n\nAsking a taxpayer for its SIRET sends it hunting for a number; asking what\nit is called does not. This turns a name fragment into a short list of real\ncompanies with their SIREN, ready to pass to\n[`POST /v1/portability/resolve`](#resolve-portability-taxpayer).\n\nSuggestions are re-ranked before they are returned — the directory answers\nin SIREN order, which puts *CARREFOUR GIGNAC* above the company anyone\ntyping \"carrefour\" means. Exact names come first, then names starting with\nwhat was typed, shortest first.\n\nA query that already **is** an identifier (SIREN, SIRET, VAT number, Peppol\nid) short-circuits to a direct lookup and returns that one company;\n`match` says which happened. Fewer than three characters, or a directory\nthat does not answer, returns an empty list with `match` explaining why —\nnever an error, because a suggestion is a shortcut, not a dependency.\n\n**Coverage.** Two registers answer, in that order: the national company\nregister where one is connected — France's *annuaire* today — and the\nPeppol Directory, which spans every country on the network. So a name typed\nin Brussels or Milan finds its company, and an identifier (VAT number,\nPeppol id) resolves on either. A company registered nowhere is reachable by\nidentifier alone, which is why a form should accept both.\n\nThe network's own search matches across every indexed field, so a name query\nis filtered back down to hits on the *name*: \"Carrefour\" otherwise returns\na company in *rue du Carrefour*.\n\n**Which directory answered.** Both registers have a test twin, and staging\nreads both — the PPF sandbox and the Peppol test directory. They do not\ncarry real companies, so the same query that finds a company on production\nfinds nothing there. `directory` says which dataset produced the answer, and\na `sandbox` answer is never `exhaustive`: an empty list from a test register\nis a fact about the register, not about the company.","operationId":"suggest_companies_v1_portability_companies_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"A company name, or an identifier you already have (SIREN, SIRET, VAT, Peppol id).","title":"Q"},"description":"A company name, or an identifier you already have (SIREN, SIRET, VAT, Peppol id)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10,"minimum":1,"default":10,"title":"Limit"},"description":"Maximum number of items to return in one page."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Ranked suggestions, or the single company an identifier resolves to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanySuggestListResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/platforms":{"get":{"tags":["Portability"],"summary":"List the registered Plateformes Agréées and their portability contacts","description":"The address book behind `POST /v1/portability/messages`, and the\nmatricule directory behind `GET /v1/portability/annuaire/{siren}`.\n\nMerges the DGFiP official lists (commercial name, website, `courriel de\ncontact`, immatriculation date — both the fully registered operators and\nthose still awaiting the interoperability tests) with the dedicated\nportability addresses platforms circulated between themselves. `contactEmail`\nis what a port request would actually be sent to.\n\n**Matricules.** The DGFiP publishes who the platforms are but never their\nfour-digit numbers, which is the only thing the PPF annuaire speaks. Each\nrow carries the number we could prove plus how we proved it —\n`matriculeConfidence` `confirmed` (the operator told us directly),\n`published` (the operator says so, with the URL and the sentence) or\n`inferred` (its own SIREN routes on it in the annuaire).\n`null` means unknown, and unknown is published as unknown: a wrong\nmatricule routes invoices to the wrong platform.\n\n**This endpoint is the only public way to the numbers, and it\nauthenticates you.** Operators have told us the DGFiP treats the matricule\nas data exchanged between PAs, so the docs page shows it only to a signed-in\nreader and the static directory asset no longer carries it.\n\nSnapshot date is returned with the payload; the list moves weekly.","operationId":"list_platforms_v1_portability_platforms_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Free-text match on name, email or website. A bare 3-4 digit value is read as a matricule.","title":"Q"},"description":"Free-text match on name, email or website. A bare 3-4 digit value is read as a matricule."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`registered` or `pending_interop`.","title":"Status"},"description":"`registered` or `pending_interop`."},{"name":"matricule","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Resolve one PA by its four-digit `matriculePlateforme`, e.g. `0003`.","title":"Matricule"},"description":"Resolve one PA by its four-digit `matriculePlateforme`, e.g. `0003`."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Every PA the DGFiP has registered, with the address a port request should go to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformListResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/access-point/{participant_id}":{"get":{"tags":["Portability"],"summary":"Who routes this Peppol participant — the access point, named","description":"The international twin of `GET /v1/portability/annuaire/{siren}`.\n\nFrance publishes a closed registry, so there the answer is a four-digit\nmatricule joined to a name. Peppol has no registry: the answer is resolved\nlive, per participant — SML (DNS NAPTR) names the SMP, the SMP lists the\ndocument types, and the endpoint certificate names the access point. `apName`\nis the platform that routes this participant today.\n\nPass the participant as `scheme:value` (`0208:0848934496`); the\n`iso6523-actorid-upis::` prefix is accepted and stripped. Results are cached\nbriefly, and nothing here ever raises on a third-party SMP being down — read\n`error` before trusting an empty answer.","operationId":"get_access_point_v1_portability_access_point__participant_id__get","parameters":[{"name":"participant_id","in":"path","required":true,"schema":{"type":"string","title":"Participant Id"}},{"name":"zone","in":"query","required":false,"schema":{"enum":["production","test","production-legacy","test-legacy"],"type":"string","description":"Which SML to ask. `production` and `test` are the live OpenPeppol zones; the `-legacy` pair are the retired eDelivery ones, kept so a caller pinned to them gets a clear answer rather than a bare NXDOMAIN. Only these four are accepted — an arbitrary hostname here would be an arbitrary DNS lookup.","default":"production","title":"Zone"},"description":"Which SML to ask. `production` and `test` are the live OpenPeppol zones; the `-legacy` pair are the retired eDelivery ones, kept so a caller pinned to them gets a clear answer rather than a bare NXDOMAIN. Only these four are accepted — an arbitrary hostname here would be an arbitrary DNS lookup."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"The resolved access point. A participant nobody routes answers 200 with `error` set, not 404 — 'not registered' is an answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessPointResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/annuaire/{siren}":{"get":{"tags":["Portability"],"summary":"What the PPF annuaire says about a taxpayer's routing","description":"Who routes this taxpayer today, since when, and until when.\n\nThe annuaire answers with the taxpayer's *lignes d'adressage*; this returns\nthe one that decides where invoices go (a SIRET-level declaration beats the\nSIREN-level one, a real platform beats the PPF default) plus the raw lines\nas evidence.\n\nRead it **before** a port to see which platform you are porting away from\n(`currentPaMatricule`; `9998` means nobody has been declared and there may be\nnothing to port), and **after** to see whether the switch has propagated\n(`isFlowie`) and on which date the directory thinks it starts. An\n`effectiveTo` already set means a departure is scheduled.\n\n`directory` names the dataset behind the answer — `production` for the real\nPPF directory, `sandbox` for the PISTE test one that staging reads. Read\n`declared` against it: in the sandbox a taxpayer who is really routed by a\nplatform comes back with no lignes at all, so `declared: false` from there\nmeans \"not in the test directory\", never \"not declared\".","operationId":"get_annuaire_routing_v1_portability_annuaire__siren__get","parameters":[{"name":"siren","in":"path","required":true,"schema":{"type":"string","title":"Siren"}},{"name":"siret","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Narrow to one establishment's line when the taxpayer has several.","title":"Siret"},"description":"Narrow to one establishment's line when the taxpayer has several."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnuaireRoutingResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The annuaire is not configured, or did not answer for this SIREN.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/routing":{"post":{"tags":["Portability"],"summary":"Switch the taxpayer's routing at the date d'effet","description":"Declare — or wind down — the taxpayer's French routing address.\n\nThis is the write side of a port. The PPF *annuaire* has no API to write a\nport into; what routes invoices is the e-invoicing address on the\ncompliance backend, so that is what this switches, and it carries the two\ndates the reform cares about:\n\n- `role: \"GAINING_PA\"` declares `siren` / `siret` with **validFrom = the\n  date d'effet** — a port agreed in August for 1 October does not start\n  pulling invoices in August.\n- `role: \"LOSING_PA\"` does not delete anything: emission stops on the date\n  d'effet while reception stays open until `effectiveDate +\n  minimalServiceMonths` (12 by default, per LFI 2026), so flows already in\n  flight still resolve.\n\nIdempotent on the SIREN: an address already declared for it is updated\nrather than duplicated. Pass `connectionId` when the organization has more\nthan one connection — picking one for you is how a port lands on the wrong\ncompany.","operationId":"switch_portability_routing_v1_portability_routing_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityRoutingRequest"}}},"description":"`PortabilityRoutingRequest` — payload to switch the taxpayer's routing at the date d'effet."},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityRoutingResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such Sovos connection on this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"No connection to route on, several connections and no `connectionId`, or nothing to wind down.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"The compliance backend is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/messages/parse":{"post":{"tags":["Portability"],"summary":"Parse an inbound inter-PA portability message","description":"Parse a received inter-PA message back into structured fields.\n\nValidates the normalised subject grammar and, when a `csvRow` is supplied,\nthe 18-column CSV payload. A subject that does not match the grammar returns\n400 (the caller should dead-letter it rather than open a request).","operationId":"parse_portability_message_v1_portability_messages_parse_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityParseRequest"}}},"description":"`PortabilityParseRequest` — payload to parse an inbound inter-PA portability message."},"responses":{"200":{"description":"Subject (and optional CSV row) parsed into structured fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityParseResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/resolve":{"post":{"tags":["Portability"],"summary":"Resolve a taxpayer from one identifier, and say what its switch requires","description":"Turn one string into everything a migration request needs.\n\nAccepts a SIRET, a SIREN, a VAT number, a Peppol id, a national\nregistration number, a domain, an e-mail or `name:<company>`, and answers\nwith the legal name, country, identifiers, the French annuaire addressing\nline where there is one — plus what changes in that country and what the\ntaxpayer must re-grant.\n\nThis is what makes the request form a single field: the company types its\nSIRET, everything else is filled in from our own records and the registries\nwe already query. Whatever cannot be resolved is listed in `stillNeeded`\nrather than guessed.","operationId":"resolve_portability_taxpayer_v1_portability_resolve_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityResolveRequest"}}},"description":"`PortabilityResolveRequest` — payload to resolve a taxpayer from one identifier, and say what its switch requires."},"responses":{"200":{"description":"Resolved identity plus the regime rules that govern the switch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityResolveResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No layer could resolve the identifier.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/requests":{"post":{"tags":["Portability"],"summary":"Open a migration request from one identifier","description":"Open a request, resolving everything but the identifier.\n\nProduces the **designation agreement** the decree requires (art. 242 nonies\nE bis: the taxpayer, the incoming platform, the previous one, the effective\ndate and the scope of addresses), numbers it, and starts a hash-linked\nevidence chain — so an outgoing platform that later disputes the port is\nanswered with a signed, timestamped, verifiable record rather than an\nargument.\n\nDeadlines are computed in *jours ouvrés* including *jours fériés*: `notifyBy`\n(D+2), `objectionWindowEnds` (5 days), `annuaireUpdateBy` (15 days from\nexpress or tacit agreement), `continuityUntil` (1 year).\n\nA missing signatory does not fail the call — it comes back in\n`mandate.gaps`, because a request that names the gap is more useful than a\n422 that hides it.","operationId":"open_portability_request_v1_portability_requests_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityOpenRequest"}}},"description":"`PortabilityOpenRequest` — payload to open a migration request from one identifier."},"responses":{"201":{"description":"Request opened. Carries the agreement number, the computed clocks and the first evidence entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityRequestResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No layer could resolve the taxpayer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/requests/{request_ref}":{"get":{"tags":["Portability"],"summary":"Read a migration request: state, clocks and proof","description":"Re-derive a request's state from its evidence chain.\n\nState is never stored, always folded from the chain, so this call is the\nsingle truth about where a port stands. `tacitApproval` flips to true by\nitself once the objection window lapses with no admissible objection — no\nscheduler required, and no way for silence to read as refusal.","operationId":"get_portability_request_v1_portability_requests__request_ref__get","parameters":[{"name":"request_ref","in":"path","required":true,"schema":{"type":"string","title":"Request Ref"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"Current state, re-derived from the evidence chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityRequestResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such request for this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/requests/{request_ref}/mandate/signature":{"post":{"tags":["Portability"],"summary":"Ask someone to sign the designation agreement, in Flowie","description":"Put the designation agreement in front of someone, and record that you did.\n\nThe agreement is rendered from the mandate this request already holds — the\nfive items article 242 nonies E bis requires — and an **approval check** is\nopened on it for the users you name. They sign by deciding in Flowie; what\nis stored is their decision, its instant and its author, none of which this\nAPI supplies.\n\n**What binds the decision to this agreement** is the context key,\n`portability:<requestRef>:<mandateSha256>`. An approval vote is scoped to\nthe *object*, so without it a decision taken on the same document for\nanother reason would read as a signature here. Change the mandate — a new\n*date d'effet*, a different address scope — and the digest changes with it,\nso an earlier signature no longer answers for the new agreement. That is\ndeliberate: it is not the agreement that was signed.\n\nThe check hangs on a **document**, never on the request: approval's\n`ObjectType` is a closed enum with no portability member. Pass `objectId` to\nuse a document you already hold, or leave it out and the rendered agreement\nis stored for you.\n\n`409` when the mandate is still missing a decree item — `taxpayerId`,\n`incomingPlatform`, `effectiveDate` or `addressScope`. A request can be\nopened on an identifier alone, and rendering an agreement with blanks where\nthe decree wants values, then asking someone to sign it, would be the very\ndefect this endpoint exists to remove.\n\nRead it back with `GET /v1/portability/requests/{ref}/mandate/signature`, or\nrecord a signature taken elsewhere with `signedAt` + `signatureMethod` when\nthe request is opened.","operationId":"request_mandate_signature_v1_portability_requests__request_ref__mandate_signature_post","parameters":[{"name":"request_ref","in":"path","required":true,"schema":{"type":"string","title":"Request Ref"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandateSignatureRequest"}}},"description":"`MandateSignatureRequest` — payload to ask someone to sign the designation agreement, in Flowie."},"responses":{"201":{"description":"The agreement is waiting for a decision.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandateSignatureResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such request for this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The mandate is missing a decree-required item, so there is no complete agreement to sign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"The approval service did not answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Portability"],"summary":"Has the designation agreement been signed?","description":"Read the decision back, and record it on the request the first time it exists.\n\n`signed` is false while the check is open, refused or retracted — only a\npassed check carrying **this** agreement's context key is a signature. When\none is found it is written into the evidence chain, so the request carries\nits `signedAt` from then on without this endpoint being called again.","operationId":"read_mandate_signature_v1_portability_requests__request_ref__mandate_signature_get","parameters":[{"name":"request_ref","in":"path","required":true,"schema":{"type":"string","title":"Request Ref"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"responses":{"200":{"description":"`signed` says whether the decision has been taken.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandateSignatureResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such request, or nobody has been asked to sign it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"The approval service did not answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/requests/{request_ref}/mandate/signature/document":{"post":{"tags":["Portability"],"summary":"Record a signature by uploading the signed agreement","description":"Record a signature taken on your own document, and keep that document.\n\nThe third way to sign, between the two that already existed. Signing in\nFlowie gives an authenticated actor and an observed instant; asserting a\npaper signature with `signedAt` at opening gives neither and keeps nothing.\nThis sits between them honestly: **you** assert who signed and when, exactly\nas for paper, but the artefact is stored and hashed — so what was signed\nstops being a claim.\n\n`agreementSha256` is the digest of **the file you uploaded**, not of the text\nthis API would have rendered. They are different documents, and recording\nours as the one signed would be a statement we cannot support.\n`mandateSha256` still binds to the decree content, so amending the mandate\nafterwards invalidates this signature exactly as it invalidates an in-app\none.\n\nA `signedAt` in the future is refused: an act cannot be dated after the\nmoment it is recorded, and a mandate that claims otherwise is worse than one\nwith a gap. So is a second signature on an agreement that already has one\n(`409`) — the chain folds signatures in order, so accepting it would replace\nthe recorded signatory, instant and document with no trace of the first.","operationId":"upload_mandate_signature_v1_portability_requests__request_ref__mandate_signature_document_post","parameters":[{"name":"request_ref","in":"path","required":true,"schema":{"type":"string","title":"Request Ref"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_mandate_signature_v1_portability_requests__request_ref__mandate_signature_document_post"}}},"description":"`Body_upload_mandate_signature_v1_portability_requests__request_ref__mandate_signature_document_post` — payload to record a signature by uploading the signed agreement."},"responses":{"201":{"description":"The signature is recorded, bound to the uploaded file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandateSignatureResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such request for this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Already signed, or the mandate is missing a decree-required item.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"The file is larger than 10 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"The signed agreement could not be stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/portability/requests/{request_ref}/events":{"post":{"tags":["Portability"],"summary":"Record a step against a migration request (notice, objection, agreement, annuaire)","description":"Append one step to the chain and get the new state back.\n\n**An objection is recorded, then judged.** The decree limits the outgoing\nplatform to grounds that question the taxpayer's *intent* to switch\n(`more_recent_agreement`, `identity_mismatch`, `mandate_invalid`). Any other\nground — an unexpired contract, unpaid invoices, a notice period — is stored\nverbatim and flagged `admissible: false`: the request keeps running, and the\nentry carries the mandate digest so the refusal can be answered with the\nagreement it ignores.","operationId":"record_portability_event_v1_portability_requests__request_ref__events_post","parameters":[{"name":"request_ref","in":"path","required":true,"schema":{"type":"string","title":"Request Ref"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"},"description":"Bearer credential: an API key (`flw_live_…`, `flw_test_…`, `flw_plat_…`, `flw_wl_…`) or a Flowie JWT. Format: `Authorization: Bearer <token>`."},{"name":"X-Flowie-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Flowie-Organization-Id"},"description":"Organization the call acts for. Required when the credential can act for more than one organization; otherwise the caller's own organization is used."},{"name":"Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization-Id"},"description":"Legacy alias of `X-Flowie-Organization-Id`. Prefer the prefixed header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityRequestEvent"}}},"description":"`PortabilityRequestEvent` — payload to record a step against a migration request (notice, objection, agreement, annuaire)."},"responses":{"200":{"description":"Step appended to the evidence chain; state re-derived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortabilityRequestResponse"}}}},"400":{"description":"Malformed message (bad subject grammar, wrong CSV column count, unknown state) or request body that failed schema validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key / bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such request for this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AccessPointResponse":{"properties":{"participantId":{"type":"string","title":"Participantid","description":"The participant, as `scheme:value` (e.g. `0208:0848934496`)."},"smpUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Smpurl","description":"The SMP that serves this participant, from the SML NAPTR record."},"apName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Apname","description":"The access point's legal name, from the endpoint certificate's `O=`. This is the platform that routes them."},"apPeppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Appeppolid","description":"The access point's Peppol id, from the certificate's `CN=` (e.g. `POP000016`)."},"apCountry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Apcountry","description":"The access point's own country, from the certificate's `C=`. Often differs from the participant's — providers serve across borders."},"certificateSubject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Certificatesubject"},"certificateExpiresOn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Certificateexpireson","description":"When the access point's certificate expires (ISO date)."},"endpointUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endpointurl","description":"The AS4 endpoint invoices are delivered to."},"transportProfile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transportprofile"},"documentTypeCount":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Documenttypecount","description":"How many document types the participant has registered."},"smlZone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Smlzone","description":"The SML DNS zone queried."},"resolvedVia":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolvedvia","description":"`naptr` (current scheme) or `cname` (retired scheme, still answered by some participants)."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why nothing was resolved: not registered, SMP unreachable, or certificate unreadable."}},"type":"object","required":["participantId"],"title":"AccessPointResponse","description":"Who routes a Peppol participant — the international twin of the French\nannuaire answer, resolved live rather than read from a registry."},"AckDetail":{"properties":{"level":{"type":"string","enum":["Error","Warning"],"title":"Level"},"item":{"type":"string","title":"Item"},"reasonCode":{"type":"string","title":"Reasoncode"},"reasonMessage":{"type":"string","title":"Reasonmessage"}},"type":"object","required":["level","item","reasonCode","reasonMessage"],"title":"AckDetail","description":"Single acknowledgement detail line (AcknowledgementDetail)."},"AckRequest":{"properties":{"eventIds":{"items":{"type":"string"},"type":"array","title":"Eventids"}},"type":"object","required":["eventIds"],"title":"AckRequest"},"ActionRequest":{"properties":{"action":{"type":"string","enum":["mark-read","mark-unread","archive","unarchive","tag","untag","assign","unassign","add-note","link"],"title":"Action"},"tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag"},"userId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Userid"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"relatedDocumentId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relateddocumentid"}},"type":"object","required":["action"],"title":"ActionRequest"},"Address":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Address UUID in org-v2"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Optional label (e.g. 'HQ', 'Warehouse 2')"},"street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Street","description":"Street and number — the first address line. On an invoice this is BT-35 for the seller and BT-50 for the buyer, part of the postal address every invoice must show for both parties."},"streetLine2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Streetline2","description":"Second address line — building, industrial estate, PO box. BT-36 (seller) / BT-51 (buyer) on an invoice. Also accepted as `street2`."},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"Town or city — BT-37 (seller) / BT-52 (buyer) on an invoice."},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"State / province / region name — the country subdivision, BT-39 (seller) / BT-54 (buyer) on an invoice. Required by some jurisdictions to fix which local tax applies; optional in the EU."},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"Alias of state — kept for backward compat"},"postalCode":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Postalcode","description":"Post code — BT-38 (seller) / BT-53 (buyer) on an invoice. Also accepted as `zipCode`."},"country":{"anyOf":[{"type":"string","maxLength":2,"minLength":2,"pattern":"^[A-Z]{2}$"},{"type":"null"}],"title":"Country","description":"ISO 3166-1 alpha-2 country code — BT-40 (seller) / BT-55 (buyer) on an invoice, and mandatory there. It decides whose VAT rules the invoice is judged by, so it is the one address field you should never leave to a guess: absent it, Flowie falls back to the country prefix of the party's VAT number."},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude","description":"Geographic latitude, if the address was geocoded. Not carried onto invoices."},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude","description":"Geographic longitude, if the address was geocoded. Not carried onto invoices."},"isDefaultShipping":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Isdefaultshipping","description":"Whether this is the organization's default delivery address. Set on addresses read back from the company record; ignored on an invoice."},"isDefaultBilling":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Isdefaultbilling","description":"Whether this is the organization's default billing address — the one used for the invoice's postal address when none is stated explicitly. Ignored when you send an address inline."},"instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instructions","description":"Free-text access or delivery instructions held on the company record (gate code, loading bay). Not carried onto invoices."},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat","description":"When the address record was created. Read-only."},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat","description":"When the address record was last changed. Read-only."}},"type":"object","title":"Address","description":"Postal address — mirrors org-v2 Address with backward-compatible field names.","examples":[{"city":"ARNAS","country":"FR","id":"019a6e5b-3b4c-7000-a460-a09181acc257","isDefaultBilling":true,"isDefaultShipping":false,"postalCode":"69400","street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD"}]},"AfnorError":{"properties":{"errorCode":{"type":"string","title":"Errorcode"},"errorMessage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Errormessage"},"type":{"type":"string","title":"Type","default":"about:blank"},"details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Details"},"instance":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instance"}},"type":"object","required":["errorCode"],"title":"AfnorError","description":"RFC 7807-style problem detail (AFNOR Error schema)."},"AfnorWebhookCreated":{"properties":{"webhookId":{"type":"string","title":"Webhookid"},"signingKey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signingkey"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"}},"type":"object","required":["webhookId"],"title":"AfnorWebhookCreated","description":"WebhookIdParam — 201 response body of createWebhook.\n\nPer the AFNOR swagger `WebhookIdParam`: ``{webhookId, signingKey, createdAt}``.\n``signingKey`` is the secret used to verify the HMAC signature of callbacks."},"AfnorWebhookItem":{"properties":{"webhookId":{"type":"string","title":"Webhookid"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"callbackUrl":{"type":"string","title":"Callbackurl"},"flowTypes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Flowtypes"},"flowDirection":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flowdirection"},"ackStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ackstatus"}},"type":"object","required":["webhookId","callbackUrl"],"title":"AfnorWebhookItem","description":"Webhook (= WebhookIdParam + WebhookParams) — item in listWebhooks / getWebhook.\n\n``signingKey`` is intentionally OMITTED from list/get payloads: it is only\nreturned once at creation time, and all WebhookIdParam fields are optional in\nthe swagger, so leaving it out stays conformant while avoiding leaking the\nsecret on every read."},"AfnorWebhookList":{"properties":{"count":{"type":"integer","title":"Count"},"webhooks":{"items":{"$ref":"#/components/schemas/AfnorWebhookItem"},"type":"array","title":"Webhooks"}},"type":"object","required":["count","webhooks"],"title":"AfnorWebhookList","description":"WebhookListResponse — 200 response body of listWebhooks."},"AnnuaireRoutingResponse":{"properties":{"siren":{"type":"string","title":"Siren"},"siret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Siret"},"declared":{"type":"boolean","title":"Declared","description":"False when the taxpayer has no platform declared (PPF default routing)."},"lineCount":{"type":"integer","title":"Linecount","default":0},"currentPaMatricule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currentpamatricule","description":"Platform matricule routing this taxpayer; `9998` is the PPF default."},"currentPaName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currentpaname","description":"The platform behind that matricule, resolved against the PA registry. `null` when the number is 9998/9999 or we cannot prove who holds it."},"currentPaConfidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currentpaconfidence","description":"How that name is known: `confirmed` (the operator told us), `published` (the operator says so) or `inferred` (proven from the annuaire). See `GET /v1/portability/platforms`."},"isDefaultPpf":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Isdefaultppf"},"isFlowie":{"type":"boolean","title":"Isflowie","description":"True when that matricule is ours."},"level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Level","description":"`siren` or `siret` — how specific the deciding line is."},"addressingId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Addressingid"},"effectiveFrom":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effectivefrom"},"effectiveTo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effectiveto","description":"Set when the routing is already scheduled to end — a port in progress."},"matricules":{"items":{"type":"string"},"type":"array","title":"Matricules","default":[]},"directory":{"type":"string","title":"Directory","description":"Which PPF directory answered: `production` (the real one) or `sandbox` (the PISTE test directory, which staging reads). It decides how to read `declared`: in the sandbox a company that routes perfectly well in real life comes back with no lignes at all, so an answer from there is about the test dataset, never about the taxpayer.","default":"production"},"checkedAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkedat"},"lignes":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Lignes","description":"The raw lignes d'adressage, as evidence.","default":[]}},"type":"object","required":["siren","declared","isFlowie"],"title":"AnnuaireRoutingResponse","description":"What the PPF annuaire says about a taxpayer's routing right now."},"ApiKeyCreateRequest":{"properties":{"companyId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","default":[]},"expiresAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expiresat"},"rateLimit":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ratelimit"}},"type":"object","required":["name"],"title":"ApiKeyCreateRequest"},"ApiKeyCreateResponse":{"properties":{"id":{"type":"string","title":"Id"},"key":{"type":"string","title":"Key"},"keyPrefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Keyprefix"},"name":{"type":"string","title":"Name"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","default":[]},"companyId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"expiresAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expiresat"}},"type":"object","required":["id","key","name"],"title":"ApiKeyCreateResponse","description":"Same as ApiKeyResponse but includes the full key (shown only once)."},"ApiKeyResponse":{"properties":{"id":{"type":"string","title":"Id"},"keyPrefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Keyprefix"},"name":{"type":"string","title":"Name"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","default":[]},"companyId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"expiresAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expiresat"}},"type":"object","required":["id","name"],"title":"ApiKeyResponse"},"AuthorizeRequest":{"properties":{"client_name":{"type":"string","maxLength":200,"minLength":1,"title":"Client Name","description":"Display name shown on the consent page (e.g. 'Claude Desktop · Acme')."},"scopes":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Scopes","description":"Requested scopes — must all be in the public catalogue."},"redirect_uri":{"type":"string","title":"Redirect Uri","description":"Where to redirect with the auth code. Default = out-of-band (code shown on screen).","default":"urn:ietf:wg:oauth:2.0:oob"},"code_challenge":{"type":"string","maxLength":128,"minLength":43,"title":"Code Challenge","description":"Base64URL(SHA256(code_verifier)) — RFC 7636 PKCE."},"code_challenge_method":{"type":"string","const":"S256","title":"Code Challenge Method","description":"Only S256 is supported. ``plain`` is intentionally rejected.","default":"S256"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"Opaque value the agent gets back on redirect — use it to bind the code to the session."}},"type":"object","required":["client_name","scopes","code_challenge"],"title":"AuthorizeRequest","description":"Step 1 — agent registers its consent intent.\n\nResponse carries a ``consent_url`` the agent must show to the user."},"AuthorizeResponse":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Server-side handle for this consent request."},"consent_url":{"type":"string","title":"Consent Url","description":"URL the agent shows to the user."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the request expires (consent must complete inside this window)."}},"type":"object","required":["request_id","consent_url","expires_in"],"title":"AuthorizeResponse"},"BatchLifecycleUpdateRequest":{"properties":{"updates":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Updates"}},"type":"object","required":["updates"],"title":"BatchLifecycleUpdateRequest"},"BatchSendItem":{"properties":{"idempotencyKey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotencykey"},"type":{"type":"string","enum":["invoice","credit-note","debit-note","purchase-order","purchase-request","sales-order","quote","goods-receipt","event"],"title":"Type"},"format":{"anyOf":[{"type":"string","enum":["json","ubl-xml","cii-xml","auto","raw"]},{"type":"null"}],"title":"Format"},"from":{"type":"string","title":"From","description":"Sender company (required). Accepts `0208:…` / `peppol:…` / `vat:…` / `comp_…` / `org:…`; normalised to the canonical Peppol id. For a multi-org caller it also selects the owning organization."},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To","description":"Recipient. A Peppol id (`0208:…`/`peppol:…`) used as-is, or any resolvable identifier (`vat:`/`siren:`/`siret:`/`duns:`/`gln:`/`lei:`/`eori:`/`email:`/`domain:`/`name:`/`org:` or their bare forms). Omit for `type=event`."},"document":{"anyOf":[{"$ref":"#/components/schemas/DocumentBody"},{"type":"null"}]},"xml":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Xml"},"file":{"anyOf":[{"$ref":"#/components/schemas/FileAttachment"},{"type":"null"}]}},"type":"object","required":["type","from"],"title":"BatchSendItem"},"BatchSendRequest":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/BatchSendItem"},"type":"array","title":"Documents"}},"type":"object","required":["documents"],"title":"BatchSendRequest"},"BatchSendResponse":{"properties":{"results":{"items":{},"type":"array","title":"Results"},"summary":{"additionalProperties":true,"type":"object","title":"Summary"}},"type":"object","required":["results","summary"],"title":"BatchSendResponse"},"Body_create_flow_afnor_flow_service_v1_flows_post":{"properties":{"flowInfo":{"type":"string","title":"Flowinfo","description":"JSON-encoded FlowInfo object"},"file":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"File"}},"type":"object","required":["flowInfo"],"title":"Body_create_flow_afnor_flow_service_v1_flows_post"},"Body_upload_mandate_signature_v1_portability_requests__request_ref__mandate_signature_document_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The signed designation agreement, as signed."},"signatory":{"type":"string","title":"Signatory","description":"Who signed it, for the taxpayer."},"signedAt":{"type":"string","title":"Signedat","description":"When they signed it, ISO-8601."}},"type":"object","required":["file","signatory","signedAt"],"title":"Body_upload_mandate_signature_v1_portability_requests__request_ref__mandate_signature_document_post"},"BusinessTermListResponse":{"properties":{"terms":{"items":{"$ref":"#/components/schemas/BusinessTermOut"},"type":"array","title":"Terms"},"summary":{"additionalProperties":true,"type":"object","title":"Summary","description":"Counts — groups, terms, how many France makes mandatory."},"legend":{"additionalProperties":true,"type":"object","title":"Legend","description":"What each `fr` and `apiState` value means."},"referential":{"additionalProperties":true,"type":"object","title":"Referential","description":"Which editions of the standard and rules this tracks."}},"type":"object","required":["terms","summary","legend","referential"],"title":"BusinessTermListResponse","description":"Every business term of EN 16931, with its French obligation."},"BusinessTermOut":{"properties":{"id":{"type":"string","title":"Id","description":"`BT-1`, `BG-4`, or a `-1` scheme attribute such as `BT-29-1`."},"kind":{"type":"string","enum":["group","term","attribute"],"title":"Kind","description":"`group` for a BG, `term` for a BT, `attribute` for a scheme identifier."},"name":{"type":"string","title":"Name","description":"The standard's English name."},"nameFr":{"type":"string","title":"Namefr","description":"The French label."},"group":{"type":"string","title":"Group","description":"Parent business group id — empty at invoice level."},"scope":{"type":"string","enum":["document","line"],"title":"Scope","description":"Which half of the invoice it sits in."},"cardinality":{"type":"string","title":"Cardinality","description":"EN 16931 core cardinality, e.g. `1..1`, `0..n`."},"ubl":{"type":"string","title":"Ubl","description":"The UBL 2.1 path. CII shares the BT id under a different element."},"fr":{"type":"string","enum":["mandatory","conditional","restricted","optional","unused"],"title":"Fr","description":"What France requires — see `legend.fr` on the list response."},"frNote":{"type":"string","title":"Frnote","description":"The French rule behind that status, named (`BR-FR-*`)."},"api":{"type":"string","title":"Api","description":"The `POST /v1/documents/send` field that carries it, if any."},"apiState":{"type":"string","enum":["sent","derived","accepted","xml-only"],"title":"Apistate","description":"What happens to that field — see `legend.apiState`."}},"type":"object","required":["id","kind","name","nameFr","group","scope","cardinality","ubl","fr","frNote","api","apiState"],"title":"BusinessTermOut","description":"One EN 16931 business term or group, as France requires it."},"CallbackParams":{"properties":{"url":{"type":"string","title":"Url"},"headers":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Headers"},"authentication":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Authentication"},"signature":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Signature"}},"type":"object","required":["url"],"title":"CallbackParams","description":"CallbackParameters for webhook creation/update."},"CompanyComplianceItem":{"properties":{"companyId":{"type":"string","title":"Companyid"},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber"},"peppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"smpRegistered":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Smpregistered"},"smpRegisteredAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Smpregisteredat"},"compliance":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Compliance"},"lastCheckedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastcheckedat"}},"type":"object","required":["companyId"],"title":"CompanyComplianceItem"},"CompanyCreate":{"properties":{"vatNumber":{"type":"string","maxLength":20,"minLength":4,"pattern":"^[A-Z]{2}[A-Z0-9]+$","title":"Vatnumber","description":"VAT number with country prefix (e.g. FR86797978996)"},"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name"},"address":{"anyOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}]},"additionalIdentifiers":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Additionalidentifiers"},"capabilities":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Capabilities"},"settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Settings"},"compliance":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Compliance"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["vatNumber"],"title":"CompanyCreate","examples":[{"address":{"city":"Paris","country":"FR","postalCode":"75001","street":"123 Rue de la Paix"},"name":"Acme SAS","vatNumber":"FR86797978996"}]},"CompanyImportBatchRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CompanyImportRequest"},"type":"array","maxItems":500,"minItems":1,"title":"Items"}},"type":"object","required":["items"],"title":"CompanyImportBatchRequest","description":"Body for POST /v1/companies/import/batch — import many Sovos companies.\n\nEach item is a full ``CompanyImportRequest``. Items are imported\nconcurrently (bounded) and each is idempotent; per-item failures do not\nabort the batch — the response reports the outcome of every item in order."},"CompanyImportBatchResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/CompanyImportBatchResult"},"type":"array","title":"Results"},"imported":{"type":"integer","title":"Imported","description":"Count of items that imported successfully."},"failed":{"type":"integer","title":"Failed","description":"Count of items that failed."}},"type":"object","required":["results","imported","failed"],"title":"CompanyImportBatchResponse"},"CompanyImportBatchResult":{"properties":{"index":{"type":"integer","title":"Index","description":"0-based position of the item in the request."},"sovosCompanyId":{"type":"string","title":"Sovoscompanyid"},"status":{"type":"string","enum":["imported","failed"],"title":"Status"},"companyId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"},"peppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolid"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["index","sovosCompanyId","status"],"title":"CompanyImportBatchResult"},"CompanyImportRequest":{"properties":{"siret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Siret","description":"French SIRET (14 digits) — the primary portal input; SIREN, country and Peppol id are derived from it."},"siren":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Siren","description":"French SIREN (9 digits) — alternative to `siret`."},"sovosOrganizationId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sovosorganizationid","description":"Sovos organization (tenant). Defaults to the configured SOVOS_ORGANIZATION_ID when omitted."},"sovosCompanyId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sovoscompanyid","description":"Existing Sovos company id to import; omit to provision from the SIRET instead."},"companyName":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Companyname","description":"Company display name (resolved from the annuaire when omitted)."},"countryCode":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"Countrycode","description":"ISO 3166-1 alpha-2; implied FR from a SIRET/SIREN."},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","description":"Sovos connection mode. Defaults to 'managed'.","default":"managed"},"credentials":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Credentials","description":"Optional {apiKey, apiSecret} for a self-managed (unmanaged) connection; omit for managed mode."},"effectiveDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Effectivedate","description":"Date d'effet of the port (ISO-8601). Carried onto the routing address as `validFrom`, so a migration agreed today for next month does not start routing today. Omit for immediate routing."}},"type":"object","title":"CompanyImportRequest","description":"Body for POST /v1/companies/import — the portability *migration* step.\n\nTwo entry points, one of which is required:\n\n* **SIRET only** (the portal case) — the taxpayer files a portability\n  request giving just its ``siret`` (or ``siren``). Flowie derives the SIREN,\n  country (FR) and Peppol id, resolves the legal name (and current PA) from\n  the PPF annuaire, then provisions the company on Sovos (using the request's\n  ``sovosOrganizationId`` or the configured ``SOVOS_ORGANIZATION_ID`` default),\n  or registers it locally as pending when no Sovos org is known.\n* **Existing Sovos company** — supply ``sovosOrganizationId`` +\n  ``sovosCompanyId`` to PULL an already-provisioned Sovos company instead of\n  provisioning a new one.","examples":[{"siret":"92137626500018"},{"companyName":"HOZELOCK EXEL","countryCode":"FR","mode":"managed","sovosCompanyId":"SOVOS-CMP-42","sovosOrganizationId":"SOVOS-ORG-1"}]},"CompanyListItem":{"properties":{"id":{"type":"string","title":"Id"},"organizationId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organizationid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"legalName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legalname"},"type":{"anyOf":[{"type":"string","enum":["Association","Branch","Charity","Cooperative","EducationalInstitution","FamilyOffice","ForeignEntity","Franchise","Government","Headquarters","Healthcare","HoldingCompany","Individual","InvestmentFund","JointVenture","LimitedLiabilityCompany","Municipal","Nonprofit","Partnership","PrivateCompany","PublicCompany","SoleProprietorship","Subsidiary","Trust","Other","Unknown"]},{"type":"null"}],"title":"Type"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber"},"taxNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Taxnumber"},"registrationNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registrationnumber"},"legalStatus":{"anyOf":[{"type":"string","enum":["Active","Bankrupt","Closed","Inactive","Merged","UnderInvestigation","Unknown"]},{"type":"null"}],"title":"Legalstatus"},"legalAddressId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legaladdressid"},"isPublic":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ispublic"},"dataProviderType":{"anyOf":[{"type":"string","enum":["Bce","Dnb","Pappers","Whitepages"]},{"type":"null"}],"title":"Dataprovidertype"},"dataProviderSyncDate":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Dataprovidersyncdate"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"},"peppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolid"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Stats"}},"type":"object","required":["id"],"title":"CompanyListItem","description":"Lightweight company entry returned by GET /v1/companies — surfaces enough\nfields to render a list (name + identifiers + status) without forcing the\ncaller to fetch the full profile for every row."},"CompanyRegisterRequest":{"properties":{"sovosOrganizationId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sovosorganizationid","description":"Genuine Sovos organization id. Required to provision a new Sovos customer configuration / managed connection."},"sovosProduct":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sovosproduct","description":"Sovos product flow. Defaults to 'compliancenetwork'."},"erpSystemId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Erpsystemid","description":"ERP system id label on the Sovos config."},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","description":"Sovos connection mode. Defaults to 'managed'."}},"type":"object","title":"CompanyRegisterRequest","description":"Body for POST /v1/companies/{id}/register — deploy the org on Peppol.\n\nAll fields optional: if the org already has a Sovos customer config, the\nregister call re-syncs and activates it. To provision a NOT-yet-configured\norg, ``sovosOrganizationId`` is required (a genuine Sovos-issued org id;\nSovos rejects arbitrary values)."},"CompanyResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Organization ID (UUID)"},"organizationId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organizationid","description":"Canonical organization ID — usually equal to `id`"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"internalName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Internalname","description":"Operator-only display name"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug","description":"URL-safe slug"},"legalName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legalname"},"type":{"anyOf":[{"type":"string","enum":["Association","Branch","Charity","Cooperative","EducationalInstitution","FamilyOffice","ForeignEntity","Franchise","Government","Headquarters","Healthcare","HoldingCompany","Individual","InvestmentFund","JointVenture","LimitedLiabilityCompany","Municipal","Nonprofit","Partnership","PrivateCompany","PublicCompany","SoleProprietorship","Subsidiary","Trust","Other","Unknown"]},{"type":"null"}],"title":"Type","description":"Legal form classification (e.g. PrivateCompany)"},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"},"industryCodeLabel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industrycodelabel"},"dateEstablished":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Dateestablished"},"registrationDate":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Registrationdate"},"websiteUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Websiteurl"},"missionStatement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Missionstatement"},"visionStatement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visionstatement"},"sizeRange":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sizerange","description":"Employee headcount range"},"annualRevenueRange":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Annualrevenuerange"},"capital":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Capital","description":"Registered share capital"},"capitalCurrency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Capitalcurrency","description":"ISO 4217 currency code"},"ownershipStructure":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ownershipstructure"},"socialMediaHandle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Socialmediahandle"},"logoUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logourl"},"country":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"title":"Country","description":"ISO 3166-1 alpha-2"},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber"},"taxNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Taxnumber"},"dunsNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dunsnumber","description":"D-U-N-S 9-digit identifier"},"euid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Euid","description":"European Unique Identifier"},"registrationNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registrationnumber","description":"Preferred registration number (e.g. SIRET / SIREN / KvK)"},"additionalIdentifiers":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Additionalidentifiers"},"legalForm":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legalform","description":"e.g. SAS, SARL, GmbH"},"legalStatus":{"anyOf":[{"type":"string","enum":["Active","Bankrupt","Closed","Inactive","Merged","UnderInvestigation","Unknown"]},{"type":"null"}],"title":"Legalstatus"},"legalStatusReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legalstatusreason"},"legalStatusEffectiveDate":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Legalstatuseffectivedate"},"legalAddressId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legaladdressid","description":"UUID of the legal address (also embedded as `address`)"},"address":{"anyOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}],"description":"Resolved legal address"},"mergedWithOrganization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mergedwithorganization","description":"If this org was merged, the surviving org ID"},"isRecoverable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Isrecoverable"},"isPublic":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ispublic"},"dataProviderType":{"anyOf":[{"type":"string","enum":["Bce","Dnb","Pappers","Whitepages"]},{"type":"null"}],"title":"Dataprovidertype","description":"Source registry that last enriched this record"},"dataProviderSyncDate":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Dataprovidersyncdate"},"createdBy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Createdby"},"updatedBy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updatedby"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"},"peppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolid"},"capabilities":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Capabilities"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"active, inactive, suspended"},"smpRegistered":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Smpregistered"},"smpRegisteredAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Smpregisteredat"},"compliance":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Compliance"},"enrichment":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Enrichment"},"settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Settings"},"stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Stats"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["id"],"title":"CompanyResponse","description":"Full company profile — mirrors org-v2 Organization plus Peppol-specific fields.","examples":[{"address":{"city":"ARNAS","country":"FR","postalCode":"69400","street":"891 ROUTE DES FRENES","streetLine2":"Z.I.DE JOUX ARNAS NORD"},"country":"FR","createdAt":"2025-11-10T15:21:03.581980Z","dataProviderSyncDate":"2025-11-10T15:21:04.068319Z","dataProviderType":"Whitepages","id":"019a6e5b-3960-7000-ab88-153aed5a6240","isPublic":false,"legalAddressId":"019a6e5b-3b4c-7000-a460-a09181acc257","legalName":"HOZELOCK EXEL","name":"HOZELOCK EXEL","organizationId":"019a6e5b-3960-7000-ab88-153aed5a6240","peppolId":"0009:02779658772","registrationNumber":"77965877200024","slug":"hozelock-exel","smpRegistered":true,"status":"active","taxNumber":"FR02779658772","updatedAt":"2026-03-06T09:08:26.855434Z","vatNumber":"FR02779658772"}]},"CompanySuggestListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CompanySuggestion"},"type":"array","title":"Data"},"total":{"type":"integer","title":"Total"},"query":{"type":"string","title":"Query"},"match":{"type":"string","title":"Match","description":"How the query was read: `name` (ranked suggestions), `relaxed` (nothing matched as typed, so a looser query answered — see `relaxedTo`), `siren` / `siret` / `vat` / `peppol` (a direct lookup, at most one row, in any country), `too_short`, or `unavailable` when no register answered."},"relaxedTo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Relaxedto","description":"The query that actually ran, when the one you sent found nothing. The register matches a literal, un-accented substring, so `solution connected` finds nothing while `solution connect` finds two companies; rather than answer *no such company*, the search retries on the folded, shortened and single-word forms and says here which one answered. Show it: these are matches for a question the caller did not ask."},"exhaustive":{"type":"boolean","title":"Exhaustive","description":"False when this list is not the whole answer: the directory returned a full page of name matches with no exact hit (the company you want may simply not be in it), or no register answered at all (`match: unavailable`). Either way, offer the identifier as a way out — and never read an empty list with `exhaustive: false` as *no such company*.","default":true},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why the answer is empty, when it is empty for a reason that is not about the company — set with `match: unavailable`, so an outage or a rate limit can be told apart from a register that answered *nothing here*, and set on `sandbox` to say the registers searched were the test ones."},"directory":{"type":"string","title":"Directory","description":"Which registers answered: `production` (the real PPF annuaire and Peppol Directory) or `sandbox` (their test twins, which staging reads). A sandbox answer is never `exhaustive`: the test directories do not carry real companies, so an empty list from there says nothing about whether the company exists.","default":"production"}},"type":"object","required":["data","total","query","match"],"title":"CompanySuggestListResponse"},"CompanySuggestion":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"siren":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Siren"},"siret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Siret"},"peppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolid"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber"}},"type":"object","title":"CompanySuggestion","description":"One company a taxpayer can pick instead of typing an identifier."},"CompanyUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"address":{"anyOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}]},"capabilities":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Capabilities"},"settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Settings"},"compliance":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Compliance"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"CompanyUpdate"},"ComplianceReportRecord":{"properties":{"documentId":{"type":"string","title":"Documentid"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Number"},"issueDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuedate"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"reportedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reportedat"},"reportedTo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reportedto"},"platformResponse":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Platformresponse"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["documentId"],"title":"ComplianceReportRecord"},"ComplianceReportResponse":{"properties":{"records":{"items":{"$ref":"#/components/schemas/ComplianceReportRecord"},"type":"array","title":"Records","default":[]}},"type":"object","title":"ComplianceReportResponse"},"ComplianceStatusResponse":{"properties":{"companies":{"items":{"$ref":"#/components/schemas/CompanyComplianceItem"},"type":"array","title":"Companies","default":[]}},"type":"object","title":"ComplianceStatusResponse"},"DirectoryLineSearchRequest":{"properties":{"filters":{"additionalProperties":true,"type":"object","title":"Filters"},"sorting":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Sorting"},"fields":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Fields"},"include":{"anyOf":[{"items":{"type":"string","enum":["siren","siret","routingCode"]},"type":"array"},{"type":"null"}],"title":"Include"},"limit":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","default":50},"ignore":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Ignore","default":0}},"type":"object","title":"DirectoryLineSearchRequest","description":"Request body for POST /v1/directory-line/search."},"DirectoryLineSearchResponse":{"properties":{"search":{"additionalProperties":true,"type":"object","title":"Search"},"totalNumberOfResults":{"type":"integer","title":"Totalnumberofresults"},"results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Results"}},"type":"object","required":["search","totalNumberOfResults","results"],"title":"DirectoryLineSearchResponse","description":"Response body for POST /v1/directory-line/search."},"DirectoryLookupResponse":{"properties":{"peppolId":{"type":"string","title":"Peppolid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"postalCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postalcode"},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber"},"registeredSince":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registeredsince"},"documentTypes":{"items":{"type":"string"},"type":"array","title":"Documenttypes","default":[]},"accessPoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accesspoint"},"capabilities":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Capabilities"},"enrichment":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Enrichment"},"smpStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Smpstatus"},"participants":{"items":{"$ref":"#/components/schemas/DirectorySearchResult"},"type":"array","title":"Participants","description":"Every Peppol participant line of the same legal entity, one row per participant ID. A French company is typically listed several times under the French addressing scheme — `0225:<siren>` and `0225:<siren>_<suffix>` — plus any SIRET (`0009`) or VAT (`9957`) line. Empty when the entity has no line on the network, or when the lookup did not resolve a French SIREN."}},"type":"object","required":["peppolId"],"title":"DirectoryLookupResponse"},"DirectorySearchResult":{"properties":{"peppolId":{"type":"string","title":"Peppolid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"postalCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postalcode"},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber"},"registeredSince":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Registeredsince"},"documentTypes":{"items":{"type":"string"},"type":"array","title":"Documenttypes","default":[]},"accessPoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accesspoint"}},"type":"object","required":["peppolId"],"title":"DirectorySearchResult"},"DocumentBody":{"properties":{"number":{"type":"string","maxLength":100,"minLength":1,"title":"Number","description":"Your invoice number (BT-1)."},"issueDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Issuedate","description":"Issue date (BT-2) as an ISO 8601 date (YYYY-MM-DD) — required for invoices/credit-notes, optional for events."},"dueDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duedate","description":"Date the payment is due (BT-9)."},"currency":{"type":"string","maxLength":3,"minLength":3,"pattern":"^[A-Z]{3}$","title":"Currency","description":"ISO 4217 currency code (BT-5).","default":"EUR"},"buyerReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyerreference","description":"The buyer's own reference for this invoice (BT-10) — the \"Service Exécutant\" a Chorus Pro recipient routes on. Renders as `cbc:BuyerReference`."},"orderReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Orderreference","description":"Buyer's purchase order number (BT-13). Renders as the UBL `cac:OrderReference/cbc:ID`. Maps to the tx-docs `purchaseOrderId` field. For an invoice covering SEVERAL orders, leave this empty and set `orderReference` per line instead."},"despatchAdviceReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Despatchadvicereference","description":"Despatch advice / delivery note number for the whole document (BT-16). Renders as `cac:DespatchDocumentReference/cbc:ID`. When the delivery note differs per line, use the line's `objectIdentifier` with `objectIdentifierScheme: \"DQ\"` instead."},"incoterms":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incoterms","description":"Delivery terms code (EXT-FR-FE-185, CII BT-X-145). Renders as `cac:DeliveryTerms/cbc:ID`. This is an EXT-CTC-FR extension term, not part of the EN 16931 core: it is optional, and a recipient on the core profile may ignore it.\n\nOne of the UNTDID 4053 + INCOTERMS values: `1` (delivery arranged by supplier), `2` (delivery arranged by logistic service provider), or an Incoterms 2020 code — `EXW`, `FCA`, `CPT`, `CIP`, `DAP`, `DPU`, `DDP`, `FAS`, `FOB`, `CFR`, `CIF`."},"incotermsLocation":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Incotermslocation","description":"Named place the delivery terms refer to (EXT-FR-FE-186) — a port, a city, a site. Renders as `cac:DeliveryTerms/cac:DeliveryLocation/cbc:Name`, and only when `incoterms` is also set: the code is mandatory inside the group, the place is not. Carried in the XML only — there is no transaction-document field behind it."},"billingReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Billingreference","description":"Reference to the invoice this document amends (BT-25). Renders as the UBL `BillingReference/InvoiceDocumentReference/ID`. Required for credit-notes and debit-notes under the FR reform (BR-FR-CO-04/BR-FR-CO-05); ignored for plain invoices. Maps to the tx-docs `originalInvoiceId` field. Also accepted under its former name `precedingInvoiceReference`."},"billingReferenceDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Billingreferencedate","description":"Issue date (YYYY-MM-DD) of the referenced invoice (BT-26). Renders as `BillingReference/InvoiceDocumentReference/IssueDate`. Maps to the tx-docs `originalInvoiceIssuedAt` field. Also accepted under its former name `precedingInvoiceIssueDate`."},"exchangeRate":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Exchangerate","description":"Rate used to convert this document's currency into EUR, the accounting currency (BT-111 / BR-FR-CO-12). State it whenever your ERP has already posted the document at a given rate: the rate then comes from YOUR books rather than from a market feed, so the VAT we report in EUR always ties back to your ledger. Ignored when `currency` is EUR.\n\nWhen omitted, the rate is looked up for `issueDate` (not for the day the document happens to reach us), so re-sending a document later yields the same EUR amount.","examples":[1.0842]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Free-text note on the invoice (BT-22). Renders as `cbc:Note`."},"seller":{"anyOf":[{"$ref":"#/components/schemas/PartyInfo"},{"type":"null"}],"description":"The party issuing the invoice and owed the money (BG-4). Overrides the seller Flowie would otherwise derive from `from`. Its name, VAT number, address and contact become BT-27, BT-31, BG-5 and BT-41/42/43 — all mandatory identification of who sold."},"buyer":{"anyOf":[{"$ref":"#/components/schemas/PartyInfo"},{"type":"null"}],"description":"The party receiving the invoice and owing the money (BG-7). Overrides the buyer Flowie would otherwise derive from `to`. Its name, VAT number, address and contact become BT-44, BT-48, BG-8 and BT-56/57/58. The VAT number is what your customer reclaims its VAT against, so an incorrect one costs them money."},"parties":{"anyOf":[{"items":{"$ref":"#/components/schemas/PartyRef"},"type":"array"},{"type":"null"}],"title":"Parties","description":"Explicit, role-tagged party list for multi-party documents and self-billing. When provided it OVERRIDES the default seller/buyer derivation; exactly one entry must be the `initiator` (the acting org). Omit for the common seller→buyer case."},"payment":{"anyOf":[{"$ref":"#/components/schemas/PaymentInfo"},{"type":"null"}],"description":"How you are to be paid (BG-16) — means, remittance reference, account and its provider. Every field of it reaches its own BT-* element; see `PaymentInfo`. Omit it and the e-invoice states no account, which is the most common reason an otherwise valid invoice goes unpaid."},"delivery":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Delivery","description":"Delivery information (BG-13). Two parts are rendered by the structured `format=json` pipeline:\n\n- the deliver-to COUNTRY (BT-80), read from `deliveryLocation.country` — required by BR-IC-12 on intra-community supplies, and defaulted to the buyer's country on such a supply (`vatCategory: \"K\"`) when omitted;\n- the actual delivery date (BT-72), read from `actualDeliveryDate` (`deliveryDate` and `date` are also accepted).\n\nThe rest of the delivery group is accepted but not yet rendered — carry it in the XML with `format=ubl-xml` if you need it."},"lines":{"anyOf":[{"items":{"$ref":"#/components/schemas/InvoiceLine"},"type":"array"},{"type":"null"}],"title":"Lines","description":"Required for invoices/credit-notes, optional for events"},"allowances":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Allowances","description":"Document-level allowances/discounts, e.g. `[{\"amount\": 50.0, \"reason\": \"Loyalty discount\", \"vatRate\": 21.0}]`. ACCEPTED but NOT YET rendered by the structured `format=json` pipeline — use `format=ubl-xml` to carry them today."},"charges":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Charges","description":"Document-level charges (shipping, packaging…), e.g. `[{\"amount\": 25.0, \"reason\": \"Shipping\", \"vatRate\": 21.0}]`. ACCEPTED but NOT YET rendered by the structured `format=json` pipeline — use `format=ubl-xml` to carry them today."},"attachments":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Attachments","description":"Embedded supporting documents (base64), e.g. `[{\"filename\": \"timesheet.pdf\", \"contentType\": \"application/pdf\", \"content\": \"<base64>\"}]`. ACCEPTED but NOT YET attached by the structured `format=json` pipeline — POST the file via `format=auto`/`raw`, or embed it in `format=ubl-xml`."},"totals":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Totals","description":"Pre-computed totals — `{\"netAmount\": …, \"vatAmount\": …, \"grossAmount\": …}` (the short keys `net`/`vat`/`gross` are also accepted). Omit and Flowie sums the lines; if provided, these override the computed amounts."},"templateId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Templateid","description":"UUID of the transaction-document template to file this document under (your org's template for this document type). The template defines which `customFields` are accepted, their types and whether each is line- or document-level. Omit to use your org's default template for the type. List your templates via tx-docs `GET /v1/organizations/{org}/fields` (field definitions) / the templates API."},"customFields":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Customfields","description":"Document-level custom field values, keyed by the field's name OR its definition UUID (names are resolved to UUIDs before submission). Attached to YOUR party on the document (the acting org / initiator). Values follow the field's type: a bare string for text/date fields, `{\"currency\": \"EUR\", \"amount\": 12.5}` for monetary fields, or an address object (`{street, street2, city, zipCode, country}`) for address fields. The fields must be declared on the document's `templateId`."}},"type":"object","required":["number"],"title":"DocumentBody","description":"Structured document body. Invoice-specific fields (issueDate, lines)\nare optional at the schema level so the same shape can carry events\nthat only need `number`. Invoice/credit-note pipelines re-validate\npresence of these fields before submitting to tx-docs."},"DocumentListItem":{"properties":{"id":{"type":"string","title":"Id"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"},"number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Number"},"issueDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuedate"},"dueDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duedate"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"grossAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Grossamount"},"sender":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Sender"},"receiver":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Receiver"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"statusReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statusreason"},"deliveryStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deliverystatus"},"receivedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Receivedat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["id"],"title":"DocumentListItem"},"DocumentResponse":{"properties":{"id":{"type":"string","title":"Id"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"INVOICE, CREDIT_NOTE, DEBIT_NOTE, PURCHASE_ORDER"},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction","description":"incoming or outgoing"},"number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Number"},"issueDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuedate"},"dueDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duedate"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"grossAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Grossamount"},"netAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Netamount"},"vatAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Vatamount"},"sender":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Sender"},"receiver":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Receiver"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Exact lifecycleStatus (org-specific, may be localized, e.g. 'draft', 'sent')."},"statusReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statusreason","description":"Why the document reached `status` — the validation errors behind a rejection, when the platform recorded one."},"deliveryStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deliverystatus","description":"pending, delivered, failed, rejected"},"lifecycleStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lifecyclestatus"},"document":{"anyOf":[{"$ref":"#/components/schemas/DocumentBody"},{"type":"null"}]},"xml":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Xml"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"},"receivedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Receivedat"},"sentAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sentat"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"}},"type":"object","required":["id"],"title":"DocumentResponse","examples":[{"currency":"EUR","deliveryStatus":"delivered","direction":"outgoing","grossAmount":1815.0,"id":"doc_abc123","issueDate":"2026-04-15","netAmount":1500.0,"number":"INV-2026-0042","status":"sent","type":"INVOICE","vatAmount":315.0}]},"ErrorDetail":{"properties":{"field":{"type":"string","title":"Field"},"rule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rule"},"message":{"type":"string","title":"Message"}},"type":"object","required":["field","message"],"title":"ErrorDetail"},"ErrorResponse":{"properties":{"type":{"type":"string","title":"Type"},"code":{"type":"string","title":"Code"},"message":{"type":"string","title":"Message"},"details":{"items":{"$ref":"#/components/schemas/ErrorDetail"},"type":"array","title":"Details","default":[]},"requestId":{"type":"string","title":"Requestid"},"docUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Docurl"}},"type":"object","required":["type","code","message","requestId"],"title":"ErrorResponse"},"EventResponse":{"properties":{"id":{"type":"string","title":"Id"},"type":{"type":"string","title":"Type"},"createdAt":{"type":"string","format":"date-time","title":"Createdat"},"data":{"additionalProperties":true,"type":"object","title":"Data","default":{}}},"type":"object","required":["id","type","createdAt"],"title":"EventResponse"},"FileAttachment":{"properties":{"content":{"type":"string","minLength":1,"title":"Content","description":"Base64-encoded file bytes. No data: prefix."},"contentType":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Contenttype","description":"MIME type. Optional — server falls back to magic-byte sniff."},"filename":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Filename","description":"Original filename, persisted on the file record."}},"type":"object","required":["content"],"title":"FileAttachment","description":"Raw file payload — used when sending a document whose body cannot be\nrepresented as structured JSON or UBL XML (e.g. a scanned PDF, an image,\na proprietary format). The content is base64-encoded; the server sniffs\nthe bytes to decide whether to route through the structured pipeline or\njust persist the file as-is via the document file API."},"Flow":{"properties":{"flowId":{"type":"string","maxLength":64,"title":"Flowid"},"submittedAt":{"type":"string","format":"date-time","title":"Submittedat"},"name":{"type":"string","maxLength":255,"title":"Name"},"flowSyntax":{"type":"string","enum":["CII","UBL","Factur-X","CDAR","FRR"],"title":"Flowsyntax"},"trackingId":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Trackingid"},"processingRule":{"anyOf":[{"type":"string","enum":["B2B","B2BInt","B2C","B2G","B2GInt","OutOfScope","B2GOutOfScope","ArchiveOnly","NotApplicable","Undefined"]},{"type":"null"}],"title":"Processingrule"},"flowProfile":{"anyOf":[{"type":"string","enum":["Basic","CIUS","Extended-CTC-FR","Undefined"]},{"type":"null"}],"title":"Flowprofile"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"},"flowType":{"type":"string","title":"Flowtype"},"processingRuleSource":{"type":"string","enum":["Input","Computed"],"title":"Processingrulesource"},"flowDirection":{"type":"string","enum":["In","Out"],"title":"Flowdirection"},"acknowledgement":{"$ref":"#/components/schemas/FlowAcknowledgement"}},"type":"object","required":["flowId","submittedAt","name","flowSyntax","updatedAt","flowType","processingRuleSource","flowDirection","acknowledgement"],"title":"Flow","description":"CoreFlowInfo + FullFlowInfoExtension + FlowExtension."},"FlowAcknowledgement":{"additionalProperties":true,"type":"object"},"FullFlowInfo":{"properties":{"flowId":{"type":"string","maxLength":64,"title":"Flowid"},"submittedAt":{"type":"string","format":"date-time","title":"Submittedat"},"name":{"type":"string","maxLength":255,"title":"Name"},"flowSyntax":{"type":"string","enum":["CII","UBL","Factur-X","CDAR","FRR"],"title":"Flowsyntax"},"trackingId":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Trackingid"},"processingRule":{"anyOf":[{"type":"string","enum":["B2B","B2BInt","B2C","B2G","B2GInt","OutOfScope","B2GOutOfScope","ArchiveOnly","NotApplicable","Undefined"]},{"type":"null"}],"title":"Processingrule"},"flowProfile":{"anyOf":[{"type":"string","enum":["Basic","CIUS","Extended-CTC-FR","Undefined"]},{"type":"null"}],"title":"Flowprofile"},"sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha256"}},"type":"object","required":["flowId","submittedAt","name","flowSyntax"],"title":"FullFlowInfo","description":"FullFlowInfoExtension + FlowInfo."},"GenerateAndValidateRequest":{"properties":{"scenarioId":{"type":"string","title":"Scenarioid","description":"Scenario id from `GET /v1/tools/fr/ubl/scenarios`, e.g. `uc-20-deposit-invoice`."},"number":{"anyOf":[{"type":"string","maxLength":35},{"type":"null"}],"title":"Number","description":"Invoice number (BT-1). BR-FR-01 caps it at 35 characters."},"issueDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Issuedate","description":"Issue date (BT-2), `YYYY-MM-DD`. BR-FR-03 requires a year between 2000 and 2099."},"dueDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Duedate","description":"Payment due date (BT-9), `YYYY-MM-DD`."},"currency":{"anyOf":[{"type":"string","pattern":"^[A-Z]{3}$"},{"type":"null"}],"title":"Currency","description":"Invoice currency (BT-5), ISO 4217, upper case. Anything other than EUR additionally requires the VAT accounting currency (BR-FR-CO-12) — the generator does not add it for you."},"seller":{"anyOf":[{"$ref":"#/components/schemas/PartyOverride"},{"type":"null"}]},"buyer":{"anyOf":[{"$ref":"#/components/schemas/PartyOverride"},{"type":"null"}]},"format":{"anyOf":[{"type":"string","enum":["json","ubl-xml"]},{"type":"null"}],"title":"Format","description":"Which `POST /v1/documents/send` body to build. Defaults to the scenario's own recommendation — `json` where the structured pipeline carries every field the case needs, `ubl-xml` where it does not."},"includePassedRules":{"type":"boolean","title":"Includepassedrules","description":"Ask the validator to list the rules that passed, not only the failures.","default":false}},"type":"object","required":["scenarioId"],"title":"GenerateAndValidateRequest","description":"Generate a scenario and run it past the official French schematrons."},"GenerateAndValidateResponse":{"properties":{"scenario":{"$ref":"#/components/schemas/ScenarioOut"},"document":{"additionalProperties":true,"type":"object","title":"Document","description":"The invoice model the scenario built, after your overrides."},"ubl":{"type":"string","title":"Ubl","description":"EN 16931 UBL 2.1 XML — an `Invoice` or a `CreditNote`."},"totals":{"additionalProperties":true,"type":"object","title":"Totals","description":"BT-106 … BT-115 plus the per-rate VAT breakdown."},"sendRequest":{"additionalProperties":true,"type":"object","title":"Sendrequest","description":"The exact `POST /v1/documents/send` body that sends it."},"valid":{"type":"boolean","title":"Valid","description":"True when every official and complementary check passed."},"validation":{"additionalProperties":true,"type":"object","title":"Validation","description":"The validator's own verdict: `official` (FNFE XP Z12-012 schematrons — XSD, profile, BR-FR) and `custom` (the complementary French field checks), plus counts."},"errors":{"items":{"$ref":"#/components/schemas/ValidationMessageOut"},"type":"array","title":"Errors","description":"Every failing rule, flattened for quick reading."}},"type":"object","required":["scenario","document","ubl","totals","sendRequest","valid","validation"],"title":"GenerateAndValidateResponse"},"GenerateRequest":{"properties":{"scenarioId":{"type":"string","title":"Scenarioid","description":"Scenario id from `GET /v1/tools/fr/ubl/scenarios`, e.g. `uc-20-deposit-invoice`."},"number":{"anyOf":[{"type":"string","maxLength":35},{"type":"null"}],"title":"Number","description":"Invoice number (BT-1). BR-FR-01 caps it at 35 characters."},"issueDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Issuedate","description":"Issue date (BT-2), `YYYY-MM-DD`. BR-FR-03 requires a year between 2000 and 2099."},"dueDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Duedate","description":"Payment due date (BT-9), `YYYY-MM-DD`."},"currency":{"anyOf":[{"type":"string","pattern":"^[A-Z]{3}$"},{"type":"null"}],"title":"Currency","description":"Invoice currency (BT-5), ISO 4217, upper case. Anything other than EUR additionally requires the VAT accounting currency (BR-FR-CO-12) — the generator does not add it for you."},"seller":{"anyOf":[{"$ref":"#/components/schemas/PartyOverride"},{"type":"null"}]},"buyer":{"anyOf":[{"$ref":"#/components/schemas/PartyOverride"},{"type":"null"}]},"format":{"anyOf":[{"type":"string","enum":["json","ubl-xml"]},{"type":"null"}],"title":"Format","description":"Which `POST /v1/documents/send` body to build. Defaults to the scenario's own recommendation — `json` where the structured pipeline carries every field the case needs, `ubl-xml` where it does not."}},"type":"object","required":["scenarioId"],"title":"GenerateRequest","description":"Generate one scenario, optionally with your own header data."},"GenerateResponse":{"properties":{"scenario":{"$ref":"#/components/schemas/ScenarioOut"},"document":{"additionalProperties":true,"type":"object","title":"Document","description":"The invoice model the scenario built, after your overrides."},"ubl":{"type":"string","title":"Ubl","description":"EN 16931 UBL 2.1 XML — an `Invoice` or a `CreditNote`."},"totals":{"additionalProperties":true,"type":"object","title":"Totals","description":"BT-106 … BT-115 plus the per-rate VAT breakdown."},"sendRequest":{"additionalProperties":true,"type":"object","title":"Sendrequest","description":"The exact `POST /v1/documents/send` body that sends it."}},"type":"object","required":["scenario","document","ubl","totals","sendRequest"],"title":"GenerateResponse","description":"Everything you need to run the case: the story, the XML and the call."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HandoffCreateRequest":{"properties":{"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Scopes pre-approved for the agent. Default = read-mostly send/receive set."},"label":{"type":"string","maxLength":200,"minLength":1,"title":"Label","description":"Human-readable label — appears on the issued key, in audit logs, and in the dashboard.","default":"AI agent handoff"},"ttl_seconds":{"type":"integer","maximum":3600.0,"minimum":60.0,"title":"Ttl Seconds","description":"How long the handoff token is valid before redemption (60–3600s).","default":600},"mode":{"type":"string","enum":["test","live"],"title":"Mode","description":"test → flw_test_… key (default, safe). live → flw_live_… (only if your account is on a paid tier).","default":"test"}},"type":"object","title":"HandoffCreateRequest","description":"Generate a single-use link to hand to an AI agent.\n\nThe authenticated user pre-approves a scope set bound to their org.\nThe agent receives the URL, calls ``POST /v1/oauth/handoff/exchange``\nwith the embedded token, and gets a scoped API key tied to the same\norganization — no consent UI required.\n\nScopes are filtered against the caller's own scopes: you cannot\npre-approve more than you have."},"HandoffCreateResponse":{"properties":{"handoff_token":{"type":"string","title":"Handoff Token","description":"Single-use token. Travels embedded in the URL."},"handoff_url":{"type":"string","title":"Handoff Url","description":"Full URL ready to paste into your LLM/agent prompt."},"expires_at":{"type":"string","title":"Expires At","description":"ISO 8601 expiry timestamp (UTC)."},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"The scopes the issued key will carry."},"organization_id":{"type":"string","title":"Organization Id","description":"The org the key will be bound to on redemption."}},"type":"object","required":["handoff_token","handoff_url","expires_at","scopes","organization_id"],"title":"HandoffCreateResponse"},"HandoffExchangeRequest":{"properties":{"handoff_token":{"type":"string","minLength":10,"title":"Handoff Token","description":"The token from the URL the user shared."}},"type":"object","required":["handoff_token"],"title":"HandoffExchangeRequest","description":"Step 2 — agent redeems the handoff token for an API key.\n\nThe token from the URL is the only credential needed. Single-use:\na second exchange returns 400."},"InferenceOut":{"properties":{"field":{"type":"string","title":"Field","description":"The EN 16931 / CIUS-FR term, e.g. `BT-23`."},"name":{"type":"string","title":"Name","description":"What it is called in the reform's vocabulary."},"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value","description":"The value that was set."},"why":{"type":"string","title":"Why","description":"Why it had to be this — the business rule behind it."}},"type":"object","required":["field","name","why"],"title":"InferenceOut","description":"One thing the generator decided for you, and the rule that decided it."},"InvoiceLine":{"properties":{"description":{"type":"string","maxLength":500,"minLength":1,"title":"Description","description":"Name of the item, as it appears on the line — BT-153. Mandatory on every invoice. Renders as `cac:Item/cbc:Name`. For a longer explanation of the item, add `itemDescription` (BT-154) as well: this field is the short label a reader scans, not the full text."},"quantity":{"type":"number","title":"Quantity","description":"Quantity invoiced on the line — BT-129. Mandatory. Expressed in `unit` (BT-130), and multiplied by `unitPrice` to give the line total. To reverse a charge, prefer a credit note (`type: \"credit-note\"`) over a negative quantity — but a negative quantity is legal under EN 16931 and is accepted: it is how many emitters write a reversal line, and refusing it would reject documents their own validator passes. What must never be negative is the price (BR-27), which `unitPrice` still enforces."},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit","description":"Unit the `quantity` is counted in — BT-130, as a UN/ECE Rec 20 code: `\"C62\"` piece, `\"HUR\"` hour, `\"DAY\"` day, `\"KGM\"` kilogram, `\"LTR\"` litre, `\"MTR\"` metre. Mandatory under EN 16931; defaults to `C62` (piece) when omitted. Your customer's system reads the code, not a word like \"hours\".","examples":["HUR"]},"unitPrice":{"type":"number","minimum":0.0,"title":"Unitprice","description":"Price of one `unit`, **excluding VAT** — BT-146. Mandatory. VAT is never included here: it is stated separately per line via `vatRate`. For a price quoted per batch rather than per unit (say per 1000 pieces), set `priceBaseQuantity` (BT-149) alongside."},"vatRate":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Vatrate","description":"VAT rate as a percentage (e.g. 20.0) — BT-152, the invoiced item's VAT rate. Renders as `cac:ClassifiedTaxCategory/cbc:Percent`."},"vatCategory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatcategory","description":"VAT category of the line — BT-151, a UNCL5305 code. Defaults to `\"S\"` (standard rate), which is what an ordinary domestic sale uses. The zero-VAT categories are `\"E\"` exempt, `\"Z\"` zero-rated, `\"AE\"` reverse charge (the buyer accounts for the VAT), `\"K\"` intra-community supply, `\"G\"` export outside the EU, `\"O\"` outside the scope of VAT.\n\nEvery one of those except `Z` obliges you to say WHY no VAT is charged, in `vatExemptionReason` (BT-120) and ideally `vatExemptionCode` (BT-121) — EN 16931 rejects the invoice otherwise (BR-E-10/BR-IC-10/BR-G-10/BR-O-10/BR-AE-10).","examples":["S"]},"vatExemptionReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatexemptionreason","description":"Human-readable reason why this line carries no VAT (BT-120), e.g. \"Exoneration TVA, art. 262 ter I du CGI\". Required by EN 16931 (BR-E-10/BR-IC-10/BR-G-10/BR-O-10) whenever `vatCategory` is a zero-VAT category (E/Z/AE/K/G/O). Ignored for standard-rated lines."},"vatExemptionCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatexemptioncode","description":"VATEX code backing `vatExemptionReason` (BT-121), e.g. \"VATEX-EU-IC\" for an intra-community supply or \"VATEX-EU-AE\" for reverse charge. Optional: EN 16931 accepts the reason text alone."},"netAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Netamount","description":"The line's own net amount, VAT excluded (BT-131). **State it to stop Flowie recomputing the line.** Left empty, the line total is `quantity x unitPrice`, which is not what the line is worth as soon as it carries a discount, a surcharge, or a price quoted per batch (BT-149): 519.1 ML at 12.60 less a 3 % line discount is 6344.44, not the 6540.66 the multiplication gives. When present, this amount is used verbatim -- as the line total (BT-131), as the taxable base its (category, rate) VAT bucket contributes to (BT-116), and as the transaction document's `priceWithoutTax`. `quantity` and `unitPrice` still travel as BT-129 / BT-146; they simply stop deciding the amount. The document totals have the same escape hatch one level up: `totals`."},"itemCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Itemcode","description":"Seller's identifier for the item (BT-155). Renders as `cac:Item/cac:SellersItemIdentification/cbc:ID`."},"buyerItemCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyeritemcode","description":"Buyer's identifier for the item (BT-156) — the part number as the BUYER knows it, which is what many large customers reconcile on. Renders as `cac:Item/cac:BuyersItemIdentification/cbc:ID`."},"itemDescription":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Itemdescription","description":"Long description of the item (BT-154), rendered as `cac:Item/cbc:Description`. Distinct from `description`, which is the item NAME (BT-153, `cac:Item/cbc:Name`). Omit to send the name alone."},"orderLineReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Orderlinereference","description":"Line number of the purchase order this line answers (BT-132). Renders as `cac:OrderLineReference/cbc:LineID`. Combine with `orderReference` on the line to invoice several purchase orders in one document."},"orderReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Orderreference","description":"Purchase order this LINE belongs to, for invoices spanning several orders. Renders as `cac:OrderLineReference/cac:OrderReference/cbc:ID`. When every line shares one order, set `document.orderReference` instead (BT-13) and leave this empty."},"despatchAdviceReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Despatchadvicereference","description":"Despatch advice / delivery note this line was delivered on, when it differs per line (AFNOR EXT-FR-FE-140). Defaults to `document.despatchAdviceReference`."},"despatchAdviceLineReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Despatchadvicelinereference","description":"Line number within the despatch advice (AFNOR EXT-FR-FE-141)."},"objectIdentifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Objectidentifier","description":"Invoice line object identifier (BT-128) — a document the line refers to, typically the despatch advice / delivery note number. Renders as `cac:InvoiceLine/cac:DocumentReference/cbc:ID` with `objectIdentifierScheme` as its `schemeID`."},"objectIdentifierScheme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Objectidentifierscheme","description":"Scheme of `objectIdentifier` (BT-128-1), from UNTDID 1153 — e.g. `\"DQ\"` for a delivery-note number, `\"VN\"` for a supplier reference. Required by EN 16931 (BR-CO-24) whenever `objectIdentifier` is present."},"priceBaseQuantity":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Pricebasequantity","description":"Number of item units the `unitPrice` applies to (BT-149) — send `1000` when the price is quoted per 1000 pieces. Renders as `cac:Price/cbc:BaseQuantity`. Defaults to 1 when omitted."},"priceBaseUnit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pricebaseunit","description":"Unit of measure of `priceBaseQuantity` (BT-150), as a UN/ECE Rec 20 code — e.g. `\"C62\"` (piece), `\"KGM\"` (kilogram), `\"LTR\"` (litre). Renders as the `unitCode` of `cac:Price/cbc:BaseQuantity`; defaults to `unit` (BT-130) when omitted."},"period":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Period","description":"Billing period this line covers (BG-26) — `{\"startDate\": \"2026-07-01\", \"endDate\": \"2026-07-31\"}`. Renders as BT-134/BT-135 (`cac:InvoicePeriod/cbc:StartDate` / `cbc:EndDate`). The short key pairs `start`/`end` and `from`/`to` are also accepted."},"customFields":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Customfields","description":"Line-level custom field values, keyed by the field's name OR its definition UUID. Names are resolved to UUIDs against the org's field definitions before submission. Values follow the field's type: a bare string for text/date fields, `{\"currency\": \"EUR\", \"amount\": 12.5}` for monetary fields, or an address object (`{street, street2, city, zipCode, country}`) for address fields. The fields must be declared on the document's template — see `document.templateId`."}},"type":"object","required":["description","quantity","unitPrice","vatRate"],"title":"InvoiceLine","examples":[{"description":"Consulting services - April 2026","quantity":10.0,"unit":"HUR","unitPrice":150.0,"vatRate":21.0}]},"KeyDatumOut":{"properties":{"bt":{"type":"string","title":"Bt","description":"The EN 16931 / CIUS-FR term, e.g. `BT-113`."},"label":{"type":"string","title":"Label","description":"What it is, in the reform's own vocabulary."},"why":{"type":"string","title":"Why","description":"Why this case needs it."}},"type":"object","required":["bt","label","why"],"title":"KeyDatumOut"},"LifecycleHistoryEntry":{"properties":{"status":{"type":"string","title":"Status"},"at":{"type":"string","format":"date-time","title":"At"},"setBy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Setby"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"reported":{"type":"boolean","title":"Reported","default":false},"reportedTo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reportedto"},"reportedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reportedat"}},"type":"object","required":["status","at"],"title":"LifecycleHistoryEntry"},"LifecycleHistoryResponse":{"properties":{"documentId":{"type":"string","title":"Documentid"},"currentStatus":{"type":"string","title":"Currentstatus"},"currentStatusAt":{"type":"string","format":"date-time","title":"Currentstatusat"},"currentStatusReason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currentstatusreason"},"compliance":{"additionalProperties":true,"type":"object","title":"Compliance","default":{}},"history":{"items":{"$ref":"#/components/schemas/LifecycleHistoryEntry"},"type":"array","title":"History","default":[]},"allowedTransitions":{"items":{"type":"string"},"type":"array","title":"Allowedtransitions","default":[]}},"type":"object","required":["documentId","currentStatus","currentStatusAt"],"title":"LifecycleHistoryResponse"},"LifecycleUpdateError":{"properties":{"status":{"type":"integer","title":"Status","description":"HTTP status this item would have returned on its own."},"detail":{"type":"string","title":"Detail","description":"Human-readable reason the transition was refused."}},"type":"object","required":["status","detail"],"title":"LifecycleUpdateError","description":"Why one entry of a batch update failed.\n\nOnly ever set on `POST /documents/lifecycle/batch`, where individual items\nfail independently and the overall call still returns 200."},"LifecycleUpdateRequest":{"properties":{"status":{"type":"string","enum":["under_review","approved","sent","rejected","partially_paid","paid","disputed","failed"],"title":"Status","description":"Target lifecycle status. `rejected` is **terminal** (in France it transmits 210 Refusée, cancelling the invoice for VAT and forcing a corrective) — when the issue may still be resolved, prioritize the reversible paths first: `disputed` to contest, or `disputed` + `reasonCode=\"suspended\"` to put the invoice on hold pending documents. `failed` transmits 213 Rejetée, the platform-side technical reject."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Free-text explanation shown to the counterparty. Required alongside `reasonCode=OTH`; forwarded verbatim as MDT-114 on the French DGFiP leg."},"reasonCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reasoncode","description":"Coded reason for the transition — required for `rejected` and `disputed`. Use one of the 14 official Peppol status reason codes (OPStatusReason): `NON` (No issue), `REF` (References incorrect), `LEG` (Legal information incorrect), `REC` (Receiver unknown), `QUA` (Item quality insufficient), `DEL` (Delivery issues), `PRI` (Prices incorrect), `QTY` (Quantity incorrect), `ITM` (Items incorrect), `PAY` (Payment terms incorrect), `UNR` (Not recognized), `FIN` (Finance incorrect), `PPD` (Partially paid), `OTH` (Other). Full table: https://docs.get-flowie.com/reference/#reason-codes. France: an AFNOR XP Z12-012 motif code is forwarded verbatim as MDT-113 instead, and `suspended` (with `status=disputed`) transmits 208 Suspendue — so the field accepts free-form codes for those flows.","examples":["NON","REF","LEG","REC","QUA","DEL","PRI","QTY","ITM","PAY","UNR","FIN","PPD","OTH","suspended"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"paymentDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Paymentdate"},"paymentReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paymentreference"},"paymentAmount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Paymentamount"},"paymentCurrency":{"anyOf":[{"type":"string","maxLength":3,"minLength":3,"pattern":"^[A-Z]{3}$"},{"type":"null"}],"title":"Paymentcurrency"},"remainingAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Remainingamount"}},"type":"object","required":["status"],"title":"LifecycleUpdateRequest","examples":[{"note":"Invoice verified against PO","status":"approved"},{"paymentAmount":1815.0,"paymentCurrency":"EUR","paymentDate":"2026-04-15","paymentReference":"PAY-2026-0042","status":"paid"},{"reason":"Unit price on line 3 does not match the quote","reasonCode":"PRI","status":"rejected"}]},"LifecycleUpdateResponse":{"properties":{"documentId":{"type":"string","title":"Documentid"},"previousStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Previousstatus"},"currentStatus":{"type":"string","title":"Currentstatus"},"updatedAt":{"type":"string","format":"date-time","title":"Updatedat"},"compliance":{"additionalProperties":true,"type":"object","title":"Compliance","default":{}},"allowedTransitions":{"items":{"type":"string"},"type":"array","title":"Allowedtransitions","default":[]},"error":{"anyOf":[{"$ref":"#/components/schemas/LifecycleUpdateError"},{"type":"null"}],"description":"Present only on a failed batch item. When set, `currentStatus` is `\"unknown\"` and no transition was applied — never treat the presence of a status field alone as success."}},"type":"object","required":["documentId","currentStatus","updatedAt"],"title":"LifecycleUpdateResponse"},"MandateSignatureRequest":{"properties":{"userIds":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"Userids","description":"Who is asked to sign — Flowie users. The agreement is a designation by the taxpayer, so this is its representative, not your own team."},"objectId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Objectid","description":"The object the approval hangs on. Leave it out and the rendered agreement is stored as a document and used. Supply it to point at a document you already hold — the approval service's `ObjectType` has no portability member, so the check always hangs on a document, never on the request."},"objectType":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Objecttype","description":"Defaults to `DocumentVersion`. Only change it if you passed an `objectId` of another kind that approval accepts."},"expiresAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiresat","description":"When the request to sign lapses, ISO-8601. A mandate nobody signs should expire rather than sit open against a date d'effet that has passed."}},"type":"object","required":["userIds"],"title":"MandateSignatureRequest","description":"Ask someone to sign the designation agreement, in Flowie.","examples":[{"userIds":["usr_01J8..."]},{"objectId":"docv_01J9...","objectType":"DocumentVersion","userIds":["usr_01J8..."]}]},"MandateSignatureResponse":{"properties":{"requestRef":{"type":"string","title":"Requestref"},"checkId":{"type":"string","title":"Checkid","description":"The approval check holding the decision. Sign it in Flowie, or read it back here."},"contextKey":{"type":"string","title":"Contextkey","description":"`portability:<requestRef>:<mandateSha256>` — what binds the decision to this agreement. A vote carrying any other key is not a signature on it."},"objectId":{"type":"string","title":"Objectid"},"objectType":{"type":"string","title":"Objecttype"},"mandateSha256":{"type":"string","title":"Mandatesha256"},"agreementSha256":{"type":"string","title":"Agreementsha256","description":"Digest of the rendered text below — what the signatory actually reads."},"agreement":{"type":"string","title":"Agreement","description":"The designation agreement as presented, with the five items art. 242 nonies E bis requires."},"signed":{"type":"boolean","title":"Signed","description":"False until the check passes."},"signature":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Signature","description":"The recorded act once it exists: who, when, over which digests, and the check it came from."}},"type":"object","required":["requestRef","checkId","contextKey","objectId","objectType","mandateSha256","agreementSha256","agreement","signed"],"title":"MandateSignatureResponse","description":"Where to sign, and what exactly is being signed."},"PaginatedResponse_ApiKeyResponse_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ApiKeyResponse"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},"type":"object","required":["data","hasMore"],"title":"PaginatedResponse[ApiKeyResponse]"},"PaginatedResponse_CompanyListItem_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CompanyListItem"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},"type":"object","required":["data","hasMore"],"title":"PaginatedResponse[CompanyListItem]"},"PaginatedResponse_CompanyResponse_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CompanyResponse"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},"type":"object","required":["data","hasMore"],"title":"PaginatedResponse[CompanyResponse]"},"PaginatedResponse_DirectorySearchResult_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/DirectorySearchResult"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},"type":"object","required":["data","hasMore"],"title":"PaginatedResponse[DirectorySearchResult]"},"PaginatedResponse_DocumentListItem_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/DocumentListItem"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},"type":"object","required":["data","hasMore"],"title":"PaginatedResponse[DocumentListItem]"},"PaginatedResponse_EventResponse_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/EventResponse"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},"type":"object","required":["data","hasMore"],"title":"PaginatedResponse[EventResponse]"},"PaginatedResponse_PartnerListItem_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PartnerListItem"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},"type":"object","required":["data","hasMore"],"title":"PaginatedResponse[PartnerListItem]"},"PaginatedResponse_WebhookResponse_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/WebhookResponse"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},"type":"object","required":["data","hasMore"],"title":"PaginatedResponse[WebhookResponse]"},"PartnerCreate":{"properties":{"peppolId":{"anyOf":[{"type":"string","pattern":"^\\d{4}:.+$"},{"type":"null"}],"title":"Peppolid","description":"Peppol participant ID (scheme:identifier)"},"vatNumber":{"anyOf":[{"type":"string","maxLength":20,"minLength":4},{"type":"null"}],"title":"Vatnumber"},"role":{"anyOf":[{"type":"string","enum":["supplier","buyer","both"]},{"type":"null"}],"title":"Role"},"contactName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contactname"},"contactEmail":{"anyOf":[{"type":"string","pattern":"^[^@]+@[^@]+\\.[^@]+$"},{"type":"null"}],"title":"Contactemail"},"defaults":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Defaults"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"PartnerCreate","examples":[{"contactEmail":"jean@example.com","contactName":"Jean Dupont","peppolId":"0009:BE0123456789","role":"supplier","vatNumber":"BE0123456789"}]},"PartnerListItem":{"properties":{"id":{"type":"string","title":"Id"},"peppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"peppolStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolstatus"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","default":[]},"stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Stats"}},"type":"object","required":["id"],"title":"PartnerListItem"},"PartnerResponse":{"properties":{"id":{"type":"string","title":"Id"},"peppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"contactName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contactname"},"contactEmail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contactemail"},"peppolStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolstatus"},"defaults":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Defaults"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","default":[]},"enrichment":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Enrichment"},"stats":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Stats"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"}},"type":"object","required":["id"],"title":"PartnerResponse","examples":[{"country":"FR","id":"65b3f2f4492c6a505518fe0b","name":"FLOWIE","peppolId":"0009:921376265","role":"supplier","tags":[],"vatNumber":"FR32921376265"}]},"PartnerUpdate":{"properties":{"peppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peppolid"},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"contactName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contactname"},"contactEmail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contactemail"},"defaults":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Defaults"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"PartnerUpdate"},"PartyInfo":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Legal name of the party — BT-27 (seller) / BT-44 (buyer)."},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber","description":"VAT identifier of the party — BT-31 (seller) / BT-48 (buyer). Renders as `cac:PartyTaxScheme/cbc:CompanyID`."},"identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Identifier","description":"Identifier of the party — BT-29 (seller) / BT-46 (buyer). Distinct from `registrationNumber`: this is the party's own identifier in a scheme it chooses, and in France it is typically the 14-digit SIRET (scheme `0009`), where the registration number is the 9-digit SIREN (scheme `0002`). Renders as `cac:PartyIdentification/cbc:ID` in UBL and `ram:GlobalID` in CII."},"identifierScheme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Identifierscheme","description":"Scheme of `identifier` — BT-29-1 (seller) / BT-46-1 (buyer), an ISO/IEC 6523 code. `0009` for a French SIRET."},"registrationNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registrationnumber","description":"Legal registration identifier of the party — BT-30 (seller) / BT-47 (buyer). In France this is the 9-digit SIREN. Renders as `cac:PartyLegalEntity/cbc:CompanyID` in UBL and `ram:SpecifiedLegalOrganization/ram:ID` in CII. When omitted on a French party it is derived from the VAT identifier, which embeds the SIREN (FR + 2 check digits + SIREN)."},"registrationScheme":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registrationscheme","description":"Scheme of `registrationNumber` — BT-30-1 (seller) / BT-47-1 (buyer), an ISO/IEC 6523 code. Defaults to `0002` (SIREN) for a French registration number."},"address":{"anyOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}],"description":"Postal (billing) address. Mapped to the party's billingAddress on the e-invoice: `{street, street2 (from streetLine2), city, zipCode (from postalCode), country}`. Rendered as the party's postal-address group — BG-5 for the seller (BT-35 street, BT-36 street 2, BT-37 city, BT-38 post code, BT-39 country subdivision, BT-40 country) and BG-8 for the buyer (BT-50 / BT-51 / BT-52 / BT-53 / BT-54 / BT-55)."},"shippingAddress":{"anyOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}],"description":"Shipping address (same shape as `address`)."},"contact":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Contact","description":"Single contact `{name?, email?, phone?}`. Its `email` is added to the party's `contacts` list on the e-invoice. Use `contacts` to send several. Rendered as the party's contact group — BT-41/BT-42/BT-43 for the seller, BT-56/BT-57/BT-58 for the buyer."},"contacts":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Contacts","description":"Contact email addresses carried on the party (free-text strings)."}},"type":"object","title":"PartyInfo"},"PartyOverride":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Legal name (BT-27 / BT-44)."},"siret":{"anyOf":[{"type":"string","maxLength":14,"minLength":14,"pattern":"^\\d{14}$"},{"type":"null"}],"title":"Siret","description":"14-digit SIRET. Moves the routing address (BT-34 / BT-49), the party identifier (BT-29b / BT-46b) and — via its first nine digits — the legal identifier (BT-30 / BT-47) together, so the three stay consistent."},"siren":{"anyOf":[{"type":"string","maxLength":9,"minLength":9,"pattern":"^\\d{9}$"},{"type":"null"}],"title":"Siren","description":"9-digit SIREN (BT-30 / BT-47). Overrides the value derived from `siret`."},"vatNumber":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Vatnumber","description":"VAT identifier (BT-31 / BT-48), e.g. `FR96552100554`."},"legalForm":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Legalform","description":"Seller additional legal information (BT-33) — legal form and share capital. France makes it legally mandatory; it is ignored on the buyer, where UBL-CR-244 forbids it."},"address":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Address","description":"`{street, city, postalCode, country}` — the postal address (BG-5 / BG-8)."},"contact":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Contact","description":"`{name?, email?, phone?}` (BG-6 / BG-9)."}},"type":"object","title":"PartyOverride","description":"Swap the catalogue's cast for your own company.\n\nOnly the fields below move; the identifier *schemes* and the address shape\nstay as the catalogue set them, so an override can't quietly produce a\nparty that fails BR-FR-09 (the SIRET/SIREN consistency rule)."},"PartyRef":{"properties":{"role":{"type":"string","enum":["seller","buyer","payer","payee"],"title":"Role","description":"What this party does on the document. `seller` issues and is owed, `buyer` receives and owes; `payer` and `payee` split those apart when the party settling the invoice is not the one that bought (a parent company paying for a subsidiary, a factor collecting on the seller's behalf). Only `seller` and `buyer` map to EN 16931 groups (BG-4 / BG-7) on this pipeline."},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Identifier for the party, in any form `to` accepts — a bare Peppol id (`0208:0123456789`), `peppol:<id>`, `vat:<vatNumber>`, `comp_<orgId>` / `org:<orgId>`. Used as the party's Peppol id when it already is a participant id, which is what lets the document route to them."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Legal name of the party — BT-27 (seller) / BT-44 (buyer)."},"vatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vatnumber","description":"VAT identifier — BT-31 (seller) / BT-48 (buyer)."},"address":{"anyOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}],"description":"Postal (billing) address. Mapped to billingAddress: `{street, street2 (from streetLine2), city, zipCode (from postalCode), country}`. Rendered as BG-5 (BT-35…BT-40) for the seller and BG-8 (BT-50…BT-55) for the buyer."},"shippingAddress":{"anyOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}],"description":"Shipping address (same shape as `address`)."},"contact":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Contact","description":"Single contact `{name?, email?, phone?}`; its `email` is folded into `contacts`."},"contacts":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Contacts","description":"Contact email addresses carried on the party (free-text strings)."},"initiator":{"type":"boolean","title":"Initiator","description":"Marks the party acted by YOUR organization — the one whose API key is sending. Exactly one entry in `parties` must set it, because the document is filed under that party and your template's `customFields` attach to it. On a normal sale that is the seller; on self-billing, where the buyer issues the invoice on the seller's behalf, it is the buyer.","default":false}},"type":"object","required":["role"],"title":"PartyRef","description":"A document party with an explicit role — the building block for\nmulti-party documents (a payer/payee distinct from buyer/seller) and for\nlayouts where the issuer isn't the seller (self-billing).\n\nWhen a `document.parties` list is supplied it OVERRIDES the default\nseller/buyer derivation. Exactly one entry must set `initiator: true` — the\nparty acted by the calling organization (tx-docs requires the acting org to\nbe a party). Roles beyond seller/buyer/payer/payee are not modelled by the\nstructured pipeline today — use `format=ubl-xml` for those."},"PaymentInfo":{"properties":{"means":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Means","description":"How the payment is made. A UNTDID 4461 code is sent as BT-81, the payment means code — `\"30\"` credit transfer, `\"42\"` payment to bank account, `\"48\"` bank card, `\"49\"` direct debit, `\"58\"` SEPA credit transfer, `\"97\"` clearing between partners (the netting a customer and supplier settle against each other), or `\"ZZZ\"` for a means the two of you defined between yourselves. The code list runs 1 to 97 plus `ZZZ`.\n\nAnything else — `\"Virement SEPA\"`, `\"credit_transfer\"` — is sent as BT-82, the payment means description, because free text in BT-81 fails EN 16931 `BR-CL-16`. A number outside the code list is treated the same way rather than being passed off as a code.","examples":["58"]},"iban":{"anyOf":[{"type":"string","maxLength":34,"pattern":"^[A-Z]{2}\\d{2}[A-Z0-9]+$"},{"type":"null"}],"title":"Iban","description":"Account the payment is credited to (BT-84). Renders as `cac:PayeeFinancialAccount/cbc:ID`; the account holder (BT-85) is taken from the seller's name."},"bic":{"anyOf":[{"type":"string","maxLength":11,"pattern":"^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$"},{"type":"null"}],"title":"Bic","description":"BIC of the account's provider (BT-86). Renders as `cac:FinancialInstitutionBranch/cbc:ID`."},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference","description":"Remittance information (BT-83) — the reference the buyer quotes on the transfer so you can reconcile it, e.g. a structured creditor reference. Renders as `cbc:PaymentID`."},"discountTerms":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Discountterms","description":"Early-payment (settlement) discounts, e.g. `[{\"days\": 10, \"percent\": 2.0}]` for \"2 % if paid within 10 days\". **ACCEPTED but NOT YET rendered** by the structured `format=json` pipeline — EN 16931 carries these in the payment terms (BT-20), so state them in `note` (BT-22) if the customer has to read them, or send `format=ubl-xml` to place them exactly."}},"type":"object","title":"PaymentInfo","description":"How the invoice is to be paid (BG-16). Every field below reaches its own\nBT-* element of the e-invoice; they also stay in the human-readable\n`paymentInstructions` line, which is all they used to feed."},"PlatformListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PlatformResponse"},"type":"array","title":"Data"},"total":{"type":"integer","title":"Total"},"source":{"type":"string","title":"Source"},"snapshotDate":{"type":"string","title":"Snapshotdate"}},"type":"object","required":["data","total","source","snapshotDate"],"title":"PlatformListResponse"},"PlatformOnboardRequest":{"properties":{"vatNumber":{"type":"string","title":"Vatnumber"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"address":{"anyOf":[{"$ref":"#/components/schemas/Address"},{"type":"null"}]},"receiveDocuments":{"type":"boolean","title":"Receivedocuments","default":true},"autoVerify":{"type":"boolean","title":"Autoverify","default":false},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"},"webhook":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Webhook"},"apiKey":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Apikey"}},"type":"object","required":["vatNumber"],"title":"PlatformOnboardRequest"},"PlatformOnboardResponse":{"properties":{"company":{"$ref":"#/components/schemas/CompanyResponse"},"apiKey":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Apikey"},"webhook":{"anyOf":[{"$ref":"#/components/schemas/WebhookResponse"},{"type":"null"}]}},"type":"object","required":["company"],"title":"PlatformOnboardResponse"},"PlatformResponse":{"properties":{"name":{"type":"string","title":"Name"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"DGFiP `courriel de contact`."},"portabilityEmail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Portabilityemail","description":"Dedicated inter-PA portability address, when the platform published one."},"contactEmail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contactemail","description":"The address a portability message is actually sent to (portabilityEmail, else email)."},"street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Street","description":"Establishment address, as the DGFiP prints it."},"postcode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postcode"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"City, or the country when the operator is not French."},"registeredOn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registeredon"},"status":{"type":"string","title":"Status","description":"`registered` or `pending_interop` (awaiting the interoperability tests)."},"matricule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matricule","description":"The four-digit `matriculePlateforme` the PPF annuaire routes on, e.g. `0064`. `null` when we cannot prove one — never a guess. The DGFiP publishes the names but not the numbers, so read `matriculeConfidence` before treating this as fact."},"matriculeConfidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matriculeconfidence","description":"`confirmed` — the operator told us the number directly (`matriculeEvidence` carries the date and channel): the strongest source. `published` — the operator states the number itself (`matriculeEvidence` is the URL, `matriculeQuote` the sentence). `inferred` — its own SIREN routes on exactly one matricule in the PPF annuaire and it is the earliest-registered claimant: strong, not proof. `null` — unknown."},"matriculeEvidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matriculeevidence","description":"Where the matricule comes from: a URL, the annuaire reasoning, or when and how the operator confirmed it."},"matriculeQuote":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matriculequote","description":"The operator's own sentence, when `matriculeConfidence` is `published`."},"siren":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Siren","description":"The operator's SIREN, when an `inferred` matricule was proven from it."}},"type":"object","required":["name","status"],"title":"PlatformResponse","description":"One Plateforme Agréée, as registered by the DGFiP."},"PlatformSettingsUpdate":{"properties":{"branding":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Branding"},"defaults":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Defaults"},"customDomain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customdomain"}},"type":"object","title":"PlatformSettingsUpdate"},"PlatformUsageResponse":{"properties":{"period":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Period"},"total":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Total"},"byCompany":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Bycompany"}},"type":"object","title":"PlatformUsageResponse"},"PortabilityMessageListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PortabilityMessageLog"},"type":"array","title":"Data"},"hasMore":{"type":"boolean","title":"Hasmore"},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["data","hasMore","total"],"title":"PortabilityMessageListResponse"},"PortabilityMessageLog":{"properties":{"id":{"type":"string","title":"Id"},"direction":{"type":"string","title":"Direction"},"requestRef":{"type":"string","title":"Requestref"},"messageType":{"type":"string","title":"Messagetype"},"statusCode":{"type":"string","title":"Statuscode"},"state":{"type":"string","title":"State"},"directionRole":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Directionrole"},"taxpayerSiren":{"type":"string","title":"Taxpayersiren"},"taxpayerName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Taxpayername"},"counterpartyPaName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Counterpartypaname"},"effectiveDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effectivedate"},"subject":{"type":"string","title":"Subject"},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"},"recipientSource":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipientsource"},"dispatched":{"type":"boolean","title":"Dispatched"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"sentAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentat"},"createdAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Createdat"},"organizationId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organizationid"},"taxpayerSiret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Taxpayersiret"},"counterpartyPaId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Counterpartypaid"},"csvRow":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Csvrow"},"csvSha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Csvsha256"},"cc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Cc"},"bcc":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Bcc"},"transport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transport"},"providerMessageId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Providermessageid"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"annuaire":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annuaire"}},"type":"object","required":["id","direction","requestRef","messageType","statusCode","state","taxpayerSiren","subject","dispatched"],"title":"PortabilityMessageLog","description":"A logged inter-PA message. Full detail (CSV, hash, annuaire) on GET by id."},"PortabilityMessageRequest":{"properties":{"messageType":{"type":"string","enum":["REQUEST","ACK","DECISION","COMPLETION"],"title":"Messagetype"},"state":{"type":"string","enum":["received","acknowledged","accepted","rejected","auto_accepted","executing","completed","failed"],"title":"State","description":"Internal FSM state; mapped to the codified wire status."},"requestRef":{"type":"string","maxLength":64,"minLength":1,"title":"Requestref","description":"Stable portability request reference."},"directionRole":{"type":"string","enum":["GAINING_PA","LOSING_PA"],"title":"Directionrole","default":"GAINING_PA"},"taxpayerSiren":{"type":"string","pattern":"^[0-9]{9}$","title":"Taxpayersiren","description":"Taxpayer SIREN (9 digits)."},"taxpayerSiret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Taxpayersiret"},"taxpayerName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Taxpayername"},"gainingPaId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gainingpaid"},"gainingPaName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gainingpaname"},"losingPaId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Losingpaid"},"losingPaName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Losingpaname"},"effectiveDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effectivedate","description":"Date d'effet (ISO-8601)."},"transferredAddresses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Transferredaddresses"},"mandateRef":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mandateref"},"mandateSignatory":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mandatesignatory"},"reasonCode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reasoncode","description":"Required when state=rejected."},"reasonText":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reasontext"},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To","description":"Counterparty PA channel address (only used if dispatch is enabled)."}},"type":"object","required":["messageType","state","requestRef","taxpayerSiren"],"title":"PortabilityMessageRequest","description":"Build (and, behind the kill-switch, dispatch) an inter-PA message.","examples":[{"directionRole":"GAINING_PA","effectiveDate":"2026-10-01","gainingPaId":"0064","gainingPaName":"Flowie","losingPaId":"0009:552081317","losingPaName":"Autre PA","mandateRef":"MANDATE-2026-42","mandateSignatory":"Jane Dupont","messageType":"REQUEST","requestRef":"POR-2026-000123","state":"received","taxpayerName":"FLOWIE","taxpayerSiren":"921376265","taxpayerSiret":"92137626500018","transferredAddresses":["0009:92137626500018"]}]},"PortabilityMessageResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Log id (`pmsg_…`) — fetch it back at /v1/portability/messages/{id}."},"subject":{"type":"string","title":"Subject"},"messageType":{"type":"string","title":"Messagetype"},"statusCode":{"type":"string","title":"Statuscode"},"state":{"type":"string","title":"State"},"filename":{"type":"string","title":"Filename"},"csvHeader":{"type":"string","title":"Csvheader"},"csvRow":{"type":"string","title":"Csvrow"},"csv":{"type":"string","title":"Csv"},"csvSha256":{"type":"string","title":"Csvsha256","description":"SHA-256 of the CSV row exactly as sent — the tamper-evident proof."},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To","description":"Counterparty address the message was addressed to."},"recipientSource":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipientsource","description":"How `to` was found: `explicit`, `registry:<PA name>`, or `unresolved`."},"cc":{"items":{"type":"string"},"type":"array","title":"Cc","default":[]},"bcc":{"items":{"type":"string"},"type":"array","title":"Bcc","default":[]},"dispatched":{"type":"boolean","title":"Dispatched"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"`sent`, or why not: `dispatch_disabled`, `not_configured`, `no_recipient`, `sandbox`, or the SMTP error."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"transport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transport","description":"How it left: `notification_center` (platform mail path) or `smtp` (direct relay). Null when it did not leave."},"providerMessageId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Providermessageid"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"annuaire":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annuaire","description":"What the PPF annuaire answered for this SIREN at send time (null when the annuaire is not configured or did not answer)."},"requestDatetime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requestdatetime"},"createdAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Createdat"}},"type":"object","required":["id","subject","messageType","statusCode","state","filename","csvHeader","csvRow","csv","csvSha256","dispatched"],"title":"PortabilityMessageResponse","description":"The built message, where it was sent, and the id it was logged under."},"PortabilityOpenRequest":{"properties":{"taxpayer":{"type":"string","maxLength":256,"minLength":2,"title":"Taxpayer","description":"Same one-string identifier as `/v1/portability/resolve`."},"outgoingPlatform":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Outgoingplatform","description":"The platform being left. Resolved from the annuaire when omitted."},"effectiveDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Effectivedate","description":"ISO date the port takes effect. Defaults to the first business day after the objection window could close."},"addressScope":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Addressscope","description":"Electronic addresses covered by the agreement. Defaults to the resolved annuaire line and Peppol id."},"signatory":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Signatory","description":"Who signs the designation agreement, for the taxpayer. Required by the decree; a request opened without it reports the gap rather than failing. Naming someone **declares** who will sign — it does not sign: `signedAt` stays null and `mandateGaps` keeps reporting `signature` until an act is recorded."},"signedAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signedat","description":"When the agreement was actually signed, ISO-8601. Supply it when the mandate was signed **outside** the platform (on paper, or in your own tool) — you are asserting an act happened, and it is kept as such in the evidence chain. Leave it null to sign in Flowie instead, where an approval decision provides it."},"signatureMethod":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Signaturemethod","description":"How the signature was taken: `paper`, `external`, or `approval` when it came from a Flowie approval decision. Recorded verbatim so a later reader knows what kind of proof exists."},"overrides":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Overrides","description":"Explicit values that win over anything resolved (`name`, `country`, `vatNumber`, `registrationNumber`, `peppolId`)."}},"type":"object","required":["taxpayer"],"title":"PortabilityOpenRequest","description":"Open a migration request. Only `taxpayer` is required.","examples":[{"signatory":"Camille Roy, Directrice Générale","taxpayer":"92137626500017"},{"effectiveDate":"2026-10-01","outgoingPlatform":"0009:552081317","signatory":"Camille Roy","taxpayer":"FR92921376265"}]},"PortabilityParseRequest":{"properties":{"subject":{"type":"string","title":"Subject"},"csvRow":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Csvrow","description":"One CSV data row (18 columns)."}},"type":"object","required":["subject"],"title":"PortabilityParseRequest","description":"Parse an inbound inter-PA message (normalised subject + one CSV row).","examples":[{"csvRow":"POR-2026-000123;REQUEST;REQ;GAINING_PA;921376265;92137626500018;FLOWIE;0064;Flowie;0009:552081317;Autre PA;2026-10-01;0009:92137626500018;MANDATE-2026-42;Jane Dupont;2026-07-17T09:00:00+00:00;;","subject":"[PORTABILITE][REQUEST][REQ][SIREN:921376265][REF:POR-2026-000123]"}]},"PortabilityParseResponse":{"properties":{"messageType":{"type":"string","title":"Messagetype"},"statusCode":{"type":"string","title":"Statuscode"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"Internal FSM state resolved from the wire status code."},"siren":{"type":"string","title":"Siren"},"requestRef":{"type":"string","title":"Requestref"},"fields":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Fields","description":"Parsed CSV columns (present when csvRow was supplied)."}},"type":"object","required":["messageType","statusCode","siren","requestRef"],"title":"PortabilityParseResponse"},"PortabilityRequestEvent":{"properties":{"kind":{"type":"string","enum":["notified","objection","acceptance","annuaire_updated"],"title":"Kind","description":"Which step happened."},"ground":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Ground","description":"For `objection`: the ground stated by the outgoing platform. Only grounds questioning the taxpayer's intent are admissible."},"statement":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Statement","description":"For `objection`: the wording they used, kept verbatim as evidence."},"channelRef":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Channelref","description":"For `notified`: your reference for the message sent."},"entryRef":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Entryref","description":"For `annuaire_updated`: the directory entry reference."}},"type":"object","required":["kind"],"title":"PortabilityRequestEvent","description":"Record one step of the procedure against an open request.","examples":[{"channelRef":"msg-2026-10-01-001","kind":"notified"},{"ground":"unpaid_invoices","kind":"objection","statement":"Contract runs to December."},{"kind":"acceptance"},{"entryRef":"annuaire-921376265-001","kind":"annuaire_updated"}]},"PortabilityRequestResponse":{"properties":{"requestRef":{"type":"string","title":"Requestref","description":"The agreement number. Travels on the wire and is what the administration asks for."},"state":{"type":"string","enum":["received","acknowledged","accepted","rejected","auto_accepted","executing","completed","failed"],"title":"State"},"statusCode":{"type":"string","title":"Statuscode","description":"Wire status code for the state (`REQ`, `ACK`, `ACC`, `REJ`, `TAC`, `MIG`, `CMP`, `ERR`)."},"taxpayer":{"additionalProperties":true,"type":"object","title":"Taxpayer"},"mandate":{"additionalProperties":true,"type":"object","title":"Mandate","description":"The designation agreement, with the five items art. 242 nonies E bis requires."},"mandateGaps":{"items":{"type":"string"},"type":"array","title":"Mandategaps","description":"Which of those items are still missing — `signatory` when nobody is named, `signature` while the agreement is unsigned. Read it before notifying: these are exactly the grounds on which the outgoing platform can object (`mandate_invalid`), five business days later. It sits **beside** the mandate rather than inside it because the mandate object is hashed, and a derived list folded into it would change its own digest when you signed."},"clocks":{"additionalProperties":true,"type":"object","title":"Clocks","description":"Computed deadlines in *jours ouvrés*: `notifyBy`, `objectionWindowEnds`, `annuaireUpdateBy`, `continuityUntil`."},"tacitApproval":{"type":"boolean","title":"Tacitapproval","description":"True once the objection window lapsed with no admissible objection — *le silence vaut accord*."},"objections":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Objections","description":"Every objection received, each flagged `admissible` or not."},"evidence":{"additionalProperties":true,"type":"object","title":"Evidence","description":"The hash-linked evidence chain, its verification result and a manifest digest."}},"type":"object","required":["requestRef","state","statusCode","taxpayer","mandate","clocks","tacitApproval","evidence"],"title":"PortabilityRequestResponse","description":"A migration request: state, the legal clocks, and the proof."},"PortabilityResolveRequest":{"properties":{"taxpayer":{"type":"string","maxLength":256,"minLength":2,"title":"Taxpayer","description":"Any identifier the resolver understands: a SIRET, a SIREN, a VAT number, a Peppol id, a national registration number, a domain, an e-mail, or `name:<company name>`. This is the only value a company has to supply."},"country":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"Country","description":"ISO 3166-1 alpha-2 hint, used only when the identifier does not carry its own country."}},"type":"object","required":["taxpayer"],"title":"PortabilityResolveRequest","description":"Everything we can learn about a taxpayer from one string.","examples":[{"taxpayer":"92137626500017"},{"country":"FR","taxpayer":"name:ACME SAS"}]},"PortabilityResolveResponse":{"properties":{"taxpayer":{"additionalProperties":true,"type":"object","title":"Taxpayer","description":"Resolved identity: name, country, SIREN/SIRET, VAT, Peppol id, annuaire addressing line, and which layer answered (`resolvedFrom`)."},"regime":{"type":"string","title":"Regime","description":"The e-invoicing regime that governs the switch, e.g. `PPF · plateforme agréée`."},"requirements":{"additionalProperties":true,"type":"object","title":"Requirements","description":"What changes and what must be re-granted: `addressChanges`, `reGrant`, `archiveHolder`."},"stillNeeded":{"items":{"type":"string"},"type":"array","title":"Stillneeded","description":"Fields the resolver could not fill and the caller should supply. Empty means the request can be opened as-is."}},"type":"object","required":["taxpayer","regime","requirements"],"title":"PortabilityResolveResponse","description":"What we resolved, and what the switch will require in that country."},"PortabilityRoutingRequest":{"properties":{"organizationId":{"type":"string","title":"Organizationid","description":"The Flowie organization the taxpayer is (or becomes) attached to."},"siren":{"type":"string","pattern":"^[0-9]{9}$","title":"Siren"},"siret":{"anyOf":[{"type":"string","pattern":"^[0-9]{14}$"},{"type":"null"}],"title":"Siret"},"effectiveDate":{"type":"string","title":"Effectivedate","description":"Date d'effet (ISO-8601). Routing starts — or stops — on this date, not on the call."},"role":{"type":"string","enum":["GAINING_PA","LOSING_PA"],"title":"Role","description":"`GAINING_PA` declares the address from the date d'effet; `LOSING_PA` winds it down (emission stops, reception stays open for the minimal-service window).","default":"GAINING_PA"},"connectionId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connectionid","description":"Required when the organization has more than one Sovos connection."},"minimalServiceMonths":{"type":"integer","maximum":36.0,"minimum":0.0,"title":"Minimalservicemonths","description":"How long the outgoing platform keeps receiving after the switch. 12 months since LFI 2026.","default":12}},"type":"object","required":["organizationId","siren","effectiveDate"],"title":"PortabilityRoutingRequest","description":"Declare (or wind down) the taxpayer's routing address at the date d'effet.","examples":[{"effectiveDate":"2026-10-01","organizationId":"019c76b2-9c94-7000-8cb6-ef104afb6093","role":"GAINING_PA","siren":"921376265","siret":"92137626500018"}]},"PortabilityRoutingResponse":{"properties":{"organizationId":{"type":"string","title":"Organizationid"},"connectionId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connectionid"},"role":{"type":"string","title":"Role"},"siren":{"type":"string","title":"Siren"},"siret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Siret"},"effectiveDate":{"type":"string","title":"Effectivedate"},"serviceUntil":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Serviceuntil","description":"End of the minimal-service window (LOSING_PA only)."},"created":{"type":"boolean","title":"Created","description":"True when the address did not exist and was declared, false when an existing one was updated."},"address":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Address","description":"The routing address as the compliance backend now holds it."},"annuaire":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Annuaire","description":"The PPF annuaire read straight back: `propagated` says whether it already routes on our matricule (usually false for a few days), with the dates it holds."}},"type":"object","required":["organizationId","role","siren","effectiveDate","created"],"title":"PortabilityRoutingResponse"},"RoutingCodeSearchRequest":{"properties":{"filters":{"additionalProperties":true,"type":"object","title":"Filters"},"include":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Include"},"fields":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Fields"},"sorting":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Sorting"},"ignore":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Ignore","default":0},"limit":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","default":50}},"type":"object","title":"RoutingCodeSearchRequest","description":"Request body for POST /v1/routing-code/search."},"RoutingCodeSearchResponse":{"properties":{"search":{"additionalProperties":true,"type":"object","title":"Search"},"totalNumberOfResults":{"type":"integer","title":"Totalnumberofresults"},"results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Results"}},"type":"object","required":["search","totalNumberOfResults","results"],"title":"RoutingCodeSearchResponse","description":"Response body for POST /v1/routing-code/search."},"SandboxBootstrapRequest":{"properties":{"label":{"type":"string","maxLength":64,"title":"Label","description":"Free-form tag for the issued key — appears in the dashboard.","default":"quickstart"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Optional contact email (we may follow up with usage tips)."},"keyType":{"type":"string","pattern":"^(personal|platform|white_label)$","title":"Keytype","description":"Key type to mint. `personal` (default) is the regular tenant key every integrator gets. `platform` and `white_label` mint a key that satisfies the platform-key gate on `/v1/platform/*` so you can exercise multi-tenant ops in sandbox without involving sales.","default":"personal"}},"type":"object","title":"SandboxBootstrapRequest","description":"Optional metadata for the bootstrapped sandbox tenant."},"SandboxBootstrapResponse":{"properties":{"organizationId":{"type":"string","title":"Organizationid"},"apiKey":{"type":"string","title":"Apikey"},"keyPrefix":{"type":"string","title":"Keyprefix"},"keyType":{"type":"string","title":"Keytype","default":"personal"},"company":{"additionalProperties":true,"type":"object","title":"Company"},"expiresAt":{"type":"string","title":"Expiresat"},"nextSteps":{"items":{"type":"string"},"type":"array","title":"Nextsteps"},"docs":{"additionalProperties":true,"type":"object","title":"Docs"}},"type":"object","required":["organizationId","apiKey","keyPrefix","company","expiresAt","nextSteps","docs"],"title":"SandboxBootstrapResponse","description":"The single response a brand-new integrator needs to start testing."},"SandboxClockAdvanceRequest":{"properties":{"companyId":{"type":"string","title":"Companyid","description":"Company whose virtual clock should be advanced."},"by":{"type":"string","title":"By","description":"How far to jump. Accepts compact units: `1h`, `3d`, `2w`, `1m`, `1y`.","examples":["60d","12h","1y"]}},"type":"object","required":["companyId","by"],"title":"SandboxClockAdvanceRequest"},"SandboxClockResetRequest":{"properties":{"companyId":{"type":"string","title":"Companyid"}},"type":"object","required":["companyId"],"title":"SandboxClockResetRequest"},"SandboxClockResponse":{"properties":{"companyId":{"type":"string","title":"Companyid"},"offsetSeconds":{"type":"integer","title":"Offsetseconds"},"virtualNow":{"type":"string","title":"Virtualnow"}},"type":"object","required":["companyId","offsetSeconds","virtualNow"],"title":"SandboxClockResponse"},"SandboxIdempotencyFlushResponse":{"properties":{"organizationId":{"type":"string","title":"Organizationid"},"deletedEntries":{"type":"integer","title":"Deletedentries"}},"type":"object","required":["organizationId","deletedEntries"],"title":"SandboxIdempotencyFlushResponse"},"SandboxRateLimitExhaustRequest":{"properties":{"durationSeconds":{"type":"integer","maximum":3600.0,"minimum":1.0,"title":"Durationseconds","description":"How long the forced 429 should last. Max 1 hour.","default":60}},"type":"object","title":"SandboxRateLimitExhaustRequest"},"SandboxRateLimitExhaustResponse":{"properties":{"organizationId":{"type":"string","title":"Organizationid"},"durationSeconds":{"type":"integer","title":"Durationseconds"},"expiresAt":{"type":"string","title":"Expiresat"}},"type":"object","required":["organizationId","durationSeconds","expiresAt"],"title":"SandboxRateLimitExhaustResponse"},"SandboxResetRequest":{"properties":{"confirm":{"type":"string","const":"yes","title":"Confirm","description":"Type the literal string `yes` to acknowledge the wipe."},"scope":{"type":"string","enum":["all","documents","events","idempotency"],"title":"Scope","description":"What to wipe. Defaults to everything.","default":"all"}},"type":"object","required":["confirm"],"title":"SandboxResetRequest","description":"Body for POST /v1/sandbox/reset.\n\nRequires ``confirm: \"yes\"`` to prevent accidental wipes of an integrator's\nfixtures during day-to-day work. ``scope`` lets the caller narrow the\nblast radius to a single resource type."},"SandboxResetResponse":{"properties":{"deletedDocuments":{"type":"integer","title":"Deleteddocuments","default":0},"deletedEvents":{"type":"integer","title":"Deletedevents","default":0},"deletedIdempotencyEntries":{"type":"integer","title":"Deletedidempotencyentries","default":0},"organizationId":{"type":"string","title":"Organizationid"}},"type":"object","required":["organizationId"],"title":"SandboxResetResponse"},"ScenarioListResponse":{"properties":{"scenarios":{"items":{"$ref":"#/components/schemas/ScenarioOut"},"type":"array","title":"Scenarios"},"summary":{"additionalProperties":true,"type":"object","title":"Summary","description":"Counts by theme, family and channel."},"referential":{"additionalProperties":true,"type":"object","title":"Referential","description":"Which referential and schematron version this catalogue tracks."}},"type":"object","required":["scenarios","summary","referential"],"title":"ScenarioListResponse"},"ScenarioOut":{"properties":{"id":{"type":"string","title":"Id"},"case":{"type":"string","title":"Case","description":"XP Z12-014 case number, or `—` where the concept is an attribute rather than a numbered case."},"caseTitleFr":{"type":"string","title":"Casetitlefr","description":"The official French title from Annexe A."},"title":{"type":"string","title":"Title","description":"What happens, in one line of business English."},"theme":{"type":"string","title":"Theme"},"family":{"type":"string","title":"Family","description":"AFNOR's own split: `data`, `third-party`, `lifecycle` — or `foundation`."},"channel":{"type":"string","title":"Channel","description":"`e-invoicing` (through the PA network) or `e-reporting` (data to the DGFiP)."},"documentType":{"type":"string","title":"Documenttype"},"typeCode":{"type":"string","title":"Typecode","description":"BT-3 — the invoice type code (BR-FR-04)."},"typeCodeLabel":{"type":"string","title":"Typecodelabel"},"cadre":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cadre","description":"BT-23 — the cadre de facturation (BR-FR-08)."},"cadreLabel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cadrelabel"},"story":{"type":"string","title":"Story","description":"The business narrative: who does what, and for how much."},"why":{"type":"string","title":"Why","description":"Why the reform treats this as its own case."},"watchOut":{"type":"string","title":"Watchout","description":"The trap that gets this case rejected or mis-declared."},"keyData":{"items":{"$ref":"#/components/schemas/KeyDatumOut"},"type":"array","title":"Keydata"},"lifecycle":{"items":{"type":"string"},"type":"array","title":"Lifecycle","description":"The 200–213 statuses this case drives."},"sendFormat":{"type":"string","title":"Sendformat","description":"`json` or `ubl-xml` — the format that carries every field the case needs."},"validatesAsEInvoice":{"type":"boolean","title":"Validatesaseinvoice","description":"False when the case is deliberately not an e-invoice — a B2C receipt has no buyer SIREN and no routing address, so it cannot pass an e-invoicing validator and is not meant to."},"versionAdded":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Versionadded","description":"The referential revision that introduced the case."}},"type":"object","required":["id","case","caseTitleFr","title","theme","family","channel","documentType","typeCode","typeCodeLabel","story","why","watchOut","keyData","lifecycle","sendFormat","validatesAsEInvoice"],"title":"ScenarioOut","description":"A business situation the French reform recognises."},"ScopeCatalogueResponse":{"properties":{"scopes":{"items":{"$ref":"#/components/schemas/ScopeDescriptor"},"type":"array","title":"Scopes"}},"type":"object","required":["scopes"],"title":"ScopeCatalogueResponse"},"ScopeDescriptor":{"properties":{"scope":{"type":"string","title":"Scope"},"label":{"type":"string","title":"Label"},"description":{"type":"string","title":"Description"}},"type":"object","required":["scope","label","description"],"title":"ScopeDescriptor"},"SearchFlowFilters":{"properties":{"updatedAfter":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedafter"},"updatedBefore":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedbefore"},"processingRule":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Processingrule"},"flowType":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Flowtype"},"flowDirection":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Flowdirection"},"trackingId":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Trackingid"},"ackStatus":{"anyOf":[{"type":"string","enum":["Pending","Ok","Error"]},{"type":"null"}],"title":"Ackstatus"}},"type":"object","title":"SearchFlowFilters","description":"Filters for POST /v1/flows/search (SearchFlowFilters)."},"SearchFlowParams":{"properties":{"limit":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","default":25},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"},"where":{"$ref":"#/components/schemas/SearchFlowFilters"}},"type":"object","title":"SearchFlowParams","description":"Request body for POST /v1/flows/search."},"SearchFlowResponse":{"properties":{"limit":{"type":"integer","title":"Limit"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nextcursor"},"filters":{"additionalProperties":true,"type":"object","title":"Filters"},"results":{"items":{"$ref":"#/components/schemas/Flow"},"type":"array","title":"Results"}},"type":"object","required":["limit","filters","results"],"title":"SearchFlowResponse","description":"SearchFlowContent — response body for POST /v1/flows/search."},"SearchRequest":{"properties":{"query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Query","description":"Free-text query — same semantics as the list `search` param (matches document number and party names)."},"filters":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filters","description":"Predicate tree. Each leaf is `{\"<field>\": <match>}` where the match is a bare scalar (exact), a bare list (any-of) or an operator object such as `{\"$gte\": …}`; combine leaves with `$and` / `$or`. Operators: `$eq` `$ne` `$lt` `$lte` `$gt` `$gte` `$contains` `$notContains` `$in` `$notIn` `$null` `$notNull` (no `$not`). Fields are the stored names (`documentType`, `issuedAt`, `totalAmountDue`, `lifecycleStatus`, `number`, `currency`, `sellerId`/`payerId`, `createdAt`/`updatedAt`), not the friendly list query params. `companyId` is special-cased as a tenant selector, not a column filter."},"sort":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"string"},{"items":{},"type":"array"},{"type":"null"}],"title":"Sort","description":"Sort spec as a `{field: \"asc\"|\"desc\"}` map, e.g. `{\"issuedAt\": \"desc\"}`. Multiple keys apply left to right. A `\"field:direction\"` string, or a list mixing either form, is also accepted."},"limit":{"type":"integer","title":"Limit","description":"Page size, clamped to 1–100. Default 20.","default":20},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor","description":"Opaque pagination cursor from the previous page."}},"type":"object","title":"SearchRequest","examples":[{"filters":{"$and":[{"documentType":{"$eq":"INVOICE"}},{"issuedAt":{"$gte":"2026-01-01","$lte":"2026-06-30"}},{"$or":[{"lifecycleStatus":{"$eq":"disputed"}},{"totalAmountDue":{"$gte":10000}}]}]},"limit":50,"query":"ACME","sort":{"issuedAt":"desc"}}]},"SendDocumentResponse":{"properties":{"id":{"type":"string","title":"Id"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Number"},"from":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"From"},"to":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"To"},"totals":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Totals"},"deliveryStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deliverystatus"},"sentAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sentat"},"fileId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fileid"},"storedFormat":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Storedformat"}},"type":"object","required":["id"],"title":"SendDocumentResponse"},"SimpleGenerateRequest":{"properties":{"seller":{"$ref":"#/components/schemas/SimpleParty"},"buyer":{"$ref":"#/components/schemas/SimpleParty"},"lines":{"items":{"$ref":"#/components/schemas/SimpleLine"},"type":"array","maxItems":500,"minItems":1,"title":"Lines"},"number":{"type":"string","maxLength":35,"title":"Number","description":"Invoice number (BT-1). BR-FR-01 caps it at 35 characters."},"documentType":{"type":"string","enum":["invoice","credit-note"],"title":"Documenttype","description":"A credit note becomes type 381, not a negative invoice (BR-FR-04).","default":"invoice"},"issueDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Issuedate","description":"Issue date (BT-2), `YYYY-MM-DD`."},"dueDate":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Duedate","description":"Payment due date (BT-9), `YYYY-MM-DD`."},"currency":{"anyOf":[{"type":"string","pattern":"^[A-Z]{3}$"},{"type":"null"}],"title":"Currency","description":"Invoice currency (BT-5), ISO 4217, upper case.","default":"EUR"},"vatRegime":{"type":"string","enum":["standard","reverse-charge-subcontracting","franchise-en-base","intra-community","export"],"title":"Vatregime","description":"The VAT situation. Anything but `standard` zero-rates every line and attaches the exemption reason EN 16931 demands (BR-E/AE/IC/G-10).","default":"standard"},"alreadyPaid":{"type":"boolean","title":"Alreadypaid","description":"The invoice is settled on issue. Moves the cadre to B2 / S2 / M2 **and** sets the paid amount (BT-113) to the full total, so BT-115 comes out at zero instead of still asking to be paid.","default":false},"paidAmount":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Paidamount","description":"A part-payment already received (BT-113), deducted from the amount due (BR-CO-16). Overrides the full amount `alreadyPaid` would set."},"vatOnDebits":{"type":"boolean","title":"Vatondebits","description":"The seller opted for *TVA sur les débits*, so services are taxable on the invoice date.","default":false},"deliveryCountry":{"anyOf":[{"type":"string","maxLength":2,"minLength":2,"pattern":"^[A-Z]{2}$"},{"type":"null"}],"title":"Deliverycountry","description":"Where the goods went (BT-80). Required in substance on an intra-community supply (BR-IC-12); inferred from the buyer's country when you omit it."},"payment":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payment","description":"`{iban?, bic?, means?, meansName?, reference?, terms?}`. With an IBAN the invoice declares a credit transfer (code 30); without one it declares code 1, because claiming a transfer with no account fails BR-61."},"buyerReference":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Buyerreference","description":"The buyer's own reference (BT-10) — their PO or cost centre."},"orderReference":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Orderreference","description":"Purchase order number (BT-13)."},"corrects":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Corrects","description":"`{number, issueDate}` — the invoice this credit note or correction refers to (BG-3)."},"note":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Note","description":"A free-text note to add alongside the legal mentions (BT-22)."},"format":{"type":"string","enum":["json","ubl-xml"],"title":"Format","description":"Which `POST /v1/documents/send` body to build.","default":"ubl-xml"}},"type":"object","required":["seller","buyer","lines","number"],"title":"SimpleGenerateRequest","description":"Your parties and your lines. Everything the reform adds is inferred."},"SimpleGenerateResponse":{"properties":{"document":{"additionalProperties":true,"type":"object","title":"Document","description":"The invoice model that was built, including everything inferred."},"ubl":{"type":"string","title":"Ubl","description":"EN 16931 UBL 2.1 XML — an `Invoice` or a `CreditNote`."},"totals":{"additionalProperties":true,"type":"object","title":"Totals","description":"BT-106 … BT-115 plus the per-rate VAT breakdown."},"inferred":{"items":{"$ref":"#/components/schemas/InferenceOut"},"type":"array","title":"Inferred","description":"Every field derived from your data, with the rule that forced it."},"warnings":{"items":{"$ref":"#/components/schemas/SimpleWarningOut"},"type":"array","title":"Warnings","description":"Things that generate but deserve a second look."},"sendRequest":{"additionalProperties":true,"type":"object","title":"Sendrequest","description":"The exact `POST /v1/documents/send` body that sends it."}},"type":"object","required":["document","ubl","totals","inferred","sendRequest"],"title":"SimpleGenerateResponse"},"SimpleLine":{"properties":{"description":{"type":"string","maxLength":1000,"title":"Description","description":"What was sold (BT-153)."},"quantity":{"type":"number","title":"Quantity","description":"How many (BT-129)."},"unitPrice":{"type":"number","title":"Unitprice","description":"Price per unit, excluding VAT (BT-146)."},"vatRate":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Vatrate","description":"VAT percentage (BT-152). Ignored when `vatRegime` is not `standard` — an exempt line is 0 by definition. A rate outside the French set is generated but warned about; 0 on a standard-rated line is refused, because BR-S-05 rejects it — say which exemption applies with `vatRegime` instead.","default":20},"kind":{"anyOf":[{"type":"string","enum":["goods","services"]},{"type":"null"}],"title":"Kind","description":"What this line is. It decides the *cadre de facturation* (BT-23) and the tax point (BT-8): goods are taxable on delivery, services on collection.","default":"services"},"unit":{"anyOf":[{"type":"string","maxLength":10},{"type":"null"}],"title":"Unit","description":"UN/ECE Rec 20 unit code (BT-130). Defaults to `C62` for goods, `HUR` for services."}},"type":"object","required":["description","quantity","unitPrice"],"title":"SimpleLine","description":"One line, described the way an invoice describes it."},"SimpleParty":{"properties":{"name":{"type":"string","maxLength":200,"title":"Name","description":"Legal name (BT-27 / BT-44)."},"siret":{"type":"string","maxLength":14,"minLength":14,"pattern":"^\\d{14}$","title":"Siret","description":"14-digit SIRET. One field fills three: the routing address (BT-34 / BT-49), the party identifier (BT-29b / BT-46b) and — via its first nine digits — the legal identifier (BT-30 / BT-47), so they cannot disagree (BR-FR-09)."},"siren":{"anyOf":[{"type":"string","maxLength":9,"minLength":9,"pattern":"^\\d{9}$"},{"type":"null"}],"title":"Siren","description":"Only when the SIREN is not the first nine digits of the SIRET."},"vatNumber":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Vatnumber","description":"VAT identifier (BT-31 / BT-48), e.g. `FR96552100554`."},"legalForm":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Legalform","description":"Seller only, and required there: legal form and share capital (BT-33), e.g. `SARL au capital de 50 000 EUR — RCS Paris 552 100 554`. France makes it mandatory and UBL-CR-244 forbids it on the buyer."},"address":{"additionalProperties":true,"type":"object","title":"Address","description":"`{street, city, postalCode, country}` (BG-5 / BG-8) — all four required. BR-08 / BR-10 want the address; France adds the street (BT-35 / BT-38) and the town (BT-37 / BT-40) on top."},"contact":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Contact","description":"`{name?, email?, phone?}` (BG-6 / BG-9)."}},"type":"object","required":["name","siret","address"],"title":"SimpleParty","description":"A party on a simple invoice, in the terms a company knows about itself."},"SimpleWarningOut":{"properties":{"field":{"type":"string","title":"Field"},"message":{"type":"string","title":"Message"},"detail":{"type":"string","title":"Detail"}},"type":"object","required":["field","message","detail"],"title":"SimpleWarningOut","description":"Legal, but probably not what you meant."},"SirenSearchRequest":{"properties":{"filters":{"additionalProperties":true,"type":"object","title":"Filters"},"sorting":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Sorting"},"fields":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Fields"},"limit":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","default":50},"ignore":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Ignore","default":0}},"type":"object","title":"SirenSearchRequest","description":"Request body for POST /v1/siren/search."},"SirenSearchResponse":{"properties":{"search":{"additionalProperties":true,"type":"object","title":"Search"},"totalNumberOfResults":{"type":"integer","title":"Totalnumberofresults"},"results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Results"}},"type":"object","required":["search","totalNumberOfResults","results"],"title":"SirenSearchResponse","description":"Response body for POST /v1/siren/search."},"SiretSearchRequest":{"properties":{"filters":{"additionalProperties":true,"type":"object","title":"Filters"},"sorting":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Sorting"},"fields":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Fields"},"include":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Include"},"limit":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","default":50},"ignore":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Ignore","default":0}},"type":"object","title":"SiretSearchRequest","description":"Request body for POST /v1/siret/search."},"SiretSearchResponse":{"properties":{"search":{"additionalProperties":true,"type":"object","title":"Search"},"totalNumberOfResults":{"type":"integer","title":"Totalnumberofresults"},"results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Results"}},"type":"object","required":["search","totalNumberOfResults","results"],"title":"SiretSearchResponse","description":"Response body for POST /v1/siret/search."},"StatsResponse":{"properties":{"period":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Period"},"quota":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Quota"},"rateLimit":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ratelimit"},"documents":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Documents"},"byType":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Bytype"},"byCountry":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Bycountry"},"partners":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Partners"}},"type":"object","title":"StatsResponse"},"StructuredDocumentResponse":{"properties":{"id":{"type":"string","title":"Id"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"direction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Direction"},"number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Number"},"issueDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuedate"},"dueDate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duedate"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"grossAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Grossamount"},"netAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Netamount"},"vatAmount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Vatamount"},"lifecycleStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lifecyclestatus"},"deliveryStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deliverystatus"},"senderPeppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Senderpeppolid"},"senderName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sendername"},"senderVatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sendervatnumber"},"receiverPeppolId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Receiverpeppolid"},"receiverName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Receivername"},"receiverVatNumber":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Receivervatnumber"},"buyerReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyerreference"},"orderReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Orderreference"},"paymentIban":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paymentiban"},"paymentReference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paymentreference"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"receivedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Receivedat"},"sentAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sentat"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"updatedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updatedat"}},"type":"object","required":["id"],"title":"StructuredDocumentResponse","description":"Flat agent-friendly version with all primitives."},"TokenRequest":{"properties":{"grant_type":{"type":"string","const":"authorization_code","title":"Grant Type"},"code":{"type":"string","minLength":10,"title":"Code","description":"The one-time code returned on consent."},"code_verifier":{"type":"string","maxLength":128,"minLength":43,"title":"Code Verifier","description":"The original PKCE verifier (the agent's secret)."}},"type":"object","required":["grant_type","code","code_verifier"],"title":"TokenRequest","description":"Step 4 — agent exchanges the auth code for an API key.\n\nImplements RFC 7636 PKCE: server hashes ``code_verifier`` with SHA-256\nand compares to the original ``code_challenge``."},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"The issued API key. Format: flw_test_…"},"token_type":{"type":"string","const":"Bearer","title":"Token Type","default":"Bearer"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes"},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the issued key expires."},"company_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Id","description":"Sandbox company tied to the key."},"organization_id":{"type":"string","title":"Organization Id","description":"Sandbox org the key belongs to."}},"type":"object","required":["access_token","scopes","expires_in","organization_id"],"title":"TokenResponse"},"ValidateRequest":{"properties":{"type":{"type":"string","enum":["invoice","credit-note","debit-note","purchase-order","purchase-request","sales-order","quote","goods-receipt","event"],"title":"Type"},"format":{"anyOf":[{"type":"string","enum":["json","ubl-xml","cii-xml"]},{"type":"null"}],"title":"Format"},"from":{"type":"string","title":"From"},"to":{"type":"string","title":"To"},"document":{"anyOf":[{"$ref":"#/components/schemas/DocumentBody"},{"type":"null"}]},"xml":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Xml"}},"type":"object","required":["type","from","to"],"title":"ValidateRequest"},"ValidateResponse":{"properties":{"valid":{"type":"boolean","title":"Valid"},"errors":{"items":{"$ref":"#/components/schemas/ErrorDetail"},"type":"array","title":"Errors","default":[]},"warnings":{"items":{"$ref":"#/components/schemas/ErrorDetail"},"type":"array","title":"Warnings","default":[]},"preview":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Preview"}},"type":"object","required":["valid"],"title":"ValidateResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ValidationMessageOut":{"properties":{"step":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Step","description":"`XSD`, `EN16931` or `BR-FR-Flux2`."},"ruleId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ruleid","description":"The failing rule, e.g. `BR-CO-14`."},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"The XPath the rule fired on — where to look in your document."},"businessTerms":{"items":{"type":"string"},"type":"array","title":"Businessterms","description":"The EN 16931 terms the rule is about, e.g. `[\"BT-9\"]` — what to go and fix."},"severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"}},"type":"object","title":"ValidationMessageOut"},"VerifyRequest":{"properties":{"peppolId":{"type":"string","title":"Peppolid"},"documentType":{"type":"string","title":"Documenttype"}},"type":"object","required":["peppolId","documentType"],"title":"VerifyRequest"},"VerifyResponse":{"properties":{"peppolId":{"type":"string","title":"Peppolid"},"exists":{"type":"boolean","title":"Exists"},"canReceive":{"type":"boolean","title":"Canreceive"},"recipientName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipientname"},"documentType":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Documenttype"},"accessPoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accesspoint"},"acceptedBy":{"items":{"type":"string"},"type":"array","title":"Acceptedby","description":"Every addressing line of this company that advertises the requested document type — the addresses it can be sent to. A French company ID such as `0009:{siren}` is never one of them: the network carries the company's lines (`0225:{siren}_{suffix}`), and `canReceive` on the company ID means at least one of them accepts the document. Asking about one line answers `canReceive` for that line alone, while this still names the company's other lines that do accept it. Empty when the company publishes no line — every participant outside France."}},"type":"object","required":["peppolId","exists","canReceive"],"title":"VerifyResponse"},"WebhookCreate":{"properties":{"url":{"type":"string","pattern":"^https?://","title":"Url","description":"HTTPS webhook endpoint URL"},"events":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Events","description":"Event types to subscribe to: document.received, document.updated, document.sent, document.delivered, document.failed, lifecycle.updated, *"},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret"},"companyId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"}},"type":"object","required":["url","events"],"title":"WebhookCreate","examples":[{"events":["document.received","document.delivered"],"secret":"whsec_abc123","url":"https://example.com/webhooks/peppol"}]},"WebhookMetadata":{"properties":{"flowType":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flowtype"},"flowDirection":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flowdirection"},"processingRule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Processingrule"},"ackStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ackstatus"}},"type":"object","title":"WebhookMetadata","description":"WebhookMetadata — subscription filters.\n\nXP Z12-013 v1.3.0 removed every required metadata field — an empty\n``metadata`` object subscribes to all flow events. All fields are optional."},"WebhookParams":{"properties":{"callbackUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Callbackurl"},"flowTypes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Flowtypes"},"flowDirection":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Flowdirection"},"ackStatus":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ackstatus"},"callback":{"anyOf":[{"$ref":"#/components/schemas/CallbackParams"},{"type":"null"}]},"metadata":{"anyOf":[{"$ref":"#/components/schemas/WebhookMetadata"},{"type":"null"}]}},"type":"object","title":"WebhookParams","description":"Request body for POST /v1/webhooks.\n\nThe AFNOR XP Z12-013 v1.3.0 swagger (`WebhookParams`) is the source of\ntruth: a flat body ``{callbackUrl, flowTypes[], flowDirection, ackStatus}``.\nFor backward compatibility this model ALSO accepts the legacy nested shape\n``{callback: {url, …}, metadata: {flowType, flowDirection, ackStatus}}`` so\nexisting consumers keep working. Read normalized values via the\n``resolved_*`` properties. At least one URL source is required."},"WebhookPatchPayload":{"properties":{"headers":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Headers"},"authentication":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Authentication"},"signature":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Signature"}},"type":"object","title":"WebhookPatchPayload","description":"Request body for PATCH /v1/webhooks/{webhookUid} (technical-only update)."},"WebhookResponse":{"properties":{"id":{"type":"string","title":"Id"},"url":{"type":"string","title":"Url"},"events":{"items":{"type":"string"},"type":"array","title":"Events","default":[]},"status":{"anyOf":[{"type":"string","enum":["active","paused","disabled"]},{"type":"null"}],"title":"Status"},"companyId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companyid"},"createdAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Createdat"},"lastDeliveredAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Lastdeliveredat"},"failureCount":{"type":"integer","title":"Failurecount","default":0}},"type":"object","required":["id","url"],"title":"WebhookResponse","examples":[{"events":["document.received","document.delivered"],"failureCount":0,"id":"wh_abc123","status":"active","url":"https://example.com/webhooks/peppol"}]},"WebhookUpdate":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"events":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Events"},"rotateSecret":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rotatesecret"}},"type":"object","title":"WebhookUpdate"}}},"servers":[{"url":"https://back.p2p-flowie.com/exchange","description":"Production"},{"url":"https://back.flowie.ink/exchange","description":"Sandbox"}],"externalDocs":{"url":"https://docs.get-flowie.com","description":"Full developer documentation (HTML). Versioning and deprecation policy: https://docs.get-flowie.com/deprecation-policy"}}