Invoice
Issue an invoice from your system
With the Timbro invoicing API, your point of sale or ERP keeps recording the sale and Timbro issues the e-invoice to the DGII. You can issue before or after collecting, and the customer can pay with Timbro, in cash, by transfer, or on your card terminal. Invoicing does not require a merchant account.
To issue, you need invoicing set up and an API key with Invoicing access, which these examples store in TIMBRO_INVOICES_API_KEY. The key determines the business and the mode, Test or Live, so your requests do not state them.
Install the SDK on your server:
npm install @timbro/invoicesThe Python SDK needs Python 3.11 or newer.
pip install timbro-invoicesIssue the invoice
Section titled “Issue the invoice”Send the real fiscal data of the sale. This example issues a Consumer Invoice paid up front:
import { createTimbroInvoices } from "@timbro/invoices";
const timbro = createTimbroInvoices({ apiKey: process.env.TIMBRO_INVOICES_API_KEY! });const invoice = await timbro.invoices.create({ invoiceReference: "sale-842-invoice", type: "E32", currency: "DOP", incomeType: "01", paymentTerms: { type: "immediate" }, items: [{ description: "Service", quantity: "1", unit: "unit", kind: "service", unitPrice: "1250.00", tax: { kind: "itbis_18", included: true }, }],});TimbroInvoices is the synchronous client. If your server uses asyncio, AsyncTimbroInvoices has the same methods with await. Requests and responses are dictionaries with the API’s field names.
import os
from timbro_invoices import TimbroInvoices
timbro = TimbroInvoices(api_key=os.environ["TIMBRO_INVOICES_API_KEY"])invoice = timbro.invoices.create({ "invoiceReference": "sale-842-invoice", "type": "E32", "currency": "DOP", "incomeType": "01", "paymentTerms": {"type": "immediate"}, "items": [{ "description": "Service", "quantity": "1", "unit": "unit", "kind": "service", "unitPrice": "1250.00", "tax": {"kind": "itbis_18", "included": True}, }],})| Field | What it states |
|---|---|
invoiceReference |
This invoice’s identifier in your system. It must be unique per business and mode |
type |
The e-CF type: E31, E32, E33, E34, E41, E43, E44, E45, E46, or E47 |
incomeType |
The DGII income type. 01 is income from nonfinancial operations. If you do not send it, Timbro uses the one from the business profile |
paymentTerms |
Whether the customer paid up front, bought on credit, or the sale is free |
items |
Each item with its quantity, unit of measure, price, and tax. Amounts are strings with a decimal point |
Each type asks for different data, and the InvoiceRequest schema in the REST reference details them. Do not invent data to complete a request.
Use HTTP directly
Save the same body as invoice.json and send it from your server:
curl --request POST 'https://api.timbro.do/v1/invoices' \ --header "Authorization: Bearer $TIMBRO_INVOICES_API_KEY" \ --header 'Content-Type: application/json' \ --data-binary @invoice.jsonAvoid issuing two invoices for the same sale
Section titled “Avoid issuing two invoices for the same sale”invoiceReference identifies the invoice. If you repeat the request with the same reference and the same data, Timbro returns the same invoice instead of issuing another one. So store the request in your system before sending it, and if you lose the response, repeat it exactly as it was, without changing its data. A new reference asks for another invoice.
If your system sends invoices from a work queue, look up the reference first and issue only if it does not exist:
const found = await timbro.invoices.list({ invoiceReference: "sale-842-invoice" });found = timbro.invoices.list(invoice_reference="sale-842-invoice")To store on the invoice the document in your system that originated it, send source: { id, version }, for example the document’s ID and its version number. If your system has to invoice a sale again, for example after a rejection, send the same id with a new version and a new invoiceReference.
State how the customer paid
Section titled “State how the customer paid”paymentTerms describes the payment as it must appear on the invoice:
{ "type": "immediate" }for a sale paid up front. If you want the invoice to show how the customer paid, addallocationswith one entry per payment method, up to 7. Each payment method (method) iscash,bank_transfer,card,credit_sale,gift_certificate,barter,credit_note, orother. Amounts are in Dominican pesos and must add up to the invoice total.{ "type": "credit", "dueOn": "2026-11-30" }for a credit sale, with the date it is due.{ "type": "free" }for a free sale. The DGII does not accept this case for types E31, E33, E34, and E45.
For example, a sale of RD$ 1,250.00 paid partly in cash and partly by card:
{ "type": "immediate", "allocations": [ { "method": "cash", "amount": "500.00" }, { "method": "card", "amount": "750.00" } ]}A credit invoice can be collected in partial payments with Timbro, as Collect an invoice in partial payments explains.
Identify the buyer
Section titled “Identify the buyer”- A Tax Credit Invoice (
E31) needs inbuyerthe buyer’s RNC or cédula (taxId) and their name or business name (legalName). - A Consumer Invoice (
E32) under RD$ 250,000.00 can have no buyer. From that amount, the DGII requires the buyer to be identified. If the customer gave you their details, send them inbuyer. - A Government Invoice (
E45) needs, besides the buyer,governmentEligibilitywith the entity type.
For a foreign buyer, send kind: "foreign" with their identification in foreignId.
Check your system’s totals
Section titled “Check your system’s totals”Timbro calculates totals with the DGII rounding rules. If your system already calculated the sale’s totals, send them in expectedTotals with net, tax, total, and nonBillable. If they do not match Timbro’s to the cent, Timbro rejects the invoice instead of issuing other amounts, so your accounting and the e-CF never differ.
To check the data and totals without issuing, use invoices.preview with the same request. The preview does not assign an e-NCF or send anything to the DGII.
Invoice in another currency
Section titled “Invoice in another currency”To invoice in dollars or euros, send currency and the exchange rate in exchangeRate.rate: how many Dominican pesos one unit of that currency is worth, with up to 4 decimals. The DGII reference rate for a date is at GET /v1/exchange-rates/USD?date=2026-10-21. For euros, replace USD with EUR.
The Consumer Invoice (E32) and the notes that modify it are issued only in Dominican pesos. A sale with a legal tip or with non-billable charges is also issued in pesos. Timbro always collects in Dominican pesos.
Print the invoice at your register
Section titled “Print the invoice at your register”To print the invoice on a receipt printer, send pdfLayout: "receipt80mm"; the PDF will be 80 mm wide. If you do not send it, the PDF uses A4 size.
If your system prints the invoice in its own format, read fiscalIdentity.signature when the DGII accepts it. It carries the signing date (signedAt), the security code (securityCode), and the QR link (verificationUrl) that the printed invoice must show. It is null until Timbro has signed the e-CF.
Check the status
Section titled “Check the status”The response has the invoice’s id, its e-NCF once assigned, and its status:
| Status | What it means |
|---|---|
processing, verifying |
Timbro sent it to the DGII and is waiting for its response. Check later |
accepted |
The DGII accepted it |
acceptedWithObservations |
The DGII accepted it with observations you must review |
rejected |
Timbro rejected it before sending it to the DGII. Correct the data and issue another one |
authorityRejected |
The DGII rejected it. You can replace it |
operatorRequired |
It needs the Timbro team to review it |
closed |
An administrator of the business closed it before it reached the DGII. You can replace it |
Retrieve the invoice by its id. The invoice.accepted and invoice.rejected webhooks notify you when it changes:
const current = await timbro.invoices.retrieve(invoice.id);if (!current) throw new Error("Invoice not found");const isAccepted = current.status === "accepted" || current.status === "acceptedWithObservations";invoices.retrieve raises NotFoundError when the invoice does not exist.
current = timbro.invoices.retrieve(invoice["id"])is_accepted = current["status"] in ("accepted", "acceptedWithObservations")DGII acceptance does not mean the XML and PDF are ready. To download them or send them to the customer, follow Download and send the invoice. To collect the invoice with Timbro, follow Collect an invoice you already issued.
Issuing the invoice does not record an accounting entry or confirm a payment. Your system still keeps the sale’s accounting.