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.
El documento soporte lo expide el comprador
Sección titulada «El documento soporte lo expide el comprador»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/pdfdevuelve 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.
El cuerpo es el de la factura
Sección titulada «El cuerpo es el de la factura»Los dos endpoints reciben la misma estructura de la factura de venta, con las mismas validaciones. Estas tablas valen para los dos.
items[]
Sección titulada «items[]»| 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. |
items[].taxes[]
Sección titulada «items[].taxes[]»| 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. |
payment
Sección titulada «payment»| 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.
Los montos los calcula el servidor
Sección titulada «Los montos los calcula el servidor»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 × pricedescuento = base × discount_rate / 100línea = base − descuentoimpuesto = línea × rate / 100total línea = línea + Σ impuestosDos 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.
Documento soporte
Sección titulada «Documento soporte»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. |
customer es el vendedor
Sección titulada «customer es el vendedor»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. |
Ejemplo
Sección titulada «Ejemplo»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.
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 }'Respuesta
Sección titulada «Respuesta»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.
Documento equivalente POS
Sección titulada «Documento equivalente POS»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.
Ejemplo
Sección titulada «Ejemplo»Tres almuerzos y tres gaseosas, pagados con tarjeta débito, sin correo.
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,00gaseosas 3 × 3.500 = 10.500,00 IVA 19% 1.995,00────────────────────────────────────────────────────────base 36.000,00IVA 6.840,00total a pagar 42.840,00Respuesta
Sección titulada «Respuesta»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.
Lo que pasa después del 201
Sección titulada «Lo que pasa después del 201»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.
Errores
Sección titulada «Errores»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 |