Cufy API
Documentos

Crear una factura

Qué significa cada campo de una factura, con un ejemplo completo de petición y respuesta.

Para crear una factura envías sus datos con POST /v1/invoices. Cufy le asigna el número, calcula el CUFE, la firma, la envía a la DIAN y genera el PDF.

Tú no envías el emisor, el número, el CUFE ni la firma: los pone Cufy.

Antes de empezar

Los valores van entre comillas

Cantidades, precios e impuestos se envían como texto ("10000.00"), no como números (10000.00). Así no se pierden decimales. Admiten hasta seis decimales.

Encabezados

EncabezadoObligatorioQué es
X-API-KeySíTu API key.
Content-TypeSíSiempre application/json.
Idempotency-KeySíUn texto único por factura (de 8 a 128 letras, números, ., _, : o -). Si repites la petición con la misma clave, recibes la misma factura en lugar de una nueva.

Datos generales

CampoObligatorioQué esEjemplo
document_typeSíEl tipo de documento. Para facturas, siempre invoice."invoice"
external_referenceSíEl identificador de la factura en tu sistema, por ejemplo el número de pedido. Hasta 100 caracteres: letras, números y . _ : / -."PEDIDO-001"
profile_idSíLas reglas con las que se valida la factura. Usa co-sale-cop-v1 (venta nacional en pesos)."co-sale-cop-v1"
numbering_range_idSíEl id del rango de numeración con el que se numera."3f6c…"
issued_atSíFecha y hora de expedición, con zona horaria. En Colombia, -05:00."2026-09-16T10:00:00-05:00"
currencySíLa moneda. Hoy solo COP."COP"
notesNoUn texto libre que aparece en la factura. Hasta 2000 caracteres."Gracias por su compra"

Comprador (buyer)

Si ya guardaste al comprador como cliente, envía customer_id en lugar de buyer: Cufy copia sus datos en la factura. Nunca los dos.

CampoObligatorioQué esEjemplo
party_typeSíperson si es una persona, company si es una empresa, final_consumer si el cliente no da sus datos (consumidor final)."person"
identification.type_codeSíTipo de documento: 13 cédula de ciudadanía, 31 NIT."13"
identification.numberSíEl número, sin puntos, sin ceros al inicio y, si es NIT, sin el dígito de verificación."1000000000"
identification.check_digitSi es NITEl dígito de verificación del NIT."7"
nameSíNombre completo o razón social. Hasta 200 caracteres."Cliente de prueba"
emailNoCorreo del comprador."cliente@example.com"
addressSi es empresaDirección en Colombia (ver la tabla siguiente).
tax_registration.responsibility_codesSíResponsabilidades fiscales del comprador según el RUT. Para personas sin responsabilidades, R-99-PN.["R-99-PN"]
tax_registration.tax_scheme_codesSíTributos a los que está sujeto el comprador. 01 es IVA; ZZ ("no aplica") solo para el consumidor final.["01"]

Dirección (buyer.address)

CampoQué esEjemplo
country_codePaís. Siempre CO."CO"
department_codeCódigo DANE del departamento (2 dígitos)."11"
municipality_codeCódigo DANE del municipio (5 dígitos)."11001"
city_nameNombre de la ciudad."Bogotá"
lineDirección."Calle 1 # 2-3"
postal_codeCódigo postal (opcional)."110111"

Productos o servicios (lines)

Cada elemento de lines es un renglón de la factura.

CampoObligatorioQué esEjemplo
idSíNúmero del renglón, único dentro de la factura."1"
descriptionSíQué se vende. Hasta 300 caracteres."Servicio de prueba"
product_identification.scheme_codeSíTipo de código del producto. Usa 999 (código propio)."999"
product_identification.valueSíTu código del producto."SERV-001"
quantitySíCantidad. Mayor que cero."1"
unit_codeSíUnidad de medida. Hoy 94 (unidad)."94"
unit_priceSíPrecio de una unidad, sin impuestos."10000.00"
price_base_quantitySíA cuántas unidades corresponde unit_price. Normalmente 1."1"
net_amountSíTotal del renglón sin impuestos: cantidad × precio, menos descuentos."10000.00"
taxesSíLos impuestos del renglón (ver la tabla siguiente). Puede ir vacío.
adjustmentsSíDescuentos o cargos del renglón. Envía [] si no hay.[]
withholdingsSíRetenciones del renglón. Envía [] si no hay.[]

Impuestos (taxes y tax_totals)

CampoQué esEjemplo
codeEl impuesto: 01 es IVA, 04 es impoconsumo (INC)."01"
rateLa tarifa en porcentaje. IVA: 0, 5 o 19. Impoconsumo: 8."19"
taxable_amountLa base sobre la que se calcula el impuesto."10000.00"
amountEl valor del impuesto: base × tarifa ÷ 100. En cada renglón se acepta hasta un peso de diferencia por redondeo (precio con impuesto incluido)."1900.00"

En tax_totals va la suma de los impuestos de todos los renglones, agrupada por impuesto y tarifa.

Cada renglón lleva exactamente un impuesto: IVA o impoconsumo, nunca los dos. Una misma factura sí puede mezclar renglones con IVA y renglones con impoconsumo, como la cuenta de un restaurante: ver el ejemplo.

Totales (totals)

Cufy revisa que los totales cuadren con los renglones. Si no cuadran, la factura se rechaza antes de enviarla a la DIAN.

CampoQué esEn el ejemplo
line_extension_amountSuma de net_amount de todos los renglones."10000.00"
tax_exclusive_amountBase gravable total (suma de las bases de impuestos)."10000.00"
tax_inclusive_amountTotal con impuestos."11900.00"
allowance_total_amountDescuentos generales de la factura."0.00"
charge_total_amountCargos generales de la factura."0.00"
prepaid_amountAnticipos ya recibidos."0.00"
payable_rounding_amountAjuste por redondeo."0.00"
payable_amountLo que debe pagar el comprador."11900.00"

Pago (payment)

CampoObligatorioQué esEjemplo
termsSícash de contado o credit a crédito."cash"
means_codeUno de los dosMedio de pago, con el código de la DIAN (tabla de abajo)."48"
meansUno de los dosVarios medios, cuando la cuenta se paga con más de uno.Ver abajo
due_dateSi es a créditoFecha de vencimiento (AAAA-MM-DD)."2026-10-16"

Los medios de pago que admite la API son los de la tabla de la DIAN que usa un negocio en Colombia. El PDF muestra el nombre; el XML lleva el código.

Pagar con varios medios

Cuando una cuenta se paga parte en efectivo y parte con tarjeta, se declara means en lugar de means_code. Nunca los dos:

payment
{
  "terms": "cash",
  "means": [
    { "code": "10", "amount": "30000.00", "reference": "CAJA-1" },
    { "code": "48", "amount": "27480.00" }
  ]
}

amount y reference son opcionales. Si declaras amount en un medio, lo declaras en todos (si falta en alguno, la respuesta trae PAYMENT_SPLIT_INCOMPLETE), y la suma tiene que ser el payable_amount (si no cuadra, trae PAYMENT_SPLIT_MISMATCH con las dos cifras).

La DIAN no lleva el reparto

El XML de la DIAN dice con qué medios se pagó, no cuánto con cada uno: su anexo no tiene dónde ponerlo. El amount se queda en tu documento, para tu registro, y no aparece ni en el XML firmado ni en el PDF, que sí enumera todos los medios.

CódigoMedio de pagoCódigoMedio de pago
10Efectivo45Transferencia crédito bancario
48Tarjeta crédito46Transferencia débito interbancario
49Tarjeta débito47Transferencia débito bancaria
42Consignación bancaria2Crédito ACH
30Transferencia crédito3Débito ACH
31Transferencia débito13Crédito ahorro
20Cheque14Débito ahorro
23Cheque bancario de gerencia34Pago y depósito preacordado (PPD)
25Cheque certificado50Postgiro
26Cheque local60Nota promisoria
93Giro referenciado64Nota promisoria firmada por el banco
94Giro urgente71Bonos
95Giro formato abierto72Vales
1Instrumento no definidoZZZAcuerdo mutuo

Si te falta uno de la tabla de la DIAN que no está aquí, escríbenos: es solo agregarlo al catálogo.

Otros campos opcionales

CampoQué es
adjustmentsDescuentos o cargos generales de la factura. Obligatorio: envía [] si no hay.
withholding_totalsSuma de las retenciones. Obligatorio: envía [] si no hay.
deliveryPara que Cufy envíe la factura por correo: { "mode": "cufy_email", "recipients": ["cliente@example.com"] } (hasta 5 correos).
referencesDocumentos relacionados, como una orden de compra: { "type": "order", "number": "OC-55" }.
prepaid_paymentsDetalle de los anticipos recibidos.

Los códigos válidos de cada campo se pueden consultar con GET /v1/catalogs/{catalog}, por ejemplo identification_types o unit_codes.

Ejemplo completo

Petición

curl -s -X POST https://api-sandbox.cufy.co/v1/invoices \
  -H "X-API-Key: $CUFY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-001-v1" \
  -d @factura.json

Contenido de factura.json: una venta de un servicio de $10.000 más IVA del 19 %, pagada de contado y enviada por correo al comprador.

POST /v1/invoices
{
  "document_type": "invoice",
  "external_reference": "PEDIDO-001",
  "profile_id": "co-sale-cop-v1",
  "numbering_range_id": "RANGO_ID",
  "issued_at": "2026-09-16T10:00:00-05:00",
  "currency": "COP",
  "buyer": {
    "party_type": "person",
    "identification": { "type_code": "13", "number": "1000000000" },
    "name": "Cliente de prueba",
    "email": "cliente@example.com",
    "tax_registration": {
      "responsibility_codes": ["R-99-PN"],
      "tax_scheme_codes": ["01"]
    }
  },
  "lines": [
    {
      "id": "1",
      "description": "Servicio de prueba",
      "product_identification": { "scheme_code": "999", "value": "SERV-001" },
      "quantity": "1",
      "unit_code": "94",
      "unit_price": "10000.00",
      "price_base_quantity": "1",
      "net_amount": "10000.00",
      "adjustments": [],
      "taxes": [
        { "code": "01", "rate": "19", "taxable_amount": "10000.00", "amount": "1900.00" }
      ],
      "withholdings": []
    }
  ],
  "adjustments": [],
  "tax_totals": [
    { "code": "01", "rate": "19", "taxable_amount": "10000.00", "amount": "1900.00" }
  ],
  "withholding_totals": [],
  "totals": {
    "line_extension_amount": "10000.00",
    "tax_exclusive_amount": "10000.00",
    "tax_inclusive_amount": "11900.00",
    "allowance_total_amount": "0.00",
    "charge_total_amount": "0.00",
    "prepaid_amount": "0.00",
    "payable_rounding_amount": "0.00",
    "payable_amount": "11900.00"
  },
  "payment": { "terms": "cash", "means_code": "10" },
  "delivery": { "mode": "cufy_email", "recipients": ["cliente@example.com"] }
}

Respuesta: 202 Accepted

{
  "operation_id": "8a1d2c3e-0000-4000-8000-000000000030",
  "document_id": "8a1d2c3e-0000-4000-8000-000000000020",
  "revision": 1,
  "version": 1,
  "environment": "qualification",
  "external_reference": "PEDIDO-001",
  "processing_status": "queued",
  "dian_status": "not_sent",
  "billing_status": "not_billable",
  "created_at": "2026-09-16T15:00:00Z",
  "status_url": "/v1/documents/8a1d2c3e-0000-4000-8000-000000000020"
}
CampoQué significa
document_idEl ID de tu factura en Cufy. Guárdalo para consultarla.
environmentqualification = habilitación (pruebas) de la DIAN.
processing_statusqueued: la recibimos y está en fila para firmarse y enviarse.
dian_statusnot_sent: todavía no se ha enviado a la DIAN.
status_urlLa dirección para consultar el resultado.

202 significa que recibimos la factura, no que la DIAN la aprobó. Para saber el resultado, consulta status_url o espera un webhook. Cómo leer el resultado y obtener el CUFE: Empieza aquí, paso 4.

Si algo sale mal

RespuestaQué pasóQué hacer
400La petición está mal formada: el JSON no es válido o falta el encabezado Idempotency-Key.Revisa code y message en la respuesta.
401La API key falta o no es válida.Revisa el encabezado X-API-Key.
409Ya habías usado esa Idempotency-Key o esa external_reference con otros datos.Si es una factura nueva, usa una clave y una referencia nuevas. Si es la misma factura, envía exactamente los mismos datos.
422Los datos no cumplen una regla: falta un campo, un código no existe o los totales no cuadran.Revisa errors en la respuesta: dice qué campo falla y por qué.
429, 503 o se cortó la conexiónDemasiadas peticiones, el servicio está ocupado, o no sabes si la factura llegó.Espera y repite exactamente la misma petición con la misma Idempotency-Key. Así no se duplica.

Para revisar una factura sin crearla, envía el mismo cuerpo a POST /v1/invoice-validations. Más detalles en Errores.

La referencia técnica completa de la operación está en POST /v1/invoices.

En esta página