Flowie Exchange
Sign up Get a test key →
API v3.0 · Released April 2026

One API for Peppol e-invoicing.
From freelancer to platform.

Send and receive e-invoices across 47 countries — Europe, MENA, and Asia-Pacific. Peppol, regulatory reporting, and clearance handled — so you ship services or goods, not paperwork.

Start in 5 minutes → Browse the API

Universal Peppol access point

Coverage across 47 countries on four continents — single integration for Europe, MENA, and Asia-Pacific. Auto-SMP registration and directory verification included.

Compliance on autopilot

PPF (FR) and SDI (IT) are reported automatically when you update invoice status. Belgium runs pure Peppol — no separate report needed. Zero extra wiring.

Platform & white-label

Manage thousands of tenant companies under one account. Scoped keys, per-tenant quotas, custom branding.

Reliable webhooks

Signed deliveries, exponential retries, at-least-once guarantees. Event replay through the Events API.

Structured or raw

Send invoices as JSON and we generate valid UBL 2.1. Or send your own UBL/CII — we validate and deliver it.

AFNOR XP Z12-013 ready

French PDP-compliant adapter, cXML PunchOut, SIRET/SIREN directory — all behind the same account.

Send your first invoice in 5 minutes

Sign up, grab a test API key, and fire three requests. No SDK required — it's just JSON over HTTPS.

  1. Authenticate

    Every request carries a bearer token — either a Flowie JWT (if you already use the dashboard) or an Exchange API key (flw_live_… / flw_test_…).

    export FLOWIE_KEY="flw_test_your_key_here"
    curl https://back.p2p-flowie.com/exchange/v1/companies \
      -H "Authorization: Bearer $FLOWIE_KEY"
    import os, httpx
    client = httpx.Client(
        base_url="https://back.p2p-flowie.com/exchange/v1",
        headers={"Authorization": f"Bearer {os.environ['FLOWIE_KEY']}"},
    )
    me = client.get("/companies").json()
    const flowie = (path, init = {}) =>
      fetch(`https://back.p2p-flowie.com/exchange/v1${path}`, {
        ...init,
        headers: {
          "Authorization": `Bearer ${process.env.FLOWIE_KEY}`,
          "Content-Type": "application/json",
          ...(init.headers || {}),
        },
      }).then(r => r.json());
    req, _ := http.NewRequest("GET",
        "https://back.p2p-flowie.com/exchange/v1/companies", nil)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("FLOWIE_KEY"))
    resp, err := http.DefaultClient.Do(req)
  2. Register the sending company

    Pass a VAT number. We enrich the legal name, address, and Peppol identifier for you, then publish the company to the Peppol SMP.

    curl -X POST https://back.p2p-flowie.com/exchange/v1/companies \
      -H "Authorization: Bearer $FLOWIE_KEY" \
      -H "Content-Type: application/json" \
      -d '{"vatNumber": "BE0123456789"}'
    company = client.post("/companies", json={"vatNumber": "BE0123456789"}).json()
    print(company["peppolId"])   # → "0208:0123456789"
    const company = await flowie("/companies", {
      method: "POST",
      body: JSON.stringify({ vatNumber: "BE0123456789" }),
    });
    console.log(company.peppolId);  // "0208:0123456789"
    body := strings.NewReader(`{"vatNumber":"BE0123456789"}`)
    req, _ := http.NewRequest("POST",
        "https://back.p2p-flowie.com/exchange/v1/companies", body)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("FLOWIE_KEY"))
    req.Header.Set("Content-Type", "application/json")
    http.DefaultClient.Do(req)
  3. Send an invoice

    Describe the invoice in JSON, set an Idempotency-Key, and we deliver it — UBL-XML-formatted and Peppol-signed — to the recipient's access point.

    curl -X POST https://back.p2p-flowie.com/exchange/v1/documents/send \
      -H "Authorization: Bearer $FLOWIE_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: inv-2026-001" \
      -d '{
        "type": "invoice",
        "from": "comp_abc123",
        "to":   "0208:9876543210",
        "document": {
          "number": "INV-2026-001",
          "issueDate": "2026-04-25",
          "dueDate":   "2026-05-25",
          "currency":  "EUR",
          "lines": [{
            "description": "Consulting, April 2026",
            "quantity": 10, "unit": "hours",
            "unitPrice": 150.00, "vatRate": 21
          }]
        }
      }'
    doc = client.post(
        "/documents/send",
        headers={"Idempotency-Key": "inv-2026-001"},
        json={
            "type": "invoice",
            "from": company["id"],
            "to":   "0208:9876543210",
            "document": {
                "number": "INV-2026-001",
                "issueDate": "2026-04-25",
                "dueDate":   "2026-05-25",
                "currency":  "EUR",
                "lines": [{
                    "description": "Consulting, April 2026",
                    "quantity": 10, "unit": "hours",
                    "unitPrice": 150.00, "vatRate": 21,
                }],
            },
        },
    ).json()
    print(doc["status"], doc["deliveryStatus"])
    const doc = await flowie("/documents/send", {
      method: "POST",
      headers: { "Idempotency-Key": "inv-2026-001" },
      body: JSON.stringify({
        type: "invoice",
        from: company.id,
        to:   "0208:9876543210",
        document: {
          number: "INV-2026-001",
          issueDate: "2026-04-25",
          dueDate:   "2026-05-25",
          currency:  "EUR",
          lines: [{
            description: "Consulting, April 2026",
            quantity: 10, unit: "hours",
            unitPrice: 150.0, vatRate: 21,
          }],
        },
      }),
    });
    payload := map[string]any{
      "type": "invoice",
      "from": company.ID,
      "to":   "0208:9876543210",
      "document": map[string]any{
        "number":    "INV-2026-001",
        "issueDate": "2026-04-25",
        "currency":  "EUR",
        "lines": []any{map[string]any{
          "description": "Consulting, April 2026",
          "quantity":    10, "unitPrice": 150.0, "vatRate": 21,
        }},
      },
    }
    buf, _ := json.Marshal(payload)
    req, _ := http.NewRequest("POST",
        "https://back.p2p-flowie.com/exchange/v1/documents/send", bytes.NewReader(buf))
    req.Header.Set("Idempotency-Key", "inv-2026-001")
    req.Header.Set("Authorization", "Bearer "+os.Getenv("FLOWIE_KEY"))
    req.Header.Set("Content-Type", "application/json")
    http.DefaultClient.Do(req)
    ✓ That's it — your invoice is live on Peppol.
    You'll receive a document.delivered webhook once the recipient's access point confirms.

Complete starter programs

The same three steps, packaged as a single runnable file in your language. Copy, set FLOWIE_KEY, and you have a working integration.

# pip install httpx
import os, uuid, httpx

BASE  = "https://back.p2p-flowie.com/exchange/v1"
KEY   = os.environ["FLOWIE_KEY"]
api   = httpx.Client(base_url=BASE, headers={"Authorization": f"Bearer {KEY}"})

# 1. Register sender
sender = api.post("/companies", json={"vatNumber": "BE0123456789"}).json()
print("sender:", sender["id"], sender["peppolId"])

# 2. Verify recipient before sending
ver = api.post("/directory/verify", json={
    "peppolId": "0208:9876543210", "documentType": "INVOICE",
}).json()
assert ver["canReceive"], f"recipient unreachable: {ver}"

# 3. Send
doc = api.post(
    "/documents/send",
    headers={"Idempotency-Key": str(uuid.uuid4())},
    json={
        "type": "invoice",
        "from": sender["id"],
        "to":   "0208:9876543210",
        "document": {
            "number":    "INV-2026-0417",
            "issueDate": "2026-04-25",
            "dueDate":   "2026-05-25",
            "currency":  "EUR",
            "lines": [{
                "description": "Consulting — April 2026",
                "quantity": 10, "unit": "hours",
                "unitPrice": 150.00, "vatRate": 21,
            }],
        },
    },
).json()
print("invoice:", doc["id"], doc["status"], doc["deliveryStatus"])
// npm i undici
import { request } from "undici";
import { randomUUID } from "node:crypto";

const BASE = "https://back.p2p-flowie.com/exchange/v1";
const KEY  = process.env.FLOWIE_KEY;

async function flowie(path, init = {}) {
  const { body, statusCode } = await request(`${BASE}${path}`, {
    ...init,
    headers: {
      Authorization:  `Bearer ${KEY}`,
      "Content-Type": "application/json",
      ...(init.headers || {}),
    },
  });
  const json = await body.json();
  if (statusCode >= 400) throw new Error(JSON.stringify(json));
  return json;
}

// 1. Register sender
const sender = await flowie("/companies", {
  method: "POST",
  body: JSON.stringify({ vatNumber: "BE0123456789" }),
});

// 2. Verify recipient
const ver = await flowie("/directory/verify", {
  method: "POST",
  body: JSON.stringify({ peppolId: "0208:9876543210", documentType: "INVOICE" }),
});
if (!ver.canReceive) throw new Error("recipient unreachable");

// 3. Send invoice
const doc = await flowie("/documents/send", {
  method: "POST",
  headers: { "Idempotency-Key": randomUUID() },
  body: JSON.stringify({
    type: "invoice", from: sender.id, to: "0208:9876543210",
    document: {
      number: "INV-2026-0417",
      issueDate: "2026-04-25", dueDate: "2026-05-25", currency: "EUR",
      lines: [{ description: "Consulting", quantity: 10,
                unit: "hours", unitPrice: 150.00, vatRate: 21 }],
    },
  }),
});
console.log("invoice:", doc.id, doc.status, doc.deliveryStatus);
<?php
// composer require guzzlehttp/guzzle ramsey/uuid
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use Ramsey\Uuid\Uuid;

$api = new Client([
  'base_uri' => 'https://back.p2p-flowie.com/exchange/v1/',
  'headers'  => ['Authorization' => 'Bearer ' . getenv('FLOWIE_KEY')],
]);

// 1. Register sender
$sender = json_decode(
  $api->post('companies', ['json' => ['vatNumber' => 'BE0123456789']])
       ->getBody(), true);

// 2. Verify recipient
$ver = json_decode(
  $api->post('directory/verify', ['json' => [
    'peppolId' => '0208:9876543210', 'documentType' => 'INVOICE',
  ]])->getBody(), true);
if (!$ver['canReceive']) throw new Exception('recipient unreachable');

// 3. Send invoice
$doc = json_decode(
  $api->post('documents/send', [
    'headers' => ['Idempotency-Key' => Uuid::uuid4()->toString()],
    'json'    => [
      'type' => 'invoice', 'from' => $sender['id'], 'to' => '0208:9876543210',
      'document' => [
        'number' => 'INV-2026-0417',
        'issueDate' => '2026-04-25', 'currency' => 'EUR',
        'lines' => [['description' => 'Consulting',
                     'quantity' => 10, 'unitPrice' => 150.00, 'vatRate' => 21]],
      ],
    ],
  ])->getBody(), true);

echo "invoice: {$doc['id']} {$doc['status']} {$doc['deliveryStatus']}\n";
# gem install faraday
require 'faraday'
require 'json'
require 'securerandom'

KEY = ENV.fetch('FLOWIE_KEY')
api = Faraday.new(url: 'https://back.p2p-flowie.com/exchange/v1') do |c|
  c.request  :json
  c.response :json
  c.headers['Authorization'] = "Bearer #{KEY}"
end

# 1. Register sender
sender = api.post('companies', { vatNumber: 'BE0123456789' }).body

# 2. Verify recipient
ver = api.post('directory/verify',
               { peppolId: '0208:9876543210', documentType: 'INVOICE' }).body
raise 'recipient unreachable' unless ver['canReceive']

# 3. Send invoice
doc = api.post('documents/send', {
  type: 'invoice', from: sender['id'], to: '0208:9876543210',
  document: {
    number: 'INV-2026-0417',
    issueDate: '2026-04-25', currency: 'EUR',
    lines: [{ description: 'Consulting', quantity: 10,
              unit: 'hours', unitPrice: 150.00, vatRate: 21 }],
  },
}, { 'Idempotency-Key' => SecureRandom.uuid }).body

puts "invoice: #{doc['id']} #{doc['status']} #{doc['deliveryStatus']}"
// go get github.com/google/uuid
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"github.com/google/uuid"
)

const Base = "https://back.p2p-flowie.com/exchange/v1"

func call(method, path string, body any, extra map[string]string) map[string]any {
	var rdr *bytes.Reader
	if body != nil {
		buf, _ := json.Marshal(body)
		rdr = bytes.NewReader(buf)
	}
	req, _ := http.NewRequest(method, Base+path, rdr)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("FLOWIE_KEY"))
	req.Header.Set("Content-Type", "application/json")
	for k, v := range extra { req.Header.Set(k, v) }
	resp, err := http.DefaultClient.Do(req)
	if err != nil { panic(err) }
	defer resp.Body.Close()
	var out map[string]any
	json.NewDecoder(resp.Body).Decode(&out)
	return out
}

func main() {
	sender := call("POST", "/companies",
		map[string]any{"vatNumber": "BE0123456789"}, nil)

	ver := call("POST", "/directory/verify",
		map[string]any{"peppolId": "0208:9876543210", "documentType": "INVOICE"}, nil)
	if !ver["canReceive"].(bool) { panic("recipient unreachable") }

	doc := call("POST", "/documents/send", map[string]any{
		"type": "invoice", "from": sender["id"], "to": "0208:9876543210",
		"document": map[string]any{
			"number":    "INV-2026-0417",
			"issueDate": "2026-04-25", "currency": "EUR",
			"lines": []any{map[string]any{
				"description": "Consulting", "quantity": 10,
				"unitPrice":   150.00, "vatRate": 21,
			}},
		},
	}, map[string]string{"Idempotency-Key": uuid.NewString()})

	fmt.Println("invoice:", doc["id"], doc["status"], doc["deliveryStatus"])
}
// build.gradle: implementation 'com.squareup.okhttp3:okhttp:4.12.0'
//               implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import java.util.*;

public class Quickstart {
  static final String BASE = "https://back.p2p-flowie.com/exchange/v1";
  static final OkHttpClient HTTP = new OkHttpClient();
  static final ObjectMapper M = new ObjectMapper();
  static final String KEY = System.getenv("FLOWIE_KEY");
  static final MediaType JSON = MediaType.get("application/json");

  static Map<String,Object> call(String method, String path,
                                  Object body, Map<String,String> extra) throws Exception {
    Request.Builder rb = new Request.Builder()
        .url(BASE + path)
        .header("Authorization", "Bearer " + KEY);
    RequestBody rbody = body == null ? null
        : RequestBody.create(M.writeValueAsBytes(body), JSON);
    rb.method(method, rbody);
    if (extra != null) extra.forEach(rb::header);
    try (Response r = HTTP.newCall(rb.build()).execute()) {
      return M.readValue(r.body().string(), Map.class);
    }
  }

  public static void main(String[] args) throws Exception {
    Map<String,Object> sender = call("POST", "/companies",
        Map.of("vatNumber", "BE0123456789"), null);

    Map<String,Object> ver = call("POST", "/directory/verify",
        Map.of("peppolId", "0208:9876543210", "documentType", "INVOICE"), null);
    if (!Boolean.TRUE.equals(ver.get("canReceive")))
      throw new RuntimeException("recipient unreachable");

    Map<String,Object> doc = call("POST", "/documents/send",
      Map.of(
        "type", "invoice",
        "from", sender.get("id"),
        "to",   "0208:9876543210",
        "document", Map.of(
          "number",    "INV-2026-0417",
          "issueDate", "2026-04-25",
          "currency",  "EUR",
          "lines", List.of(Map.of(
            "description", "Consulting",
            "quantity",    10,
            "unitPrice",   150.00,
            "vatRate",     21
          ))
        )
      ),
      Map.of("Idempotency-Key", UUID.randomUUID().toString()));

    System.out.println("invoice: " + doc.get("id") + " " +
                       doc.get("status") + " " + doc.get("deliveryStatus"));
  }
}
// dotnet add package Newtonsoft.Json
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

class Quickstart {
  static readonly string Base = "https://back.p2p-flowie.com/exchange/v1";
  static readonly HttpClient http = new();

  static async Task<JObject> Call(string method, string path,
                                   object body, (string,string)? idem = null) {
    var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
    req.Headers.Authorization = new AuthenticationHeaderValue(
        "Bearer", Environment.GetEnvironmentVariable("FLOWIE_KEY"));
    if (idem is var (k, v)) req.Headers.Add(k, v);
    if (body != null) req.Content = new StringContent(
        JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
    var resp = await http.SendAsync(req);
    return JObject.Parse(await resp.Content.ReadAsStringAsync());
  }

  static async Task Main() {
    var sender = await Call("POST", "/companies",
        new { vatNumber = "BE0123456789" });

    var ver = await Call("POST", "/directory/verify",
        new { peppolId = "0208:9876543210", documentType = "INVOICE" });
    if (!(bool)ver["canReceive"]) throw new Exception("recipient unreachable");

    var doc = await Call("POST", "/documents/send", new {
      type = "invoice", from = sender["id"], to = "0208:9876543210",
      document = new {
        number    = "INV-2026-0417",
        issueDate = "2026-04-25", currency = "EUR",
        lines = new[] { new {
          description = "Consulting", quantity = 10,
          unitPrice   = 150.00,       vatRate  = 21
        }}
      }
    }, ("Idempotency-Key", Guid.NewGuid().ToString()));

    Console.WriteLine($"invoice: {doc["id"]} {doc["status"]} {doc["deliveryStatus"]}");
  }
}
#!/usr/bin/env bash
set -euo pipefail
BASE="https://back.p2p-flowie.com/exchange/v1"
H=(-H "Authorization: Bearer $FLOWIE_KEY" -H "Content-Type: application/json")

# 1. Register sender
SENDER=$(curl -s -X POST "$BASE/companies" "${H[@]}" \
  -d '{"vatNumber":"BE0123456789"}' | jq -r .id)

# 2. Verify recipient
RECV=$(curl -s -X POST "$BASE/directory/verify" "${H[@]}" \
  -d '{"peppolId":"0208:9876543210","documentType":"INVOICE"}' | jq -r .canReceive)
[ "$RECV" = "true" ] || { echo "recipient unreachable" >&2; exit 1; }

# 3. Send invoice
curl -s -X POST "$BASE/documents/send" "${H[@]}" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "type":"invoice","from":"'"$SENDER"'","to":"0208:9876543210",
    "document":{
      "number":"INV-2026-0417","issueDate":"2026-04-25","currency":"EUR",
      "lines":[{"description":"Consulting","quantity":10,"unitPrice":150.00,"vatRate":21}]
    }
  }' | jq '{id, status, deliveryStatus}'

Need another language? Every example follows the same pattern: bearer auth, JSON body, Idempotency-Key. Open a PR for your language at github.com/flowie-fr/exchange-api-docs.

Where to go next

Pick the track that matches what you're building.

API Reference →

Every endpoint, every field, every error. With runnable examples in four languages.

Integration Guides →

Playbooks for sending, receiving, going live, and building white-label products.

Compliance · 47 countries →

Per-country deep-dives across Europe, MENA, and Asia-Pacific. Mandates, formats, deadlines, and primary government sources for France PPF, Italy SDI, KSA Fatoora, India GST, Malaysia MyInvois, Singapore InvoiceNow, and more.

Webhook Cookbook →

Event catalog, signing, retry policy, and idempotency patterns for robust listeners.

MCP for AI Agents →

Plug Claude Desktop, Claude Code, Cursor, or your own Python agent into the API as native tools.

Agent onboarding →

Two paths for an agent to self-provision: zero-friction sandbox bootstrap (no human in the loop) or OAuth-style consent flow with PKCE for production-grade scope grants.

Error Catalog →

Every error code with a remediation. Because "500 Internal Server Error" isn't a diagnosis.

Sandbox →

Every test scenario as a row. Force any error, advance any clock, simulate any recipient.

Platform Onboarding Kit →

Build under your own brand. Onboard 1 tenant or 1,000 with the same playbook.

Data Model →

One diagram that makes the whole API click. Read this first, thank yourself later.

Webhook Fixtures →

Real JSON payloads for every event. Drop them into your handler tests.