Ir al contenido

Notas crédito y débito

Una factura validada no se edita ni se borra

Sección titulada «Una factura validada no se edita ni se borra»

No existe PUT /v1/invoices/:id, y tampoco un DELETE. No es un hueco del API: un documento que la DIAN validó ya está en su poder y no se deshace desde aquí. Lo que se hizo mal se corrige emitiendo otro documento que apunta al primero.

Lo que necesitas Emites Ruta type operation_type
Anular la factura completa, devolver mercancía, rebajar, descontar Nota crédito /v1/credit-notes 91 20
Cobrar de más: intereses, gastos por cobrar, un valor que subió Nota débito /v1/debit-notes 92 30

La regla mental es corta: la crédito baja, la débito sube.

Una nota es un documento electrónico completo

Sección titulada «Una nota es un documento electrónico completo»

No es una marca sobre la factura. Pasa por exactamente lo mismo que ella:

  • Consume su propio rango de numeración (CREDIT_NOTE o DEBIT_NOTE), y con él un consecutivo que ya no vuelve.
  • Ese rango no lleva resolución ni vigencia: el consecutivo de una nota es del emisor, no de un acto de la DIAN. Ver rangos de numeración.
  • Se numera, se arma en UBL, se firma y se transmite en segundo plano.
  • Gasta cupo del plan, igual que una factura.
  • Tiene su propio código único. Es un CUDE, no un CUFE — aunque el campo de la respuesta se siga llamando cufe.
  • Necesita su propio reference_code. Reutilizar el de la factura da un 409 (ver Errores).

POST/v1/credit-notes

POST/v1/debit-notes

Las dos reciben el mismo cuerpo y devuelven la misma forma. Lo único que cambia entre ellas es la lista de valores válidos de discrepancy_code.

Es el cuerpo de la factura más dos campos obligatorios. Nada se quita.

Campo Tipo Obligatorio Descripción
related_document_id string (UUID) Sí El documento que se corrige. Es el id de esta API, no su full_number ni su CUFE.
discrepancy_code string Sí Concepto de corrección. Las listas de crédito y débito son distintas: ver abajo.
discrepancy_description string No Se acepta, pero no se usa. La descripción sale siempre del catálogo.
Campo Tipo Obligatorio Descripción
reference_code string Sí Tu identificador, máximo 100 caracteres. Es la clave de idempotencia.
numbering_range_id string (UUID) Sí Rango de la nota, no el de la factura.
customer objeto Sí El adquirente. Mismos campos que en la factura.
items array Sí Al menos una línea. Es lo que se está acreditando o cobrando.
payment objeto No Por defecto { "form_code": "1", "means_code": "10" } — contado, efectivo.
notes string No Observación libre. Va al UBL y a la representación gráfica.
send_email boolean No Por defecto true: la nota le sale por correo al adquirente.

El detalle campo por campo de customer, items y payment está en Facturas; aquí se comportan idéntico.

En qué se diferencia del cuerpo de la factura

Sección titulada «En qué se diferencia del cuerpo de la factura»

El rango es otro. Cada tipo de documento consume su propia serie y la DIAN no deja mezclarlas. Mandar el numbering_range_id de facturas a /v1/credit-notes responde 422:

El rango 6f0a9c2e-4b1d-4a77-9f3e-2c8d5b71e004 es de tipo INVOICE, no aplica para este documento

operation_type no se envía. Lo pone el servidor: 20 en la crédito, 30 en la débito. Aparece en la respuesta, no en la petición.

Los montos siguen siendo del servidor. No hay campo de totales, ni aquí ni en la factura, y un campo de más es un 400, no un aviso. La nota vale lo que sumen sus propias líneas, con las mismas fórmulas.

related_document_id es el UUID interno que devolvió la creación de la factura en el campo id, el mismo que usas en GET /v1/documents/:id. No es full_number (SETP990000841) ni el CUFE.

Tres condiciones, y las tres las revisa el API antes de crear nada:

  1. El documento existe y es de tu emisor. Si no: 422 related_document_id no existe.
  2. Está en VALIDATED. Si no: 422 Solo se pueden emitir notas sobre documentos VALIDATED.
  3. El discrepancy_code está en la lista de esa nota.

En la respuesta de la nota, related_document_id vuelve poblado. Es el único tipo de documento en el que ese campo no es null.

El código dice por qué se corrige, y de él sale la descripción que viaja en el XML.

Código Concepto
1 Devolución parcial de los bienes y/o no aceptación parcial del servicio
2 Anulación de factura electrónica
3 Rebaja o descuento parcial o total
4 Ajuste de precio
5 Descuento comercial por pronto pago
6 Descuento comercial por volumen de ventas
Código Concepto
1 Intereses
2 Gastos por cobrar
3 Cambio del valor
4 Otros

Un código fuera de la lista responde 422 y te dice cuáles valen:

{
"status": 422,
"code": "VALIDATION",
"message": "discrepancy_code 9 no existe. Válidos: 1, 2, 3, 4, 5, 6",
"errors": []
}

La regla CBF04 de la DIAN compara la descripción con su lista oficial y rechaza cualquier texto que no esté ahí. Con el texto libre del cliente se caían todas las notas. Por eso el API arma la descripción a partir del código y copia la cadena literal de la tabla oficial, espacios incluidos. El campo se sigue aceptando, pero no llega a ninguna parte.

Ejemplo — nota crédito que anula una factura

Sección titulada «Ejemplo — nota crédito que anula una factura»

Anula por completo la factura SETP990000841, cuyo id es b1f3a7d0-9c42-4e18-8a5b-6d0e2f4c9a11. Las líneas son las mismas de la factura, así que la nota vale exactamente lo mismo.

Ventana de terminal
curl -X POST https://sandbox.facturacion.misofk.com/v1/credit-notes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reference_code": "NC-2026-000129",
"numbering_range_id": "8d5f2c60-71ab-4e09-b3c7-4f9e1d802a75",
"related_document_id": "b1f3a7d0-9c42-4e18-8a5b-6d0e2f4c9a11",
"discrepancy_code": "2",
"customer": {
"identification_type": "31",
"identification": "901234567",
"dv": "8",
"person_type": "1",
"name": "Distribuidora El Poblado S.A.S.",
"tax_regime": "48",
"responsibilities": ["O-15"],
"email": "facturacion@elpoblado.com.co",
"address": "Carrera 43A 5A-113 Oficina 502",
"municipality_code": "05001"
},
"items": [
{
"code": "SKU-001",
"name": "Camiseta algodón talla M",
"quantity": 2,
"unit_measure": "94",
"price": 45000,
"discount_rate": 10,
"taxes": [{ "code": "01", "rate": 19 }]
},
{
"code": "SKU-045",
"name": "Gorra bordada",
"quantity": 1,
"unit_measure": "94",
"price": 32000,
"taxes": [{ "code": "01", "rate": 19 }]
}
],
"payment": { "form_code": "1", "means_code": "45" },
"notes": "Anulación total de SETP990000841 por devolución completa del pedido 4471."
}'

La cuenta que hace el servidor:

línea 1 2 × 45.000 = 90.000 −10% → 81.000,00 IVA 19% 15.390,00
línea 2 1 × 32.000 → 32.000,00 IVA 19% 6.080,00
───────────────────────────────────────────────────────────────────────
subtotal 113.000,00
IVA 21.470,00
total 134.470,00

Respuesta 201, recortada:

{
"id": "7c2e91a4-0f38-4b6d-a15c-3e8b0d6f2419",
"type": "91",
"reference_code": "NC-2026-000129",
"full_number": null,
"prefix": "NC",
"number": null,
"status": "QUEUED",
"cufe": null,
"operation_type": "20",
"currency": "COP",
"related_document_id": "b1f3a7d0-9c42-4e18-8a5b-6d0e2f4c9a11",
"totals": {
"lineExtensionAmount": "113000.00",
"taxExclusiveAmount": "113000.00",
"taxInclusiveAmount": "134470.00",
"allowanceTotalAmount": "0.00",
"payableAmount": "134470.00",
"taxTotals": { "01": { "taxableAmount": "113000.00", "taxAmount": "21470.00" } }
}
}

La respuesta completa tiene la misma forma que la de una factura —customer, items recalculados, payment, dian_status_code…— con type: "91", operation_type: "20" y el related_document_id poblado.

Ejemplo — nota débito por un cobro que faltó

Sección titulada «Ejemplo — nota débito por un cobro que faltó»

El flete no entró en la factura. Se cobra aparte, contra el mismo documento.

Ventana de terminal
curl -X POST https://sandbox.facturacion.misofk.com/v1/debit-notes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reference_code": "ND-2026-000034",
"numbering_range_id": "e21b4f09-8c73-4a55-b0d6-9f14c7e35a80",
"related_document_id": "b1f3a7d0-9c42-4e18-8a5b-6d0e2f4c9a11",
"discrepancy_code": "2",
"customer": {
"identification_type": "31",
"identification": "901234567",
"dv": "8",
"person_type": "1",
"name": "Distribuidora El Poblado S.A.S.",
"tax_regime": "48",
"responsibilities": ["O-15"],
"email": "facturacion@elpoblado.com.co",
"address": "Carrera 43A 5A-113 Oficina 502",
"municipality_code": "05001"
},
"items": [
{
"code": "FLETE-ITG",
"name": "Flete a bodega Itagüí no incluido en SETP990000841",
"quantity": 1,
"unit_measure": "94",
"price": 60000,
"taxes": [{ "code": "01", "rate": 19 }]
}
],
"payment": { "form_code": "2", "means_code": "45", "due_date": "2026-10-04" },
"notes": "Cobro de transporte pactado por fuera del pedido 4471."
}'

Aquí discrepancy_code: "2" es Gastos por cobrar, no «Anulación»: es la tabla de la nota débito.

línea 1 × 60.000 → 60.000,00 IVA 19% 11.400,00
─────────────────────────────────────────────────────────
total 71.400,00

Respuesta 201, recortada:

{
"id": "0a4d7b13-56ce-42f7-9c80-b8e5f1a20d67",
"type": "92",
"reference_code": "ND-2026-000034",
"status": "QUEUED",
"operation_type": "30",
"due_date": "2026-10-04",
"related_document_id": "b1f3a7d0-9c42-4e18-8a5b-6d0e2f4c9a11",
"totals": {
"lineExtensionAmount": "60000.00",
"taxExclusiveAmount": "60000.00",
"taxInclusiveAmount": "71400.00",
"allowanceTotalAmount": "0.00",
"payableAmount": "71400.00",
"taxTotals": { "01": { "taxableAmount": "60000.00", "taxAmount": "11400.00" } }
}
}

Los propios de las notas:

HTTP code Mensaje
422 VALIDATION related_document_id no existe
422 VALIDATION Solo se pueden emitir notas sobre documentos VALIDATED
422 VALIDATION discrepancy_code {valor} no existe. Válidos: {lista}
422 VALIDATION El rango {uuid} es de tipo {tipo}, no aplica para este documento
404 NOT_FOUND Rango de numeración no encontrado
409 IDEMPOTENCY_CONFLICT reference_code {valor} ya existe con otro tipo de documento

Los demás son los de cualquier emisión —401, 403, el 400 de validación y los de plan— y están en Errores.

Y ojo con el otro lado de la idempotencia: repetir un reference_code del mismo tipo no falla — devuelve 201 con la nota que ya existía y descarta en silencio el cuerpo nuevo. Si te equivocaste de discrepancy_code, cambiarlo y reenviar con el mismo reference_code no corrige nada.

El 201 solo dice que se aceptó. El número, el CUDE, el XML y el PDF llegan después, igual que en una factura:

  • Escucha document.validated o document.rejected en tus webhooks.
  • O consulta GET /v1/documents/{id} hasta ver status: "VALIDATED".
  • Los archivos salen por GET /v1/documents/{id}/xml y GET /v1/documents/{id}/pdf.

Para listar solo notas, filtra el listado por tipo:

Ventana de terminal
curl "https://sandbox.facturacion.misofk.com/v1/documents?type=91&status=VALIDATED" \
-H "Authorization: Bearer $TOKEN"