Exchange
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.
Coverage across 47 countries on four continents — single integration for Europe, MENA, and Asia-Pacific. Auto-SMP registration and directory verification included.
PPF (FR) and SDI (IT) are reported automatically when you update invoice status. Belgium runs pure Peppol — no separate report needed. Zero extra wiring.
Manage thousands of tenant companies under one account. Scoped keys, per-tenant quotas, custom branding.
Signed deliveries, exponential retries, at-least-once guarantees. Event replay through the Events API.
Send invoices as JSON and we generate valid UBL 2.1. Or send your own UBL/CII — we validate and deliver it.
French PDP-compliant adapter, cXML PunchOut, SIRET/SIREN directory — all behind the same account.
Sign up, grab a test API key, and fire three requests. No SDK required — it's just JSON over HTTPS.
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)
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)
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)
document.delivered webhook once the recipient's access point confirms.
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.
Pick the track that matches what you're building.
Every endpoint, every field, every error. With runnable examples in four languages.
Playbooks for sending, receiving, going live, and building white-label products.
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.
Event catalog, signing, retry policy, and idempotency patterns for robust listeners.
Plug Claude Desktop, Claude Code, Cursor, or your own Python agent into the API as native tools.
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.
Every error code with a remediation. Because "500 Internal Server Error" isn't a diagnosis.
Every test scenario as a row. Force any error, advance any clock, simulate any recipient.
Build under your own brand. Onboard 1 tenant or 1,000 with the same playbook.
One diagram that makes the whole API click. Read this first, thank yourself later.
Real JSON payloads for every event. Drop them into your handler tests.