Skip to main content
Required permission: sales The v2 sale is the same as v1 (same fields, point of sale, cost centers, and document types) with three differences: it accepts a currency (currency_code, exchange_rate), it requires tax_ids per line, and it validates taxes in strict mode. Its errors carry a code; see API v2: what changes.
Fields not described here (observation, is_credit, credit_days, point_of_sale_id, cost_center_id, project_id, etc.) are the ones from Create sale in v1. The GET /sales listing remains v1 only.

Create sale

Example: a sale (CCF, tax credit invoice) for a USD-based business, in quetzales (GTQ), with 13 % VAT (IVA) and FOVIAL. The current GTQ rate is 0.128 (1 GTQ = 0.128 USD).
All document amounts are in the currency of currency_code: prices, subtotals, taxes, payments, and totals. Do not convert them yourself. The server applies the rate, converts each amount to the base currency, and issues the DTE (electronic tax document) in the country’s legal currency.
In the example, 750.00 GTQ taxed equals 96.00 USD; the 97.50 GTQ VAT, 12.48 USD; and the 15.63 GTQ FOVIAL, 2.00 USD. The total to pay is 863.13 GTQ = 110.48 USD.

New or different fields from v1

200 response

The remaining response fields (sale_id, generation_code, control_number, etc.) are those from v1. A document in base currency responds the same way, with the base currency as currency_code, rate "1.00000000", and foreign_total null:

Taxes by id

In v2 each line declares the taxes that apply to it with tax_ids, and the document totals them in totals.taxes and totals.withholdings. Before transmitting the DTE, the server validates and recalculates the taxes:
  • Each id must exist, be active, belong to the branch’s country, and have the correct effect (those in tax_ids and taxes add; those in withholdings withhold).
  • The sum of the declared amounts must match totalTax, totalRetein, and totalRent.
  • In strict mode (always on in v2), if totalTax, totalRetein, or totalRent differ from what the server calculates by more than 0.02, the sale is rejected and nothing is transmitted. This includes the 1 % VAT withholding and income tax when the customer’s rules require them (for example, a large taxpayer with taxed sales of 100 or more).
Example of a 1 % withholding for a large taxpayer in USD (taxed 200.00, VAT 26.00, withholding 2.00, total to pay 224.00):
Withheld income tax is sent the same way: totalRent with its amount and a row in withholdings with the income tax’s tax_id. Taxed sales of 300.00 with VAT of 39.00 and 10 % income tax (30.00) pay 309.00.

tax_invalid error

In tax_invalid, errors is a list; each element carries field, message, a detail code, and, when applicable, tax_id. The field names are the document’s internal ones (invoiceResumenDetail.*, invoiceProductsDetail.tax_ids). If a tax_ids value is not a UUID, the response is validation_error (not tax_invalid).
Validation scope: the server does not compare the declared amount of fixed-per-unit taxes (for example FOVIAL and COTRANS) against quantity × catalog value; it validates percentage-based taxes (VAT), withholdings, and income tax. Also, a sale with no value in tax_ids, totals.taxes, or totals.withholdings is treated as legacy and is not validated. In foreign currency, fixed-tax amounts are sent in the document’s currency.

Currency errors

They are validated before issuing; no error saves or transmits the document.

Get sale

Returns the same as v1 plus the currency reference. The amounts in totals and products are in base currency; the total in the document’s currency is in foreign_total.
A sale from another business or one that does not exist responds 404 with { "status": false, "message": "..." }.
GET /receivables (v1) also exposes the currency reference of each receivable document. See Accounts receivable.

Payment methods: use the id

Each payment_details line identifies its payment method with payment_method_id, the id returned by GET /countries/{code} in payment_methods[]. The code ("01") is not unique: another country may use the same code for a different method, and within a country there can be repeated methods. If the payment method does not exist, is not from the country, or both the id and the code are missing, the sale is rejected with 422 and code: "payment_method_invalid" (in errors, the field payment_details.N.payment_method_id). Previously an unknown code left the sale without that payment, with no warning.

Let FileXpress calculate the totals

Instead of declaring every amount, you can send only the lines and FileXpress calculates each line’s amounts, the taxes, the withholdings, and the total to pay. It is the recommended mode for new integrations: there can be no difference of cents between your system and ours. When it activates: when totals does not include saleTotalToPayment. If you send it, the mode above is used (you declare and we validate). What you send per line totals can include only freight and insurance. If the customer qualifies (for example, a large taxpayer or a public institution), the 1 % withholding is applied automatically.
The response includes what was calculated in data.calculated_totals:
Calculation rules (the same ones the New sale screen uses):
  • Everything is calculated with exact decimals and rounded only once per total, rounding half up (0.115 → 0.12).
  • The taxed amount is the sum of quantity × price × (1 − discount) without rounding per line, rounded at the end; exempt and not-subject amounts are rounded per line.
  • Each percentage-based tax is the sum of each line’s taxed amount times its rate, rounded at the end. Fixed-per-unit taxes (FOVIAL, COTRANS) are quantity × catalog value.
  • In foreign currency the amounts are in the document’s currency; the catalog’s fixed taxes (in base currency) are expressed in it by dividing them by the rate.
  • The amounts we return never have more decimals than the currency.
Automatic calculation is not available for final consumer (fc): VAT is included in the price and its composition depends on the branch’s configuration. For fc, send the full totals. It responds 422 with code: "totals_required".