Skip to main content

Send CSV invoices to SdI

Send a CSV from your ERP to SdI through A-Cube Transfer. Transfer converts each invoice to FatturaPA, and A-Cube's Italian invoicing API handles the submission to SdI. You will prepare one test invoice, upload the file, check processing through the API and follow its SdI outcome.

Before you start​

  • A-Cube has enabled sandbox REST upload and tracking access for Italy, and configured your seller data for the standard CSV.
  • You have sandbox access to the Italian invoicing API, with the test data agreed with A-Cube.
  • You have curl and Python 3. The commands below run in a shell on macOS or Linux.

Sandbox tests the integration without sending real invoices to SdI. Use it to check conversion, errors and the invoice's test status. Real SdI submission requires separately enabled production access.

The flow​

StepServiceWhat you keep or check
Prepare the CSVYour ERPOne invoice in the Italian standard layout
Sign in and uploadCommon API and Transfer APISandbox token, upload id and your e2e_id
Wait for processingTransfer APIUpload status and document violations
Find the created invoiceTransfer APIThe document's transmission_uuid
Follow the SdI outcomeItalian invoicing APIInvoice marking and notifications

202 Accepted confirms registration of the upload. completed confirms that the Italian invoicing service accepted the invoice. Neither confirms delivery by SdI.

Step 1: Prepare one invoice​

Download the Italian standard CSV sample and save your prepared file as invoice.csv.

The sample contains two invoices, including a draft. For this tutorial:

  1. Keep the header and the two rows with invoice_sequence=1. Remove the row with invoice_sequence=2.
  2. Replace the buyer identifiers and address with the sandbox values agreed with A-Cube.
  3. Set a new invoice_number and the test dates on both rows. Dates use d/m/Y, for example 09/10/2026.
  4. Keep invoice_draft=FALSE on both rows. A TRUE value creates a draft, which is not sent to SdI until you send it through the Italian invoicing API.
  5. Keep the comma separator, header names and dot decimal separator. Edit the CSV in a text editor to avoid changing its format.

Rows with the same invoice_sequence belong to one invoice. line_number distinguishes its lines. Seller data comes from your onboarding configuration; buyer data comes from the CSV. See the Italian CSV layout for the rules and mapping reference.

Step 2: Get a sandbox token​

Sign in with your A-Cube credentials and environment: sandbox:

curl --fail-with-body -sS https://common.api.acubeapi.com/login \
-H 'Content-Type: application/json' \
-d '{"email": "<your-email>", "password": "<your-password>", "environment": "sandbox"}'

Copy the returned token and set the two sandbox service URLs:

TOKEN='<your-token>'
TRANSFER_URL='https://transfer-sandbox.api.acubeapi.com'
INVOICING_URL='https://api-sandbox.acubeapi.com'

The token lasts 24 hours. If a request returns 401, sign in again.

Step 3: Upload the CSV​

Use an End-to-end ID from your ERP to find this upload later. Build the request with the whole file encoded in base64:

python3 -c 'import base64,json,pathlib; print(json.dumps({
"country": "IT",
"file_name": "invoice.csv",
"content": base64.b64encode(pathlib.Path("invoice.csv").read_bytes()).decode("ascii"),
"e2e_id": "ERP-IT-2026-00042"
}))' > upload-request.json

curl --fail-with-body -sS -D upload-headers.txt -o upload-receipt.json \
-X POST "$TRANSFER_URL/upload" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
--data-binary @upload-request.json

Check the HTTP status in upload-headers.txt and the body in upload-receipt.json. On 202, you receive:

{"id": "<upload-id>", "e2e_id": "ERP-IT-2026-00042", "status": "pending"}

The Location header points to /uploads/<upload-id>. Save the ID:

UPLOAD_ID=$(python3 -c 'import json; print(json.load(open("upload-receipt.json"))["id"])')

If the response is not 202, follow Upload errors before proceeding. For a timeout or 503, see Uncertain results and retries.

Step 4: Wait for processing and read the invoice UUID​

Read the upload:

curl --fail-with-body -sS "$TRANSFER_URL/uploads/$UPLOAD_ID" \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json'
Upload statusNext action
pending, ingested, or reserved processingWait and read again.
completedRead the documents to obtain the invoice UUID.
partialRead all documents: some invoices succeeded and some failed.
failedRead all documents and their violations before correcting the file.

Start with a check every 10 seconds, then use longer intervals if processing continues. Stop frequent polling after a bounded period and retain the upload ID for later checks or support. These intervals are recommendations, not processing-time guarantees. Do not upload again just because processing is slow.

Retrieve all documents, including errors found while reading or converting the CSV:

curl --fail-with-body -sS "$TRANSFER_URL/uploads/$UPLOAD_ID/transfer-documents?itemsPerPage=100&page=1" \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json'

The response is an array. A successfully transmitted invoice includes:

{
"id": "<document-id>",
"status": "transmitted",
"transmission_uuid": "<invoice-uuid>",
"transmission_outcome": "ok",
"violations": []
}

Save the transmission_uuid of the transmitted invoice. It is the invoice UUID in the Italian invoicing API. The CSV file is also listed, so not every entry is an invoice.

For a failed entry, read violations, including the code, message and CSV row details when available. Conversion errors can be reported on the input CSV before any invoice is created. Use Violation codes to decide what to fix.

If you later send a batch, keep the UUID of every transmitted invoice and read subsequent pages until you receive fewer than itemsPerPage entries. For a partially successful batch, correct and upload only the failed invoices with a new file name.

Step 5: Follow the SdI outcome​

Set the UUID from the transmitted document and read the invoice in the Italian invoicing API:

INVOICE_UUID='<transmission-uuid>'

curl --fail-with-body -sS "$INVOICING_URL/invoices/$INVOICE_UUID" \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json'

Read the invoice's marking. The main values in the SdI flow are:

markingMeaning and next action
waiting, sentSending or awaiting SdI's answer. Keep checking this invoice.
quarantenaSdI could not be reached. A-Cube retries; keep checking the invoice.
delivered, delivered-paSdI delivered the invoice to the recipient.
not-deliveredSdI could not deliver the invoice to the recipient. Read the notification.
rejectedSdI rejected the invoice. Read the rejection details before correcting and resending it.
invoice-errorA-Cube could not complete the submission after its retries. Contact A-Cube with the UUID.

Sandbox does not contact SdI. Check the test status expected for your sandbox setup; a sandbox result does not prove real delivery. For the complete status list and production behavior, see SdI outcome.

For an in-progress invoice, check periodically with longer intervals than the Transfer processing checks. Keep the UUID and reduce polling frequency if it remains in progress. An unexpected status needs investigation, not an automatic resend.

SdI's later answer does not change the Transfer document's transmitted status. A successful Transfer upload can still lead to a rejected invoice in the Italian invoicing API.

Read the SdI notification types: NS is a rejection, RC a delivery receipt, and MC a delivery failure. Store the ERP reference, upload ID, invoice UUID and outcome together.

Uncertain results and retries​

e2e_id is a correlation value, not an idempotency key. Every accepted upload request creates a new upload.

If the upload request times out, search by your ERP reference before retrying:

curl --fail-with-body -sS --get "$TRANSFER_URL/uploads" \
--data-urlencode 'e2e_id=ERP-IT-2026-00042' \
-H "Authorization: Bearer $TOKEN" \
-H 'Accept: application/json'

The search is exact and case-sensitive, and it can return several uploads. Inspect their IDs and times. An empty result immediately after a timeout does not prove the request will never finish: wait and check again.

For 503, use the receipt ID in the error response. A failed upload can be retried; if it stays pending, contact A-Cube before retrying. Once it is ingested or later, Transfer has the file. Continue tracking it instead of uploading it again.

For a partially successful CSV, resend only the corrected failed invoices. For an invoice rejected later by SdI, follow the SdI outcome guidance; this is separate from a Transfer processing failure.

Go to production​

After the sandbox checks, confirm that A-Cube enabled production access and seller configuration. Sign in with environment: production, use https://transfer.api.acubeapi.com for Transfer and https://api.acubeapi.com for Italian invoicing, and prepare production invoice data.

Follow the same upload and processing checks, then continue until you can read the real SdI outcome. Keep checking every invoice in a batch, even when its upload is already completed.

  • UBL JSON to KSeF: send a Polish invoice and retrieve its KSeF outcome and UPO.
  • Transfer webhooks: configure processing callbacks where supported. Confirm correlation and error coverage with A-Cube.
  • Italy's webhooks: receive SdI events from the Italian invoicing service.