Skip to main content

The invoice lifecycle

E-invoicing reform does not stop at transmitting invoices: it standardizes how they are tracked. Every invoice, sales as well as purchases, moves through a status framework defined by the AIFE (Agence pour l'informatique financière de l'État), from deposit to collection. This page is the lifecycle reference for the whole guide: the observable statuses, the transitions your system can trigger depending on the invoice direction, and the reason codes required for a dispute or a refusal. Issue a sales invoice and Receive supplier invoices build on it without repeating it.

What Scribee does for you​

  • History: every status change is recorded as a lifecycle event, with its timestamp, its code, and its reason, viewable via include=lifecycle_events in chronological order.
  • Automatic collection: recording a payment that settles the invoice triggers collection (212) without a call on your part, and modifying or deleting it removes it (see Record payments).
  • Computed transitions: the lifecycle_available_transitions field lists, on every invoice, the events your next call can trigger from the current state.
  • VAT in euros at deposit: on an invoice denominated in a currency other than EUR, the deposit (deposit) sets the VAT accounting currency to EUR itself and converts the VAT total into it, at the reference rate for the issue date. You only have currency_code to send; with no rate published for that date, the deposit is refused and the invoice stays a draft (see Issue a sales invoice). make_available, the purchase-invoice transition, is not concerned.

Your transition calls and the payments you record are the two levers you hold, but they are not the only sources of writes. Scribee also applies the statuses that reach it through inbound regulatory flows from another platform - 202 Received, 213 Rejected, and 220 Cancelled among them - and records them in the history like any other transition. An invoice can therefore change state without you calling anything.

On a sales invoice, your customer's statuses also arrive this way: 203 Available, 204 Taken in charge, 205 Approved, 206 Partially approved, 207 Disputed, 208 Suspended, 210 Refused, and 211 Payment sent. Statuses 202 and 203 are optional and your customer's platform may not send them: Scribee therefore applies each of these statuses from 200 Deposited or 202 Received, without waiting for the skipped steps. An earlier status that arrives late - a 202 after a 204, for example - is recorded in the history with applied set to false and never moves the invoice backward. Received statuses previously recorded without effect may also be re-applied: each status that then transitions the invoice is announced a second time, with applied set to true (Webhooks).

The statuses​

The lifecycle_state field carries the API value, lifecycle_status_code the corresponding AIFE code.

Codelifecycle_stateLabelMeaning
000draftDraftDocument under construction: editable via PATCH. For an invoice created directly in Scribee, not deletable as soon as it carries a final number, that is, a number that is present and does not start with DRAFT-; an invoice imported from a file is exempt from this condition (see Reverting to draft).
200depositedDepositedThe sales invoice is validated and enters the lifecycle. A self-billed invoice received over the Peppol network, recorded as a sale, does not go through it: it enters directly at 203.
203availableAvailableThe purchase invoice, or the self-billed invoice received over the Peppol network, has entered the lifecycle and is awaiting your processing.
204taken_in_chargeTaken in chargeYou have started processing the purchase invoice.
205approvedApprovedYou accept the purchase invoice.
206approved_partiallyPartially approvedYou accept only part of the purchase invoice, supported by a standardized reason.
207disputedDisputedYou contest all or part of the purchase invoice, supported by a standardized reason.
208suspendedSuspendedYou suspend processing of the purchase invoice pending further information, supported by a standardized reason and a comment; processing then resumes through one of the other decisions.
210refusedRefusedYou refuse the purchase invoice, supported by a standardized reason; the refusal is final, except for a reopening from the interface of a refusal that stayed in Scribee (see Reopening a refused purchase invoice).
211payment_sentPayment sentThe payment is issued, awaiting collection.
212collectedCollectedThe payment is received; the invoice is settled.

The AIFE framework has other codes (202 Received, 213 Rejected, 220 Cancelled among them). No call to this API produces them directly, but the processing of inbound regulatory flows applies them and records them in the history. One more deviation is worth knowing: modifying or deleting a payment that had settled the invoice can move it back from 212 Collected to 211 Payment sent, with no transition call. Treat lifecycle_state as an open value - keep a default branch for a code you do not know rather than assuming the list is closed, and do not assume the state is monotonic.

Another platform may also send you the statuses 214 Endorsed, 224 Direct payment request, 225 Factored, 227 Payee account change, and 228 Unfactored. They are purely informational: they appear in the history with applied set to false, their state field reads endorsed, direct_payment_requested, factored, payee_account_changed, and unfactored respectively, and they never change lifecycle_state. Status 226 is never recorded. No call to this API produces these statuses.

The transition graph​

The diagram shows the typical path; the table that follows lists each transition individually.

Intermediate statuses are optional: collect (212) accepts any active status after deposit as its starting point. The contract, transition by transition:

EventFromToTriggerreason_code
deposit000200You, sales invoice-
make_available000 or 202203You, purchase invoice; automatic for a self-billed invoice received over the Peppol network-
take_in_charge203 or 208204You, purchase invoice-
approve203, 204, 207, or 208205You, purchase invoice-
approve_partially203, 204, 207, or 208206You, purchase invoicerequired
dispute203, 204, or 208207You, purchase invoicerequired
suspend203, 204, or 207208You, purchase invoicerequired, with reason
refuse203, 204, 207, or 208210You, purchase invoicerequired, with reason
send_payment203, 204, 205, or 206211You, purchase invoice-
collect200, 202, 203, 204, 205, 206, 207, 208, or 211212You, sales invoice; automatic when a payment settles the invoice-
uncollect212211Automatic, when a modified or deleted payment no longer settles the invoice-
revert_to_draft200, or 203 for a purchase000You, under conditions (see below)-

Payment sent (211) requires neither a prior taking in charge nor a prior approval. On an account where the purchase invoice approval workflow is enabled, however, send_payment only leaves 205: the invoice must be approved before its payment is declared.

On a sales invoice, make_available, take_in_charge, approve, approve_partially, dispute, suspend, refuse, and send_payment also leave 200 and 202: these are your customer's statuses, and those transitions come only from inbound flows (see What Scribee does for you).

Who triggers what​

The events your system can trigger depend on the invoice direction (direction):

  • Sales (direction: "sales"): deposit, collect, revert_to_draft. On a self-billed invoice received over the Peppol network, which enters at 203, only collect is open: deposit starts from 000, and revert_to_draft from 203 is reserved for purchase invoices.
  • Purchases (direction: "purchases"): make_available, take_in_charge, approve, approve_partially, suspend, dispute, refuse, send_payment, revert_to_draft. Collection (212) of a purchase invoice is obtained by recording payments, not through the transition endpoint.
  • uncollect never triggers via the transition endpoint: it is automatic, driven by your payments. Sending it returns 422 with the unknown-event message.
  • receive, reject, and cancel exist in the state machine, but no API call can trigger them, whatever the invoice direction. You get 422: La transition receive ne peut pas être déclenchée manuellement. when the current state would permit the transition, and Impossible de passer de ... otherwise.

An event of the wrong direction follows the same rule. collect on a deposited purchase invoice returns La transition collect ne peut pas être déclenchée manuellement.; approve on a deposited sales invoice likewise returns La transition approve ne peut pas être déclenchée manuellement., because the state permits the transition to your customer's status, never to your call. On a deposited purchase invoice, by contrast, approve returns Impossible de passer de deposited à approve. L'état actuel ne permet pas cette transition.: the state already forbids the transition before the direction rule is evaluated.

You do not need to recompute these rules: lifecycle_available_transitions lists exactly the events your next call can trigger from the current state, guards and direction rules included.

lifecycle_state cannot be changed with PATCH​

The lifecycle_state field is read-only on PATCH /api/v1/invoices/{id}. Sending it with a value fails the whole call with 422 and the code invalid_argument: no change is applied, not even to the other fields of the payload.

{
"error": "unprocessable_entity",
"code": "invalid_argument",
"message": "lifecycle_state ne peut pas être modifié sur ce point d'entrée. Utilisez PATCH /api/v1/invoices/{id}/transition pour faire évoluer la facture dans son cycle de vie."
}

The refusal is about the presence of a value: lifecycle_state: null or an empty string passes through the endpoint with no error and no effect. To move the invoice forward, drop lifecycle_state from your PATCH payload and call the transition endpoint, the only one that carries reason_code and reason.

The one place lifecycle_state is still accepted as a write is creation: POST /api/v1/workspaces/{workspace_id}/invoices can create and deposit the invoice in the same call (see Issue a sales invoice).

The reason codes​

Four statuses require a standardized reason (AFNOR XP Z12-012 standard): partial approval (206), dispute (207), suspension (208), and refusal (210). Every other event, deposit (200) included, accepts no code at all: attaching a reason_code returns 422.

The NON_TRANSMISE (Not transmitted) code is nonetheless readable on an invoice in status 200: Scribee sets it itself, when the buyer is registered (SIREN or SIRET) and the national directory publishes no active electronic invoicing address for it.

Two codes additionally require free text in reason: AUTRE and REF_ERR. For every code, reason remains accepted as a supplement, within a limit of 250 characters, and is never accepted without reason_code.

A refusal (210) always requires a reason, whichever reason_code you pick. Rule G7.25 asks for a comment motivating the refusal in the MDT-126 tag, and the MDT-113 reason code does not stand in for it: the code says which check is at stake, the text says why this particular invoice is refused. A refuse without reason returns 422 with code: operation_failed, and the invoice does not move. A dispute (207) is not affected: it is motivated by its reason_code alone, except when that code is AUTRE or REF_ERR. Those two codes do not change either: they already required free text, and they still do, whatever the status.

A suspension (208) requires a reason as well, under the same rule G7.25: a suspend without reason returns 422, and the invoice does not move. A partial approval (206) is motivated, like a dispute, by its reason_code alone, except when that code is AUTRE or REF_ERR.

:::warning Behaviour change Until now, a refuse carrying only reason_code was accepted: the invoice did move to 210, but the refusal acknowledgement owed to the issuer could not be built for want of that comment, and nothing told you so - the transition had answered 200. The check is now made at the moment of the transition, where your call can still supply the text. If your integration refuses invoices sending only reason_code, it now receives 422: add reason to its payload. :::

CodeLabel206207208210
AUTREOther (free text required)xx
COORD_BANC_ERRWrong bank detailsxx
TX_TVA_ERRWrong VAT ratexx
MONTANTTOTAL_ERRWrong total amountxx
CALCUL_ERRInvoice calculation errorxx
NON_CONFORMEMissing legal mentionxx
DOUBLONDuplicate invoicexx
DEST_INCUnknown recipientx
DEST_ERRWrong recipientxx
TRANSAC_INCUnknown transactionxx
EMMET_INCUnknown senderxx
CONTRAT_TERMContract endedxx
DOUBLE_FACTDuplicate F1 regulatory dataxx
CMD_ERRWrong or missing order referencexxxx
ADR_ERRWrong e-invoicing addressxx
SIRET_ERRWrong or missing SIRETxxx
CODE_ROUTAGE_ERRWrong or missing routing codexxx
REF_CT_ABSENTMissing contractual referencexxxx
REF_ERRWrong reference (free text required)xxx
PU_ERRWrong unit pricexx
REM_ERRWrong discountxx
QTE_ERRWrong invoiced quantityxx
ART_ERRWrong invoiced itemxx
MODPAI_ERRWrong payment termsxx
QUALITE_ERRDefective delivered itemxx
LIVR_INCOMPIncomplete or missing deliveryxx
JUSTIF_ABSMissing or insufficient supporting documentx

A code sent on a status where it is not allowed returns 422: the validation is done per target status, not against the global list.

The requested action of a dispute​

A dispute (dispute, status 207) can state, in addition to its reason, what you expect from the supplier. Two optional fields carry it, under invoice, next to reason_code and reason:

  • requested_action_code: the requested action in coded form (MDT-121), taken from the closed list below.
  • requested_action: its free-text description (MDT-122), up to 250 characters.

The two are independent: send one, the other or both. Neither is inferred: a dispute sent without them carries no requested action, not even NOA. An empty or whitespace-only value is treated as absent.

CodeRequested action
NOANo action required
PINProvide additional information
NINIssue a corrective invoice
CNFIssue a full credit note
CNPIssue a partial credit note
CNARefund the amount paid
OTHOther
curl -X PATCH https://app.scribee.tech/api/v1/invoices/12345/transition \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"invoice": {"event": "dispute", "reason_code": "QTE_ERR", "requested_action_code": "CNP", "requested_action": "Avoir pour les 3 unités non livrées"}}'

Each of these cases returns 422 with code: operation_failed, and the invoice does not move:

  • either field sent with an event other than dispute, approve_partially, suspend and refuse included: it is refused, not ignored;
  • a requested_action_code outside the list above;
  • a requested_action sent as a number rather than text, or that exceeds 250 characters.

Both fields are recorded with the 207 event, but include=lifecycle_events does not return them.

The amount collected​

Collection (212) is the only transition that carries money, and the only one that requires a field beyond event: collected_amount. Every other event ignores it.

That amount is stated, never inferred. Annexe 7 rule P1.15 asks for the sum actually received (montant encaissé, MDT-215), not the invoice total: declaring the total in place of a partial collection would over-declare to the PPF. Scribee does not guess that sum, your call is what names it.

curl -X PATCH https://app.scribee.tech/api/v1/invoices/12345/transition \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"invoice": {"event": "collect", "collected_amount": 812.34}}'
{
"data": {
"id": 12345,
"lifecycle_state": "collected",
"lifecycle_status_code": "212",
"lifecycle_available_transitions": []
}
}

Three rules frame the value:

  • Omitting the field returns 422, with code: operation_failed, and the invoice does not move. There is no default: a collect with no amount is never read as collecting the total.
  • 0.00 is accepted. That is how a correction which moved no cash is declared. Zero is a stated amount, not an absent one: do not send it to mean that you do not know the sum received.
  • A non-numeric value and a negative amount are both refused, each with 422 and code: invalid_argument, and no collection is recorded. A non-numeric value is refused rather than read as zero, so the invoice never reaches 212 carrying a figure you did not write. A negative amount is a décaissement (Annexe 7 rule P1.17): its motif d'annulation (MDT-126) has no outbound element mapping yet, so the matching status report could not be produced.

No ceiling is applied: collected_amount is compared neither to the remaining balance nor to the invoice total.

The other door: record a payment​

collect remains reserved for sales invoices (see the Who triggers what section above). Recording a payment leads to the same collection whatever the invoice direction, and Scribee then supplies the amount collected: that of the payment you have just recorded, never the running paid total.

  • A payment that settles the invoice fires collect itself: the invoice moves to 212 Collected, and the event carries that payment's amount.
  • A partial payment also writes a 212 lifecycle event carrying its own amount, but does not settle the invoice: lifecycle_state does not move. A 212 event in the history is therefore not enough to conclude that the invoice is collected - lifecycle_state is what counts.

The detail is in Record payments.

Step by step​

The example follows a purchase invoice imported by file, made available (203). Always start by reading: it has no side effect and gives you the exact list of possible transitions.

1. Read the current status​

GET /api/v1/invoices/{id} returns the lifecycle state and the available transitions; the read scope is enough.

curl https://app.scribee.tech/api/v1/invoices/12345 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Abridged response, lifecycle fields only:

{
"data": {
"id": 12345,
"invoice_number": "FRN-2026-0042",
"direction": "purchases",
"lifecycle_state": "available",
"lifecycle_status_code": "203",
"lifecycle_available_transitions": ["take_in_charge", "approve", "dispute", "refuse", "send_payment", "revert_to_draft"]
}
}

2. Trigger a transition​

This call writes a timestamped lifecycle event to the invoice history; the history is never deleted, and no transition moves an approved invoice backward. Approval (205) is not transmitted to your supplier or to any external network. The write scope is required.

curl -X PATCH https://app.scribee.tech/api/v1/invoices/12345/transition \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"invoice": {"event": "approve"}}'
{
"data": {
"id": 12345,
"lifecycle_state": "approved",
"lifecycle_status_code": "205",
"lifecycle_available_transitions": ["send_payment"]
}
}

3. Give a reason for a dispute or a refusal​

A refusal is final: no transition leaves status 210. Only a Scribee user can, from the interface, reopen a purchase invoice whose refusal never left Scribee (see Reopening a refused purchase invoice). When in doubt, prefer a dispute (dispute), which keeps approval and refusal both open. Both require a reason_code from the table above, and a refusal additionally requires the free text reason.

curl -X PATCH https://app.scribee.tech/api/v1/invoices/12345/transition \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"invoice": {"event": "refuse", "reason_code": "TX_TVA_ERR", "reason": "Taux de TVA à 20 % au lieu de 10 % sur la ligne 2"}}'
{
"data": {
"id": 12345,
"lifecycle_state": "refused",
"lifecycle_status_code": "210",
"lifecycle_available_transitions": []
}
}

4. Read back the history​

include=lifecycle_events returns the audit trail, in ascending chronological order.

curl "https://app.scribee.tech/api/v1/invoices/12345?include=lifecycle_events" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Abridged response:

{
"data": {
"id": 12345,
"lifecycle_state": "refused",
"lifecycle_events": [
{
"id": 881,
"status_code": "203",
"status_label": "Mise à disposition",
"state": "available",
"occurred_at": "2026-03-02T09:15:00+01:00",
"sender_role": null,
"sender_id": null,
"sender_scheme_id": null,
"reason": null,
"reason_code": null,
"terminal": false,
"rejected": false,
"applied": true,
"created_at": "2026-03-02T09:15:00+01:00"
},
{
"id": 902,
"status_code": "210",
"status_label": "Refusée",
"state": "refused",
"occurred_at": "2026-03-04T16:40:00+01:00",
"sender_role": null,
"sender_id": null,
"sender_scheme_id": null,
"reason": "Taux de TVA à 20 % au lieu de 10 % sur la ligne 2",
"reason_code": "TX_TVA_ERR",
"terminal": true,
"rejected": false,
"applied": true,
"created_at": "2026-03-04T16:40:00+01:00"
}
]
}
}

Three fields are always null on the events your calls produce: sender_role, sender_id, and sender_scheme_id. The transition endpoint sends no sender, and the API exposes no parameter to supply one. Do not use these fields to tell events apart by origin. Likewise, applied is always true and rejected always false on those events. terminal is true on statuses 210 and 212, except on a reopened 210, where it is false.

Reopening a refused purchase invoice​

For the API, a refusal stays final: no transition leaves refused, lifecycle_available_transitions is empty there, and no event takes the invoice out of it. A single exception exists, and it does not go through the API: a Scribee user can, from the interface, reopen a purchase invoice refused by mistake, with a mandatory reason. Reopening is only possible when the refusal never left Scribee, that is, when all of these conditions hold:

  • the invoice is a received purchase invoice, not a self-billed invoice drawn up in Scribee;
  • it was received neither through the network (PPF or Peppol) nor through Chorus Pro;
  • no transmission was registered for its 210 status, even one that never went out: a registered send is enough to block the reopening.

A refused invoice that fails any of these conditions stays refused, without exception.

What your integration observes after a reopening:

  • lifecycle_state goes back to available and lifecycle_status_code to 203; lifecycle_available_transitions becomes that of an invoice made available again.
  • No lifecycle event is created: reopening is not an AIFE status and nothing is transmitted. The 210 event stays in lifecycle_events, with applied set to true, but its terminal turns to false. The history can therefore end on a 210 while the invoice is available: read the current state from lifecycle_state, never from the last event.
  • No webhook is emitted. The invoice.lifecycle_event.created delivery for the refusal carried terminal set to true, and nothing announces the reopening: re-read GET /api/v1/invoices/{id} for the current state.
  • A new refusal after the reopening creates a new 210 event, terminal again.

Reverting to draft​

revert_to_draft only applies to invoices that entered Scribee by file - POST /api/v1/workspaces/{workspace_id}/invoices/upload, see Import existing invoices - and that were never attached to a Peppol or PPF flow. A deposited invoice reverts from 200 to draft; a purchase invoice made available reverts from 203 to draft.

An invoice created with POST /api/v1/workspaces/{workspace_id}/invoices is never concerned: revert_to_draft does not appear in its lifecycle_available_transitions, and sending it returns 422. A correction goes through a credit note, described in Issue a sales invoice.

Back in draft, the invoice is editable again via PATCH. DELETE /api/v1/invoices/{id} requires the destroy or write scope: a token with neither receives 403 forbidden. With the scope present, deletion stays refused until the invoice meets every one of these conditions: in draft (lifecycle_state: draft), never exchanged over an external network (Peppol or PPF), with no lifecycle event ever recorded, absent from any accounting export, and not created by converting a quote. One more condition applies only to invoices created directly in Scribee (the invoice form, quote conversion, or POST /api/v1/workspaces/{workspace_id}/invoices): their number must still start with DRAFT-, or deletion is refused. An invoice imported by file (POST /api/v1/workspaces/{workspace_id}/invoices/upload) escapes that last condition: its original number, even a final one, never blocks deletion. A confirmed purchase match, on the other hand, blocks deletion regardless of the invoice's state, draft or not.

Each unmet condition returns 422, in an {error, code, message} envelope with no details key, carrying the code operation_failed:

{
"error": "unprocessable_entity",
"code": "operation_failed",
"message": "Cette facture a consommé un numéro séquentiel et ne peut pas être supprimée. Passez par une annulation ou un avoir."
}
messageCause
Cette facture ne peut pas être supprimée car elle est rapprochée d'un bon de commande ou d'un reçu confirmé.confirmed purchase match, regardless of the invoice's state
Cette facture a été échangée via un réseau externe (Peppol/PPF) et ne peut pas être supprimée.draft attached to a Peppol or PPF flow
Cette facture est déjà entrée dans le cycle de vie de facturation et ne peut pas être supprimée.at least one lifecycle event already recorded
Cette facture fait partie d'un export comptable et ne peut pas être supprimée.accounting entry already included in an export
Cette facture est issue de la conversion d'un devis et ne peut pas être supprimée.created by converting a quote
Cette facture a consommé un numéro séquentiel et ne peut pas être supprimée. Passez par une annulation ou un avoir.created directly in Scribee, with a number that no longer starts with DRAFT-
Seules les factures en brouillon peuvent être suppriméesthe invoice is no longer a draft

In very rare concurrency cases - an invoice entering deposit at the exact moment of its deletion - the failure returns a generic message, "Échec de la suppression de la facture", unrelated to the conditions above.

:::caution Never send an invoice_number starting with DRAFT- DRAFT- is the prefix reserved for the internal placeholder, and invoice_number is not filtered at creation: an invoice created with a number of that shape is deletable, and more importantly its deposit does not keep that number - it is replaced by the company sequence, or refused when no numbering template is configured. Pick any other prefix. :::

An invoice that fails one of these conditions is fixed with PATCH while it stays a draft, or with a credit note once it leaves draft.

The payment status​

Scribee derives a payment status from every lifecycle status, displayed in the Scribee interface. It is not exposed by the API. On the API side, apply the same derivation to lifecycle_state:

Payment statusLifecycle statuses
Pending000, 200, 202, 203, 204, 205, 207
In progress211
Paid212
Not applicable210, 213, 220

The PPF verdict on the deposit​

lifecycle_state above is Scribee's own tracking of the invoice's commercial lifecycle. What the French tax administration decided about the deposit of this invoice is an entirely different fact, published by three separate fields, all read-only: deposit_outcome, deposit_outcome_at, and deposit_receptions.

Do not conflate the two. An invoice refused by the buyer is lifecycle_state: "refused" (AIFE code 210): the document was deposited and delivered, and it is your customer rejecting it for a commercial reason. A deposit_outcome of "251" says the opposite: it is the deposit itself the administration did not accept, so the invoice never reached a buyer at all. The two facts have different code lists, different causes, and different follow-ups. An integration that merges them will get both wrong.

deposit_outcome: the verdict code​

String or null. Once the Portail Public de Facturation (PPF) has ruled, this field carries its verdict as the reform's own code (Dossier de specifications externes FE v3.2, section 3.6.8, Tableau 9), carried back on interface FFE0604A:

ValueMeaning
"250"Deposee (filed): the invoice was checked as conformant and taken into account
"251"Rejetee (rejected): the invoice was checked as non-conformant, it is neither integrated nor taken into account

null means the PPF has not yet ruled on this deposit. It is not an error, not a default, and not a third status: read it as "no answer yet" and read the invoice again later.

One case where null is permanent, and where reading the invoice again later changes nothing. The reform phases its issuing obligation in by waves, the first of which starts on 1 September 2026. Until the issuing company is declared as part of that wave, Scribee reports nothing to the PPF for its sales invoices: no verdict is expected, so deposit_outcome stays null indefinitely rather than temporarily. This is a company setting, under Company settings > Electronic invoicing, not a property of the invoice and not an API field.

The mirror image of that case, for a company that IS in the wave. A sales invoice from a company declared as part of the wave must be addressed to one of the recipient's electronic invoicing addresses (scheme 0225) before it can be deposited. That resolution only applies to sales to a buyer carrying a SIREN and established in the French VAT territory, or stating no country, the only ones that are deposited as an invoice: a sale to any other buyer - without a SIREN, or carrying a SIREN but established in a foreign country or in the overseas territories outside that territory - is deposited without any directory address being looked up, and e-reporting is what carries it (see Declare transactions). No address is looked up either when the seller is established in Guyane, Mayotte, an overseas collectivity or the French Southern and Antarctic Lands: the invoice is then not deposited with the PPF (see Overseas territories outside the VAT territory). Scribee resolves the address at deposit time, with no network call, in this order: the value carried by the invoice (the buyer's directory_routing_identifier, including the one the invoice copied from the customer record when your creation payload's buyer carries a party_id), then the buyer's electronic address carried by the invoice (endpoint_id) when its endpoint_scheme_id is aife (0225) - the one in your creation payload, or the BT-49 of an imported UBL, CII or Factur-X invoice -, then the value on the customer record, then the national directory. An electronic address under any other scheme (siret, siren...) plays no part in this choice.

Three outcomes, and two are a failure of your call:

  • No active address published for a registered buyer: the deposit succeeds. The invoice moves to 200 and Scribee sets the NON_TRANSMISE reason on it (see The reason codes). This is the normal case of a buyer that is not equipped yet: the invoice is deposited, it is simply not handed to a recipient platform.
  • Several active addresses published, with no way for Scribee to choose: the deposit is refused with 422, and nothing is recorded.
  • A recorded address the directory gives no way to use: the value carried by the invoice, failing that its aife (0225) electronic address, or failing that again the one on the customer record, does not read as a scheme 0225 address. Scribee does not fall back to the next level - that would reroute the invoice to an address you did not choose - so the deposit is refused with 422 and nothing is recorded either.

In these two refusals, the response body carries details keyed on buyer.directory_routing_identifier: that is the field to fill in, either on the customer record or in the creation payload. An endpoint_id under endpoint_scheme_id aife (0225) on the payload's buyer also designates the address, but only when the invoice carries no directory_routing_identifier: when several active addresses are published, it is then what makes the choice. A buyer designated by party_id inherits the customer record's directory_routing_identifier, which wins over that endpoint_id; to choose another address on such an invoice, send directory_routing_identifier in the buyer entry, which takes precedence over the copied value. A third refusal, far rarer, comes from concurrency: when the address on the customer record changes during the deposit itself, the invoice is not deposited rather than being announced on one address and stored under another; simply deposit it again. Once the invoice is deposited the address is frozen on it: a retry reads that value back, runs no new lookup, and therefore never silently reroutes an invoice that was already announced elsewhere.

These refusals come before the semantic check, and you will notice. The address is resolved before the invoice is validated, because the bytes being checked carry that address themselves (BT-49). An invoice with both problems - an unaddressable recipient and a fatal semantic assertion - therefore surfaces the address error first, and the Schematron verdict never gets to speak. Do not read its absence as the invoice being semantically valid: fix the address, then deposit again to get the verdict.

A separate refusal, and one that is not fixed in the same place. A buyer established in the French VAT territory whose registration number is on file but reads as neither a SIREN nor a SIRET makes the deposit fail with 422, before the address is even resolved. The response body carries details keyed on buyer.legal_registration_id: it is the registration number that has to be fixed on the customer record, not the address. That is where the line falls: a registration number that is present but unreadable is data to fix, whereas a buyer carrying none at all is a customer with no registration number - its invoice is deposited, and the sale goes to e-reporting (see Declare transactions).

One more refusal, on a piece of data your payload does not always carry. A sales invoice whose seller or buyer carries no country makes the deposit fail with 422. The country of every party is mandatory on an electronic invoice - BT-40 for the seller, BT-55 for the buyer - and Scribee fabricates none: a party recorded with no country stays without one, and it is the deposit that asks for it. The response body carries details keyed on seller.address.country_code, buyer.address.country_code, or both when both parties are silent - the exact path under which reading the invoice returns that field. The seller's country is filled in on your company's headquarters establishment, the buyer's in the address on the customer record or in the creation payload. This refusal follows the same perimeter as the address resolution above: it only applies to sales that are deposited as an invoice, never to a sale carried by e-reporting, whose report asks for no country.

:::warning Behaviour change Until now, a party with no country was given one by default: Scribee wrote FR, and the invoice was deposited asserting a territory nobody had declared. That code is no longer fabricated. A customer record whose address carries no country, like a headquarters establishment with no country, now produces a party with no country, and its deposit is refused rather than accepted on invented data. Fill the country in on the records concerned before your next deposit. :::

The rest of the lifecycle is unchanged for those companies: the invoice is created, edited, deposited (status 200), exported, delivered to a connected accounting system and notified to your webhooks exactly as described above. Only the regulatory transmissions to the PPF and to Peppol wait for the wave to start.

Their deposit is not judged against the electronic-invoice rules either. Since nothing a company outside the wave issues is transmitted, the Schematron check described below does not apply to its sales, and neither do the B2C refusals. The other deposit checks apply as they do to everyone: blocking import errors, conformance of the PDF you supplied, mandatory legal mentions, numbering template, exchange rate.

deposit_outcome_at: the instant of the verdict​

String or null. This is the instant the PPF stamped that verdict, republished exactly as it sent it, in the YYYY-MM-DDTHH:MM:SS format.

It deliberately carries no time zone designator. The reform declares no time reference for this field: attaching an offset would publish a precision the message never carried. So do not parse it as a UTC instant, unlike created_at, updated_at, and a reception's received_at, which are full zoned instants.

This field is null exactly when deposit_outcome is null.

deposit_receptions: rejection motives, grouped by reception​

Always an array, never null. An empty array means no rejection motive is on file, which is the normal case for a deposited invoice as well as for one with no answer yet.

Each entry corresponds to one FFE0604A reception that carried rejection motives for this invoice's deposit, oldest first. It carries two fields:

  • received_at: when Scribee received that reception.
  • motives: the motives it carried, in document order. A rejection carries at least one.

Each motive in turn carries:

  • code: the control that failed (MDT-113, section 3.6.9, Tableau 11). Three values, and the list is closed:

    ValueFailed control
    REJ_SEMANSemantic or format control
    REJ_UNIUniqueness control: the data was already transmitted and processed
    REJ_COHData coherence control

    REJ_PER, the period control, does not exist on this interface: it belongs to the e-reporting list (section 3.7.10, Tableau 6, see E-reporting). The two lists look alike and are not the same. An invoice deposit declares no period for a period control to fail on.

  • anomaly_source: string or null (MDT-126). The PPF's own free text saying where the anomaly is, as opposed to code, which says which control failed. This is the actionable part for your user: display it. It carries up to 2,000 characters and is never truncated. Only the code is mandatory on a rejection, so this field is null when the PPF sent no text.

deposit_outcome is the latest verdict, not the history​

This is the point not to miss, and the reason motives are grouped by reception rather than listed flat on the invoice. The administration can rule more than once on the same invoice, and each answer carries its own reasons. deposit_outcome carries the latest known verdict and nothing else. deposit_receptions carries the history of what was held against it. The two never contradict each other: they answer different questions, and a later verdict does not erase an earlier reception's motives.

The sequence for an invoice that was rejected, corrected, and re-deposited:

  1. The PPF rejects the deposit. deposit_outcome is "251", deposit_outcome_at carries the instant of that rejection, and deposit_receptions holds one entry: the reception that carried the motives.
  2. The invoice is corrected and re-deposited, and the PPF accepts it. deposit_outcome becomes "250" and deposit_outcome_at carries the instant of that acceptance. But the previous rejection's entry stays in deposit_receptions, with its received_at and its motives unchanged.

At step 2, the invoice looks like this:

{
"id": 4312,
"invoice_number": "FA-2026-0087",
"lifecycle_state": "deposited",
"lifecycle_status_code": "200",
"deposit_outcome": "250",
"deposit_outcome_at": "2026-03-11T14:05:00",
"deposit_receptions": [
{
"received_at": "2026-03-04T09:30:15Z",
"motives": [
{
"code": "REJ_COH",
"anomaly_source": "Ligne 3 : total incohérent"
},
{
"code": "REJ_SEMAN",
"anomaly_source": null
}
]
}
]
}

An integration that flattens deposit_receptions into a plain list of motives and displays them as "the reasons this invoice was rejected" will therefore be wrong at exactly the moment the customer has already fixed the problem: it will announce a rejection on a deposited invoice.

The rule is simple. To know where the deposit stands, read deposit_outcome. To know what was held against it and when, read deposit_receptions, keeping each group of motives attached to its received_at. A non-empty deposit_receptions alongside a deposit_outcome of "250" is not an inconsistency: it is the record of a rejection that was since corrected.

What happens next​

  • Every transition pushes an invoice.lifecycle_event.created event to your webhook endpoints (Webhooks, Platform category), including the collections and reversals triggered by your payments. Your system does not need to poll the API in a loop to track the transitions it triggers.
  • Two statuses follow a transmission rule of their own. A 212 Collected is declared to the PPF only when the invoice's VAT is due on collection: a prepayment invoice, tax_due_date_code at 72, or, without that code, a billing framework (BT-23) that does not start with B; otherwise it goes only to the other party's platform. A 213 Rejected that Scribee raises when issuing a sales invoice goes to the PPF alone, never to the recipient's platform.
  • Deposit assigns a number from the company's numbering sequence only when the invoice still carries a DRAFT- prefixed number, which is the case for invoices created from the Scribee interface - and would equally be the case for an API invoice you gave a number of that shape yourself. An invoice created with POST /api/v1/workspaces/{workspace_id}/invoices therefore keeps its number at deposit as long as it does not carry that prefix: the one you sent, or the TEMP-... placeholder generated at creation if you sent none. If you want a partner-controlled number, send it at creation or correct it with PATCH before depositing.
  • On entering the lifecycle (deposit or make_available from draft), Scribee regenerates the invoice files: UBL, CII, Factur-X, and PDF (see Formats and downloads). The PDF is the exception in two cases: when you supplied the PDF yourself, and when the invoice was imported as a PDF or an image - the original file is then kept as is. Factur-X is an exception too when the invoice was deposited in that form: the deposited file is returned rather than regenerated. Generation is asynchronous: a download started immediately after the transition may still return the previous file.

Errors and edge cases​

Transition failures return 422 with error: "unprocessable_entity", a machine code, a message in French, and a details object. The code is invalid_argument when the event itself is unknown or missing, operation_failed in most other cases, one of the three Schematron-check codes described further down when it is that check refusing the deposit, and one of the two codes of the PDF/A-3 check of an imported Factur-X, also described further down, when it is that one. details is empty except for the mandatory-legal-mentions refusal and the refusal of a Factur-X that is not PDF/A-3 conformant, both described further down. Base your handling on the HTTP status and on code, never on the message text (API conventions).

{
"error": "unprocessable_entity",
"code": "operation_failed",
"message": "Un motif normalisé est requis pour le statut En litige",
"details": {}
}
messageCauseWhat you do
Évènement de cycle de vie inconnu ou manquant. Fournissez un nom d'évènement valide parmi les transitions disponibles de la facture.event missing, unknown, or uncollectsend an event listed in lifecycle_available_transitions
Impossible de passer de approved à deposit. L'état actuel ne permet pas cette transition.the transition does not exist from the current statereread the invoice and start again from lifecycle_available_transitions
La facture comporte des erreurs d'import bloquantes. Corrigez-les avant de changer son statut.deposit or make_available on a draft carrying a blocking import error other than a Factur-X non-compliance verdictPATCH the whole invoice to clear the errors, then replay the transition
Le PDF Factur-X joint n'est pas conforme. Remplacez-le par un fichier conforme avant de déposer la facture.deposit of an invoice whose PDF you supplied and which was evaluated as non-compliantsupply a compliant PDF with PATCH, then deposit again
Des mentions légales obligatoires sont absentes de la facture (pénalités de retard, frais de recouvrement et escompte). Renseignez-les dans les paramètres de facturation de l'entreprise, puis réessayez.deposit of a sales invoice missing one of the three mandatory legal mentionsread details, which names the attributes to fill in
Cette facture a été créée alors que la société était membre d'un assujetti unique, mais elle ne le déclare pas en entier (SIREN du vendeur sous le schéma 0231, raison sociale, numéro de TVA et adresse de l'assujetti unique, note MEMBRE_ASSUJETTI_UNIQUE). Complétez l'assujetti unique dans les paramètres de la société, puis réenregistrez le brouillon avant de le déposer.deposit of a sale created while the company declared a single taxable entity, which does not carry the complete statementcomplete the company's single taxable entity settings, PATCH the draft, then deposit again
La transition receive ne peut pas être déclenchée manuellement.event reserved for the state machine, or event of the wrong direction while the state would permit the transitioncheck direction and start again from lifecycle_available_transitions
Un motif normalisé est requis pour le statut En litigedispute or refuse without reason_codeadd a code from the reason-code table
Un statut Refusée (210) exige un commentaire motivant le refus dans la balise MDT-126 (règle G7.25) ; le code motif MDT-113 ne le remplace pas, et aucun commentaire n'a été enregistré avec ce statut.refuse without reasonadd the free text motivating the refusal
Marquer une facture comme encaissée exige le montant encaissé. Transmettez collected_amount avec la transition, ou enregistrez le paiement sur la facture, ce qui déclare l'encaissement avec le montant réellement reçu.collect without collected_amountadd collected_amount, or record a payment
collected_amount doit être un nombre. Le montant réellement encaissé (MDT-215) est enregistré tel qu'il est transmis, jamais déduit : une valeur non numérique est donc refusée plutôt qu'interprétée comme zéro.collected_amount is not a numbersend a number
Un montant encaissé négatif (décaissement) sous le code de statut 212 exige un motif d'annulation dans le champ commentaire (règle P1.17 de l'annexe 7, MDT-126), et ce champ n'a aucun élément d'émission défini à ce jour ; le CDAR n'est donc pas construit plutôt qu'émis sans son motif.collected_amount is negativesend a positive or zero amount
Le motif REJ_SEMAN n'est pas autorisé pour le statut En litigecode outside the target status's list, or reason_code sent on an event that accepts nonechoose an allowed code, or remove the code
Un commentaire est requis pour le motif AUTREAUTRE or REF_ERR without reasonadd the free text
Le motif doit comporter au maximum 250 caractèresreason too longshorten the text
Le motif doit être une valeur textuellereason sent as a number or a booleansend a string
Un code motif est requis lorsqu'un commentaire est fournireason sent without reason_codeadd the code, or remove the text
Impossible de déposer la facture : veuillez renseigner un numéro de facture unique.deposit of an imported invoice whose number stayed at the internal DRAFT- placeholderset the invoice's original number with PATCH before depositing
Impossible de déposer la facture : aucun modèle de numérotation configuré pour cette entreprisedeposit of an invoice at the DRAFT- placeholder coming from the Scribee interface, with no numbering templateconfigure the numbering template from the Scribee interface
Impossible de déposer cette facture en USD : aucun taux de change n'est disponible pour le 12/03/2026. Une facture en devise étrangère doit porter son total de TVA en EUR, ce qui exige un taux publié. Attendez la synchronisation des taux, puis relancez le dépôt.deposit of an invoice denominated in a currency other than EUR, with no rate published for its issue datedeposit again once the rate is available, or fix issue_date if it is wrong
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Cette déclaration précise si l'opération porte sur des biens ou sur des services, or la facture ne porte aucun cadre de facturation et ses lignes n'en impliquent aucun. Renseignez le cadre de facturation sur la facture, puis redéposez-la.deposit of a sale to an unidentified buyer, with no invoicing frameworksend invoicing_process_id, then deposit again
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Son cadre de facturation M1 couvre à la fois des biens et des services, or cette déclaration compte les deux séparément, ligne à ligne. Émettez les biens et les services sur des factures distinctes, puis redéposez-les.deposit of a sale to an unidentified buyer, under a mixed invoicing framework (M1, M2, M4)split the invoice into a goods invoice and a services invoice
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Cette déclaration porte une vente relevant du régime de la marge dans une catégorie qui lui est propre, or la facture mêle des ventilations de TVA relevant du régime de la marge et des ventilations ordinaires - une répartition qui n'est pas encore prise en charge. Émettez les ventes relevant du régime de la marge sur une facture distincte, puis redéposez-les.deposit of a sale to an unidentified buyer that mixes a margin-scheme VAT breakdown with another VAT breakdownissue the margin-scheme sales on a separate invoice
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Relevant du régime de la marge, elle y est déclarée sur sa marge hors TVA et la TVA due sur cette marge, deux montants que la facture n'indique pas : sa ventilation au régime de la marge porte le prix de vente au taux nul. Renseignez la base de marge de la vente pour chaque taux de TVA (marge hors TVA et TVA sur la marge, réelles ou estimées), puis redéposez-la.deposit of a sale to an unidentified buyer that carries a margin-scheme VAT breakdown, with no margin basesupply the bases with a PATCH carrying only margin_bases, then deposit again
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Une base de marge fournie pour cette vente relevant du régime de la marge n'est pas cohérente : son taux n'est pas un taux de TVA français admis, sa TVA sur la marge s'écarte de plus d'un centime de sa marge hors TVA taxée à ce taux, ou les marges TVA comprise dépassent le prix de vente de la facture. Corrigez la base, la TVA ou le taux de chaque base de marge pour que la marge ne dépasse pas le prix de vente, puis redéposez-la.deposit of a margin-scheme sale to an unidentified buyer with an inconsistent margin basecorrect the bases with a PATCH carrying only margin_bases, then deposit again
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Cette déclaration additionne les opérations de la journée, or la facture ne porte pas de montant total hors TVA ou pas de montant total de TVA. Complétez les totaux de la facture, puis redéposez-la.deposit of a sale to an unidentified buyer, with no total excluding VAT or no total VAT amountcomplete the totals, then deposit again
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Cette déclaration est faite en euros, or la facture est libellée en USD et ne porte aucune contre-valeur en euros de son montant de TVA. Renseignez le montant de TVA en euros, puis redéposez-la.deposit of a sale to an unidentified buyer, denominated in a currency other than EUR and with no euro equivalent of the VAT totalsupply the VAT total in euros, then deposit again
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Cette déclaration porte toujours une ventilation par taux de TVA, or la facture n'en porte aucune, y compris lorsque ses montants sont nuls. Ajoutez au moins une ligne de ventilation de TVA - au taux 0 et pour un montant nul s'il s'agit d'un ticket exonéré -, puis redéposez-la.deposit of a sale to an unidentified buyer carrying no VAT breakdown line at alladd at least one breakdown line, then deposit again
Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. Cette déclaration porte les totaux de la journée en regard de leur ventilation par taux de TVA, or les totaux de la facture ne correspondent pas à la somme de sa ventilation de TVA. Corrigez la ventilation de TVA ou les totaux de la facture pour qu'ils concordent exactement, au centime près, puis redéposez-la.deposit of a sale to an unidentified buyer whose totals do not match the sum of its VAT breakdownalign the breakdown and the totals to the centime, then deposit again
Cette facture ne peut pas être déposée au Portail Public de Facturation : toute sa ventilation de TVA relève de la catégorie O (hors champ de la TVA), que le portail rejette sur une facture électronique (règle G2.32). S'il s'agit de débours, marquez chaque ligne comme débours pour que la facture sorte de la réforme ; sinon, corrigez les catégories de TVA, puis redéposez-la.deposit of a sale deposited with the PPF (flux 1), to a buyer that is not a public entity, whose VAT breakdown lines are all in category Omark every disbursement line with disbursement, or correct the VAT categories, then deposit again

The message Le motif X n'est pas autorisé pour le statut ... interpolates a status label that does not exist for revert_to_draft (000): the text then contains a raw translation key. One more reason never to parse message text.

Check refusals name their cause​

Some transitions are declared from the current state yet remain blocked by a business check. The state is not at fault then, and the message does not say Impossible de passer de ...: each of these checks has its own message. Three target every invoice, one targets only the sales of a company that belongs to a single taxable entity, nine target the B2C lane only, and one targets only the deposit with the PPF.

  • deposit - Le PDF Factur-X joint n'est pas conforme. Remplacez-le par un fichier conforme avant de déposer la facture. You supplied the invoice PDF and its Factur-X conformance was evaluated as non-compliant. A conformance still unknown does not block the deposit; only a non-compliant verdict does. That verdict is also recorded on the invoice as a blocking import error, but this check is evaluated before the import-error one: this is therefore the message you receive.
  • deposit and make_available - La facture comporte des erreurs d'import bloquantes. Corrigez-les avant de changer son statut. This is the case for an invoice created without invoice_number, issue_date, type_code, or currency_code: Scribee fills the missing field with a placeholder and records a blocking error. A PATCH on the draft clears those errors and unblocks the transition.
  • deposit - Des mentions légales obligatoires sont absentes de la facture (...). Renseignez-les dans les paramètres de facturation de l'entreprise, puis réessayez. One of the three mandatory legal mentions is missing as an item note. Together with the refusal of a Factur-X that is not PDF/A-3 conformant (see further down), it is one of the two transition refusals that fill details; it is detailed right below.
  • deposit - Cette facture a été créée alors que la société était membre d'un assujetti unique, mais elle ne le déclare pas en entier (SIREN du vendeur sous le schéma 0231, raison sociale, numéro de TVA et adresse de l'assujetti unique, note MEMBRE_ASSUJETTI_UNIQUE). Complétez l'assujetti unique dans les paramètres de la société, puis réenregistrez le brouillon avant de le déposer. The sales invoice was created by Scribee while the company declared a single taxable entity, but it does not carry the three items of that statement: the single taxable entity's SIREN on the seller, the complete tax_representative party, and the single tax_declaration note MEMBRE_ASSUJETTI_UNIQUE. This is the case when the company's settings only carried the SIREN when the invoice was created. Complete them in the Scribee interface, send a PATCH on the draft, then deposit again (Issue a sales invoice).

On deposit, these four checks are evaluated in that order and the first one to fail supplies the message: an invoice that carries both a blocking import error and a missing legal mention only reports the import error, and you have to deposit again to discover the next one.

Nine further refusals target only sales, by a company declared in the emission wave, to a buyer the invoice does not identify: no SIREN, no legal_registration_id, no vat_identifier, or no buyer party at all. Such a sale is never sent as an invoice; unless every line is marked as a disbursement, it is declared in the daily B2C aggregate (Declare transactions), so its deposit is checked against what that declaration requires. Outside the wave, none of these nine refusals can reach it - and no Schematron check does either, as described below. The nine messages all start with Cette vente s'adresse à un client que la facture n'identifie pas : elle est déclarée en données de transaction et non transmise comme facture. and then name the cause; they appear in full in the table above and return code: "operation_failed" with an empty details, like the four checks before them.

  • Invoicing framework absent - the declared operation's category is derived from the invoicing framework alone, and neither invoicing_process_id nor the invoice lines supply one. Send invoicing_process_id on the invoice.
  • Mixed invoicing framework - goods_and_services_invoice (M1), paid_goods_and_services_invoice (M2), and goods_and_services_final_invoice_after_retainer (M4) announce goods and services together, which the declaration counts apart. Issue the goods and the services on separate invoices.
  • Margin-scheme sale mixed with other breakdowns - the declaration counts a margin-scheme sale in a category of its own. An invoice that carries both a VAT breakdown of category E whose exemption reason is VATEX-EU-F, VATEX-EU-I, VATEX-EU-J, or VATEX-EU-D, and another VAT breakdown - taxable, G or O -, is refused, whatever its currency. Issue the margin-scheme sales on a separate invoice.
  • Margin-scheme sale without a margin base - the declaration states such a sale on its margin excluding VAT and the VAT due on that margin, two amounts the invoice does not carry. An invoice that carries a VAT breakdown of category E whose exemption reason is VATEX-EU-F, VATEX-EU-I, VATEX-EU-J, or VATEX-EU-D is refused, whatever its currency, for want of a margin base. Supply these amounts in margin_bases, with a PATCH carrying only that key, then deposit the invoice again (Sales under the margin scheme).
  • Inconsistent margin base - the bases supplied are declared as stated, and Scribee checks their plausibility. A margin-scheme sale is refused, whatever its currency, as soon as a margin_vat_amount differs by more than one cent from its margin_base_amount taxed at its vat_rate, or the margins including VAT exceed the invoice's selling price. Correct the bases with a PATCH carrying only margin_bases, then deposit the invoice again.
  • Totals missing - the declaration sums the day's operations: both the total excluding VAT and the total VAT amount are required.
  • VAT total not in euros - the declaration is made in euros. An invoice denominated in another currency must carry the euro equivalent of its VAT total.
  • VAT breakdown absent - the declaration always carries a breakdown by VAT rate, including when the amounts are nil. An invoice carrying no breakdown line has nothing to record there: add at least one, at rate 0 and for a nil amount when the ticket is exempt.
  • Breakdown and totals disagree - the declaration carries the day's totals alongside their breakdown. The invoice's total excluding VAT and total VAT amount must therefore equal the sum of its VAT breakdown, exactly and to the centime, in the invoice's own currency.

These nine refusals are evaluated after the four checks above, and in the order they are listed: a sale that carries both a blocking import error and an absent invoicing framework only reports the import error.

A last refusal targets only sales, by a company declared in the emission wave, whose invoice is deposited with the PPF (flux 1) - a buyer carrying a SIREN and established in the French VAT territory (Declare transactions): an invoice whose VAT breakdown lines are all in category O (outside the scope of VAT). The PPF rejects a flux 1 whose VAT breakdown is entirely O (rule G2.32); Scribee therefore refuses the deposit itself, after the four checks above and before any Schematron check. The message, in full in the table above, starts with Cette facture ne peut pas être déposée au Portail Public de Facturation and returns code: "operation_failed" with an empty details. An invoice that mixes an O breakdown with a taxable breakdown is not affected, nor is an invoice whose every line is marked as a disbursement: that one leaves the reform and is not deposited with the PPF (Issue a sales invoice). If these are disbursements, mark every line with disbursement through a PATCH on the draft; otherwise, correct the invoice's VAT categories; then deposit it again. This refusal does not apply when the buyer is a public entity, which rule G2.32 excludes: Scribee recognizes one when the buyer's SIREN designates, in the national directory, a legal unit of public type, decides it at deposit and keeps that answer for the whole transmission of the invoice. Such an invoice is deposited; nor does the flux 1 check apply G2.32 to it when its whole VAT breakdown is in category E with an exemption reason from article 261 of the CGI (VATEX-FR-CGI261-1, VATEX-FR-CGI261A...), or when it mixes O and E breakdowns.

Two refusals, on the other hand, do keep the generic Impossible de passer de ... message, because no named check is at fault:

  • make_available on an invoice with direction sales that is in neither 200 nor 202, a draft for example. From 200 or 202, the state permits the transition to your customer's status, never to your call: you get La transition make_available ne peut pas être déclenchée manuellement.
  • revert_to_draft when the invoice did not enter Scribee by file, or when it is attached to a Peppol or PPF flow.

The three mentions are normally inherited from the company's disclaimer settings when you omit item_notes. You can also supply them yourself as item notes with the codes payment_detail_remittance_information, payment_information, and terms_of_payment. Sending your own item_notes array replaces the inherited mentions entirely: an explicit array that omits them blocks the deposit. The check only targets sales invoices Scribee itself issues: an invoice created with POST /api/v1/workspaces/{workspace_id}/invoices counts, an invoice that came in through POST /api/v1/workspaces/{workspace_id}/invoices/upload or another external channel does not. Purchase invoices are never concerned.

The refusal lists the missing mentions in the message, and repeats them field by field in details, under the key of the attribute to fill in in the company's invoicing settings:

{
"error": "unprocessable_entity",
"code": "operation_failed",
"message": "Des mentions légales obligatoires sont absentes de la facture (pénalités de retard, frais de recouvrement et escompte). Renseignez-les dans les paramètres de facturation de l'entreprise, puis réessayez.",
"details": {
"late_payment_disclaimer": ["Cette mention légale obligatoire est absente des paramètres de facturation de l'entreprise."],
"recovery_costs_disclaimer": ["Cette mention légale obligatoire est absente des paramètres de facturation de l'entreprise."],
"cash_discount_disclaimer": ["Cette mention légale obligatoire est absente des paramètres de facturation de l'entreprise."]
}
}

details only carries the mentions that are genuinely missing: a single key when a single mention is missing. The three possible keys, in the order they appear:

details keyMention quoted in the message
late_payment_disclaimerpénalités de retard
recovery_costs_disclaimerfrais de recouvrement
cash_discount_disclaimerescompte

The message repeats the same labels in the same order, in parentheses, separated by commas with the last one introduced by et.

A deposit requested in a single call - POST /api/v1/workspaces/{workspace_id}/invoices with lifecycle_state: "deposited" - is refused by the same checks, with the same message and the same details. One difference in shape only: where the transition endpoint always sends details, even empty, creation omits the key when it has nothing to put there. Treat details as optional on both sides.

Sending the original from the interface​

When validating in the interface, if no electronic delivery to the customer is planned, Scribee offers to email the original invoice. This includes a French B2B customer without an active directory address: the deposit then carries the NON_TRANSMISE reason. A selected email is prepared as soon as validation succeeds, without waiting for Peppol or the PPF. The PPF flow is still submitted when owed. A valid customer email address is required when the option is checked; unchecking it allows validation without that email.

The email uses the available original file without a duplicate watermark. When electronic delivery to the customer is planned, the duplicate option keeps its existing behavior. Schematron controls remain blocking before validation and sending for sales by a company declared in the emission wave, except for a sale to a buyer the invoice does not identify: such a sale, unless every line is marked as a disbursement, produces no document to submit to Schematron, and the nine B2C refusals described above guard its deposit instead. Outside the wave, no Schematron control guards the deposit.

The Schematron refusal: three causes, three codes​

The Schematron check applies at deposit and can refuse it for three distinct reasons, which you do not act on the same way. The code field tells them apart. The status is 422 in all three cases and the refusal is the same on every workspace.

This check only targets sales by a company declared in the emission wave. Before depositing such an invoice, Scribee validates the UBL document generated from its current data, including the final number, recipient address and VAT amounts selected for sending. When flux 1 is owed, its extract is also checked against the AIFE rules. This covers invoices created through the API, entered in Scribee or imported from SAP, even without an original XML file. An earlier export report does not replace this new check. Controls on an imported source file still apply when one exists.

:::warning Behaviour change Until now, this check applied to every sales invoice, including those of a company outside the emission wave - whose invoices are nonetheless never transmitted: no flux 1, no e-reporting, no Peppol copy. An invoice refused with 422 was refused in the name of rules no transmission applied to it. Outside the wave, the deposit no longer submits any document to Schematron: none of the three codes below can be raised against those invoices any more, and they deposit where they used to receive a 422. If your integration relied on that refusal to detect incomplete data, it is no longer what will tell you. :::

Within the wave, what the buyer carries as identifiers decides what is checked, because it decides what is transmitted.

A buyer the invoice does not identify - no SIREN, no legal_registration_id, and no vat_identifier, or no buyer party at all - is declared in the daily B2C aggregate and never sent as an invoice, so no UBL document is generated or judged for it: none of the three codes below can be raised against it, and its deposit is guarded by the nine B2C refusals described above. A sale whose every line is marked as a disbursement, and which therefore leaves the reform (Issue a sales invoice), is the exception: it is not declared in the B2C aggregate, none of the nine B2C-lane refusals reaches it, and its UBL document is generated and checked like that of the buyer described below, without the two addressing rules withdrawn from it.

A buyer with no SIREN but carrying a legal_registration_id or a vat_identifier - a business established outside the French VAT territory -, like a buyer carrying a SIREN but established outside that territory, also falls under a transaction declaration rather than an invoice deposit (Declare transactions). Its UBL document is generated and checked, and it is checked against the complete rule set: its profile's - EN 16931, or the EXTENDED-CTC-FR extended structures depending on the customization_id the invoice carries - and the BR-FR-Flux2 rule set, the business rules specific to France, which remain applicable to an international B2B sale.

Exactly two rules are withdrawn from it: BR-FR-12_BT-49, which requires the buyer's electronic address, and BR-FR-13_BT-34, which requires the seller's. The reform imposes them only "once the electronic invoice must be transmitted and awaits lifecycle statuses in return"; a sale declared through e-reporting transmits no invoice and awaits no status, so neither rule bears on it and neither is raised as a refusal or as a warning. Every other BR-FR-* rule keeps judging the document: invoice identifiers BR-FR-01 and BR-FR-02, type codes BR-FR-04, legal mentions BR-FR-05, and the BR-FR-BD-*, BR-FR-CO-*, BR-FR-DEC-* and BR-FR-MV-* families. An invoice that breaks one of them is refused at deposit, exactly as a domestic invoice is.

The flux 1 extract is not checked, since no flux 1 is owed. An invoice that only those two addressing rules refused now deposits; an invoice that breaks a rule of its profile - a VAT-breakdown rule such as BR-E-10, for example - is refused exactly as before.

On refusal, the existing invoice stays in draft, no final number is consumed and validation-triggered deliveries are not launched. Correct the reported data, then request deposit again. Warnings alone do not block validation. When creating with lifecycle_state: "deposited", refusal also rolls back creation: no invoice is recorded.

codeCauseWhat you do
schematron_fatalThe invoice carries a fatal Schematron assertion; a fatal assertion is never a mere warning.Deterministic refusal: fix the fields the response names, then deposit again.
schematron_engine_unavailableThe validation engine owed a verdict and could not produce one.Transient refusal: nothing says the invoice is invalid, it was not judged. Deposit the same document again later.
schematron_profile_unsupportedNo rule set the reform admits applies to this profile.Deterministic refusal: depositing again changes nothing, send the invoice under a profile the reform admits.

On the transition endpoint, schematron_fatal answers in a specific envelope: a top-level errors object, with no error, no message, and no details. code sits beside errors, and nothing is removed from it. When creation requests a deposit, the same refusal keeps the error, message, code envelope, with the affected fields in details.

{
"errors": {
"document.payment_means[].payee_account.id": ["BR-61: [BR-61]-If the Payment means type code (BT-81) means SEPA credit transfer, Local credit transfer or Non-SEPA international credit transfer, the Payment account identifier (BT-84) shall be present."],
"document.payment_means[].type_code": ["BR-61: [BR-61]-If the Payment means type code (BT-81) means SEPA credit transfer, Local credit transfer or Non-SEPA international credit transfer, the Payment account identifier (BT-84) shall be present."]
},
"code": "schematron_fatal"
}

The errors keys are field paths, the values arrays of messages in the form IDENTIFIER: message.

One assertion may be listed under several keys, as BR-61 is above. When the rule stands on a set of elements - the same constraint replayed over several parties, a calculation tying several amounts together, a test comparing two fields - it appears under each of the fields the rule puts in question, with the same text every time: the set is what is at fault, and naming a single member of it would blame a conformant field. So do not assume one key per assertion, and do not count keys to count assertions - deduplicate on the identifier at the head of the message. The shape of the body is unchanged by this: errors is still an object whose keys are field paths and whose values are arrays of strings.

The paths use the API field names, not the BT codes. A path names the field as you write it and as GET /api/v1/invoices/{id} returns it to you: the VAT breakdown therefore reads document.tax_subtotals[].vat_amount, document.tax_subtotals[].tax_category_id or document.tax_subtotals[].vat_rate, never BT-117 or BT-118. Only the message still quotes the BT terms of the standard. If your integration had frozen a correspondence between assertion identifier and field path, check it again: several paths have been corrected, those of the VAT breakdown in particular.

An assertion that no single field carries on its own arrives under document._schematron. That covers the rules constraining the structure of the document or the format of a value, and those bearing on a field the API does not publish. The message is still actionable, there is simply no field to point at.

The other two refusals have no field to name - the invoice was not judged at fault, it was not judged at all - and take the common envelope, with an empty details:

{
"error": "unprocessable_entity",
"code": "schematron_engine_unavailable",
"message": "La facture n'a pas pu être contrôlée : le moteur de validation est indisponible. Réessayez le dépôt dans quelques instants.",
"details": {}
}
{
"error": "unprocessable_entity",
"code": "schematron_profile_unsupported",
"message": "La facture n'a pas pu être déposée : aucun jeu de règles sémantiques ne s'applique à ce profil. Transmettez-la sous un profil admis par la réforme.",
"details": {}
}

So handle both shapes on deposit: the body carries either errors and code, or error/code/message/details. code is present in both, and it is what you branch on.

The same three codes reach you when you request the deposit as part of the import - POST /api/v1/workspaces/{workspace_id}/invoices/upload with lifecycle_state: "deposited", see Import existing invoices - but never in the errors shape: that endpoint keeps its common envelope and passes the map of offending fields through its details, in place of the file key it usually puts there. The asymmetry is deliberate: each endpoint keeps the shape it already published, one errors, the other details, and code is added to both without removing anything.

The PDF/A-3 check of an imported Factur-X​

A sales invoice imported as a Factur-X (source_format: "facturx", provided_pdf: false) is transmitted byte for byte. On deposit, after the Schematron check and before status 200, Scribee checks the PDF/A-3b (ISO 19005-3) conformance of that file with veraPDF. Purchase invoices and the invoices Scribee generates itself are not concerned.

A non-conformant file is refused. The invoice stays a draft, and details.file repeats the message, which names the failed PDF/A rules (three at most) and the file:

{
"error": "unprocessable_entity",
"code": "facturx_not_pdfa",
"message": "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.",
"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."]
}
}

The facturx_not_pdfa code signals a final verdict for that file: do not replay the call. The verdict is recorded on the invoice, whose facturx_conformance becomes non_compliant, deposit disappears from lifecycle_available_transitions, and a new deposit request returns the same message and the same code. Fix the file, delete the draft, then import the corrected file (Import existing invoices, which also details the most frequent cause and how to avoid it).

If veraPDF cannot produce a verdict, the deposit is refused with the facturx_validation_unavailable code and an empty details. The file was not judged and nothing is recorded, facturx_conformance included: deposit the same invoice again later.

{
"error": "unprocessable_entity",
"code": "facturx_validation_unavailable",
"message": "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.",
"details": {}
}

Branch on code: facturx_not_pdfa calls for a corrected file, facturx_validation_unavailable for the same call replayed later. An accepted deposit carries facturx_conformance: "compliant"; before any check, this field is null.

The other HTTP statuses​

401 (missing or expired token) and 403 (missing write scope) follow the common format described in API conventions.

404 covers more cases than an unknown invoice:

  • the invoice belongs to a workspace your application has no access to;
  • your IP address is refused by the workspace allowlist. The response is indeed 404, not the 403 documented elsewhere: refused workspaces are filtered out before the invoice is looked up;
  • the invoice direction is sales and the company's offer does not cover sales;
  • the invoice has been withdrawn from the workspace after it was created. A draft can be withdrawn from the Scribee interface: it then leaves GET /api/v1/workspaces/{workspace_id}/invoices and its identifier stops being resolved.

This withdrawal applies to every operation that designates the invoice by its identifier, without exception: GET /api/v1/invoices/{id}, PATCH /api/v1/invoices/{id}, PATCH /api/v1/invoices/{id}/transition, DELETE /api/v1/invoices/{id}, POST /api/v1/invoices/{id}/send_by_email, GET /api/v1/invoices/{id}/email_deliveries and GET /api/v1/invoices/{id}/download. It applies as well to the collections attached to the invoice, which resolve through that same identifier: the payments (/api/v1/invoices/{invoice_id}/payments) and the supporting documents (/api/v1/invoices/{invoice_id}/supporting_documents) go with their invoice, for reads as for writes. All answer 404 where they answered 200 before the withdrawal. No response field reports this withdrawal, and none was added for it: the v1 contract is unchanged. Nor is there any endpoint that lists withdrawn invoices or restores them.

Treat a withdrawn invoice like an invoice deleted by DELETE /api/v1/invoices/{id}, which already produces the same 404: your integration therefore already handles the case of an invoice it knew about becoming unavailable. Remove it from your local store and stop querying it. A 404 is not a transient error: replaying the call will change nothing.