The national directory
The national directory is the reform's reference registry, kept by the PPF (Portail Public de Facturation) operated by the AIFE (Agence pour l'informatique financière de l'État): it links every French SIREN or SIRET to the platform that receives its e-invoices. Until a company is registered there with Scribee as the receiving platform, its invoices cannot be addressed to it through the reform. This page covers both sides of the directory: looking up the addressing of any French company before invoicing it, and registering, modifying, or masking the directory entries of your own companies.
What Scribee does for you
- Fills in the regulatory fields of each entry: the SIREN (from the company record), the receiving platform (Scribee's registration), and the routing code qualifier. You provide only the addressing level and the effective dates.
- Checks the shape of your request before transmission - required fields, J+1 dates, internal consistency - and rejects with an immediate
422whatever it can decide on its own. The directory's own rules stay arbitrated by the PPF: a202does not prejudge them, and a deferred rejection remains possible. - Orders requests when an entry references a new routing code: the directory requires the code to be registered before the entry; Scribee submits the two in that order.
- Publishes the registered addressing to the Peppol network in parallel, with no extra call on your part.
- Serves your lookups from the copy of the directory that Scribee maintains: reads answer immediately, without depending on the PPF's availability on every request.
Asynchronous writes by design
The national directory is an external registry: Scribee cannot write to it synchronously. Every write (POST, PATCH, DELETE) therefore responds 202 Accepted with a change_request_id - the request is recorded locally and queued, neither transmitted nor applied at the time of the response. You observe the result by relisting the company's entries: the created or modified entry appears there once the request has been processed.
Event kinds and statuses
Every entry carries an event kind (event_kind), set at creation and immutable afterward:
event_kind | Label | Origin |
|---|---|---|
regular | Regular | A registration (POST) always creates an entry of this kind |
masking | Masking | Results from a masking request (DELETE, step 5) |
An entry's registration status is read from its effective dates - the end date is exclusive, an entry stops being in force on the day of its effective_to_on:
| Status | Label | Condition |
|---|---|---|
upcoming | Upcoming | effective_from_on is later than today |
in_force | In force | effective_from_on has been reached and effective_to_on is absent or in the future |
ended | Ended | effective_to_on is today or in the past |
Step 1: look up the directory before invoicing
This call is a read: it creates nothing and transmits nothing externally - the read scope is enough. It returns the directory entries in force as of today for a SIREN or a SIRET - exactly one of the two parameters, never both.
curl "https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/france/pid/directory_lookups?siret=12345678900012" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"data": [
{
"directory_identifier": "FR000000001",
"peppol_addressing_value": "FR000000001",
"siren": "123456789",
"siret": "12345678900012",
"suffix_code": null,
"routing_code_identifier": null,
"routing_code_qualifier": null,
"nature_label": "D",
"disclosure_status": "Diffusible",
"directory_line_status": "Enabled",
"sales_prospecting_forbidden": false
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total_pages": 1,
"total_count": 1
}
}
These eleven fields make up the response for every caller.
peppol_addressing_value is the address the entry actually routes to, and it is the one to keep when addressing a document. It equals directory_identifier whenever the directory assigned one; otherwise it is composed from the entry's addressing level (SIREN, SIREN_SUFFIXE, SIREN_SIRET, or SIREN_SIRET_CODEROUTAGE). The distinction matters: directory_identifier is legitimately null on an entry whose address the directory composes, so a caller reading only that field concludes "no address" for a perfectly reachable recipient. peppol_addressing_value therefore carries an address where directory_identifier is empty. It is itself null in exactly one case, deliberately: when the entry resolves to no usable address. directory_identifier is validated nowhere - the import writes whatever the PPF sent - so it can carry text that is not a Peppol identifier. Rather than publish an address nothing can route to, which you would store only to have the deposit refused later, this field is null for such an entry. Read null as "this entry is not addressable", not as "field missing".
This is the value to write into directory_routing_identifier on the customer record or on the invoice (see Invoice lifecycle). directory_identifier is unchanged and still carries exactly what it carried: the field is not replaced, it is complemented. nature_label here carries the Annexe 3 DT-7-2 regulatory code, "D" (Definition, an entry an invoice can be addressed to): since the lookup only returns regular entries, the other value "M" (Masking) never appears here. This field does not carry the same value as at step 3, where it reports the entry's addressing level; here, the addressing level is read from siret, suffix_code, and routing_code_identifier, never from nature_label. disclosure_status is most often "Diffusible", "Partiellement diffusible", "Refus de prospection", or null: Scribee derives it from its local copy of the recipient's legal unit or establishment, and returns null when that copy is absent or carries no disclosure value. The list is not closed: a non-blank value Scribee cannot translate is returned verbatim rather than replaced by null. Treat the field as a free-form display string, with a null case and a default case. The list is paginated (page, per_page) and filters by routing_code_identifier when the recipient routes its invoices by department.
directory_line_status reports the entry's regulatory status (AFNOR XP Z12-013 §6.2.2) as of as_of (today by default) for every caller, with one exception: for a Plateforme Agréée using include_history (detailed below), as_of plays no part in this computation, which stays anchored on today's date instead. It is "Enabled" (in force and not on the PPF's default platform code), "Disabled" (in force but still on the PPF's default platform code, for lack of an assigned receiving platform), or null when the entry's confirmed effective end - the earlier of effective_to_on and effective_to_confirmed_on, the latter detailed below - has been reached or passed as of that date. The value "Upcoming" (not yet in force) exists only for a Plateforme Agréée using the include_history parameter: without it, the lookup never returns an upcoming entry for any caller, so directory_line_status cannot be "Upcoming" either. Reserved to that same Plateforme Agréée-with-include_history case, two other null values are possible: the upcoming entry is still on the PPF's default platform code - a combination the reference standard leaves undefined - or the entry surfaced is already ended, the same computation as the first null case but made visible here because include_history lifts the date window that would otherwise have excluded it from the response. The field itself is returned to every caller.
sales_prospecting_forbidden (XP Z12-013 §6.2.3) is a boolean derived from disclosure_status: true when it is "Refus de prospection", false in every other case, including when disclosure_status is null. Returned to every caller.
Integrators operating white-label as Plateforme Agréée (French approved e-invoicing platform) receive six extra fields, bringing their response to seventeen fields - platform_registry_number, platform_registry_qualifier, effective_from_on, effective_to_on, effective_to_confirmed_on, and instance_number. effective_to_confirmed_on (DT-7-3-3, the effective end date as computed by the PPF) can be earlier than effective_to_on; whichever of the two is earlier is what flips directory_line_status to null (see above). These integrators also have the include_history parameter, reserved to them: it lifts the lookup's date window and returns entries regardless of their effective dates, without lifting the other two filters - masked entries and masking-type entries stay excluded in every case.
as_of moves the lookup window to another date - the response is still the entries in force on that date, not a history. It is honored for every caller, except a Plateforme Agréée using include_history (see above): for it, as_of is not taken into account at all, including for an invalid value, and the lookup responds 200 as if the parameter had not been supplied. Outside that case, an invalid value (a non-ISO-8601 format, a date that does not exist, or a year outside the bounds of the database's DATE column) is handled differently depending on your status: a standard caller gets a silent fallback to today's date, the lookup responds 200 as if as_of had not been supplied; a Plateforme Agréée without include_history gets a 422 instead (see Errors and edge cases), because for it as_of must be honored and an invalid value cannot be silently ignored.
An empty data response means no entry in force matches the filters you supplied - the identifier you searched for, but also routing_code_identifier when you send one: the entity may exist without carrying that routing code. That is not the same as "the company has no entry": a search by SIRET does not surface an entry registered under the SIREN alone, which nonetheless covers the whole legal entity. Search again on the SIREN before concluding.
Step 2: register a company in the directory
This call records a registration request and transmits it to the national directory: the resulting entry is visible to every platform in the reform. While it is Upcoming, it is masked with the DELETE of step 5; once In force, it is closed by setting its end date (step 4). The company is a record in your workspace (Companies and establishments) and must have an active mandate.
curl -X POST https://app.scribee.tech/api/v1/companies/YOUR_COMPANY_ID/france/pid/entries \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entry": {
"siret": "12345678900012",
"effective_from_on": "2026-09-01"
}
}'
{
"data": {
"change_request_id": 99
}
}
202 response: the request is recorded, not yet applied - relist the entries (step 3) to observe the result. effective_from_on must be at least J+1. The addressing level is chosen by the fields provided: none (the entry carries the SIREN alone), siret (an establishment), suffix_code with the SIREN alone, or siret plus routing_code_identifier to route by department. The addressing level retained sets the created entry's nature_label, which step 3 returns: "Etablissement" as soon as a siret is supplied, "Suffixe" for an entry carrying a suffix_code and no siret, and "Unite legale" for an entry carrying the SIREN alone. The SIREN itself and the receiving platform are not provided: Scribee sets them from the company record and its own registration.
Step 3: track the request and read the entries
GET /api/v1/companies/{company_id}/france/pid/entries lists the company's entries, from most recent to oldest (sort_by: created_at, effective_from_on, or effective_to_on; sort_order: asc or desc).
curl https://app.scribee.tech/api/v1/companies/YOUR_COMPANY_ID/france/pid/entries \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Abridged response, fields useful here:
{
"data": [
{
"id": 512,
"siren": "123456789",
"siret": "12345678900012",
"routing_code_identifier": null,
"event_kind": "regular",
"effective_from_on": "2026-09-01",
"effective_to_on": null,
"directory_identifier": "FR000000001",
"masked": false
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total_pages": 1,
"total_count": 1
}
}
The entry from step 2 appears here once the request has been processed by the directory. An entry can also be read individually, without going through the company: GET /api/v1/france/pid/entries/{id} - this path is used in particular to reread a masked entry, which the list no longer displays.
Step 4: modify an entry
This call records a modification request and transmits it to the national directory; the response is a 202 with change_request_id, as in step 2. It bears only on the entry's end-of-validity date: set effective_to_on (at least J+1, the end date is exclusive) to end an In force entry, or send it as null to clear it - Scribee then transmits an explicitly null dateFinEffet to the directory, and the end-of-validity date it stores is cleared once the request has been processed. Masking goes through the DELETE of step 5, never through a modification.
:::danger effective_to_on is the only attribute transmitted to the directory
effective_to_on (DT-7-3-2) is the only attribute this API transmits to the directory; Annexe 3 V1.8 DG-7 also allows the receiving-platform matricule (DT-7-6) to be changed on a line not yet in force, which this API does not yet support. Any other attribute sent in entry is refused: the request answers 422 with "code": "invalid_argument", the entry is left untouched, and no modification request is created. The refusal is on the keys you send, including a key sent empty and a key the API does not know. Refused this way are siren, siret, suffix_code, routing_code_identifier, routing_code_qualifier, platform_registry_number, platform_registry_qualifier, nature_label, presence_reason, and effective_from_on, which earlier versions accepted with a 202 before ignoring them, along with event_kind, which never had any effect here. To correct an addressing level, end the existing entry with effective_to_on and then create a new one (step 2).
:::
curl -X PATCH https://app.scribee.tech/api/v1/companies/YOUR_COMPANY_ID/france/pid/entries/512 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entry": {
"effective_to_on": "2026-12-31"
}
}'
To clear an end of validity already set, send the same date as null:
{
"entry": {
"effective_to_on": null
}
}
Once the request has been processed by the directory, rereading the entry (step 3) reports the new effective_to_on - null if you cleared it.
A masked entry can no longer be modified.
Step 5: mask an upcoming entry
This call records a masking request and transmits it to the national directory: the entry is withdrawn from active publication without its history being erased. Masking only targets an Upcoming entry - the directory's regulations reserve it for entries not yet in force; an In force entry is closed through its end date (step 4), and the call responds 403 on such an entry.
curl -X DELETE https://app.scribee.tech/api/v1/companies/YOUR_COMPANY_ID/france/pid/entries/512 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"data": {
"change_request_id": 101
}
}
202 response. Once the masking has taken effect, the entry carries "masked": true, drops out of the company's list, and remains readable individually at GET /api/v1/france/pid/entries/{id}. The write scope is sufficient; a token carrying only the destroy scope also works.
What happens next
- The transmitted request is processed by the national directory; relist the company's entries to observe the registration, modification, or masking. The
change_request_ididentifies each request in your exchanges with your Scribee contact. - Approval of a mandate covering invoice reception is the prerequisite for directory activation on the company concerned, and can trigger it: when the entry meets the conditions, approval starts a background activation. It is not guaranteed for all that - re-list the company's entries to observe their state, and contact your Scribee representative if activation does not appear (Electronic invoicing mandates).
- Once the entry is In force, the invoices your suppliers issue through the reform are addressed to Scribee and appear in your workspace: Receive supplier invoices.
Errors and edge cases
422: invalid lookup parameter
The lookup requires exactly one identifier - siren or siret, neither none nor both:
{
"error": "unprocessable_entity",
"code": "invalid_argument",
"message": "La validation a échoué",
"details": {
"base": ["un seul paramètre parmi siren ou siret doit être fourni"]
}
}
An invalid format produces "le siren doit contenir exactement 9 chiffres" or "le siret doit contenir exactement 14 chiffres" in the same format.
422: invalid as_of (Plateforme Agréée)
Reserved to Plateformes Agréées without include_history: a standard caller, or a Plateforme Agréée using include_history, always gets a 200 for an invalid as_of - a silent fallback to today's date for the standard caller, as_of being simply ignored in the include_history case (see step 1). A Plateforme Agréée without include_history gets this 422 instead, because as_of must be honored for it:
{
"error": "unprocessable_entity",
"code": "invalid_argument",
"message": "La validation a échoué",
"details": {
"base": ["invalid date"]
}
}
This message is the underlying date parser's own, untranslated - a deliberate departure from the French of this page's other errors. The same response covers a non-ISO-8601 format and a date that does not exist.
422: write request rejected
The directory's rules are checked before transmission; the cause is in details:
{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"base": ["Date d'effet doit être au moins J+1 (demain ou plus tard)"]
}
}
Other frequent causes, in the same format: "SIRET la racine (9 premiers chiffres) doit correspondre au SIREN" when the SIRET provided does not descend from the company record's SIREN, and "Code suffixe ne peut pas être renseigné lorsqu'un SIRET est présent" when the two addressing levels are mixed. Correct the request and replay the call.
422: attribute not modifiable (PATCH)
A modification bearing on anything other than effective_to_on is refused before any transmission, and no request is recorded:
{
"error": "unprocessable_entity",
"code": "invalid_argument",
"message": "effective_to_on (DT-7-3-2) est le seul attribut que cette API transmet à l'annuaire ; l'Annexe 3 V1.8 DG-7 autorise également le matricule de la plateforme de réception (DT-7-6) sur une ligne pas encore en vigueur, ce que cette API ne prend pas encore en charge. Retirez : nature_label, presence_reason",
"details": {
"nature_label": ["ne peut pas être modifié sur une ligne d'annuaire existante"],
"presence_reason": ["ne peut pas être modifié sur une ligne d'annuaire existante"]
}
}
details is keyed by refused attribute, and message lists them in the order you sent them. Remove those keys from the body and replay the call.
403: mandate required
Writes (POST, PATCH, DELETE) require, on the company, a mandate in the active state whose contractual period covers the day of the call:
{
"error": "forbidden",
"message": "Un mandat actif est requis pour gérer l'annuaire de cette entreprise."
}
The two bounds do not work the same way: start_date counts the day itself, so a mandate starting today already permits those writes whereas a mandate starting tomorrow still refuses them; end_date, on the other hand, does not count the day itself, so a mandate whose end_date falls today already refuses them. An approved mandate whose period has not begun therefore produces exactly this 403, just as a company with no mandate at all does.
The mandate is obtained and approved via Electronic invoicing mandates.
403: directory access not activated or insufficient scope
Directory access is activated by Scribee on your workspace according to your plan; until it is, the endpoints on this page respond:
{
"error": "forbidden",
"message": "Vous n'êtes pas autorisé à effectuer cette action"
}
The same body answers a token without the required scope (write for POST and PATCH; write or destroy for DELETE). If your scopes are correct, request directory activation from your Scribee contact. On the lookup, a workspace not attached to your OAuth client responds 403 with "L'application n'a pas accès à cet espace de travail" (Your first call).
This activation check is the last one in the chain. On writes, a company_id outside your application's scope responds 404 and a company without an active mandate responds 403 with the mandate message above, before activation is checked at all; on the lookup, workspace attachment is checked first. So do not conclude from the absence of this message that the directory is activated: fix the reported error first, then replay the call.
404 Not Found
The company or the entry does not exist, or belongs to a workspace outside your application's scope:
{
"error": "not_found",
"message": "La ressource demandée est introuvable"
}
400 Bad Request: page beyond the last
Both lists on this page are paginated and respond 400 beyond the last page, as everywhere else: API conventions.
Related pages
- Companies and establishments - create the company records you register in the directory
- Receive supplier invoices - receive invoices once the registration is in force
- API reference: look up the national directory
- API reference: list a company's directory entries
- API reference: register a directory entry