Skip to main content

Import existing invoices

Not all your invoices exist in Scribee: supplier invoices received outside the exchange networks, invoices issued by another application, historical records to bring over. POST /api/v1/workspaces/{workspace_id}/invoices/upload brings them into your workspace in a single call: you send the file, Scribee detects the format, reads the data, and creates a draft ready to enter the regulatory lifecycle. The write scope is required (Authentication).

This endpoint accepts two families of file, handled differently. A structured file - UBL or CII XML, or a PDF that embeds that XML (Factur-X) - is read during the call and the invoice is returned with a 201. A PDF with no recognised invoice XML and an image (jpg, jpeg, png, heic) go to AI-assisted extraction: the call answers 202 with no invoice, and the invoice is created in the background. That second case depends on the workspace's offer: offers that only allow structured deposits reject it with a 422.

What Scribee does for you​

  • Content-based format detection: the file is classified from its bytes and embedded XML - UBL 2.1, CII (EN16931), or Factur-X (PDF with embedded CII XML) - never from its extension or declared MIME type, which serve only as a fallback. Inside a PDF, the attachment carrying the XML is recognised by its name whatever text-string encoding was used to write it (UTF-16, UTF-8, PDFDocEncoding): whoever produces your Factur-X needs to meet no requirement beyond the standard itself. An ancillary XML file such as index.xml does not make the PDF structured: under a nonstandard name, the XML must have an Invoice, CreditNote, or CrossIndustryInvoice root. The search continues through the other attachments before classifying the PDF as plain. Standard invoice filenames retain priority and their content still undergoes structured validation, even when invalid.
  • Structured parsing: header, seller and buyer, lines, taxes, and payment means are read from the XML, with no mapping required on your side.
  • Allowances that state their direction twice: EN 16931 carries the direction of an allowance/charge group in the indicator alone (BT-91, cbc:ChargeIndicator in UBL, ram:ChargeIndicator in CII) and keeps the amount (BT-92, BT-136) non-negative. Some issuers state the direction twice, sending an allowance whose amount is negative. Scribee reads that amount as a magnitude and imports the invoice, rather than rejecting it over a double negation. A charge with a negative amount contradicts itself and is still rejected: it would lower what you invoice while its indicator says to raise it. The amount Scribee stores, and the one it puts back on the wire when it regenerates the file, is therefore always non-negative - a zero amount is still a valid amount and passes through the import unchanged.
  • The emitter's readable representation: when the structured file carries a PDF (BT-125-1 application/pdf) as a BG-24 attachment whose description (BT-123) reads LISIBLE, in any casing, that PDF becomes the invoice's visible layer instead of the one Scribee would render, and provided_pdf then reads true. AFNOR XP Z12-012 reserves that marker for the complete readable representation of the invoice. As a tolerance towards emitters that do not set it, a PDF whose file name is exactly lisible.pdf, in any casing, is recognised the same way; a name that merely contains it, such as facture_lisible.pdf, is not enough. When an attachment marked LISIBLE and an attachment named lisible.pdf are both present, the one marked LISIBLE wins. The PDF selected is not saved among the invoice's supporting documents. Outside those two cases, an attachment carrying any other description (RIB, BON_LIVRAISON, ...) stays a supporting document and never replaces the rendering. A readable representation Scribee cannot process - encrypted, corrupt, above 50 MB - is ignored without failing the import: the invoice is created and falls back to the Scribee rendering.
  • Flow direction: Scribee compares your company's identifiers (SIREN, VAT number) to the parties in the file - your company as buyer gives a purchase invoice, as seller a sales invoice. With no match, the direction stays the one carried by the call's direction field, purchases by default.
  • Third-party matching: the seller and buyer are matched to your existing suppliers and customers (see below).
  • Original file retained: the imported file is attached to the invoice as long as it is under 50 MB and is indeed a PDF or an XML. The MIME type your HTTP client declares on the file part does not have to be exact: an unrecognized type - application/octet-stream in particular - is replaced by the one derived from the already-analyzed content, and the file is retained anyway. If it is still refused (above 50 MB, or content that is neither PDF nor XML), the call answers 201 and the invoice is created without its original file. upload_source reads api on every invoice this endpoint creates: the provenance is written in the same transaction as the invoice, and a refusal of that write cancels the import rather than letting it through. The API serves that file back in one case: downloading an invoice deposited as a Factur-X in the facturx format returns the deposited bytes themselves (Formats and downloads). Everywhere else, downloads only return the formats Scribee produces (pdf, ubl, cii, facturx).
  • Attachments embedded in the Factur-X PDF: complementary files embedded in the PDF are saved as invoice supporting documents, subject to the accepted file formats. The XML used to read the invoice is excluded. These documents have kind: other and attachable_to_invoice: false. The original PDF stays unchanged. Extraction allows at most 100 embedded files and less than 50 MB of decoded data in total, including the XML. A refused file or a reached limit produces an import warning. Manual uploads retain the 10 MB limit per file.

The path of an imported file​

Import a structured file​

This call creates a draft invoice in your workspace; it transmits nothing to the PPF (Portail Public de Facturation), the Peppol network, or any recipient. The draft stays as is until you trigger a lifecycle transition. It cannot, however, be deleted through the API: an imported invoice carries the file's number, and only drafts without a final number can be deleted. A correction goes through updating the draft (PATCH /api/v1/invoices/{id}, API reference).

Send the file as multipart/form-data, field file:

curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/invoices/upload \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@facture-fournisseur.xml" \
-F "company_id=317" \
-F "direction=purchases"

Three optional fields go alongside file:

  • company_id: the workspace company that receives the invoice. Without it, the first company of the workspace is used - in a multi-company workspace, this is the only way to choose which one. An unknown identifier returns 404.
  • direction: sales or purchases, purchases by default. On this file-upload endpoint the value is not validated: it is overwritten when the parties in the structured file identify your company, and the import then succeeds normally even if you sent nonsense. When the parties do not identify it, the value you sent is kept as is and the import fails further down, with no message pointing at direction. Send only sales or purchases. (JSON creation via POST /api/v1/workspaces/{workspace_id}/invoices does validate this field and answers 422.)
  • lifecycle_state: triggers a lifecycle transition on the invoice in the same call, instead of a separate PATCH /api/v1/invoices/{id}/transition. Accepted values: draft (default, no transition), deposited (deposit, on a sales invoice) or available (made available, on a purchase invoice) - any other value is rejected with 422. On a file that goes to AI extraction, only draft passes without rejection: deposited, available, and any out-of-list value are rejected (see below).

A structured file (UBL, CII, Factur-X) is processed during the call and the created invoice is returned (status 201). Response trimmed to the fields useful here:

{
"data": {
"id": 12456,
"invoice_number": "FAC-2026-0183",
"issue_date": "2026-07-10",
"type_code": "invoice",
"currency_code": "EUR",
"direction": "purchases",
"tax_inclusive_amount": 1770.0,
"lifecycle_state": "draft",
"lifecycle_status_code": "000",
"lifecycle_available_transitions": ["make_available"],
"source_format": "ubl",
"upload_source": "api"
}
}

source_format is ubl, cii, facturx or api: for a received document it carries the carrier, not the syntax it embeds. api is the odd one out: it marks an invoice created through the API with no structured file deposited, so there is no carrier to describe. A Factur-X PDF is returned as facturx. A CII XML stays cii even when it carries a Factur-X guideline, because it is an XML and not a PDF/A-3.

:::warning Behaviour change Until now a Factur-X PDF was returned as cii and the value facturx never appeared. If your integration tests source_format == "cii" to recognise a Factur-X deposit, it must now accept facturx. :::

lifecycle_available_transitions lists the transitions you can trigger: make_available on a purchase invoice (status 203, made available), deposit on a sales invoice (status 200, deposited). The details of the states and codes are in The invoice lifecycle.

lifecycle_state: the real cause of a rejection is in details.file, not in message​

Passing lifecycle_state triggers the requested transition in the same call as the import, under the same rules as the dedicated transition endpoint (PATCH /api/v1/invoices/{id}/transition, The invoice lifecycle). It is all or nothing: if the transition is refused, nothing is created, including the invoice itself.

Three rejection causes all return the same 422 envelope as the other file-processing failures: message stays the generic Échec du traitement du fichier de facture, and details.file[0] is what carries the real cause. A partner that logs only message sees nothing actionable. The third, specific to deposited: this transition can only be triggered manually on a sales invoice, and direction defaults to purchases on this endpoint (see above) - if the file does not flip the direction to sales, the transition is refused with details.file[0]: La transition deposit ne peut pas être déclenchée manuellement., a message that does not mention direction. A deposit refused by the Schematron check is the exception to this shape when it names fields: details is then keyed by field path rather than by file (see below).

Requesting deposited or available on a file that goes to AI extraction (a PDF with no recognised invoice XML, an image) is rejected - draft is still accepted (see above):

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "Échec du traitement du fichier de facture",
"details": {
"file": ["lifecycle_state ne peut être demandé que pour une facture électronique structurée (Factur-X, UBL, CII). Ce fichier nécessite une extraction assistée : rien n'a été créé. Déposez-le sans lifecycle_state, puis utilisez l'endpoint de transition."]
}
}

A value outside draft, deposited, or available:

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "Échec du traitement du fichier de facture",
"details": {
"file": ["Valeur lifecycle_state invalide à la création : 'approved'. Valeurs autorisées : draft, deposited, available"]
}
}

An unstructured file answers 202, not 201​

A PDF with no recognised invoice XML and an image are accepted when the workspace's offer allows unstructured deposits (otherwise, 422: see below). The file then goes to AI-assisted extraction, in the background. The response is not the invoice, and its body has nothing in common with the 201 one:

{
"data": {
"status": "processing",
"upload_file_id": 4821
}
}

No invoice exists yet at that point. It is created later, once the extraction succeeds, and that is when the invoice.created event goes out to your webhook endpoints. No API endpoint lets you follow upload_file_id: wait for the webhook, or re-read the workspace's invoice list. The extraction can also fail, in which case no invoice is created and no event is emitted.

Finally, the extraction can succeed without producing a visible invoice. When automatic document sorting is enabled for the workspace, a file the extraction recognises as something other than an invoice to pay is filed outside your invoices: a delivery note, a quote, a purchase order, a bank statement, a shipping or customs document - including an invoice that carries the statement for customs purposes only, no commercial value or billing invoice will be sent separately -, and an invoice whose read number appears nowhere in the document's text. That document appears neither in GET /api/v1/workspaces/{workspace_id}/invoices nor through GET /api/v1/invoices/{id}, and no invoice.created event is emitted. If it is restored from the Scribee interface, it becomes a visible invoice and invoice.created goes out at that moment; set aside for a number not found, it comes back with a provisional DRAFT- number to replace by PATCH before the deposit.

So handle the two statuses separately: a 201 carries the invoice, a 202 only an acknowledgement.

:::warning Behaviour change A conformant Factur-X PDF whose XML attachment name is written in UTF-16 was not recognised as structured: it went to assisted extraction and answered 202, or was rejected with a 422 on an offer reserved for structured deposits. Those files are now read during the call and the invoice is returned with a 201. If your integration expects a 202 for such uploads, it must accept a 201 carrying the invoice. :::

Third-party matching​

The seller of a purchase invoice is compared to your suppliers, the buyer of a sales invoice to your customers, in this order - the first conclusive criterion wins:

  1. Legal identifier and its scheme (SIREN, SIRET) - the most precise criterion.
  2. SIREN cross-matching: a SIRET from the file is matched to the corresponding SIREN and vice versa, including via a French VAT number.
  3. Intra-community VAT number.
  4. Exact name (case-insensitive), compared to the third party's name and trading name.

A supplier that the company's SAP Business One synchronisation reports as frozen is never a candidate, whatever the criterion, even though it stays listed among your suppliers.

Criteria 2 and 4 stand down when two or more third parties match: no matching happens, rather than matching incorrectly. Criteria 1 and 3 do not run that check: when two third parties carry the same legal identifier, or the same VAT number, one of them is picked and you cannot predict which. Keep your identifiers unique in your directory.

The result is readable in the response: seller.party_id and buyer.party_id carry the identifier of the matched supplier or customer, and are null when no matching happened. An unmatched third party blocks nothing and adds nothing to the invoice; the matching then happens from the Scribee interface.

The seller of a purchase invoice can also be matched later, in the background, when purchase order management is enabled for the company. While the invoice is in draft or available, if the purchase orders the invoice cites - through its purchase_order_reference, through the order_line_reference of its lines, or through its number carried on a goods receipt - all belong to a single supplier, that supplier becomes its seller and seller.party_id takes its identifier. A seller matched by one of the four criteria above, or chosen by a user, is never replaced. A PATCH /api/v1/invoices/{id} that changes purchase_order_reference, a line's order_line_reference or invoice_number restarts this search.

The deliver-to location identifier (BT-71)​

The deliver-to location identifier is only kept when its identification scheme (BT-71-1) travels with the value in the file - cac:DeliveryLocation/cbc:ID/@schemeID in UBL, ram:ShipToTradeParty/ram:GlobalID/@schemeID in CII. An identifier without a scheme is ignored, and both delivery.location_id and delivery.location_scheme_id are then null: rule BR-FR-CO-10_BT-71-1 makes the scheme mandatory as soon as the identifier is stated, so an unqualified identifier could never be re-issued. The deliver-to party name (BT-70) is unaffected and is still kept in delivery.location_name.

Order, despatch advice and delivery at line level​

Each line of an imported UBL or CII file keeps its purchase order number (EXT-FR-FE-135), its despatch advice (EXT-FR-FE-140, EXT-FR-FE-141 and EXT-FR-FE-201) and its delivery (EXT-FR-FE-BG-10), read from the locations described in Issue a sales invoice. Read them back with GET /api/v1/invoices/{id}?include=lines, in purchase_order_reference, despatch_advice and delivery on the line.

Two restrictions apply to the line's delivery:

  • the location identifier is only kept with its scheme, like the header's. In CII, where a line may state several, only the first ram:GlobalID carrying a schemeID is kept;
  • the address is only kept when it carries its country - ram:PostalTradeAddress/ram:CountryID in CII, cac:Address/cac:Country/cbc:IdentificationCode in UBL. Without a country, delivery.address is null. The location name is still kept in delivery.location_name.

References to earlier invoices​

A structured file's references to earlier invoices are kept with their type. At invoice level (BG-3), each invoice_references entry returns the number, date, and type of the referenced invoice (EXT-FR-FE-02), and document_id is null there. At line level (EXT-FR-FE-BG-06), for example the line of a final invoice that takes back a down payment, the number, date, type, and line of the referenced invoice come back in the line's invoice_reference object. The type is returned under its Scribee key, retainer_invoice for a retainer invoice (386); the list of keys is in Issue a sales invoice.

External identifiers (external_source, external_id)​

These two fields are silently ignored by the import endpoint: the call still answers 201 with no error, but the invoice does not carry them. Send them through the draft update (PATCH /api/v1/invoices/{id}, API reference) or at JSON creation (POST /api/v1/workspaces/{workspace_id}/invoices) to link the imported invoice to the matching record in your system - an ERP, for instance.

The two fields go together: sending one without the other fails with 422, on both creation and update.

{
"error": "unprocessable_entity",
"code": "operation_failed",
"message": "external_source et external_id doivent être fournis tous les deux, ou omis tous les deux"
}

external_id is unique per (company, external_source) and capped at 255 characters, same as external_source. external_source cannot take a value reserved for one of Scribee's own integrations: its accounting integrations (the SAP Business One import, among others) and the reception of invoices coming from Chorus Pro, which reserves the value chorus. Sending one fails with 422, on both creation and update:

{
"error": "unprocessable_entity",
"code": "operation_failed",
"message": "'sap_business_one' est une valeur external_source réservée à l'une des intégrations de Scribee et ne peut pas être définie via l'API"
}

Once an invoice carries a reserved external_source - because it was imported by one of these integrations - neither external_source nor external_id can be modified through the API anymore, even to other values:

{
"error": "unprocessable_entity",
"code": "operation_failed",
"message": "external_source et external_id ne peuvent pas être modifiés sur un document importé par une intégration automatisée"
}

These three rejections do not have the shape of the file-processing errors above: the body carries no details key, only error, code and message.

What happens next​

  • The invoice.created event is delivered to your webhook endpoints (Webhooks).
  • The invoice's download formats (pdf, ubl, cii, facturx) are generated, unless a blocking import error occurs: see Formats and downloads.
  • An EN16931 validation (Schematron rules) is started in the background on the XML. Neither its report nor its findings appear in the invoice payload; they only surface at deposit (see below).
  • Nothing goes out: transmission to the recipient and the regulatory lifecycle only begin at the transition you trigger.

Errors and edge cases​

Most file-processing failures return a 422 status in the same shape: error is unprocessable_entity, message is Échec du traitement du fichier de facture, and details.file carries the precise cause. An unknown company_id falls outside that shape and returns 404. An invalid direction that inference did not correct produces two shapes, depending on what the file allows to be decided:

  • If the structured parties identify a company but not yours, the membership check fails first and you receive the file-processing envelope above, with the cause in details.file.
  • If that check cannot decide - no usable party identifier, for instance - the invalid value reaches persistence and you receive the generic validation envelope (message is La validation a échoué, details is keyed by field, here details.direction).

When the file carries structured parties that do identify your company, inference wins and the invalid value is replaced without an error - see above. The errors common to all endpoints (401, 403, 404) follow the format described in API conventions.

422: unreadable file or unknown format​

An XML file that is neither UBL nor CII and an invalid XML file are rejected:

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "Échec du traitement du fichier de facture",
"details": {
"file": ["Contenu XML invalide"]
}
}

Depending on the cause, details.file contains Un fichier est requis, Format de fichier non reconnu (XML, PDF ou image attendu), Format de facture inconnu : <identifiant>, Contenu XML invalide, or Le fichier PDF est malformé : <détail>. That last message is reserved for a PDF that no reader can open: a PDF that is technically malformed but still readable - a declared stream length that does not match the real bytes, a cross-reference table that cannot be found, an object of an unexpected type - is accepted and, carrying no recognised invoice XML, goes to AI extraction (202, see above) like any other PDF with no recognised invoice XML. For the other causes, fix the file or re-export it from the original software before replaying the call.

422: the offer only allows structured deposits​

A PDF with no recognised invoice XML and an image (jpg, jpeg, png, heic) normally go to AI extraction (202, see above). Some offers reserve the endpoint for structured invoices: the file is then rejected, and no invoice is created.

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "Échec du traitement du fichier de facture",
"details": {
"file": ["Votre offre n'autorise que le dépôt de factures électroniques structurées (Factur-X, UBL, CII). Fichier(s) rejeté(s) : facture-scan.pdf"]
}
}

Deposit the file in Factur-X, UBL, or CII format.

422: your company is not a party to the invoice​

When the file carries identifiers and the target company appears in it as neither seller nor buyer:

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "Échec du traitement du fichier de facture",
"details": {
"file": ["L'entreprise sélectionnée (843295671) n'apparaît ni comme vendeur (152836408) ni comme acheteur (503241636) dans ce fichier."]
}
}

Check the company_id you send - without it, the first company of the workspace is used - and that this company's SIREN or VAT number matches one of the parties in the file. The check is skipped when your company carries neither a legal identifier nor a VAT number, and when any party named by the file carries none: a party given by name alone may well be your company, and refusing on that silence would reject a sound purchase - a foreign supplier whose buyer block carries no SIRET, for one. The refusal above therefore only applies when every party named by the file carries an identifier and none of them matches your company.

422: invoice number already imported​

The invoice number is unique per company. Replaying the import of the same file returns:

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "Échec du traitement du fichier de facture",
"details": {
"file": ["Une facture avec le numéro 'FAC-2026-0183' existe déjà pour cette entreprise."]
}
}

This refusal protects you from duplicates as long as the file carries a number. A file with no invoice number receives a randomly drawn placeholder number: replaying that import does create a second draft.

422: your offer does not include sales invoicing​

A file where your company is the seller creates a sales invoice; some offers do not include it. details.file then contains: Votre offre n'inclut pas la facturation de vente. Contactez votre cabinet comptable pour mettre à niveau votre offre.

Import errors are not HTTP errors​

The import succeeds (status 201) even when a required field is missing: a missing invoice number, issue date, type, or currency receives a placeholder value and an import error is recorded on the invoice. The API does not expose those errors; the observable signal is lifecycle_available_transitions, from which deposit and make_available disappear while a blocking error is present. Update the draft with the correct data (PATCH /api/v1/invoices/{id}): the update clears the import errors and the transition becomes available again. It also purges the four download formats and regenerates them in the background. A download requested right after a PATCH does not fail for that reason: a missing format is regenerated during the request. It can still fail for a substantive one - data still incomplete, for instance. It cannot fail because of a PDF you had provided: the update sets provided_pdf back to false and purges the original PDF, so regeneration starts from the Scribee rendering.

A country code outside ISO 3166-1 alpha-2 is not stored​

EN 16931 types the country code (BT-40, BT-55, BT-69) as ISO 3166-1 alpha-2: two letters, FR and not FRA. A value that is not one - an alpha-3, a country name, free text - is not stored: the field comes back null and the call succeeds, instead of the 422 you used to receive.

This is not a relaxation of the rule, it is a change of judge. The requirement is carried by the EN16931 findings BR-09 and BR-11, both fatal, which surface at deposit exactly as described just above, with the field path in details. The same rule used to be enforced at write time as well, where an unreadable country code failed the whole invoice rather than the single field; on AI-assisted imports the counterparty itself was then not recorded either.

Correct the value with PATCH /api/v1/invoices/{id} before depositing.

Schematron findings surface at deposit​

A finding from the EN16931 validation is not an import error: it does not remove deposit from lifecycle_available_transitions, where an import error does. The transition stays listed and callable, and the check applies at the moment you call it. So do not watch the transitions list for findings, handle the 422. The invoice stays in its original state.

Three distinct causes refuse that deposit, and the code field tells you which: schematron_fatal (a fatal assertion - fix the fields the response names, then deposit again), schematron_engine_unavailable (no verdict could be produced - the invoice is not at fault, deposit it again later), and schematron_profile_unsupported (no rule set applies to this profile - depositing again changes nothing, send another profile). The three envelopes are detailed in The invoice lifecycle.

On the transition endpoint, schematron_fatal returns a top-level errors object together with code, with no error and no message. Requested as part of the import with lifecycle_state: "deposited", the same refusal keeps this endpoint's file-processing envelope, and details is what carries the offending fields, in place of its usual file key:

{
"error": "unprocessable_entity",
"code": "schematron_fatal",
"message": "Échec du traitement du fichier de facture",
"details": {
"document.lines[].price.unit_price": ["BR-27: [BR-27]-The Item net price (BT-146) shall NOT be negative."]
}
}

The keys of this details follow the same rules as those of errors on the transition endpoint: they are the API field names and not the BT codes, one assertion may be listed under several fields when the rule stands on a set of elements, and one that no single field carries on its own arrives under document._schematron. The detail is in The invoice lifecycle.

The other two codes keep the usual details.file, whose first element carries the cause. In every case, fix the document or wait, then deposit again.

422 at deposit: Factur-X not PDF/A-3 conformant​

A sales invoice imported as a Factur-X (source_format: "facturx", provided_pdf: false) is transmitted as is, byte for byte. Before depositing it - before status 200, before any transmission -, Scribee therefore checks the PDF/A-3b (ISO 19005-3) conformance of the imported file with veraPDF. The check applies at deposit, whether you request it as part of the import (lifecycle_state: "deposited") or later through PATCH /api/v1/invoices/{id}/transition. Purchase invoices are not concerned, nor are the invoices Scribee generates itself (POST /api/v1/workspaces/{workspace_id}/invoices, with or without pdf_base64).

A non-conformant file is refused, and nothing is created when the deposit was requested as part of the import. details.file[0] names the failed PDF/A rules (three at most) and the file:

{
"error": "unprocessable_entity",
"code": "facturx_not_pdfa",
"message": "Échec du traitement du fichier de facture",
"details": {
"file": ["Factur-X non conforme PDF/A-3 (ISO 19005-3) : 6.8.1: The MIME type of an embedded file shall be specified using the Subtype key - FA-2026-0042.pdf. Corrigez le fichier puis déposez-le à nouveau."]
}
}

code: "facturx_not_pdfa" signals a verdict about the file, and an imported file cannot be replaced: depositing the same invoice again returns the same refusal, so do not replay the call. Fix the file in your generator, then import it again. When the refusal happened on the transition endpoint, the invoice stays a draft with its number; delete it (DELETE /api/v1/invoices/{id}) before importing the corrected file, otherwise the import is refused as an already-imported number. The transition endpoint's envelope is described in The invoice lifecycle.

If veraPDF cannot produce a verdict, the deposit is refused as well, with code: "facturx_validation_unavailable" and in details.file[0]: La conformité PDF/A-3 (ISO 19005-3) du Factur-X n'a pas pu être contrôlée : le service de validation est indisponible. Réessayez le dépôt dans quelques instants. The file was not judged and nothing is recorded: replay the same call later. Branch your handling on code, not on the text of details.file[0].

The verdict is published on the invoice in facturx_conformance. On a sales invoice imported as a Factur-X, this field is null until the deposit has been checked, including after a facturx_validation_unavailable refusal. It becomes compliant when the deposit is accepted, and non_compliant after a facturx_not_pdfa refusal on the transition endpoint. After a refusal of the deposit requested as part of the import, no invoice exists to carry it.

The most frequent cause is an extra attachment embedded without a MIME type: rule 6.8.1 requires every embedded file to declare its MIME type (the /Subtype key of the file specification). Before sending your Factur-X files:

  • give every embedded file, the invoice XML included, a MIME type (/Subtype) and an AFRelationship;
  • validate your files with veraPDF, PDF/A-3b profile (verapdf --flavour 3b facture.pdf).