Ir al contenido

Documento soporte y equivalente POS

Estos dos documentos reciben el mismo cuerpo que la factura de venta, así que integrarlos cuesta poco. Lo que cuesta es tener claro quién expide cada uno y para qué sirve: son las dos preguntas que se responden mal todo el tiempo, y las que hacen que un documento salga con las partes cambiadas o que un cliente se quede sin poder deducir un costo.

Cuando tu empresa le compra a alguien que no está obligado a facturar, no hay factura que respalde ese costo. El documento soporte es la prueba que la DIAN acepta en su lugar, y la expide el comprador — o sea, tu propio emisor. Está en la Resolución 000167 de 2021.

Sin él, ese costo no se puede deducir. Esa es toda la razón por la que existe: no es un comprobante para el proveedor, es el soporte contable de quien compró.

En el API es el tipo DIAN 05. Su código único es el CUDS, no un CUFE, y consume un rango de numeración propio, del tipo SUPPORT_DOCUMENT.

El documento equivalente POS es el tiquete de caja

Sección titulada «El documento equivalente POS es el tiquete de caja»

Es el tiquete de máquina registradora de toda la vida, ahora electrónico. Lo regula la Resolución 000165 de 2023 y es obligatorio desde el 1 de noviembre de 2024.

No es una factura, y las diferencias son prácticas, no de forma:

  • Tiene su propia numeración. Necesita un rango del tipo EQUIVALENT_POS; el de facturas no sirve y el API lo rechaza.
  • Su representación gráfica cambia solo el rótulo. GET /v1/documents/:id/pdf devuelve el mismo PDF A4 de la factura, titulado «Documento equivalente POS»: hoy no hay plantilla de tiquete.
  • Por encima de 5 UVT no soporta costos, deducciones ni impuestos descontables. El comprador que necesite deducir tiene que pedir factura electrónica de venta.

En el API es el tipo DIAN 20 y su código único es un CUDE.

Los dos endpoints reciben la misma estructura de la factura de venta, con las mismas validaciones. Estas tablas valen para los dos.

Campo Tipo Obligatorio Descripción
code string Sí Código del producto o servicio.
name string Sí Descripción de la línea.
quantity number Sí Mayor que cero. Admite fraccionarios: peso, horas.
unit_measure string No UN/ECE rec 20. Si se omite, el servidor pone 94 (Unidad).
price number Sí Precio unitario antes de impuestos. Se admite 0.
discount_rate number No Porcentaje, no monto: 10 es 10 %. Por defecto 0.
taxes array No Sin taxes, la línea va sin impuesto.
Campo Tipo Obligatorio Descripción
code string Sí Tributo: 01 IVA, 03 ICA, 04 INC, entre otros.
rate number Sí Porcentaje: 19, no 0.19. Se admite 0.
Campo Tipo Obligatorio Descripción
form_code string Sí, si mandas payment 1 contado, 2 crédito.
means_code string Sí, si mandas payment Medio de pago: 10 efectivo, 45 transferencia crédito bancario, 48 tarjeta crédito, 49 tarjeta débito…
due_date string No Vencimiento, YYYY-MM-DD.

Si omites payment entero, el servidor guarda { "formCode": "1", "meansCode": "10" }: contado, efectivo.

No hay ningún campo de totales en el cuerpo, y no es un olvido: mandar total, subtotal o iva devuelve 400, porque el API rechaza un campo de más en vez de ignorarlo. La cuenta, por línea, es siempre esta:

base = quantity × price
descuento = base × discount_rate / 100
línea = base − descuento
impuesto = línea × rate / 100
total línea = línea + Σ impuestos

Dos decimales, redondeo al alza en el medio. Con 2 × 10.000, 10 % de descuento e IVA 19 %: línea 18.000,00, IVA 3.420,00, total 21.420,00.


POST/v1/support-documents

Campo Tipo Obligatorio Descripción
reference_code string Sí Tu identificador del documento. Máximo 100 caracteres. Es la clave de idempotencia.
numbering_range_id string (UUID) Sí Rango de numeración del tipo SUPPORT_DOCUMENT. Con uno de otro tipo, 422.
customer objeto Sí El vendedor: el sujeto no obligado a facturar. Ver abajo.
items array Sí Al menos una línea.
payment objeto No Por defecto, contado en efectivo.
notes string No Observación libre, una sola cadena. Va al XML y a la representación gráfica.
send_email boolean No Si el API manda el correo. Por defecto true.

Los campos son los mismos de un adquirente en una factura; lo que cambia es el papel. Aquí describen a quien te vendió.

Campo Tipo Obligatorio Descripción
identification_type string Sí Documento del vendedor: 13 cédula de ciudadanía, 31 NIT, 22 cédula de extranjería, 41 pasaporte, 47 PEP, 48 PPT…
identification string Sí Número, sin dígito de verificación.
dv string No Dígito de verificación. Exactamente un carácter.
person_type string Sí 1 jurídica, 2 natural. Casi todo documento soporte va con 2.
name string Sí Nombre completo o razón social del vendedor.
tax_regime string No 48 responsable de IVA, 49 no responsable.
responsibilities string[] No Responsabilidades fiscales: O-13, O-15, O-23, O-47, R-99-PN.
email string No Destino del correo de entrega. No se valida el formato: un correo mal escrito solo se descubre después, en email_error.
phone string No
address string No
municipality_code string No Código DANE, exactamente 5 caracteres.

Un mantenimiento de 800.000 comprado a un técnico independiente. Sin impuestos en la línea y sin correo: el vendedor no espera nada por email.

Ventana de terminal
curl -X POST https://sandbox.facturacion.misofk.com/v1/support-documents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reference_code": "DS-2026-000117",
"numbering_range_id": "3a7c1e88-5d02-4f6b-9c14-77b0aa39e552",
"customer": {
"identification_type": "13",
"identification": "1017234891",
"person_type": "2",
"name": "Luis Fernando Ramírez Ochoa",
"tax_regime": "49",
"email": "lf.ramirez.taller@gmail.com",
"phone": "3145550132",
"address": "Calle 30 Sur 45-12",
"municipality_code": "05360"
},
"items": [
{
"code": "SERV-MTTO",
"name": "Mantenimiento preventivo de equipos de refrigeración",
"quantity": 1,
"unit_measure": "94",
"price": 800000
}
],
"payment": { "form_code": "1", "means_code": "45" },
"notes": "Adquisición a no obligado a facturar. Res. DIAN 000167 de 2021.",
"send_email": false
}'

201, con el documento recién encolado:

{
"id": "7c2e91b4-33a8-4d6f-9012-5ae7b0c4d183",
"type": "05",
"reference_code": "DS-2026-000117",
"full_number": null,
"prefix": "DS",
"number": null,
"status": "QUEUED",
"cufe": null,
"qr_url": null,
"qr_content": null,
"issue_date": null,
"issue_time": null,
"due_date": null,
"operation_type": "10",
"currency": "COP",
"customer": {
"identificationType": "13",
"identification": "1017234891",
"personType": "2",
"name": "Luis Fernando Ramírez Ochoa",
"taxRegime": "49",
"email": "lf.ramirez.taller@gmail.com",
"phone": "3145550132",
"address": "Calle 30 Sur 45-12",
"municipalityCode": "05360"
},
"items": [
{
"code": "SERV-MTTO",
"name": "Mantenimiento preventivo de equipos de refrigeración",
"quantity": "1",
"unitMeasure": "94",
"price": "800000.00",
"discountRate": "0.00",
"discountAmount": "0.00",
"lineExtensionAmount": "800000.00",
"taxes": [],
"totalAmount": "800000.00"
}
],
"totals": {
"lineExtensionAmount": "800000.00",
"taxExclusiveAmount": "800000.00",
"taxInclusiveAmount": "800000.00",
"allowanceTotalAmount": "0.00",
"payableAmount": "800000.00",
"taxTotals": {}
},
"payment": { "formCode": "1", "meansCode": "45" },
"notes": "Adquisición a no obligado a facturar. Res. DIAN 000167 de 2021.",
"dian_status_code": null,
"dian_errors": null,
"related_document_id": null,
"environment": "PRODUCCION",
"email_sent_at": null,
"email_to": null,
"email_error": null,
"created_at": "2026-09-04T14:22:31.004Z"
}

Sin taxes en la línea, los cuatro totales valen lo mismo y taxTotals llega vacío: un tributo que no se cobró no aparece en el mapa.

Fíjate en el cambio de estilo. Mandas identification_type y te vuelve identificationType: la petición va en snake_case y, dentro de customer, items y payment, la respuesta va en camelCase. Todos los montos vuelven como cadenas con dos decimales, y number también.


POST/v1/pos-documents

Es el cuerpo de la factura, sin ninguna diferencia.

Campo Tipo Obligatorio Descripción
reference_code string Sí Tu identificador del tiquete. Máximo 100 caracteres. Es la clave de idempotencia.
numbering_range_id string (UUID) Sí Rango de numeración del tipo EQUIVALENT_POS. Con el de facturas, 422.
customer objeto Sí El comprador, con su significado de siempre. Mismos campos que el vendedor del documento soporte.
items array Sí Al menos una línea.
payment objeto No Por defecto, contado en efectivo.
notes string No Observación libre.
send_email boolean No Por defecto true. En caja lo normal es false.

customer es obligatorio aunque el cliente no se identifique. Para eso está el consumidor final: identification con el valor 222222222222.

Tres almuerzos y tres gaseosas, pagados con tarjeta débito, sin correo.

Ventana de terminal
curl -X POST https://sandbox.facturacion.misofk.com/v1/pos-documents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reference_code": "POS-CAJA2-2026-004417",
"numbering_range_id": "c94e0b71-2a6f-4d33-8e51-1b7d9f0a3c26",
"customer": {
"identification_type": "13",
"identification": "222222222222",
"person_type": "2",
"name": "Consumidor final"
},
"items": [
{
"code": "ALM-EJEC",
"name": "Almuerzo ejecutivo",
"quantity": 3,
"unit_measure": "94",
"price": 8500,
"taxes": [{ "code": "01", "rate": 19 }]
},
{
"code": "BEB-400",
"name": "Gaseosa 400 ml",
"quantity": 3,
"unit_measure": "94",
"price": 3500,
"taxes": [{ "code": "01", "rate": 19 }]
}
],
"payment": { "form_code": "1", "means_code": "49" },
"send_email": false
}'

La cuenta que hace el servidor:

almuerzos 3 × 8.500 = 25.500,00 IVA 19% 4.845,00
gaseosas 3 × 3.500 = 10.500,00 IVA 19% 1.995,00
────────────────────────────────────────────────────────
base 36.000,00
IVA 6.840,00
total a pagar 42.840,00

Misma forma que la del documento soporte. Aquí solo lo que cambia:

{
"id": "e5d81a06-4b73-42c9-8f10-9c3ab27de5f4",
"type": "20",
"reference_code": "POS-CAJA2-2026-004417",
"full_number": null,
"prefix": "POS2",
"number": null,
"status": "QUEUED",
"cufe": null,
"operation_type": "10",
"currency": "COP",
"items": [
{
"code": "ALM-EJEC",
"name": "Almuerzo ejecutivo",
"quantity": "3",
"unitMeasure": "94",
"price": "8500.00",
"discountRate": "0.00",
"discountAmount": "0.00",
"lineExtensionAmount": "25500.00",
"taxes": [
{ "code": "01", "rate": "19.00", "taxableAmount": "25500.00", "taxAmount": "4845.00" }
],
"totalAmount": "30345.00"
},
{
"code": "BEB-400",
"name": "Gaseosa 400 ml",
"quantity": "3",
"unitMeasure": "94",
"price": "3500.00",
"discountRate": "0.00",
"discountAmount": "0.00",
"lineExtensionAmount": "10500.00",
"taxes": [
{ "code": "01", "rate": "19.00", "taxableAmount": "10500.00", "taxAmount": "1995.00" }
],
"totalAmount": "12495.00"
}
],
"totals": {
"lineExtensionAmount": "36000.00",
"taxExclusiveAmount": "36000.00",
"taxInclusiveAmount": "42840.00",
"allowanceTotalAmount": "0.00",
"payableAmount": "42840.00",
"taxTotals": { "01": { "taxableAmount": "36000.00", "taxAmount": "6840.00" } }
},
"payment": { "formCode": "1", "meansCode": "49" }
}

Aunque el tipo sea 20, el campo del código único se sigue llamando cufe. Lo que trae es el CUDE.


La emisión es asíncrona en los dos casos. El 201 dice que el documento se aceptó y quedó en QUEUED; el número, el código único, el XML firmado y el PDF llegan después. Por eso full_number y cufe vuelven en null, y está bien.

Para enterarte, lo correcto es un webhook: document.validated, document.rejected, document.contingency, document.failed y document.email_failed. Mientras integras, consultar GET /v1/documents/{id} sirve.

Los dos endpoints comparten la misma ruta de creación, así que fallan igual.

HTTP code Cuándo
401 UNAUTHORIZED Falta el Authorization, o el token venció
403 FORBIDDEN La credencial es válida pero no abre esta puerta
400 VALIDATION Falta un campo, sobra uno, o el formato no cumple
404 NOT_FOUND Rango de numeración no encontrado
409 IDEMPOTENCY_CONFLICT Ese reference_code ya existe con otro tipo de documento
422 VALIDATION El rango es de otro tipo, o el emisor está suspendido
402 PLAN_REQUIRED, PLAN_EXPIRED, PLAN_EXHAUSTED Sin paquete de documentos, vencido o agotado
  • Facturas — el cuerpo completo, campo por campo
  • Webhooks — enterarte sin consultar en bucle
  • Errores — qué significa cada code