> ## Documentation Index
> Fetch the complete documentation index at: https://docs.filexpress.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Purchases and expenses (v2)

> Register foreign-currency purchases with taxes by id and query their currency reference

**Required permission:** `expenses`

The v2 purchase is the same as [v1](https://docs.filexpress.app/en/docs/api/facturacion/compras.md) (same fields, cost centers, and document types) plus **currency** (`currency_code`, `exchange_rate`) and **`tax_ids` required per line**. Its errors carry a `code`; see [API v2: what changes](https://docs.filexpress.app/en/docs/api/v2/introduccion.md#errors-and-codes).

> [!WARNING]
>
> Unlike sales, saving purchases **does not recalculate taxes**: FileXpress stores what your system sends (converted to base currency if it carries a currency) and you are the one who calculates them. It only validates that each id in `tax_ids` exists. An VAT (IVA) amount or amount payable that does not add up is stored as is, so you must send consistent totals.

> [!NOTE]
>
> Fields not described here (`observation`, `is_credit`, `credit_days`, `number_control`, `generation_code`, `stamp`, cost centers, etc.) are those of [Register a purchase in v1](https://docs.filexpress.app/en/docs/api/facturacion/compras.md#register-a-purchase). Line types remain `goods` (goods) and `services` (service).

## Register a purchase

```http theme={null}
POST /expenses
```

Example: a purchase (CCF, tax credit invoice) by a USD-based business, **in quetzales (GTQ)**, with VAT, COTRANS, FOVIAL, VAT withholding, and income tax. The current GTQ rate is `0.128`.

```json theme={null}
{
  "branch_id": "9d1c5a3e-4b7f-4e2a-9c1d-2f6b8a0e7c11",
  "provider_id": "5e6f7a8b-3c4d-4e1f-9a2b-1c0d9e8f7a6b",
  "document_type": "ccf",
  "date": "2026-10-21T10:00:00",
  "is_credit": true,
  "credit_days": 30,
  "number_control": "DTE-03-M001P001-000000000000123",
  "generation_code": "A1B2C3D4-5E6F-4A7B-8C9D-0E1F2A3B4C5D",
  "currency_code": "GTQ",
  "exchange_rate": 0.128,
  "products": [
    {
      "sku": "COM-001",
      "name": "Diésel",
      "type": "goods",
      "quantity": 5,
      "price_before_taxes": 100.00,
      "expense_taxed": 500.00,
      "expense_exent": 0,
      "expense_not_suject": 0,
      "subtotal": 500.00,
      "tax_ids": ["<id-del-IVA>", "<id-del-COTRANS>"]
    },
    {
      "sku": "COM-002",
      "name": "Gasolina",
      "type": "goods",
      "quantity": 5,
      "price_before_taxes": 50.00,
      "expense_taxed": 250.00,
      "expense_exent": 0,
      "expense_not_suject": 0,
      "subtotal": 250.00,
      "tax_ids": ["<id-del-IVA>", "<id-del-FOVIAL>"]
    },
    {
      "sku": "SRV-010",
      "name": "Servicio exento",
      "type": "services",
      "quantity": 1,
      "price_before_taxes": 125.00,
      "expense_taxed": 0,
      "expense_exent": 125.00,
      "expense_not_suject": 0,
      "subtotal": 125.00,
      "tax_ids": []
    },
    {
      "sku": "SRV-011",
      "name": "Servicio no sujeto",
      "type": "services",
      "quantity": 1,
      "price_before_taxes": 62.50,
      "expense_taxed": 0,
      "expense_exent": 0,
      "expense_not_suject": 62.50,
      "subtotal": 62.50,
      "tax_ids": []
    }
  ],
  "totals": {
    "netTotals": 937.50,
    "charge_1": 15.63,
    "charge_2": 7.81,
    "expenseTotalTax": 750.00,
    "expenseTotalExent": 125.00,
    "expenseTotalNotSuject": 62.50,
    "totalTax": 97.50,
    "totalRetein": 7.50,
    "totalRent": 10.00,
    "expenseTotalToPayment": 1040.94
  }
}
```

> [!WARNING]
>
> **All amounts are in the currency of `currency_code`.** The server converts each amount to the base currency using the document's rate. In the example, 750.00 GTQ taxed is 96.00 USD; the VAT of 97.50 GTQ, 12.48 USD; COTRANS (`charge_1`), 2.00 USD; FOVIAL (`charge_2`), 1.00 USD; the 7.50 GTQ withholding, 0.96 USD; and the 10.00 GTQ income tax, 1.28 USD. The total payable of 1,040.94 GTQ is stored as 133.24 USD.

The total payable is `netTotals + totalTax + charge_1 + charge_2 − totalRetein − totalRent`: 937.50 + 97.50 + 15.63 + 7.81 − 7.50 − 10.00 = 1,040.94.

### New or different fields from v1

| Field | Type | Required | Description |
| - | - | - | - |
| `currency_code` | string | No | 3-letter code of the document's currency. Must be active for the business; see [Currencies and rates](https://docs.filexpress.app/en/docs/api/v2/monedas-y-tasas.md). If omitted or equal to the base currency, the purchase is in base currency. |
| `exchange_rate` | number | No | Units of **base** currency per 1 unit of `currency_code`. Greater than zero. If omitted, the rate in effect on the purchase `date` is used. |
| `products[].tax_ids` | array of uuid | **Yes** | IDs of the line's taxes (must exist). The key must be present on every line; it can be `[]`. IDs are obtained with [`GET /taxes/branch/{branch_id}`](https://docs.filexpress.app/en/docs/api/facturacion/catalogos) (v1). |
| `totals.expenseTotalExent` | number | No | Document's exempt total. If omitted it is stored as 0. |
| `totals.expenseTotalNotSuject` | number | No | Document's not-subject total. If omitted it is stored as 0. |

`totals.expenseTotalTax` is the taxed base, `totalTax` the VAT, and `charge_1` / `charge_2` the special charges (in El Salvador, COTRANS and FOVIAL). If the line carries those fixed taxes in `tax_ids`, the amounts are recorded per tax. See [totals in v1](https://docs.filexpress.app/en/docs/api/facturacion/compras.md#totals-all-required).

### Withholding and income tax

`totalRetein` (VAT withheld) and `totalRent` (income tax withheld) **subtract** from the total payable. Example in USD with VAT 26.00, withholding 2.00, and income tax 5.00 on 200.00: `expenseTotalToPayment` = 200.00 + 26.00 − 2.00 − 5.00 = 219.00.

```json theme={null}
{
  "totals": {
    "netTotals": 200.00,
    "charge_1": 0,
    "charge_2": 0,
    "expenseTotalTax": 200.00,
    "totalTax": 26.00,
    "totalRetein": 2.00,
    "totalRent": 5.00,
    "expenseTotalToPayment": 219.00
  }
}
```

### `200` response

```json theme={null}
{
  "status": true,
  "message": "Expense created successfully",
  "data": {
    "expense_id": "4a9d1e7c-8b2f-4c3a-9e6d-5f0b1a2c3d4e",
    "generation_code": "A1B2C3D4-5E6F-4A7B-8C9D-0E1F2A3B4C5D",
    "control_number": "DTE-03-M001P001-000000000000123",
    "status": "processed",
    "issue_time": "2026-10-21 10:00:00",
    "total": 1040.94,
    "currency_code": "GTQ",
    "exchange_rate": "0.12800000",
    "foreign_total": "1040.94",
    "base_currency_code": "USD"
  }
}
```

| Field | Description |
| - | - |
| `total` | The `expenseTotalToPayment` you sent, as is (in the document's currency). |
| `currency_code` | Document's currency (the base currency if you did not send a currency). |
| `exchange_rate` | Rate applied, text with 8 decimals. `"1.00000000"` in base currency. |
| `foreign_total` | Total payable in the document's currency, text with 2 decimals. `null` if the purchase is in base currency. |
| `base_currency_code` | The business's base currency. |

For a purchase in base currency: `currency_code` is the base currency, `exchange_rate` is `"1.00000000"`, and `foreign_total` is `null`.

### Errors

They are validated before saving; no error creates the purchase.

| `code` | HTTP | Cause |
| - | - | - |
| `currency_not_enabled` | `422` | Multi-currency not enabled, currency nonexistent or not active for the business. |
| `exchange_rate_not_found` | `422` | No `exchange_rate` and no rate in effect on the date or earlier. |
| `exchange_rate_invalid` | `422` | `exchange_rate` is non-numeric or less than or equal to zero. |
| `base_currency_not_legal` | `422` | Electronic document (`fse`) in a foreign currency and the business's base currency is not the country's legal currency. |
| `validation_error` | `422` | Missing `products.*.tax_ids`, an id in `tax_ids` does not exist (`products.0.tax_ids.0`), or another invalid field. |

```json theme={null}
{
  "status": false,
  "code": "validation_error",
  "message": "Validation errors",
  "errors": { "products.0.tax_ids": ["The products.0.tax_ids field must be present."] }
}
```

## Get a purchase

```http theme={null}
GET /expenses/{expense_id}
```

Returns the same as [v1](https://docs.filexpress.app/en/docs/api/facturacion/compras.md#query-a-purchase) 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`.

```json theme={null}
{
  "status": true,
  "data": {
    "expense_id": "4a9d1e7c-8b2f-4c3a-9e6d-5f0b1a2c3d4e",
    "generation_code": "A1B2C3D4-5E6F-4A7B-8C9D-0E1F2A3B4C5D",
    "control_number": "DTE-03-M001P001-000000000000123",
    "document_type": "ccf",
    "status": "processed",
    "issue_time": "2026-10-21 10:00:00",
    "payment_status": "pending",
    "is_credit": true,
    "provider": { "id": "5e6f7a8b-3c4d-4e1f-9a2b-1c0d9e8f7a6b", "legal_name": "Distribuidora XYZ S.A. de C.V.", "provider_name": "Distribuidora XYZ" },
    "totals": { "expense_total": 120.00, "expense_to_payment": 133.24 },
    "products": [
      { "sku": "COM-001", "name": "Diésel", "quantity": 5, "individual_price": 12.80, "subtotal": 64.00 }
    ],
    "currency_code": "GTQ",
    "exchange_rate": "0.12800000",
    "foreign_total": "1040.94",
    "base_currency_code": "USD"
  }
}
```

A purchase from another business or a nonexistent one responds `404` with `{ "status": false, "message": "..." }`.

> [!TIP]
>
> `GET /payables` (v1) also exposes the currency reference of each payable document. See [Accounts payable](https://docs.filexpress.app/en/docs/api/cuentas-por-pagar.md#pending-documents).

## Let FileXpress calculate the totals

As in [sales](https://docs.filexpress.app/en/docs/api/v2/ventas.md#let-filexpress-calculate-the-totals), you can omit the total payable and send only the lines: FileXpress calculates the line amounts, VAT, fixed charges, and the total.

**When it activates:** when `totals` does not include `expenseTotalToPayment`.

**What you send per line:** `sku`, `name`, `type` (`goods` or `services`), `quantity`, `price_before_taxes`, `discount` or `discount_percent`, `taxability` (`taxed`, `exempt`, `not_subject`), and `tax_ids`. You do not need `subtotal`, `expense_taxed`, `expense_exent`, or `expense_not_suject`: they are filled in automatically.

**What is calculated**

* VAT: sum of each line's taxed amount times the rate, rounded at the end.
* COTRANS (`C8`) and FOVIAL (`D1`) as `charge_1` and `charge_2` (quantity × catalog value), with their row in the purchase's taxes. **They are the only fixed per-unit taxes a purchase can carry today**: the purchase only has those two charge slots.
* `totals.totalRetein` and `totals.totalRent` (VAT withholding and income tax) **are sent by you**: they are the buyer's decisions. They are subtracted from the amount payable.

```json theme={null}
{
  "branch_id": "9d1c5a3e-4b7f-4e2a-9c1d-2f6b8a0e7c11",
  "provider_id": "7c0d9e8f-2222-4a3b-8c4d-5e6f7a8b9c0d",
  "document_type": "ccf",
  "date": "2026-10-21T10:00:00",
  "is_credit": true,
  "credit_days": 30,
  "number_control": "DTE-03-M001P001-000000000000123",
  "generation_code": "8A1F3C52-0B7E-4D11-9C3A-5E2B7D9A4F10",
  "products": [
    { "sku": "SRV-1", "name": "Servicio", "type": "services", "quantity": 1, "price_before_taxes": 1234567.89, "tax_ids": ["<id-del-IVA>"] }
  ],
  "totals": { "totalRetein": 12345.68, "totalRent": 0 }
}
```

Response (`data.calculated_totals`): `netTotals` 1234567.89, `totalTax` 160493.83, `expenseTotalToPayment` 1382716.04. Amounts are stored exactly: a purchase worth millions does not lose a single cent.

The rounding and foreign-currency rules are those of sales. Errors use the same `code` values (`validation_error`, `tax_invalid`).

> [!WARNING]
>
> If a purchase line carries another fixed per-unit tax (from another country or created later in the catalog), the automatic calculation responds `422` with `code: "tax_not_supported_in_expenses"` and the `tax_id` in `errors`, instead of ignoring it and leaving the total wrong. In that case, send the complete `totals`. **Sales** do support any fixed tax in the country's catalog.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.