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

# Introducción

> Emite facturas electrónicas ante la DIAN desde OnePay — cuándo timbrar, qué datos exige el receptor, notas crédito y webhooks.

<Note>
  Esta funcionalidad está en habilitación. Para activarla en tu empresa, escríbenos por el chat del [panel de administración](https://admin.onepay.la): el proceso incluye cargar tu resolución de la DIAN y completar la habilitación de tu empresa como facturador electrónico.
</Note>

## Qué hace

OnePay emite la factura electrónica **a nombre de tu empresa** ante la DIAN, con tu resolución y tu numeración, y le entrega el documento a tu cliente.

Es **el mismo endpoint** que [Facturas](/client/invoices/index) — `POST /v1/invoices` — y el mismo recurso. Lo que cambia es si mandas `items`:

|                   | Sin `items`                                       | Con `items`                          |
| ----------------- | ------------------------------------------------- | ------------------------------------ |
| Origen            | Ya existe en tu sistema (Wispro, Siigo, SAEplus…) | Nace en OnePay                       |
| Qué hace OnePay   | La cobra                                          | La **emite** ante la DIAN y la cobra |
| Documento legal   | El de tu sistema                                  | El XML que OnePay timbra             |
| Campos que mandas | `provider`, `provider_id`, `reference`, `amount`  | `customer_id`, `items`               |

En los dos casos la factura obtiene su cobro asociado, aparece en el mismo listado y se consulta por la misma URL.

<Note>
  Si ya facturas en otro sistema y solo necesitas recaudar, no cambia nada para ti: seguí mandando el mismo payload de siempre. `items` es aditivo.
</Note>

***

## Cuándo se timbra

El parámetro más importante de la integración es `stamp_policy`, porque decide **en qué momento el documento existe ante la DIAN**.

| Política               | Emite                     | Para qué                                                              |
| ---------------------- | ------------------------- | --------------------------------------------------------------------- |
| `none` *(por defecto)* | Nunca                     | Facturas por otro sistema, o todavía no estás habilitado ante la DIAN |
| `on_payment`           | Cuando el pago se aprueba | Venta de contado                                                      |
| `on_issue`             | Al crear la factura       | Venta a crédito, cobro anticipado                                     |
| `manual`               | Cuando llamas a `/stamp`  | Necesitas revisar antes de emitir                                     |

<Note>
  **`none` no es lo mismo que `manual`.** Con `manual` la intención es emitir más tarde; con `none` no se emite nunca. La diferencia importa: una factura "pendiente de emitir" que en realidad nunca se va a emitir es indistinguible de una que alguien olvidó.

  Con `none` la factura existe igual, con sus líneas y su cobro — simplemente no hay documento fiscal. Y como no lo va a haber, **no se te exigen los datos fiscales del cliente**: dirección, dígito de verificación ni responsabilidades fiscales.
</Note>

<Warning>
  **`none` es el valor por defecto.** Si no mandas `stamp_policy`, la factura NO se emite ante la DIAN.

  Es deliberado: emitir un documento con efectos tributarios tiene que ser una decisión explícita. Si se te olvida el parámetro, el resultado es una factura sin emitir —que puedes emitir después con `/stamp`— y no un documento ante la DIAN que haya que anular con una nota crédito.
</Warning>

<Warning>
  **Con `on_issue` el documento se emite antes de saber si te van a pagar.** Si el cobro falla o lo reembolsas, tienes una factura viva ante la DIAN por una venta que no ocurrió, y la única salida legal es una nota crédito.

  OnePay la emite automáticamente al reembolsar, pero si tu operación no necesita facturar por anticipado, `on_payment` te evita el problema entero.
</Warning>

***

## Datos que exige el receptor

La DIAN no acepta una factura con el receptor incompleto. Lo que se exige depende del tipo de documento:

| Campo                      | Siempre | Solo si NIT o RUT |
| -------------------------- | ------- | ----------------- |
| Correo electrónico         | ✅       |                   |
| Nombre o razón social      | ✅       |                   |
| Tipo y número de documento | ✅       |                   |
| Dirección                  | ✅       |                   |
| Dígito de verificación     |         | ✅                 |
| Responsabilidades fiscales |         | ✅                 |

Si falta algo, la API responde `422` con **todos** los campos faltantes de una vez, no con el primero:

```json theme={null}
{
  "code": "customer_not_invoiceable",
  "message": "No se puede facturar: al cliente le faltan datos que exige la DIAN (dirección, dígito de verificación).",
  "errors": {
    "customer.address": ["required"],
    "customer.document_verification": ["required"]
  }
}
```

***

## Flujo

```mermaid theme={null}
sequenceDiagram
    participant TS as Tu sistema
    participant OP as OnePay
    participant DIAN as DIAN
    participant CF as Cliente final

    TS->>OP: 1. POST /invoices (con items)
    OP-->>TS: 2. 201 con totales calculados
    Note right of OP: Responde sin esperar a la DIAN

    alt stamp_policy = on_payment
        CF->>OP: 3. Paga la factura
        OP->>OP: 4. Pago aprobado
    end

    OP->>DIAN: 5. Timbra el documento
    DIAN-->>OP: 6. Acepta o rechaza

    alt Aceptada
        OP-->>TS: 7. Webhook invoice.stamped
        OP->>CF: 8. Envia XML + PDF por correo
    else Rechazada
        OP-->>TS: 7. Webhook invoice.stamp_rejected
    end
```

**El timbrado es asíncrono.** La DIAN puede tardar segundos o minutos, así que `POST /v1/invoices` responde de inmediato con `fiscal_status: null` y el resultado te llega por webhook. No hagas polling contra el endpoint de detalle esperando el CUFE.

***

## Ejemplo completo de implementación

Un ISP que cobra internet mensual y quiere que OnePay emita sus facturas.

### Paso 1 — Carga tu resolución de la DIAN

Una sola vez, con los datos que aparecen en tu resolución del portal MUISCA.

```bash theme={null}
curl -X POST https://api.onepay.la/v1/fiscal-resolutions \
  -H "Authorization: Bearer $ONEPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "18760000001",
    "prefix": "FE",
    "document_type": "invoice",
    "from": 1,
    "to": 5000,
    "valid_from": "2026-01-15",
    "valid_until": "2027-01-15",
    "technical_key": "fc8eac4227b2b1e8..."
  }'
```

Queda en `draft`. Actívala cuando la hayamos validado:

```bash theme={null}
curl -X POST https://api.onepay.la/v1/fiscal-resolutions/{id}/activate \
  -H "Authorization: Bearer $ONEPAY_API_KEY"
```

<Note>
  Carga también una resolución con `document_type: "credit_note"` y su propio prefijo. Las notas crédito llevan numeración aparte, y sin ella no vas a poder anular ni devolver.
</Note>

### Paso 2 — Asegura que tu cliente tenga los datos fiscales

```bash theme={null}
curl -X POST https://api.onepay.la/v1/customers \
  -H "Authorization: Bearer $ONEPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Ana",
    "last_name": "Ramírez",
    "email": "ana@ejemplo.com",
    "phone": "+573001234567",
    "document_type": "CC",
    "document_number": "1020304050",
    "address": { "line": "Calle 45 # 12-30", "city_code": "17001" }
  }'
```

### Paso 3 — Previsualiza los totales

Útil para mostrar el detalle en tu propia interfaz antes de emitir. No crea nada.

```bash theme={null}
curl -X POST https://api.onepay.la/v1/invoices/preview \
  -H "Authorization: Bearer $ONEPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "description": "Plan Fibra 200 Mbps - agosto", "unit_amount": 8990000, "tax": { "rate": 19 } }
    ]
  }'
```

```json theme={null}
{
  "data": {
    "items": [
      {
        "description": "Plan Fibra 200 Mbps - agosto",
        "quantity": 1,
        "unit_amount": 8990000,
        "subtotal": 8990000,
        "tax_rate": 19,
        "tax_amount": 1708100,
        "total": 10698100
      }
    ],
    "subtotal": 8990000,
    "tax_amount": 1708100,
    "total": 10698100
  }
}
```

<Warning>
  **Todos los montos van en centavos.** `8990000` son \$89.900 COP. Es el mismo criterio de entrada y de salida, y evita que un decimal mal redondeado haga que la DIAN rechace el documento.
</Warning>

### Paso 4 — Crea la factura

```bash theme={null}
curl -X POST https://api.onepay.la/v1/invoices \
  -H "Authorization: Bearer $ONEPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f14e45f-ea6c-4b3d-9c1a-2b7d3e5f9a01" \
  -d '{
    "customer_id": "cus_01J8XK2P9WQZ",
    "name": "Plan Fibra 200 Mbps - agosto 2026",
    "stamp_policy": "on_payment",
    "due_date": "2026-08-16",
    "reference": "1020304050",
    "items": [
      { "description": "Plan Fibra 200 Mbps - agosto", "unit_amount": 8990000, "tax": { "rate": 19 } },
      { "description": "Instalación", "unit_amount": 15000000, "tax": { "rate": 19 } }
    ],
    "metadata": { "contrato": "CT-88421" }
  }'
```

```json theme={null}
{
  "id": "inv_01J8XK9V4N",
  "status": "CREATED",
  "fiscal_status": null,
  "stamp_policy": "on_payment",
  "currency": "COP",
  "customer": { "id": "cus_01J8XK2P9WQZ", "name": "Ana Ramírez", "document_number": "1020304050" },
  "items": [ "..." ],
  "totals": { "subtotal": 23990000, "tax_amount": 4558100, "total": 28548100 },
  "amount": 285481,
  "payment_id": "pay_01J8XKC5D2",
  "stampings": []
}
```

<Note>
  El `Idempotency-Key` no es opcional en la práctica: sin él, un reintento de tu lado por un timeout de red crea **dos facturas**, que es IVA declarado dos veces y una nota crédito para arreglarlo.
</Note>

### Paso 5 — Escucha los webhooks

Con `on_payment`, cuando tu cliente pague, OnePay timbra y te avisa:

```json theme={null}
{
  "event": "invoice.stamped",
  "data": {
    "invoice_id": "inv_01J8XK9V4N",
    "stamping": {
      "type": "invoice",
      "status": "accepted",
      "number": "FE1",
      "cufe": "a3f5c8e2...",
      "xml_url": "https://...",
      "pdf_url": "https://...",
      "total": 28548100
    }
  }
}
```

Si la DIAN rechaza:

```json theme={null}
{
  "event": "invoice.stamp_rejected",
  "data": {
    "invoice_id": "inv_01J8XK9V4N",
    "stamping": {
      "status": "rejected",
      "number": "FE1",
      "rejection_reason": "Regla FAJ01: el receptor no tiene responsabilidades fiscales"
    }
  }
}
```

**Guarda el `cufe` y el `xml_url` en tu sistema.** El XML es el documento legal; el PDF es solo su representación gráfica.

<Note>
  Fíjate en `totals` y en `amount`: los totales fiscales van **en centavos** y agrupados aparte, mientras que `amount` es el monto en pesos de siempre, el que ya leen tus integraciones. Están separados a propósito — mezclar las dos unidades en el mismo nivel es la forma más fácil de cobrar cien veces de más.
</Note>

### Paso 6 — Devoluciones

Si tienes que devolver dinero, emite una nota crédito. OnePay ya la emite sola cuando reembolsas un cobro, pero también puedes hacerlo directo:

```bash theme={null}
curl -X POST https://api.onepay.la/v1/invoices/inv_01J8XK9V4N/credit-notes \
  -H "Authorization: Bearer $ONEPAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 15000000, "reason": "Instalación no realizada" }'
```

Omite `amount` para anular la factura completa. Puedes emitir varias notas parciales sobre la misma factura; OnePay no deja que la suma supere el total.

***

## Errores

Todos traen un `code` estable sobre el que puedes ramificar sin leer el mensaje.

| `code`                       | Qué pasó                                                                                                                                 |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `customer_not_invoiceable`   | Al receptor le faltan datos que exige la DIAN                                                                                            |
| `invalid_invoice_line`       | Una línea no se puede resolver (indica cuál con su índice), o el total queda fuera del rango cobrable                                    |
| `credit_note_not_issuable`   | La factura no está timbrada, o la nota excede lo que queda por devolver                                                                  |
| `resolution_not_activatable` | La resolución está vencida o sin folios                                                                                                  |
| `invoice_not_stampable`      | La factura no se puede emitir: es importada de otro sistema, su `stamp_policy` no admite emisión manual, ya se timbró, o no tiene líneas |

***

## Preguntas frecuentes

<AccordionGroup>
  <Accordion title="¿Qué pasa si me quedo sin folios?">
    OnePay te avisa al 80% y al 95% del rango consumido, y 30 días antes de que venza la resolución. Carga la siguiente resolución con anticipación y actívala: puedes tener varias cargadas, pero solo una activa por prefijo y tipo de documento.

    Si te quedas sin folios, el timbrado falla con `resolution_not_activatable` y las facturas se siguen creando pero no se emiten.
  </Accordion>

  <Accordion title="¿Puedo facturar lo que ya cobro con /invoices?">
    No automáticamente. Las facturas que importas desde tu sistema de gestión no se timbran: OnePay asume que ya las emitiste tú. Si quieres migrar tu facturación a OnePay, escríbenos — hay que apagar la emisión en tu sistema anterior primero, o terminarías facturando dos veces la misma venta.
  </Accordion>

  <Accordion title="¿El precio de mis productos incluye IVA?">
    Puedes usar cualquiera de las dos formas. Con `tax.behavior: "inclusive"` el `unit_amount` ya trae el impuesto y OnePay lo desagrega; con `"exclusive"` (por defecto) el impuesto se suma encima.

    El XML de la DIAN siempre lleva base e impuesto por separado, así que la desagregación ocurre igual — la diferencia es solo cómo nos mandas el precio.
  </Accordion>

  <Accordion title="¿Puedo usar mi catálogo de productos?">
    Sí. En vez de `description` y `unit_amount`, manda `price_id` de un [Precio](/client/prices/index) y OnePay resuelve nombre, monto e impuesto. Puedes mezclar líneas del catálogo con líneas sueltas en la misma factura, y sobreescribir el impuesto de una línea puntual sin duplicar el producto.
  </Accordion>

  <Accordion title="¿Cómo pruebo sin emitir documentos reales?">
    Usa tu API key de pruebas. El entorno de pruebas tiene su propia resolución y su propia numeración, completamente separada de producción.
  </Accordion>
</AccordionGroup>
