Module: Einvoicing::Connect::FR::Directory

Defined in:
lib/einvoicing/connect/fr/directory.rb

Overview

Consultation of the central French e-invoicing directory (the PPF directory operated by DGFiP/AIFE).

Given a French SIREN or SIRET, resolves the recipient's e-invoicing routing information: the registered reception platform (PDP) and its technical routing code. This is the lookup an issuing platform performs to know where to deliver an invoice for a company.

NOTE: The official DGFiP/AIFE directory API specification is not yet final. The reform pilot opened on 2026-02-27, with general availability on 2026-09-01. The endpoint and response shape below follow the published interoperability framework and are expected to evolve — point Directory.api_url= at the production endpoint once it is confirmed.

Constant Summary collapse

DEFAULT_API_URL =

Placeholder endpoint, overridable via Directory.api_url= or per call.

"https://annuaire.facturation.gouv.fr/api/v1/destinataires"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.api_urlObject



30
31
32
# File 'lib/einvoicing/connect/fr/directory.rb', line 30

def api_url
  @api_url ||= DEFAULT_API_URL
end

Class Method Details

.lookup(identifier, api_url: self.api_url) ⇒ Object

Look up the routing information for a recipient identified by SIREN (9 digits) or SIRET (14 digits).

Returns a Hash on success, or nil on any error / no match:

{
identifier:    "55203253400017",
level:         "SIRET",          # or "SIREN"
routing_code:  "PDP000123",      # technical routing code
platform_id:   "0000000000000",  # PDP registration id
platform_name: "Acme PDP",
status:        "active"
}


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/einvoicing/connect/fr/directory.rb', line 47

def self.lookup(identifier, api_url: self.api_url)
  id = identifier.to_s.gsub(/\s/, "")
  return nil unless id.match?(/\A\d{9}(\d{5})?\z/)

  uri = URI(api_url)
  uri.query = URI.encode_www_form(identifiant: id)

  response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
                             open_timeout: 5, read_timeout: 10) do |http|
    http.get(uri.request_uri)
  end

  return nil unless response.code == "200"

  parse(JSON.parse(response.body))
rescue StandardError
  nil
end

.route(party, api_url: self.api_url) ⇒ Object

Resolve the routing information for a Party, preferring its SIRET and falling back to its SIREN. Returns the routing Hash or nil.



68
69
70
71
72
73
74
# File 'lib/einvoicing/connect/fr/directory.rb', line 68

def self.route(party, api_url: self.api_url)
  identifier = party.siret.to_s.strip
  identifier = party.siren.to_s.strip if identifier.empty? && party.respond_to?(:siren)
  return nil if identifier.empty?

  lookup(identifier, api_url: api_url)
end