e-Factura and SPV
Romania's e-Factura is mandatory for business-to-business invoicing, so ANAF is a first-class invoice source rather than an afterthought. These routes connect a firm's qualified certificate to ANAF once, enroll client companies against the grant that produces, and read the firm-wide SPV message stream.
This is the one area of the API that is entirely Romania-specific. If you are integrating outside Romania, skip this page.
The module is a deployment switch
e-Factura is switched on for the whole deployment, not per organization, and the production API at
api.syllevo.com has it on. Where it is off, every route on this page answers:
{
"status": 404,
"code": "not_found",
"messageKey": "web.errors.not_found",
"detail": null
}
Not 403, and not an e-Factura-specific code. That is deliberate: a door that is not there tells a
scanner nothing about a door that is. If you get 404 from every route here and 200 from the rest
of the API, the module is off rather than your credential being wrong.
Everything below describes the behavior when it is on, which is the case in production.
Routes
| Method | Path | Needs |
|---|---|---|
| POST | /v1/efactura/connect |
Admin |
| POST | /v1/efactura/callback |
Admin |
| GET | /v1/efactura/connections |
Admin |
| DELETE | /v1/efactura/connections/{id} |
Admin |
| GET | /v1/efactura/connections/{id}/companies |
ManageSources |
| POST | /v1/efactura/enrollments |
ManageSources |
| PUT | /v1/efactura/enrollments/{accountId}/scope |
ManageSources |
| GET | /v1/efactura/profiles/{profileId}/enrollments |
View |
| GET | /v1/spv/messages |
View |
| POST | /v1/spv/messages/{id}/read |
View |
| POST | /v1/spv/messages/{id}/collect |
ManageSources |
| POST | /v1/spv/messages/read |
View |
| GET | /v1/spv/messages/unread-count |
View |
| GET | /v1/profiles/{profileId}/efactura/transmission-consent |
View |
| PUT | /v1/profiles/{profileId}/efactura/transmission-consent |
ManageSettings, see below |
How the pieces fit
There are two layers, and mixing them up is the usual confusion.
- A connection is one OAuth grant, obtained once by a person holding the firm's qualified
certificate. It belongs to the organization, not to a client, and it is what lets Syllevo talk to
ANAF at all.
Adminowns it. - An enrollment ties one client company's fiscal code, its CUI, to one connection. It is what
makes that client's invoices start arriving.
ManageSourceson the client owns it.
One connection can carry many enrollments. A firm connects once and enrolls each client it acts for.
Which companies a connection can act for is never configured. ANAF names them on its own listing
answers, and Syllevo reads that inventory as it polls, which is what
/v1/efactura/connections/{id}/companies hands back.
Each enrollment also carries a collection scope, three values that decide what it files rather
than what it fetches. everything files every received invoice as it arrives. messagesOnly files
none of them, leaving every received invoice for the messages inbox. listedSuppliers files only
the ones whose issuer is on the client's e-Factura-flagged supplier list and holds the rest - see
Suppliers for what puts a supplier on that
list. ANAF's message listing never says who issued an invoice - it gives a type, a date, and an id -
so there is no way to decide from the listing alone. Under listedSuppliers the invoice is
downloaded anyway, the issuer is read out of the XML, and only then is it filed or held.
Whatever a scope leaves uncollected, held or never downloaded, still shows up on the SPV message
stream, and POST /v1/spv/messages/{id}/collect files any one of them on request. What changing
scope does to a backlog already sitting there is under
PUT /v1/efactura/enrollments/{accountId}/scope below.
POST /v1/efactura/connect
Starts a connect. Admin. Answers with the URL to send a browser to.
| Field | Type | Means |
|---|---|---|
url |
string | Open this. ANAF asks for the qualified certificate |
browserNoteKey |
string | Translation key for a note about browser requirements |
browserNoteKey is a key, not a sentence. ANAF's certificate step has browser requirements worth
warning about, and the wording lives in the Syllevo web app.
The state on that URL is a short random id, about 25 characters. What it stands for, the firm
and the person who started the connect, is kept on our side for ten minutes and spent on the first
callback that presents it. A callback after that, or a second one with the same state, answers 409
efactura-state-invalid. ANAF documents its flow with state empty, and its login gateway ended
the certificate session on a "Logged out successfully" page when the state was long, so it stays
short. A host whose cache cannot hold the entry answers 409 efactura-not-configured here,
before anyone reaches for a USB token, rather than at the callback.
Connecting a second certificate while one is already connected is allowed. The sources dialog lets the accountant pick which one to enroll a client against.
POST /v1/efactura/callback
Finishes the connect: trades the authorization code for a token pair and stores the grant. Admin.
| Field | Type | Required | Means |
|---|---|---|---|
code |
string | yes | The authorization code ANAF sent back |
state |
string | yes | The state value from the redirect |
Answers 200 with the connection. Refusals are all 409:
| Code | Means |
|---|---|
efactura-state-invalid |
The state is unknown, expired, already spent, or names somebody else |
efactura-exchange-failed |
ANAF could not be reached or answered unusably |
efactura-opaque-token |
ANAF returned a token the exchange could not read |
efactura-invalid-grant |
ANAF refused the grant |
efactura-not-configured |
The module is on but this deployment has no ANAF OAuth application |
All of them mean the same thing for a client: start again at /connect. The code is single use.
GET /v1/efactura/connections
Every grant this organization holds, disconnected and expired ones included. Admin. Answers a
plain array.
| Field | Type | Means |
|---|---|---|
id |
uuid | The connection |
status |
EFacturaConnectionStatus |
0 connected, 1 refresh failed, 2 expired, 3 disconnected |
accessTokenExpiresAt |
date-time | When the access token dies |
refreshTokenExpiresAt |
date-time | When the whole grant dies and somebody must reconnect |
lastRefreshedAt |
date-time, null | Last successful refresh |
certificateIssuer |
string or null | Who issued the qualified certificate |
certificateSerialLast4 |
string or null | The last four of its serial, to tell two apart |
createdAt |
date-time | When it was connected |
Watch refreshTokenExpiresAt. ANAF grants do not renew themselves forever. When it passes, a
person with the certificate has to sit down at a browser again, and every enrolled client stops
receiving until they do. Alert on it well ahead.
Only the last four digits of the certificate serial are stored. The certificate itself never reaches Syllevo.
DELETE /v1/efactura/connections/{id}
Hands the grant back to ANAF and marks it disconnected here. Admin. Answers 200 with the
connection, now at status 3.
Documents already collected through it stay.
GET /v1/efactura/connections/{id}/companies
The client picker behind the enroll dialog: which companies this certificate can act for, so
nobody types a fiscal code from memory. ManageSources on the client named by profileId.
| Parameter | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | yes | The client the caller is about to enroll |
profileId is required because the permission is checked on a client, not on the organization.
Omitting it is 400 validation_failed. A caller without ManageSources on that client gets
403. A connection that belongs to another organization, or a profileId that is not one of this
organization's clients, is 404, the same answer as the module being off.
| Field | Type | Means |
|---|---|---|
status |
EFacturaCompanyDirectoryStatus |
0 unknown, 1 known |
companies |
array | The inventory, empty when status is 0 |
checkedAt |
date-time, null | When the inventory was last established |
| Company field | Type | Means |
|---|---|---|
cui |
string | The fiscal code, normalized, no RO prefix |
legalName |
string or null | The name ANAF's registry holds for it, when looked up |
enrolledProfileIds |
uuid array | Clients already enrolled on that code, empty when none |
status 1, known, means the list is ANAF's and can be shown as a picker. status 0, unknown,
means Syllevo has nothing to show yet and the dialog should ask for a fiscal code by hand. It is
not an error, and it is not permanent: the first invoice any client behind this grant receives
fills the inventory in.
Where the list comes from
ANAF publishes no route that lists the fiscal codes a certificate has rights over. What it does publish, on every message listing that carries at least one message, is a field naming all of them. So the inventory is discovered rather than configured. The poll pass records it for free as it runs, and this route probes for one when nothing has recorded it yet.
The probe asks about at most three fiscal codes, in this order: the codes already enrolled on this
connection, then the fiscal code on the client in profileId, then codes from the caller's other
clients. It stops at the first answer that names anything. The probed endpoint allows 1,500 calls a
day per fiscal code, which is why the count is capped and why both answers are cached.
Caching
An inventory younger than 24 hours is answered from the stored row without contacting ANAF. A
connection that was checked within the last hour and still holds nothing is answered 0
unknown without contacting ANAF either. Enrolling a client, or the poll pass picking up an invoice,
refreshes the list on its own.
ANAF being slow or refusing is never a 5xx here. It is status 0 and a picker that falls back
to a text box.
POST /v1/efactura/enrollments
Enrolls one client company's fiscal code against a connection. ManageSources on the client.
| Field | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | yes | The client company |
connectionId |
uuid | yes | The grant to enroll against |
cui |
string | yes | The client's Romanian fiscal code |
tag |
string | no | A label for the source this creates |
scope |
integer | no | What to file: 0 everything, 1 messages only, 2 listed suppliers. Default 0 |
Answers 201 with the enrollment. This creates a source on the client, so it counts against the
e-Factura source cap of one per client. See Sources.
When the client has no tax id yet, enrolling sets it to the enrolled CUI, digits only, and records
profile.updated in the audit trail. A tax id that's already there is never changed, even when it
differs from the CUI.
Refusals are 409:
| Code | Means |
|---|---|
efactura-cui-invalid |
Not a valid Romanian CUI |
efactura-already-enrolled |
That CUI is already enrolled on this client |
efactura-connection-not-connected |
The grant is disconnected, expired, or last failed to refresh |
PUT /v1/efactura/enrollments/{accountId}/scope
Changes what one enrollment files. ManageSources on the client. accountId is the accountId
from the enrollment, the source row this enrollment created.
| Field | Type | Required | Means |
|---|---|---|---|
scope |
integer | yes | 0 everything, 1 messages only, 2 listed suppliers |
Answers 200 with the enrollment, scope included. A scope that is already the one asked for is a
200 that writes nothing and audits nothing. An enrollment belonging to another firm is 404, the
same answer as an id that does not exist.
Widening to 0, everything, queues every invoice this client held under the narrower scope. They
are filed from the archives already stored, so nothing is asked of ANAF and the 60-day retention
window does not apply - a backlog held since June still files today. Expect them to appear over the
next minutes rather than instantly.
That only holds for invoices that were downloaded and held, which means scope 2. Coming back from
1, messages only, has no backlog to file: nothing was downloaded under it. Those invoices are
still on the SPV message stream and can be collected one at a time from there, while ANAF still
holds them - 60 days from publication, and then they are gone.
Setting 0 on an enrollment that is already 0 is allowed and does the release again. Nothing is
written and nothing is audited, but the held invoices are queued a second time, which is the way to
retry after a release that did not go out.
Narrowing only changes what happens next. Nothing already filed is unfiled, and no document is deleted.
GET /v1/efactura/profiles/{profileId}/enrollments
Every enrollment on one client, and where each cursor stands. View. Answers a plain array.
| Field | Type | Means |
|---|---|---|
accountId |
uuid | The source this enrollment created |
profileId |
uuid | The client company |
connectionId |
uuid | The grant behind it |
cui |
string | The fiscal code |
legalName |
string or null | The name ANAF holds for that CUI |
rightsStatus |
EFacturaRightsStatus |
0 unknown, 1 active, 2 missing |
rightsCheckedAt |
date-time, null | When rights were last checked |
eFacturaEnrolled |
boolean or null | Whether ANAF says this CUI is in the e-Factura register |
lastSuccessfulPollAt |
date-time, null | Last time messages were fetched |
nextPollAt |
date-time | When the next fetch is due |
windowEnd |
date-time | The far edge of the message window currently readable |
scope |
integer | 0 everything, 1 messages only, 2 listed suppliers |
sentWindowEnd |
date-time, null | The far edge of the sent-list window. Null before the first sent pass |
sentLastSuccessfulPollAt |
date-time, null | Last time the sent list answered |
sentConsecutiveFailures |
integer | Sent listings that failed in a row since the last success. 0 is healthy |
sentConsecutiveFailures climbing while lastSuccessfulPollAt stays fresh means the received
list works and the sent list doesn't. Sales stop arriving from SPV, and an Uncertain
submission can't be settled, until it answers again.
rightsStatus 2, missing, is the one to act on. It means the certificate holder does not have
e-Factura rights for that CUI at ANAF, so nothing will ever arrive no matter how long you wait. The
fix is at ANAF, not in Syllevo.
windowEnd matters because ANAF keeps a message downloadable for 60 days. Past that the
document is gone from ANAF's side and no retry brings it back, which surfaces as
efactura-download-expired. A client enrolled today does not get its back catalog.
The SPV message stream
Every message ANAF published for any of the firm's clients, in one stream, so nobody logs into ANAF once per client to check. Received invoices, rejections, and buyer messages all appear here.
GET /v1/spv/messages
One page, newest ANAF publication first. View.
| Parameter | Type | Means |
|---|---|---|
profileId |
uuid | Narrow to one client |
type |
string | One ANAF message type, ANAF's own exact value |
unreadOnly |
boolean | Only what nobody at the firm has read |
page |
integer | Default 1 |
pageSize |
integer | Default 25, clamped to 200 |
| Field | Type | Means |
|---|---|---|
id |
uuid | The message in Syllevo |
profileId |
uuid | The client it belongs to |
cui |
string | The fiscal code ANAF published it for |
anafMessageId |
string | ANAF's own id |
anafCreatedAt |
date-time | When ANAF published it |
type |
string | ANAF's message type, passed through unchanged |
details |
string or null | ANAF's own detail text |
requestId |
string or null | ANAF's request id, when the message carries one |
readAt |
date-time, null | Null while unread |
readById |
uuid or null | Who read it |
createdAt |
date-time | When Syllevo recorded it |
collection |
string | Where the invoice behind it stands - see below |
documentId |
uuid or null | The document it became, on collected |
heldReason |
string or null | Why it is held, on held |
type and details are ANAF's values, passed through as they arrived rather than translated into
something tidier. They are in Romanian and their vocabulary is ANAF's.
collection, documentId, and heldReason are not stored on the message. They are read off the
download ledger on the way out, one query per distinct fiscal code on the page.
collection |
Means |
|---|---|
notApplicable |
Not a received invoice, so there is nothing to collect |
notCollected |
A received invoice the enrollment's scope never picked up |
collecting |
Asked for, and the Worker has it |
held |
Downloaded and kept, nobody has asked for it. heldReason says why |
collected |
Filed. documentId points at the document |
failed |
Given up on, for a reason no retry fixes |
Only FACTURA PRIMITA can be anything but notApplicable. A rejection, a buyer message, or an
invoice the client itself sent has no document behind it.
A held invoice somebody has collected reads collecting, not held, and carries no heldReason,
until the Worker files it. Collecting stamps the request on the row; it does not move the row's
status, which stays held until the invoice is out of the archive and filed. Reading it back as
held would snap the screen to Held the moment the person pressed the button.
heldReason is notListed - the issuer matched no supplier expected through e-Factura - or
issuerUnreadable, meaning the invoice XML carried no readable issuer fiscal code.
A message here is a notification. The invoice itself, once fetched, is a document. See Documents.
POST /v1/spv/messages/{id}/read
Marks one message read for the whole firm, not for the caller. View on the client it belongs
to. Answers 200 with the message.
Read state is shared on purpose. Two people at the same firm should not each have to work through the same stream.
POST /v1/spv/messages/{id}/collect
Files one received invoice the enrollment's scope left uncollected or held. ManageSources on the
client it belongs to - this spends an ANAF download and creates a document, which is a source
decision rather than a reading one.
No body. The answer is the same shape either way:
{ "status": "collecting", "documentId": null }
| Status | Means |
|---|---|
200 |
"status": "collected" with documentId. It was already filed, nothing was queued |
202 |
"status": "collecting". The request is on the ledger row and the Worker has it |
404 |
No such message for this firm, or the module is off |
403 |
The caller does not hold ManageSources on that client |
409 |
One of the four refusals below |
| Code | Means |
|---|---|
efactura-not-an-invoice |
The message is not a FACTURA PRIMITA. There is nothing to file |
efactura-collect-failed |
The ledger already gave up on it. ANAF's 60 days ran out, or the right is missing |
efactura-not-enrolled |
The client company has no e-Factura enrollment to fetch under |
efactura-messaging-unavailable |
The request is recorded but the broker would not take the work item |
Pressing it again is safe, but it is not always free.
- Already filed.
200with the document id. Nothing is stamped, nothing is queued, nothing is asked of ANAF. - Held. Free. The invoice is filed from the archive already in storage, so nothing is asked of ANAF and the 60-day window does not apply.
- Still on its way. The request is re-stamped and the download is queued again, and that costs another ANAF attempt. The consumer treats only filed and given-up-on rows as done, so a row still waiting for its bytes is fetched again on every delivery.
That last one is why collection is on the message: a screen showing collecting should stop
offering the button rather than let somebody press it a second time. ANAF allows ten downloads of
a message per day and then locks it until tomorrow, and the row gives up on its own after six
attempts.
An invoice that was never downloaded - scope 1, messages only - is fetched now, which means ANAF
has to still be holding it.
efactura-messaging-unavailable is the one worth explaining to the person. The request is rolled
back before the refusal is answered: the message goes back to reading held or notCollected, which
means the button comes back on the next refetch and pressing it once the broker is up starts the
collect fresh. Nothing is lost in the meantime - a held invoice's ZIP is already in storage and stays
there, and a message that was never downloaded is still at ANAF for the rest of its 60 days.
A row this call had to create is removed outright rather than left behind, because a ledger row
nothing is carrying would read as collecting forever and hide the button for good.
POST /v1/spv/messages/read
Marks a set read in one act. View.
| Field | Type | Required | Means |
|---|---|---|---|
profileId |
uuid | no | One client. Omit it for every client the caller can see |
{ "markedCount": 42 }
GET /v1/spv/messages/unread-count
The badge, over the clients the caller can see.
| Field | Type | Means |
|---|---|---|
total |
integer | Unread across every visible client |
profiles |
array | { "profileId": uuid, "unreadCount": integer } |
Sales invoices from the sent list
Businesses that bill with SmartBill, FGO, or similar already upload their sales invoices to SPV.
ANAF keeps those on a second list, the sent list (filtru=T, type FACTURA TRIMISA), beside the
received list Syllevo has always polled (filtru=P, FACTURA PRIMITA). Reading it gives the
accountant both sides of the VAT return without the business setting up an API integration.
Every poll reads the sent list right after the received list, for each enrolled client, on a
cursor of its own (sent_window_end on the enrollment). The window rules are the same: thirty
minutes of overlap, ten minutes behind now, never more than 59 days back, and the cursor only moves
once ANAF has returned the last page. The first sent pass for a client starts 59 days back, the way
a new enrollment does.
- The sent list is only asked for once the received list has answered. A client without rights, a throttled grant, or an ANAF outage costs one call per pass, not two.
- A sent listing that fails leaves the sent cursor where it was and is retried next pass. It never backs off the received side or counts as a poll failure.
- Clients set to Messages only aren't asked for their sent list, since that scope files no invoices. Only listed suppliers narrows purchases by issuer and doesn't apply to sales, so those clients get their sales like everyone else.
- Sent invoices never appear on
/v1/spv/messages. That stream is what the client received from ANAF; its own sales aren't news to it.
How a sent invoice is filed:
- It is stored as outgoing: the document and the invoice both carry
direction: outgoing, the buyer goes into the counterparty fields, and there is no supplier. The provider stays e-Factura, and the ANAF archive with the Ministry of Finance signature is kept exactly as it is for received invoices. - It is not a collected document. It doesn't count toward usage, isn't queued for extraction, never creates a supplier, and never moves completeness, reminders, or the review queue.
- The seller has to be the enrolled company. If the XML names a different seller, the invoice
isn't filed as a sale and the message ends with
efactura-sent-seller-mismatch. The usual cause is self-billing, where the client uploads an invoice on its supplier's behalf, so for the client it's really a purchase. The archive is kept. - Already imported through the API with the same XML: nothing new is created. The SPV arrival is added to the document already on file.
- Imported as a PDF only, and the SPV XML agrees with it: the XML completes the sale. The sale
has no XML file, and the SPV XML carries the same currency, net, VAT, and total (the number and
issue date already match, since that's how the sale was found). The XML is linked at e-Factura
trust and becomes the main copy, it fills in anything the PDF import left blank or wrote
differently (the buyer, the due date), and the PDF stays linked beside it. No flag. The audit
trail records
invoice.spv_sent_linkedwithresolutioncompleted_pdf_import, naming both files and the fields that changed. Getting the same SPV copy again changes nothing. - Already imported with a different file under the same number and issue date: another XML,
or a PDF whose currency or amounts differ. The SPV copy wins. It's the version ANAF holds, so
it's the one the accountant books. It's linked to the invoice at e-Factura trust and becomes its
main copy, and the invoice's number, dates, amounts, and buyer are read again from it. The
imported file stays linked beside it. The clash is written to the audit trail
(
invoice.spv_sent_conflict, naming both files, both hashes, and the fields that changed), and the invoice gets aConflictingCopyanomaly flag naming the imported file incounterpartDocumentId(Invoices). The flag shows up wherever flags already do, with no email. One flag per invoice; once dismissed, it stays dismissed. Getting the same SPV copy again changes nothing. - A second, different SPV copy of a sale whose main copy ANAF already holds (it came from the sent list, or Syllevo sent it and the sent list returned the same bytes): the main copy stays. The new copy is stored as its own outgoing document, linked to no invoice, and the flag names it.
- Imported after the SPV copy was filed, with a different file: the import is refused with
409 invoice_number_existsand nothing is stored. The business's invoicing software disagrees with ANAF about that invoice, and that's where it gets fixed. - The same bytes already filed as a purchase: left alone, and the message ends with
efactura-sent-held-as-purchase. - An XML with no readable number, issue date, currency, or amounts: the message ends with
efactura-sent-xml-unreadable, and the archive is kept.
Polling the same window again creates nothing new. The message ledger is unique on ANAF's message id, and a redelivered download finds its document by content hash.
An Uncertain submission is settled here. When Syllevo can't tell whether one of its own
uploads reached ANAF, it never uploads again. It settles the submission from the sent list in only
two cases: the entry's XML has the same hash as the file Syllevo uploaded, or the entry carries the
upload index ANAF gave that upload. Then the submission moves on with the entry's upload index and
answer id. Nothing else settles it: not the invoice the entry is filed onto, and not a matching
number or issue date. An entry with the same number but different bytes leaves the submission
Uncertain. See Invoices.
Sending sales invoices to SPV
Syllevo can upload a sales invoice the business already issued, for businesses whose invoicing software doesn't do it. It sends the XML file that software made, unchanged. It never writes, numbers, or fixes an invoice. The routes and states are in Invoices; this section is the switch that allows it.
GET /v1/profiles/{profileId}/efactura/transmission-consent
View. Answers:
{
"profileId": "11111111-1111-4111-8111-111111111111",
"enabled": false,
"organizationEnabled": true,
"canEnable": true,
"lockedReason": null,
"holderName": null,
"enabledById": null,
"enabledAt": null,
"disabledById": null,
"disabledAt": null,
"enabledByName": null,
"disabledByName": null
}
| Field | Means |
|---|---|
enabled |
The business allows Syllevo to send its sales invoices to SPV |
organizationEnabled |
Sales invoice transmission is on for the firm. When false, nothing is sent either way |
canEnable |
The caller may turn it on: the person, signed in, who connected this business to e-Factura |
lockedReason |
Why canEnable is false, see below. null when it's true |
holderName |
Who connected the business to e-Factura, set only when lockedReason is notHolder |
enabledByName |
Who last turned it on: their name, or their email when we have none |
disabledByName |
Who last turned it off, the same way |
All three names are filled in by Syllevo, so a member who can't list the team still sees who did
it. They are null when no one did, or when that person has left the firm.
lockedReason names the first thing that stands in the way, checked in this order:
| Value | Means |
|---|---|
noPermission |
The caller can't manage the business's settings, or isn't a signed in person (an API key) |
noTaxId |
The business has no tax id, or it isn't a valid Romanian CUI |
notEnrolled |
The business isn't enrolled in e-Factura under that CUI |
notConnected |
The e-Factura connection behind the enrollment is disconnected, expired, or failing |
notHolder |
Someone else connected the business to e-Factura. holderName says who |
noPermission comes first because a caller without it can't fix any of the others: adding the
tax id, enrolling, and reconnecting all take the same rights.
The organization role doesn't matter. A Member who connected the business to e-Factura and has
ManageSettings on it can turn it on; an Owner who didn't connect it can't.
PUT /v1/profiles/{profileId}/efactura/transmission-consent
Body { "enabled": true } or { "enabled": false }. Answers the same shape as the GET.
- Turning it on takes the person who connected the business to e-Factura, whatever their role,
signed in (not an API key), with
ManageSettingson the profile. Uploads go out under that person's ANAF certificate, so it's their call alone. Anyone else gets403 spv_consent_not_connection_holder. A business with no readable tax id, no enrollment under it, or no working e-Factura connection gets422 spv_not_enrolled. - Turning it off takes
ManageSettingson the profile, signed in. Anyone who may manage the business can stop it. - Both are recorded in the audit trail as
profile.spv_transmission_consent_changed. Setting the value it already has changes nothing and records nothing.
Nothing already sent is withdrawn when it is turned off. A submission already queued is checked
again right before the upload, and fails with spv_consent_missing if the switch is off by then.
What e-Factura does not cover
e-Factura is domestic business-to-business only. An invoice from a supplier in another EU country never appears here, and it still has to reach the accountant some other way: a forwarded email, a supplier connector, or an upload. That gap is the reason the rest of the sources on Sources exist.