E-reporting
The French reform does not stop at e-invoices exchanged between companies established in France: operations outside that scope - B2C sales, cross-border B2B and B2G operations - as well as the payment data for B2C and international B2B services under VAT on collection, must be declared periodically to the tax administration (DGFiP). This is e-reporting. For every company in your workspaces subject to the obligation, Scribee opens declaration periods in advance and aggregates into them the data from your invoicing. Your integration reads these declarations and completes the data for operations handled outside Scribee.
What e-reporting covers
Every company subject to the obligation keeps two types of declarations, identified by the kind field:
- Transactions (
transactions): operations outside the scope of e-invoicing. A transactions declaration carries both B2C sales aggregates and individual invoices, for example cross-border invoices. - Payments (
payments): collections received on B2C and international B2B services whose VAT is due on collection, declared in euros. The collection of a domestic B2B sale is not in it: it is declared by the invoice's212Collected lifecycle status.
Data is declared by company and by declarant role. The role is carried by the role field, whose values are the regulatory codes SE (declaring seller) and BY (declaring buyer). Transactions declarations therefore exist in two series, one per role: for the same period, two distinct declarations sharing the same bounds and the same deadline, differing only by id and role. Payments declarations exist only in the seller series: their role is always SE.
What Scribee does for you
- Periods are created in advance. You never create a declaration: for every company subject to the obligation, Scribee opens periods from the first day of the current month to the end of the month three months later, each with its bounds (
start_date,end_date) and its declaration deadline (due_date). The period in progress is included even when it started before the current month. Periods appear in the list even before any data attaches to them. - Opening happens through a daily sweep. The periods of a newly created company do not appear immediately: they arrive on the next sweep, which runs once a day - usually within 24 hours, but that is not a commitment. The sweep processes companies one at a time; one whose opening fails or whose lock is already held is simply skipped, with no retry inside the same sweep, and waits for the next one. A period does appear at once, however, when Scribee's invoicing needs one to attach a document.
- The cadence follows from the VAT regime. The length of each period and its deadline are calculated from the VAT regime set on the company in Scribee (see the table further below). That regime is set in Scribee, not through the API, and the API does not return it: derive the cadence from each declaration's
start_dateandend_datebounds. - A regime change takes effect on the following 1 January. A regime change recorded during the year changes nothing straight away: it takes effect on the following 1 January (or the same day if the change is made on 1 January). Only empty periods starting on or after that date are recalculated; those starting earlier keep the cadence they were opened under. Since the opening horizon does not exceed three months, the list does not move until that 1 January enters the horizon.
- Invoicing feeds the payments declaration. Every payment recorded on a sales invoice whose VAT is due on collection is carried through to the payments declaration of its period, with no extra call, when e-reporting covers the sale: an international B2B sale (buyer identified without a SIREN, or carrying a SIREN but established outside the French VAT territory) or a B2C sale (buyer with no identifier) - and provided the issuing company's VAT regime carries the payment reporting obligation on the payment date. A sale whose buyer carries a SIREN and is established in the French VAT territory is not in it, nor is a sale of goods, nor a sale wholly under reverse charge, nor a sale whose VAT breakdowns are all in category
GorO; of a sale that mixes goods and services, only the services share is declared, aGorObreakdown never is, and amounts are declared in euros. Outside these conditions nothing is created, and the API does not signal it (Record payments, Declare payments). - You never create, modify, or delete a declaration. You act on the data attached to it, and you can trigger its transmission (see Transmit a declaration). That is the only call that acts on the declaration itself.
- Scribee transmits closed periods on its own. Once your
due_datehas passed - that is your own deadline for depositing data, not Scribee's for forwarding it to the administration - a closed period that carries data and has never been deposited is transmitted automatically, within the eight hours that follow the end of that window. You therefore keep the whole of yourdue_dateto complete the period, and you have nothing to schedule: the transmission call is there to deposit earlier, or to deposit again after a rejection.
The state field
Every declaration carries a state field, read-only: no API call modifies it. Its value is always draft - the period is open and data accumulates in it, from your API calls as well as from Scribee's invoicing. Do not build a conditional branch on this field, and do not sort on it: sort_by=state is accepted but orders nothing.
Every declaration also carries an available_transitions array. That field is deprecated - it is marked deprecated in the OpenAPI reference - and its value is always []: no transition is triggerable on a declaration, neither by you nor by Scribee. It is kept only so that clients already generated against /api/v1 do not break. Do not build anything on it.
The PPF verdict on the deposit
The state field above is a local marker. What the administration decided about the deposit of your declaration is published by three separate fields, all read-only: deposit_outcome, deposit_outcome_at, and deposit_receptions. They are returned by the declarations list and by the response of its transmission. None of the three is filterable or sortable.
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.7.9, Tableau 5):
| Value | Meaning |
|---|---|
"300" | Deposee (filed): the PPF accepted the deposit of the declaration |
"301" | Rejetee (rejected): the PPF refused the deposit |
null means the PPF has not yet ruled on this declaration. It is not an error, not a default, and not a third status: read it as "no answer yet" and read the declaration again later.
deposit_outcome_at: the instant of the verdict
String or null. This is the instant the PPF stamped that verdict (MDT-78), 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. It is the only timestamp on the resource in that case; created_at, updated_at, and a reception's received_at 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 an accepted declaration as well as for one with no answer yet.
Each entry corresponds to one FFE0624A reception that carried rejection motives for this declaration, 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.7.10, Tableau 6). Four possible values:Value Failed control REJ_SEMANSemantic or format control REJ_UNIUniqueness control REJ_COHData coherence control REJ_PERPeriod control: the transmission date is inconsistent with the declared period -
anomaly_source: string ornull(MDT-114). The PPF's own free text saying where the anomaly is, whereascodesays which control failed. Only the code is mandatory on a rejection, so this field isnullwhen the PPF sent no text. -
notes: the detailed rejection information sent by the PPF, in document order. Each note containscontent_code(for exampleG6.26),contents(all explanatory texts, without truncation) andsubject_code(for example/Report/ReportDocument/Issuer/Id). Both codes can benullandcontentscan be empty. Display these details alongsideanomaly_source: the generic reason may only say "Contrôle de cohérence de données", while the note identifies the SIREN issue. An empty array means no notes were recorded. Older receptions may require reprocessing by Scribee to recover their notes.
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 declaration. 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 do not answer the same question.
The sequence for a declaration that is rejected, corrected, then re-deposited:
- The PPF rejects the deposit.
deposit_outcomeis"301",deposit_outcome_atcarries the instant of that rejection, anddeposit_receptionsholds one entry: the reception that carried the motives. - The declaration is corrected and re-deposited, and the PPF accepts it.
deposit_outcomebecomes"300"anddeposit_outcome_atcarries the instant of that acceptance. But the entry for the earlier rejection stays indeposit_receptions, with itsreceived_atand its motives unchanged.
At step 2, the declaration looks like this:
{
"id": 287,
"kind": "transactions",
"role": "SE",
"state": "draft",
"start_date": "2025-11-01",
"end_date": "2025-11-30",
"due_date": "2025-12-10",
"available_transitions": [],
"deposit_outcome": "300",
"deposit_outcome_at": "2025-12-18T09:30:00",
"deposit_receptions": [
{
"received_at": "2025-11-03T06:15:00Z",
"motives": [
{
"code": "REJ_PER",
"anomaly_source": "période déclarée hors bornes"
},
{
"code": "REJ_UNI",
"anomaly_source": null
}
]
}
],
"company_id": 7
}
An integration that flattens deposit_receptions into a plain list of motives and shows them as "the reasons this declaration was rejected" will therefore be wrong exactly when the customer has already fixed the problem: it will report a rejection on a filed declaration.
The rule is simple. To know where the declaration 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 next to a deposit_outcome of "300" is not an inconsistency: it is the record of a rejection that has since been corrected.
The declaration cadence by VAT regime
The length of each period and its declaration deadline follow from the declarant company's VAT regime; each declaration carries its own in due_date.
| Company VAT regime | Transactions declarations | Payments declarations |
|---|---|---|
| Standard regime (monthly) | By decade: the 1st to the 10th (due on the 20th of the month), the 11th to the 20th (due on the last day of the month), the 21st to the end of the month (due on the 10th of the following month) | Monthly, due on the 10th of the following month |
| Standard regime (quarterly) | Monthly, due on the 10th of the following month | Monthly, due on the 10th of the following month |
| Simplified regime | Monthly, due on the last day of the following month | Monthly, due on the last day of the following month |
| VAT franchise (small business) | By calendar two-month period (January-February, March-April, ...), due on the last day of the month following the period | By calendar two-month period, same due date |
| Not subject to VAT, Not subject to and not liable for VAT | No obligation: no declaration is created | No obligation: no declaration is created |
View your declarations
This call is a read: it creates nothing and can be replayed freely. The read scope is enough.
GET /api/v1/workspaces/{workspace_id}/e_reportings lists the e-reporting declarations of the workspace's companies (your workspace_id).
curl "https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/e_reportings?sort_by=due_date&sort_order=asc" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response extract: only a few declarations and a few fields are shown (the full response also includes created_at, updated_at, the three PPF verdict fields described above, and the two rectification fields described below).
{
"data": [
{
"id": 311,
"kind": "transactions",
"role": "SE",
"state": "draft",
"start_date": "2026-06-21",
"end_date": "2026-06-30",
"due_date": "2026-07-10",
"available_transitions": [],
"company_id": 7
},
{
"id": 312,
"kind": "transactions",
"role": "BY",
"state": "draft",
"start_date": "2026-06-21",
"end_date": "2026-06-30",
"due_date": "2026-07-10",
"available_transitions": [],
"company_id": 7
},
{
"id": 322,
"kind": "payments",
"role": "SE",
"state": "draft",
"start_date": "2026-07-01",
"end_date": "2026-07-31",
"due_date": "2026-08-10",
"available_transitions": [],
"company_id": 7
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total_pages": 2,
"total_count": 28
}
}
The first two rows are the same transactions period in its two role series. The volume climbs fast: a company under the monthly standard regime opens 28 declarations over the horizon - 4 months x 3 decades x 2 roles for transactions, plus 4 payments periods.
The endpoint accepts no filter: no company_id, no kind, no role, no date range. Fetch the list and filter client-side.
The list is paginated (page, per_page - 20 by default, 100 maximum) and sortable (sort_by: start_date, end_date, due_date, kind, state, created_at, or updated_at; sort_order: asc or desc; default sort: start_date descending). When sort values tie - the two declarations of the same transactions period share start_date, end_date, and due_date - rows are broken by id descending. The ordering is therefore total and reproducible: on a dataset unchanged between page requests, walking the pages neither skips nor repeats a declaration. Pagination remains offset-based: a declaration opened or modified mid-walk shifts the rows after it, so on a live dataset, use per_page=100 to reduce the number of pages and deduplicate on id as you receive them.
The include parameter (comma-separated values: company, invoices, transactions, payments) adds the attached data in abridged form, not the full records: company returns id, name, and legal_identifier; invoices, transactions, and payments return a summary of each attached record. This is useful for checking a period's content before its deadline, not for retrieving its detail.
What happens next
- Nothing is expected from you for the declarations themselves: subsequent periods open over time and data accumulates in them.
- What remains your responsibility is the data: operations handled outside Scribee - B2C sales collected in another system, payments tracked elsewhere - attach to the declaration of their period via the API (Declare transactions, Declare payments).
- Payments recorded on your B2C and international B2B services invoices under VAT on collection are already covered when the issuing company carries the payment reporting obligation on the payment date: they then join the payments declaration of their period with no extra call. Those on your domestic B2B sales need not be attached to it: the invoice's
212Collected status declares them.
Transmit a declaration
POST /api/v1/workspaces/{workspace_id}/e_reportings/{id}/submit
This call builds the declaration from its period's data, validates it against the reform's conformance rules, and deposits it. It requires the read and write scopes: your token must carry both.
Transmission is asynchronous. A 201 means the deposit is registered and being carried, not that the administration has answered: its verdict arrives later and is read from deposit_outcome (see above). The response body carries the up-to-date declaration and a meta object:
| Field | Meaning |
|---|---|
transmitted | true when this call deposited the declaration; false when it was already in the administration's hands and nothing was deposited again |
transmission_id | The name of the deposited flux. The administration echoes it back on its transport acknowledgement: it is the reference a deposit is correlated by |
Replaying the call is safe. A declaration already deposited and still unanswered replies 200 with transmitted: false and deposits nothing: no second flux is created, however many times you call. Only the first call replies 201.
A 409 means another process is depositing this declaration at the same moment - your own automation, ours, or the screen. Nothing was transmitted by your call and the body carries code: "submission_contested"; retry shortly. It is not a verdict about your data. The same 409 answers a call that transmits a rectificative (see below) when a payment or an invoice of the period is being modified at the same instant: nothing is transmitted, the period still owes its rectificative, and the same call is retried shortly.
An accepted declaration is closed. When deposit_outcome is "300", the period's data can no longer be edited. A payment of that period recorded, corrected or deleted on an invoice after the acceptance, or while the verdict that pronounced it was awaited, is not lost: the change is set aside and the period owes a rectificative transmission (TT-4 = RE) (Declare payments). Calling this endpoint again on a declaration that owes a rectification transmits it, when the rectificative transmission is activated for your workspace (see below): the declaration is retransmitted in full, set-aside changes included, and replaces everything the administration held for the period under that declaration. A foreign-currency payment whose date still has no published rate stays on hold and is not in it (Declare payments). The response is 201 with transmitted: true; replaying the call while the rectificative awaits its answer replies 200 with transmitted: false, as for any deposit. In every other case - the period owes no rectification, or the rectificative transmission is not activated for your workspace - the call is refused with 422 and nothing is transmitted.
The rectificative transmission is activated per workspace. It is not activated by default: Scribee activates it, on request, for each workspace concerned. Scribee never sends a rectificative on its own: it leaves on a call to this endpoint or from the screen.
An owed rectification is read on the declaration. Every declaration, in the declarations list as in the response of this endpoint, carries two read-only fields, always present:
| Field | Meaning |
|---|---|
rectification_owed | true when the declaration owes a rectificative transmission (TT-4 = RE) and the rectificative transmission is activated for your workspace. false when no rectification is owed, or when the rectificative transmission is not activated for your workspace: false never means that the administration holds up-to-date data |
rectification_owed_at | The instant, with time zone, at which Scribee recorded that the rectification became owed. It is not a deadline. It is null exactly when rectification_owed is false |
It is an obligation, not a status. It arises when data lands in a period the administration has already accepted (deposit_outcome of "300"). It stays owed while the rectificative awaits its verdict, and after that rectificative is rejected ("301"); only a "300" on a rectificative clears it. Read it together with deposit_outcome and transmitted: next to a deposit_outcome of "300", rectification_owed at true does not say whether the rectificative has already left; transmitted does (see above). To transmit an owed rectification, call this endpoint again, as described above.
A rectificative's verdict is read like any deposit's. A 300 closes the period again; a change that arrived while it awaited its answer makes the period owe a new rectification. A 301 makes the period's data editable: correct it, then call this endpoint again - the new deposit is a rectificative again. In the meantime, deposit_outcome is "301", but the administration still holds the declaration it accepted earlier.
A rejected declaration is corrected, then retransmitted. When deposit_outcome is "301", the period's data becomes editable again: correct what the rejection motives point at (deposit_receptions), then call this endpoint again. Outside a rectification (see above), the new deposit is an initial transmission - nothing had been accepted.
While a deposit awaits its answer, the period is closed to edits. Adding, modifying, or deleting a transaction, an invoice, or a collection is refused until the administration has answered: what was transmitted and what Scribee holds must not diverge with no verdict to reconcile them.
A 422 carries the reason in details.base, and the code field separates two situations. With code: "validation_failed", it is a verdict about the declaration: the period carries no data to declare, a party has no identifier in an accepted scheme, a conformance rule is violated, or the administration has already accepted the declaration and no rectificative transmission can leave (see above). Correct what the reason points at: replaying the call unchanged gives the same result.
With code: "schematron_engine_unavailable", nothing was judged. The conformance engine could not run: this is not a verdict about your data, and nothing was transmitted. Retry the same call, with the declaration unchanged, once the engine is back.
Errors and edge cases
401 Unauthorized
Missing, expired, or invalid token. Empty body; request a new token at /oauth/token (Authentication).
403 Forbidden: workspace access
Your OAuth client is not attached to this workspace:
{
"error": "forbidden",
"message": "L'application n'a pas accès à cet espace de travail"
}
Check the workspace_id against GET /api/v1/workspaces; if it is correct, ask your Scribee contact to attach it.
403 Forbidden: IP address refused
The workspace's IPv4 allowlist rejects your address (Your first call). The message tells this case apart from the previous one:
{
"error": "forbidden",
"message": "Cette adresse IP n'est pas autorisée pour cet espace de travail"
}
404 Not Found
No workspace exists with this identifier:
{
"error": "not_found",
"message": "La ressource demandée est introuvable"
}
400 Bad Request: page beyond the last
Requesting a page beyond the last page of a non-empty collection returns:
{
"error": "bad_request",
"message": "Le numéro de page dépasse le nombre de pages disponibles"
}
Bound your requests with meta.total_pages.
Empty list
An empty data array is not an error. Three possible causes: the workspace contains no company; all its companies are outside the scope of the obligation (Not subject to VAT or Not subject to and not liable for VAT regime); or the daily sweep has not yet run for a recently created company. In that last case the periods appear on their own, usually on the next sweep; if a company is skipped by a sweep (a failure, or a lock already held), the wait can span one more sweep.
Related pages
- Declare transactions - attach your B2C sales and invoices outside e-invoicing
- Declare payments - attach collections tracked outside Scribee
- Record payments - the automatic feed of the payments declaration
- API reference: list e-reporting declarations
- API reference: transmit an e-reporting declaration