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

# POST /v1/ecommerce/sales/:id/dte

> Issue the electronic tax document (DTE) of an existing sale: a receipt (boleta) or an invoice (factura), paid or on credit.

Issues the DTE of a sale that already exists — for example one created without `dte`, or one whose DTE failed when it was created with [`POST /v1/ecommerce/sales`](https://docs.keebai.com/dev/endpoints/ecommerce-sales-create).

## Endpoint

```
POST https://api.keebai.com/v1/ecommerce/sales/{id}/dte
```

## Required scope

`ecommerce:invoicing:issue`

> [!NOTE]
>
> **The scope comes from your role.** Minting a token with `ecommerce:invoicing:issue` requires the permission to issue DTE in your Keebai role, unless you are the company owner or a superuser. See [scopes](https://docs.keebai.com/dev/scopes.md#scopes-are-independent-from-role-permissions).

## Headers

| Header | Required | Value |
| - | - | - |
| `Authorization` | Yes | `Bearer kbai_pk_<token>` |
| `Content-Type` | Yes | `application/json` |

## Path parameters

| Field | Type | Required | Description |
| - | - | - | - |
| `id` | `string` | Yes | `ObjectId` of the sale. |

## Body

| Field | Type | Required | Description |
| - | - | - | - |
| `document` | `string` | Yes | `receipt` (boleta), `exempt_receipt` (boleta exenta), `invoice` (factura), or `exempt_invoice` (factura exenta). |
| `receiver` | `object` | Conditional | `rut`, `legal_name`, `activity`, `address`, `commune`, `email?`. Required for `invoice` and `exempt_invoice`. For a RUT the company invoiced before, `rut` alone is enough: the rest is filled in from the stored data, and any field you send overrides it. |
| `due_date` | `string` | No | `YYYY-MM-DD`. Due date of an invoice issued on credit, on a sale that is not fully paid. Defaults to today plus the company's credit days. Ignored when the sale is paid. |
| `save_receiver` | `boolean` | No | Save the receiver for later documents. |

## Example request

```bash curl theme={"system"}
curl -X POST https://api.keebai.com/v1/ecommerce/sales/65e4f5a6b7c8d9e0f1a2b3c4/dte \
  -H "Authorization: Bearer kbai_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "document": "invoice",
    "receiver": {
      "rut": "76123456-7",
      "legal_name": "Comercial Ejemplo SpA",
      "activity": "Venta al por menor",
      "address": "Av. Libertador 1234",
      "commune": "Santiago",
      "email": "facturas@ejemplo.cl"
    },
    "save_receiver": true
  }'
```

```js JavaScript theme={"system"}
const response = await fetch(
  `https://api.keebai.com/v1/ecommerce/sales/${saleId}/dte`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.KEEBAI_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ document: "receipt" }),
  },
);
const { issued, failed } = await response.json();
```

```python Python theme={"system"}
import os, requests

resp = requests.post(
    f"https://api.keebai.com/v1/ecommerce/sales/{sale_id}/dte",
    headers={"Authorization": f"Bearer {os.environ['KEEBAI_API_TOKEN']}"},
    json={"document": "receipt"},
    timeout=30,
)
resp.raise_for_status()
result = resp.json()
```

## Response

### 200 OK

The call answers with what was issued and what failed. A DTE that cannot be issued comes back in `failed` with its reason, not as an HTTP error.

```json theme={"system"}
{
  "issued": [
    {
      "sale_id": "65e4f5a6b7c8d9e0f1a2b3c4",
      "document": "invoice",
      "type": 33,
      "folio": 8211,
      "document_id": "6653e4f5a6b7c8d9e0f1a2b4",
      "status": "uploaded"
    }
  ],
  "failed": []
}
```

```json Failed theme={"system"}
{
  "issued": [],
  "failed": [
    {
      "sale_id": "65e4f5a6b7c8d9e0f1a2b3c4",
      "reason": "unpaid"
    }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `issued[]` | `object[]` | Documents issued: `sale_id`, `document`, `type` (SII code), `folio`, `document_id`, `status`. |
| `failed[]` | `object[]` | Documents not issued: `sale_id`, `reason` (the error code), `message?`. |

### Failure reasons

| `reason` | Meaning |
| - | - |
| `already_issued` | The sale already has its DTE. |
| `unpaid` | A `receipt` or `exempt_receipt` on a sale that is not fully paid. Receipts always need full payment. |
| `credit_not_enabled` | An `invoice` or `exempt_invoice` on a sale that is not fully paid, and the company has not enabled credit invoices. |
| `due_date_invalid` | `due_date` is not a valid date or is earlier than the issue date. |
| `receiver_required` | An `invoice` or `exempt_invoice` needs `receiver`. |
| `not_completed` | The sale is not completed. |
| `deleted` | The sale was deleted. |
| `exempt_type_required` | The sale has no taxed lines, so it needs an exempt document (`exempt_receipt` or `exempt_invoice`). |
| `taxed_lines` | The sale has taxed lines, so an exempt document does not apply. |
| `no_lines` | The sale has no lines to invoice. |
| `too_many_items` | The sale has more lines than the document allows. |
| `amount_invalid` | The sale's amount cannot be invoiced. |

### 400 Bad Request

The body does not match the schema, `DUE_DATE_INVALID` (`due_date` is not `YYYY-MM-DD` or is before today), or `dte_issuer_missing`: the company has not registered its DTE issuer yet. That is configured in the portal.

### 401 Unauthorized

Missing, invalid, revoked, or expired token.

### 403 Forbidden

The token does not have the `ecommerce:invoicing:issue` scope.

### 404 Not Found

No sale with that id, or it belongs to a different company.

### 422 Unprocessable Entity

`RECEIVER_DATA_REQUIRED` — the invoice receiver is incomplete and the RUT has no stored data to fill it. `missing` lists the fields. Nothing is issued.

### 502 Bad Gateway

`ECOMMERCE_UPSTREAM_ERROR` — the ecommerce service could not answer.

## Operational notes

* **Branch on `failed`, not only on the status.** A request that did not issue anything can still succeed at the HTTP level.
* **`already_issued` makes retries safe.** Calling again for a sale that already has its DTE does not issue a second one; it comes back in `failed` with that reason.
* **Check the documents first.** [`GET /v1/ecommerce/invoicing/types`](https://docs.keebai.com/dev/endpoints/ecommerce-invoicing-types) tells you which `document` values the company can issue today and whether it allows credit invoices.
* **Invoices can go on credit.** With credit invoices enabled in **Sales › Settings**, an invoice on a sale that is not fully paid is issued as a credit invoice with its due date. Record the payment later from the Keebai portal or with the MCP tool `keebai_ecommerce_sale_payment_add`.
* **Read the issued documents** with [`GET /v1/ecommerce/sales/{id}/documents`](https://docs.keebai.com/dev/endpoints/ecommerce-sale-documents).


## OpenAPI

````yaml POST /v1/ecommerce/sales/{id}/dte
openapi: 3.0.0
info:
  title: Keebai Public API
  description: Superficie pública REST autenticada con Personal Access Tokens (PAT).
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.keebai.com
    description: Production
security: []
tags: []
paths:
  /v1/ecommerce/sales/{id}/dte:
    post:
      tags:
        - ecommerce
      summary: Emitir el DTE de una venta
      description: >-
        Emite `receipt` (boleta), `exempt_receipt` (boleta exenta), `invoice`
        (factura) o `exempt_invoice` (factura exenta) para la venta. La factura
        exige `receiver`. La boleta exige la venta pagada; una factura sobre una
        venta no pagada sale a crédito con `due_date`, solo si la empresa tiene
        habilitadas las facturas a crédito. Lo emitido va en `issued` y lo que
        no se pudo en `failed`.
      operationId: PublicEcommerceOperationsController_issueDte
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicDteIssueDto'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicDteIssueResultDto'
      security:
        - PAT: []
components:
  schemas:
    PublicDteIssueDto:
      type: object
      properties:
        document:
          type: string
          enum:
            - invoice
            - exempt_invoice
            - receipt
            - exempt_receipt
          description: >-
            invoice (factura), exempt_invoice (factura exenta), receipt
            (boleta), exempt_receipt (boleta exenta). La factura exige
            `receiver`; con un RUT ya facturado basta `receiver.rut`
        due_date:
          type: string
          description: >-
            Vencimiento (YYYY-MM-DD) de una factura emitida a crédito, sobre una
            venta que no está pagada completa. Sin él, hoy más los días
            configurados en la empresa. Se ignora si la venta está pagada
          example: '2026-12-10'
        receiver:
          $ref: '#/components/schemas/PublicDteReceiverDto'
        save_receiver:
          type: boolean
          description: Guarda el receptor en el cliente para la próxima factura
      required:
        - document
    PublicDteIssueResultDto:
      type: object
      properties:
        issued:
          type: array
          items:
            $ref: '#/components/schemas/PublicDteIssuedDto'
        failed:
          type: array
          items:
            $ref: '#/components/schemas/PublicDteFailedDto'
      required:
        - issued
        - failed
    PublicDteReceiverDto:
      type: object
      properties:
        rut:
          type: string
          description: RUT del receptor
          example: 76123456-7
        legal_name:
          type: string
          description: >-
            Razón social. Con un RUT ya facturado antes, se completa con los
            datos guardados
          maxLength: 100
        activity:
          type: string
          description: Giro
          maxLength: 40
        address:
          type: string
          description: Dirección
          maxLength: 70
        commune:
          type: string
          description: Comuna
          maxLength: 20
        email:
          type: string
          description: Correo del receptor
          maxLength: 80
      required:
        - rut
    PublicDteIssuedDto:
      type: object
      properties:
        sale_id:
          type: string
        document:
          type: string
          enum:
            - invoice
            - exempt_invoice
            - receipt
            - exempt_receipt
            - credit_note
            - debit_note
            - purchase_invoice
            - invoice_settlement
            - dispatch_guide
        type:
          type: number
          description: Código SII del documento
        folio:
          type: number
        document_id:
          type: string
        status:
          type: string
        net:
          type: number
          description: Neto afecto, CLP
        exempt:
          type: number
          description: Monto exento, CLP
        tax:
          type: number
          description: IVA, CLP
        total:
          type: number
          description: Total, CLP
        due_date:
          type: string
          description: Vencimiento de una factura emitida a crédito (YYYY-MM-DD)
      required:
        - sale_id
        - document
        - type
        - folio
        - document_id
        - status
    PublicDteFailedDto:
      type: object
      properties:
        sale_id:
          type: string
        reason:
          type: string
          description: >-
            Motivo: dte_error, not_found, o el código del error cuando la
            emisión no llegó a hacerse
        message:
          type: string
      required:
        - sale_id
        - reason
  securitySchemes:
    PAT:
      scheme: bearer
      bearerFormat: kbai_pk_<hex>
      type: http
      description: >-
        Personal Access Token con prefijo `kbai_pk_`. Generar desde el portal
        con permiso `developer.manage_tokens`.

````

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