Logo FactPulse

Guide E-Reporting avec l'API FactPulse

Ce guide explique comment utiliser l'API E-Reporting de FactPulse pour soumettre vos données de transactions et paiements au PPF (Portail Public de Facturation) via une PA (Plateforme Agréée).

Qu'est-ce que le E-Reporting ?

Le E-Reporting (flux 10) est l'obligation de transmettre à l'administration fiscale les données de transactions qui ne font pas l'objet d'une facture électronique (e-invoicing) :

Flux Description Cas d'usage
10.1 Transactions B2B internationales Ventes/achats avec entreprises étrangères
10.2 Paiements B2B internationaux Encaissements sur factures B2B int.
10.3 Transactions B2C agrégées Ventes à des particuliers
10.4 Paiements B2C agrégés Encaissements sur ventes B2C

Endpoints disponibles

Endpoints mono-flux (flux unique)

Méthode Endpoint Description
POST /api/v1/ereporting/generate Générer un XML e-reporting
POST /api/v1/ereporting/generate/download Générer et télécharger le XML
POST /api/v1/ereporting/validate Valider les données sans générer
POST /api/v1/ereporting/submit Soumettre à la PA
GET /api/v1/ereporting/flux-types Lister les types de flux

Endpoints multi-flux (dépôt agrégé PPF-compliant)

Méthode Endpoint Description
POST /api/v1/ereporting/generate-aggregated Générer un XML multi-flux
POST /api/v1/ereporting/validate-aggregated Valider les données agrégées
POST /api/v1/ereporting/submit-aggregated Soumettre un dépôt agrégé
GET /api/v1/ereporting/category-codes Lister les codes de catégorie PPF

Endpoints XML direct

Méthode Endpoint Description
POST /api/v1/ereporting/validate-xml Valider un XML contre les XSD PPF
POST /api/v1/ereporting/submit-xml Soumettre un XML pré-généré

Codes conformes PPF v3.1

CategoryCode (TT-81) - Catégorie de transactions

Code Description Correspondance cadres facturation
TLB1 Livraisons de biens soumises à la TVA B1, B2, B4, B7
TPS1 Prestations de services soumises à la TVA S1, S2, S3, S4, S5, S6, S7
TNT1 Transactions non taxées (exonérées) Exonérations
TMA1 Transactions mixtes agrégées M1, M2, M4 (à décomposer)

TransmissionTypeCode (TT-4) - Type de transmission

Code Description
IN Transmission initiale
RE Transmission rectificative

TaxDueDateTypeCode (TT-80) - Exigibilité TVA

Code Description
5 TVA sur les débits
29 TVA sur les encaissements
72 TVA à la livraison

Exemples avec Python (requests)

1. Flux 10.3 - Ventes B2C agrégées

import requests
from datetime import date
from decimal import Decimal

# Configuration
BASE_URL = "https://factpulse.fr/api/v1"
TOKEN = "votre_jwt_token"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json"
}

# Données e-reporting B2C - CONFORME PPF v3.1
payload = {
    "reportId": "EREPORT-2025-001",
    "reportName": "E-Reporting Janvier 2025",
    "fluxType": "10.3",
    "transmissionType": "IN",  # IN=Initial, RE=Rectificatif
    "sender": {
        "siren": "123456789",
        "name": "Ma Société SARL",
        "vatId": "FR12345678901"  # Optionnel
    },
    "period": {
        "startDate": "2025-01-01",
        "endDate": "2025-01-31"
    },
    "transactions": [
        {
            "date": "2025-01-15",
            "categoryCode": "TLB1",  # Code PPF pour livraisons de biens
            "taxExclusiveAmount": "10000.00",
            "taxAmount": "2000.00",
            "taxBreakdown": [
                {
                    "rate": "20.00",
                    "taxableAmount": "10000.00",
                    "taxAmount": "2000.00"
                }
            ],
            "transactionCount": 150
        },
        {
            "date": "2025-01-31",
            "categoryCode": "TPS1",  # Code PPF pour prestations de services
            "taxExclusiveAmount": "8000.00",
            "taxAmount": "440.00",
            "taxBreakdown": [
                {
                    "rate": "5.50",
                    "taxableAmount": "8000.00",
                    "taxAmount": "440.00"
                }
            ],
            "transactionCount": 200
        }
    ]
}

# Générer le XML
response = requests.post(
    f"{BASE_URL}/ereporting/generate",
    headers=headers,
    json=payload
)

if response.status_code == 200:
    data = response.json()
    print(f"XML généré: {data['xmlSize']} bytes")
    print(f"Report ID: {data['reportId']}")

    # Sauvegarder le XML
    with open("ereporting.xml", "w") as f:
        f.write(data["xml"])
else:
    print(f"Erreur: {response.json()}")

2. Dépôt agrégé multi-flux (recommandé)

Le PPF attend un fichier unique contenant tous les types de flux. Utilisez /generate-aggregated :

# Dépôt agrégé conforme PPF - un fichier = tous les flux
payload = {
    "reportId": "EREPORT-AGG-2025-001",
    "reportName": "E-Reporting Agrégé Janvier 2025",
    "transmissionType": "IN",  # IN=Initial, RE=Rectificatif
    "sender": {
        "siren": "123456789",
        "name": "Ma Société SARL"
    },
    "period": {
        "startDate": "2025-01-01",
        "endDate": "2025-01-31"
    },
    # Flux 10.1 - Factures B2B internationales
    "invoices": [
        {
            "invoiceId": "FAC-2025-INT-001",
            "issueDate": "2025-01-10",
            "typeCode": "380",
            "sellerSiren": "123456789",
            "sellerCountry": "FR",
            "buyerCountry": "DE",
            "taxExclusiveAmount": "50000.00",
            "taxAmount": "0.00",
            "taxBreakdown": [
                {"rate": "0.00", "taxableAmount": "50000.00", "taxAmount": "0.00"}
            ]
        }
    ],
    # Flux 10.3 - Transactions B2C agrégées par catégorie
    "transactions": [
        {
            "date": "2025-01-15",
            "categoryCode": "TLB1",  # Livraisons de biens
            "taxExclusiveAmount": "10000.00",
            "taxAmount": "2000.00",
            "taxBreakdown": [
                {"rate": "20.00", "taxableAmount": "10000.00", "taxAmount": "2000.00"}
            ],
            "transactionCount": 150
        },
        {
            "date": "2025-01-15",
            "categoryCode": "TPS1",  # Prestations de services
            "taxExclusiveAmount": "5000.00",
            "taxAmount": "1000.00",
            "taxBreakdown": [
                {"rate": "20.00", "taxableAmount": "5000.00", "taxAmount": "1000.00"}
            ],
            "transactionCount": 80
        }
    ],
    # Flux 10.4 - Paiements B2C agrégés
    "transactionPayments": [
        {
            "paymentDate": "2025-01-20",
            "amountsByRate": [
                {"rate": "20.00", "amount": "6000.00"}
            ]
        }
    ]
}

response = requests.post(
    f"{BASE_URL}/ereporting/generate-aggregated",
    headers=headers,
    json=payload
)

if response.status_code == 200:
    data = response.json()
    print(f"XML généré: {len(data['xml'])} bytes")
    print(f"FlowType AFNOR: {data['flowType']}")  # MultiFlowReport
    print(f"Contenu: {data['contentSummary']}")

3. Flux 10.1 - Factures B2B internationales

payload = {
    "reportId": "EREPORT-2025-002",
    "fluxType": "10.1",
    "transmissionType": "IN",
    "sender": {
        "siren": "123456789",
        "name": "Exportateur SA"
    },
    "period": {
        "startDate": "2025-01-01",
        "endDate": "2025-01-31"
    },
    "invoices": [
        {
            "invoiceId": "FAC-2025-001",
            "issueDate": "2025-01-10",
            "typeCode": "380",  # Facture standard
            "currency": "EUR",
            "sellerSiren": "123456789",
            "sellerCountry": "FR",
            "buyerCountry": "DE",  # Allemagne
            "buyerVatId": "DE123456789",
            "taxExclusiveAmount": "50000.00",
            "taxAmount": "0.00",  # Exonéré (export intra-EU)
            "taxBreakdown": [
                {
                    "rate": "0.00",
                    "taxableAmount": "50000.00",
                    "taxAmount": "0.00"
                }
            ]
        }
    ]
}

response = requests.post(
    f"{BASE_URL}/ereporting/generate",
    headers=headers,
    json=payload
)

4. Flux 10.4 - Paiements B2C

payload = {
    "reportId": "EREPORT-2025-003",
    "fluxType": "10.4",
    "transmissionType": "IN",
    "sender": {
        "siren": "123456789",
        "name": "Commerce SARL"
    },
    "period": {
        "startDate": "2025-01-01",
        "endDate": "2025-01-31"
    },
    "aggregatedPayments": [
        {
            "paymentDate": "2025-01-15",
            "amountsByRate": [
                {"rate": "20.00", "amount": "12000.00"},
                {"rate": "5.50", "amount": "550.00"}
            ]
        }
    ]
}

5. Récupérer les codes de catégorie PPF

# Lister les codes conformes PPF
response = requests.get(
    f"{BASE_URL}/ereporting/category-codes",
    headers=headers
)

codes = response.json()
print("Codes de catégorie PPF valides:")
for code in codes["categoryCodes"]:
    print(f"  {code['code']}: {code['description']}")
# TLB1: Livraisons de biens soumises à la TVA
# TPS1: Prestations de services soumises à la TVA
# TNT1: Transactions non taxées (exonérées)
# TMA1: Transactions mixtes agrégées

6. Validation avant soumission

# Valider sans générer
response = requests.post(
    f"{BASE_URL}/ereporting/validate",
    headers=headers,
    json={"data": payload}
)

result = response.json()
if result["valid"]:
    print("Données valides!")
else:
    print("Erreurs de validation:")
    for error in result["errors"]:
        print(f"  - {error['field']}: {error['message']}")

7. Validation d'un XML contre les XSD PPF

# Valider un fichier XML pré-généré
with open("mon_ereporting.xml", "rb") as f:
    response = requests.post(
        f"{BASE_URL}/ereporting/validate-xml",
        headers={"Authorization": f"Bearer {TOKEN}"},
        files={"xml_file": f}
    )

result = response.json()
if result["valid"]:
    print("XML conforme aux XSD PPF!")
    print(f"FlowType détecté: {result['flowType']}")
else:
    print("Erreurs XSD:")
    for error in result["errors"]:
        print(f"  - {error}")

8. Soumettre un XML pré-généré

# Soumettre un XML déjà généré par un autre système
with open("mon_ereporting.xml", "rb") as f:
    response = requests.post(
        f"{BASE_URL}/ereporting/submit-xml",
        headers={"Authorization": f"Bearer {TOKEN}"},
        files={"xml_file": f},
        data={
            "report_type": "combined",  # transactions, payments, combined
            "tracking_id": "TRACK-2025-001"
        }
    )

if response.status_code == 200:
    result = response.json()
    print(f"Flow ID: {result['flowId']}")
    print(f"SHA256: {result['sha256']}")

9. Soumission à la PA

# Soumettre avec credentials PA
payload = {
    "data": {
        "reportId": "EREPORT-2025-001",
        "fluxType": "10.3",
        "transmissionType": "IN",
        # ... reste des données
    },
    "trackingId": "TRACK-2025-001",  # Optionnel, pour suivi

    # Credentials PA (optionnel si configurés côté serveur)
    "pdpFlowServiceUrl": "https://api.ma-pa.fr/flow/v1",
    "pdpTokenUrl": "https://auth.ma-pa.fr/oauth/token",
    "pdpClientId": "mon_client_id",
    "pdpClientSecret": "mon_secret"
}

response = requests.post(
    f"{BASE_URL}/ereporting/submit",
    headers=headers,
    json=payload
)

if response.status_code == 200:
    data = response.json()
    print(f"Soumis! Flow ID: {data['flowId']}")
    print(f"SHA256: {data['sha256']}")
else:
    print(f"Erreur: {response.json()}")

Exemple avec TypeScript/JavaScript

interface EReportingRequest {
    reportId: string;
    reportName?: string;
    fluxType: "10.1" | "10.2" | "10.3" | "10.4";
    transmissionType: "IN" | "RE"; // TT-4: Initial ou Rectificatif
    sender: {
        siren: string;
        name: string;
        vatId?: string;
    };
    period: {
        startDate: string; // YYYY-MM-DD
        endDate: string;
    };
    transactions?: AggregatedTransaction[];
    invoices?: Invoice[];
    invoicePayments?: InvoicePayment[];
    aggregatedPayments?: AggregatedPayment[];
}

interface AggregatedTransaction {
    date: string;
    categoryCode: "TLB1" | "TPS1" | "TNT1" | "TMA1"; // Codes PPF
    taxExclusiveAmount: string;
    taxAmount: string;
    taxBreakdown: TaxBreakdown[];
    transactionCount?: number;
    taxDueDateTypeCode?: "5" | "29" | "72"; // UNTDID codes
}

async function generateEReporting(data: EReportingRequest): Promise<string> {
    const response = await fetch("https://factpulse.fr/api/v1/ereporting/generate", {
        method: "POST",
        headers: {
            Authorization: `Bearer ${process.env.FACTPULSE_TOKEN}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
    });

    if (!response.ok) {
        const error = await response.json();
        throw new Error(`E-Reporting error: ${error.detail?.message || error}`);
    }

    const result = await response.json();
    return result.xml;
}

// Utilisation
const xml = await generateEReporting({
    reportId: "EREPORT-2025-001",
    fluxType: "10.3",
    transmissionType: "IN",
    sender: { siren: "123456789", name: "Ma Société" },
    period: { startDate: "2025-01-01", endDate: "2025-01-31" },
    transactions: [
        {
            date: "2025-01-15",
            categoryCode: "TLB1", // Code PPF conforme
            taxExclusiveAmount: "10000.00",
            taxAmount: "2000.00",
            taxBreakdown: [
                { rate: "20.00", taxableAmount: "10000.00", taxAmount: "2000.00" },
            ],
            transactionCount: 150,
        },
    ],
});

Exemple avec cURL

# Générer un e-reporting B2C (conforme PPF v3.1)
curl -X POST "https://factpulse.fr/api/v1/ereporting/generate" \
  -H "Authorization: Bearer $FACTPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reportId": "EREPORT-2025-001",
    "fluxType": "10.3",
    "transmissionType": "IN",
    "sender": {"siren": "123456789", "name": "Ma Société"},
    "period": {"startDate": "2025-01-01", "endDate": "2025-01-31"},
    "transactions": [{
      "date": "2025-01-15",
      "categoryCode": "TLB1",
      "taxExclusiveAmount": "10000.00",
      "taxAmount": "2000.00",
      "taxBreakdown": [{"rate": "20.00", "taxableAmount": "10000.00", "taxAmount": "2000.00"}],
      "transactionCount": 150
    }]
  }'

# Lister les codes de catégorie PPF
curl -X GET "https://factpulse.fr/api/v1/ereporting/category-codes" \
  -H "Authorization: Bearer $FACTPULSE_TOKEN"

# Générer un dépôt agrégé multi-flux
curl -X POST "https://factpulse.fr/api/v1/ereporting/generate-aggregated" \
  -H "Authorization: Bearer $FACTPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reportId": "EREPORT-AGG-2025-001",
    "transmissionType": "IN",
    "sender": {"siren": "123456789", "name": "Ma Société"},
    "period": {"startDate": "2025-01-01", "endDate": "2025-01-31"},
    "transactions": [
      {"date": "2025-01-15", "categoryCode": "TLB1", "taxExclusiveAmount": "10000.00", "taxAmount": "2000.00", "taxBreakdown": [{"rate": "20.00", "taxableAmount": "10000.00", "taxAmount": "2000.00"}], "transactionCount": 150}
    ],
    "invoices": [
      {"invoiceId": "FAC-INT-001", "issueDate": "2025-01-10", "typeCode": "380", "sellerSiren": "123456789", "sellerCountry": "FR", "buyerCountry": "DE", "taxExclusiveAmount": "50000.00", "taxAmount": "0.00", "taxBreakdown": [{"rate": "0.00", "taxableAmount": "50000.00", "taxAmount": "0.00"}]}
    ]
  }'

# Valider un XML contre les XSD PPF
curl -X POST "https://factpulse.fr/api/v1/ereporting/validate-xml" \
  -H "Authorization: Bearer $FACTPULSE_TOKEN" \
  -F "xml_file=@mon_ereporting.xml"

# Télécharger directement le fichier XML
curl -X POST "https://factpulse.fr/api/v1/ereporting/generate/download" \
  -H "Authorization: Bearer $FACTPULSE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reportId": "...", ...}' \
  -o ereporting.xml

Structure des données

Champs communs

Champ Type Requis Description
reportId string Oui Identifiant unique du rapport
reportName string Non Nom descriptif
fluxType enum Oui Type de flux (10.1, 10.2, 10.3, 10.4)
transmissionType enum Non Type de transmission (IN, RE). Défaut: IN
sender object Oui Informations de l'émetteur
period object Oui Période de reporting

Sender (émetteur)

Champ Type Requis Description
siren string Oui SIREN (9 chiffres) ou SIRET (14 chiffres)
name string Oui Raison sociale
vatId string Non Numéro TVA intracommunautaire

Transaction agrégée (flux 10.3)

Champ Type Requis Description
date string Oui Date de la transaction (YYYY-MM-DD)
categoryCode enum Oui Catégorie PPF (TLB1, TPS1, TNT1, TMA1)
currency string Non Devise (défaut: EUR)
taxExclusiveAmount string Oui Montant HT
taxAmount string Oui Montant TVA
taxBreakdown array Oui Ventilation par taux de TVA
transactionCount int Non Nombre de transactions agrégées
taxDueDateTypeCode enum Non Exigibilité TVA (5, 29, 72)

Facture B2B internationale (flux 10.1)

Champ Type Requis Description
invoiceId string Oui Numéro de facture
issueDate string Oui Date d'émission
typeCode enum Oui Type (380=facture, 381=avoir)
currency string Non Devise
sellerSiren string Oui SIREN du vendeur
sellerCountry string Non Pays du vendeur (défaut: FR)
buyerCountry string Oui Pays de l'acheteur
buyerVatId string Non TVA de l'acheteur
taxExclusiveAmount string Oui Montant HT
taxAmount string Oui Montant TVA
taxBreakdown array Oui Ventilation TVA

Codes d'erreur

Code Description
MISSING_TRANSACTIONS Transactions requises pour flux 10.3
MISSING_INVOICES Factures requises pour flux 10.1
MISSING_PAYMENTS Paiements requis pour flux 10.2/10.4
INVALID_FIELD Champ incorrect pour le type de flux
INVALID_SIREN Format SIREN/SIRET invalide
INVALID_CATEGORY Code de catégorie invalide (utiliser TLB1, TPS1, TNT1, TMA1)
TAX_MISMATCH Incohérence entre montant TVA et ventilation
DATE_OUT_OF_PERIOD Date de transaction hors période
DOMESTIC_BUYER Acheteur FR (warning pour B2B int.)
NO_DATA Aucune donnée fournie dans le dépôt agrégé

FlowType AFNOR

Le FlowType est automatiquement déterminé selon le contenu du dépôt :

Contenu du dépôt FlowType AFNOR
10.3 seul (B2C agrégé) AggregatedCustomerTransactionReport
10.1 seul (B2Bi ventes) UnitaryCustomerTransactionReport
10.4 seul (paiements B2C) AggregatedCustomerPaymentReport
10.2 seul (paiements B2Bi) UnitaryCustomerPaymentReport
Plusieurs types combinés MultiFlowReport

Bonnes pratiques

  1. Utilisez les codes PPF conformes : Remplacez B2C par TLB1 ou TPS1, EXEMPT par TNT1

  2. Préférez les dépôts agrégés : Utilisez /generate-aggregated pour créer un fichier unique contenant tous vos flux

  3. Spécifiez le transmissionType : IN pour un dépôt initial, RE pour une rectification

  4. Toujours valider avant de soumettre : Utilisez /validate ou /validate-aggregated pour vérifier vos données

  5. Utilisez des IDs uniques : Le reportId doit être unique pour chaque rapport

  6. Respectez les dates : Les transactions doivent être dans la période déclarée

  7. Vérifiez les totaux TVA : La somme des taxAmount dans taxBreakdown doit égaler le taxAmount global

  8. Conservez les flow_id : Après soumission, gardez le flowId pour le suivi du statut

Workflow complet : de la génération au suivi

Étape 1 : Récupérer les codes conformes

# Vérifier les codes de catégorie PPF valides
response = requests.get(
    f"{BASE_URL}/ereporting/category-codes",
    headers=headers
)
print("Codes PPF:", response.json())

Étape 2 : Valider les données

# Valider avant de soumettre
response = requests.post(
    f"{BASE_URL}/ereporting/validate-aggregated",
    headers=headers,
    json=ereporting_data
)

result = response.json()
if not result["valid"]:
    for error in result["errors"]:
        print(f"ERREUR: {error['code']}: {error['message']}")
    sys.exit(1)

print(f"FlowType déterminé: {result['flowType']}")

Étape 3 : Générer le XML

# Générer le XML pour vérification
response = requests.post(
    f"{BASE_URL}/ereporting/generate-aggregated",
    headers=headers,
    json=ereporting_data
)

result = response.json()
print(f"XML généré: {len(result['xml'])} bytes")
print(f"FlowType: {result['flowType']}")
print(f"Contenu: {result['contentSummary']}")

# Sauvegarder pour vérification
with open(f"preview_{result['reportId']}.xml", "w") as f:
    f.write(result["xml"])

Étape 4 : Soumettre à la PA

response = requests.post(
    f"{BASE_URL}/ereporting/submit-aggregated",
    headers=headers,
    json={
        **ereporting_data,
        "trackingId": f"TRACK-{ereporting_data['reportId']}"
    }
)

if response.status_code == 200:
    result = response.json()
    flow_id = result["flowId"]
    print(f"Soumis! Flow ID: {flow_id}")

Étape 5 : Suivre le statut

# Rechercher le flux par son ID
response = requests.post(
    f"{BASE_URL}/afnor/flows/search",
    headers=headers,
    json={"flowId": flow_id}
)

for flow in response.json().get("results", []):
    print(f"Statut: {flow['acknowledgmentStatus']}")

Migration depuis l'ancienne API

Si vous utilisiez l'ancienne API avec les champs dépréciés :

Ancien champ Nouveau champ Anciennes valeurs Nouvelles valeurs
category categoryCode B2C, B2BINT, EXEMPT TLB1, TPS1, TNT1
taxDueType taxDueDateTypeCode I, S 5, 29, 72
(absent) transmissionType (aucun) IN, RE

Compatibilité arrière : L'API accepte toujours les anciennes valeurs et les convertit automatiquement :

  • "category": "B2C""categoryCode": "TLB1"
  • "category": "EXEMPT""categoryCode": "TNT1"

Cependant, nous recommandons de migrer vers les nouveaux champs pour une conformité PPF complète.

Besoin d'aide ?

Notre équipe est disponible pour vous accompagner dans l'intégration du e-reporting :


Dernière mise à jour : Janvier 2026 - Conformité PPF v3.1