Skip to main content

FactPulse REST API

Project description

FactPulse SDK Python

Official Python client for the FactPulse API - French electronic invoicing.

Features

  • Factur-X: Generation and validation of electronic invoices (MINIMUM, BASIC, EN16931, EXTENDED profiles)
  • Chorus Pro: Integration with the French public invoicing platform
  • AFNOR PDP/PA: Submission of flows compliant with XP Z12-013 standard
  • Electronic signature: PDF signing (PAdES-B-B, PAdES-B-T, PAdES-B-LT)
  • Thin HTTP wrapper: Generic post() and get() methods with automatic JWT auth and polling

Installation

pip install factpulse

Quick Start

import base64
from factpulse_helpers import FactPulseClient

# Create the client
client = FactPulseClient(
    email="your_email@example.com",
    password="your_password",
    client_uid="your-client-uuid",  # From dashboard: Configuration > Clients
)

# Read your source PDF
with open("source_invoice.pdf", "rb") as f:
    pdf_b64 = base64.b64encode(f.read()).decode()

# Generate Factur-X and submit to PDP in one call
result = client.post(
    "processing/invoices/submit-complete-async",
    invoiceData={
        "number": "INV-2025-001",
        "supplier": {
            "name": "ACME Corporation",
            "siret": "12345678901234",
            "iban": "FR7630001007941234567890185",
            "routing_address": "12345678901234",
        },
        "recipient": {
            "name": "Client Company SA",
            "siret": "98765432109876",
            "routing_address": "98765432109876",
        },
        "lines": [
            {
                "description": "Consulting services",
                "quantity": 10,
                "unitPrice": 100.0,
                "vatRate": 20.0,
            }
        ],
    },
    sourcePdf=pdf_b64,
    profile="EN16931",
    destination={"type": "afnor"},
)

# PDF is in result["content"] (auto-polled, auto-decoded from base64)
with open("facturx_invoice.pdf", "wb") as f:
    f.write(result["content"])

print(f"Flow ID: {result['afnorResult']['flowId']}")

API Methods

The SDK provides two generic methods that map directly to API endpoints:

# POST /api/v1/{path}
result = client.post("path/to/endpoint", key1=value1, key2=value2)

# GET /api/v1/{path}
result = client.get("path/to/endpoint", param1=value1)

Common Endpoints

Endpoint Method Description
processing/invoices/submit-complete-async POST Generate Factur-X + submit to PDP
processing/generate-invoice POST Generate Factur-X XML or PDF
processing/validate-xml POST Validate Factur-X XML
processing/validate-facturx-pdf POST Validate Factur-X PDF
processing/sign-pdf POST Sign PDF with certificate
afnor/flow/v1/flows POST Submit flow to AFNOR PDP
afnor/incoming-flows/{flow_id} GET Get incoming invoice
chorus-pro/factures/soumettre POST Submit to Chorus Pro

Webhooks

Instead of polling, you can receive results via webhook by adding callbackUrl:

# Submit with webhook - returns immediately
result = client.post(
    "processing/invoices/submit-complete-async",
    invoiceData=invoice_data,
    sourcePdf=pdf_b64,
    destination={"type": "afnor"},
    callbackUrl="https://your-server.com/webhook/factpulse",
    webhookMode="INLINE",  # or "DOWNLOAD_URL"
)

task_id = result["taskId"]
# Result will be POSTed to your webhook URL

Webhook Receiver Example (Flask)

import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "your-shared-secret"

def verify_signature(payload: bytes, signature: str) -> bool:
    if not signature.startswith("sha256="):
        return False
    expected = hmac.new(WEBHOOK_SECRET.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature[7:], expected)

@app.route("/webhook/factpulse", methods=["POST"])
def webhook_handler():
    signature = request.headers.get("X-Webhook-Signature", "")
    if not verify_signature(request.data, signature):
        return jsonify({"error": "Invalid signature"}), 401

    event = request.json
    event_type = event["event_type"]
    data = event["data"]

    if event_type == "submission.completed":
        flow_id = data.get("afnorResult", {}).get("flowId")
        print(f"Invoice submitted: {flow_id}")
    elif event_type == "submission.failed":
        print(f"Submission failed: {data.get('error')}")

    return jsonify({"status": "received"})

Webhook Event Types

Event Description
generation.completed Factur-X generated successfully
generation.failed Generation failed
validation.completed Validation passed
validation.failed Validation failed
signature.completed PDF signed
submission.completed Submitted to PDP/Chorus
submission.failed Submission failed

Zero-Storage Mode

Pass PDP credentials directly in the request (no server-side storage):

result = client.post(
    "processing/invoices/submit-complete-async",
    invoiceData=invoice_data,
    sourcePdf=pdf_b64,
    destination={
        "type": "afnor",
        "flowServiceUrl": "https://api.pdp.example.com/flow/v1",
        "tokenUrl": "https://auth.pdp.example.com/oauth/token",
        "clientId": "your_pdp_client_id",
        "clientSecret": "your_pdp_client_secret",
    },
)

Error Handling

from factpulse_helpers import FactPulseClient, FactPulseError

try:
    result = client.post("processing/validate-xml", xmlContent=xml_string)
except FactPulseError as e:
    print(f"Error: {e}")
    print(f"Status code: {e.status_code}")
    print(f"Details: {e.details}")  # Validation errors list

Available Helpers

The SDK provides the following helper classes:

  • FactPulseClient: Main HTTP client with auto-auth and polling
  • FactPulseError: Base exception class
  • FactPulseAuthError: Authentication failure
  • FactPulseValidationError: Validation errors with details
  • FactPulsePollingTimeout: Task polling timeout

Resources

License

MIT License - Copyright (c) 2025 FactPulse

Project details


Release history Release notifications | RSS feed

This version

0.1.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

factpulse-0.1.0.tar.gz (340.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

factpulse-0.1.0-py3-none-any.whl (1.2 MB view details)

Uploaded Python 3

File details

Details for the file factpulse-0.1.0.tar.gz.

File metadata

  • Download URL: factpulse-0.1.0.tar.gz
  • Upload date:
  • Size: 340.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.19

File hashes

Hashes for factpulse-0.1.0.tar.gz
Algorithm Hash digest
SHA256 59c00dd5759e579568e4b2574db9ba137a8590248e2c3323cb018ce366e99750
MD5 e3db65c7a70ee7ceca2af721b99d2aca
BLAKE2b-256 7ae4a4b12acd1a0a5799c18cfcd637e22137b4ad85493b462d8d272f5f4275ef

See more details on using hashes here.

File details

Details for the file factpulse-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: factpulse-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 1.2 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.19

File hashes

Hashes for factpulse-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9fdbfb36d76625caad508dba8b714c78c2c66937d4c680046f3d050e5d8b74d6
MD5 1c2f1cccf1ea8a3c9d176bab77c3bf12
BLAKE2b-256 5122c89314b688aeaac9bde99b058b7cfff0093adb5e5a6a6ba2059d509900e1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page