Editar Factura en Borrador [BETA]
Funcionalidad en desarrollo y pruebas: este servicio está disponible en el ambiente de pruebas y todavía no debe usarse en producción. El funcionamiento, los campos y los mensajes de error pueden cambiar antes del despliegue definitivo.
Este servicio modifica una factura de venta que todavía está en borrador en nuby. Puedes cambiar la cabecera y los ítems; nuby vuelve a calcular los totales, el saldo y la contabilidad del documento. La factura sigue en estado 5 (Borrador) y sigue sin numerar: la edición no consume consecutivo.
Solo puedes editar tus borradores: los que creó tu consumidor por API. Una factura generada desde un contrato de arrendamiento, una factura ya emitida o un borrador creado por la interfaz de nuby quedan fuera del alcance de este servicio. La emisión es un paso aparte: editar no envía nada a la DIAN.
¿Para qué sirve este servicio?
Sirve para corregir un borrador antes de emitirlo. La creación y la emisión son actos separados justamente para que exista este paso: entre una y otra puedes ajustar el documento tantas veces como necesites, sin gastar numeración y sin enviar nada a la DIAN.
Casos de uso concretos
- Revisión humana antes de emitir. Creas el borrador por API, un contador lo revisa en la interfaz de nuby y tu sistema aplica el ajuste final con este servicio.
- Corregir precios, cantidades o impuestos. Si el pedido cambió después de crear el borrador, reenvías los ítems completos y nuby recalcula totales y saldo.
- Cambiar la fecha o el plazo. Ajustas
fecha,dias_vencimientoofecha_vencimientopara que coincidan con la operación real. - Cambiar el tercero. Si facturaste a la persona equivocada, envías otro
tercero_id—o el objetotercero— y nuby vuelve a escribir el documento. - Completar datos comerciales. Agregas
observaciones,vendedor_id,centro_ido lasformas_de_pagoque faltaban. - Aplicar anticipos. El campo
anticipos[]también se acepta en la edición, así que puedes aplicar un anticipo que no estaba disponible al crear el borrador.
Resumen del servicio
| Aspecto | Valor |
|---|---|
| Método y ruta | PUT|PATCH /service/v2/public/invoices/{factura_id} |
| Permiso (scope) requerido | invoices:update |
| Respuesta exitosa | 200 con factura_id, estado_id, consecutivo, valor_total y valor_saldo |
| Efecto del documento | Sigue en estado 5 (Borrador), sin numerar, con totales, saldo y contabilidad recalculados |
| Protección contra duplicados | Idempotencia por referencia_externa + consumidor + factura + contenido |
| ¿Envía a la DIAN? | No. El envío es la operación de emisión |
Lo que este servicio NO hace
- No numera ni emite. El consecutivo real y el envío a la DIAN son de la operación de emisión.
- No acepta cambios parciales.
PUTyPATCHcomparten comportamiento: el payload describe el documento completo y nuby lo vuelve a grabar. Un campo que omitas no se conserva "por defecto": se valida como faltante. - No cambia el documento de destino. El
factura_iddel path manda: unfactura_iden el cuerpo nunca decide qué factura se edita. - No toca facturas fuera de borrador. Una factura emitida responde
409 FACTURA_NO_EDITABLE. - No toca facturas de contrato. El alcance se evalúa primero, sin importar quién la creó.
- No revalida el período contable ni la facturación electrónica. Ambas cosas se validaron al crear el borrador.
- No permite editar la factura de otro consumidor. La pertenencia se evalúa antes del estado.
Dónde encaja en el ciclo
1. Crear borrador POST /invoices (guía anterior)
2. Editar borrador PUT|PATCH /invoices/{factura_id} (esta página)
3. Emitir a la DIAN POST /invoices/{factura_id}/issue
4. Consultar detalle GET /invoices/{factura_id}
5. Obtener el PDF GET /invoices/{factura_id}/pdf
Si todavía no tienes un borrador, créalo primero con Crear Factura en Borrador. El factura_id que devuelve esa operación es el que va en el path de esta.
1. El Endpoint (La dirección web)
Apunta tu sistema a la siguiente dirección. Puedes usar PUT o PATCH: las dos llegan al mismo servicio y tienen la misma semántica (documento completo).
PUT https://{{instancia}}/service/v2/public/invoices/{factura_id}
PATCH https://{{instancia}}/service/v2/public/invoices/{factura_id}
¿Qué debo colocar en {{instancia}}?
La dirección web principal que utilizas a diario para ingresar a tu plataforma, incluyendo la terminación .nuby.app o .arrendasoft.co. Por ejemplo inmobiliaria.nuby.app.
Importante: no incluyas https:// ni barras diagonales (/) al final. La dirección se compone así: https:// + tu instancia + /service/v2/public/invoices/{factura_id}.
¿Qué debes colocar en {factura_id}? El identificador interno que devolvió la creación (campo body.factura_id). Debe ser un entero mayor que 0. Si no lo es, la respuesta es 400 con Debe indicar un factura_id entero mayor que 0 para operar sobre la factura. No construyas ese valor a partir del consecutivo: en un borrador el consecutivo es un marcador sin numerar.
Encabezados requeridos
| Encabezado | Valor | Obligatorio |
|---|---|---|
Content-Type |
application/json |
Sí |
Authorization |
Bearer <token>, con el token obtenido en el servicio Login |
Sí |
Permiso requerido (scope)
El cliente OAuth con el que se genera el token debe tener habilitado el permiso invoices:update. Ojo: es distinto del permiso de creación (invoices:create). Un cliente con permiso para crear no necesariamente puede editar.
403 CONSUMIDOR_NO_AUTENTICADO→ el token es válido pero no corresponde a un cliente OAuth habilitado. Mensaje: El cliente autenticado no está autorizado para consumir este endpoint.403 RECURSO_NO_AUTORIZADO→ al cliente le faltainvoices:update. Mensaje: El consumidor no tiene habilitado el recurso solicitado.
Si aún no tienes un token, consulta la página Login del libro API nuby V2.
Precondiciones del documento
| Precondición | Qué pasa si no se cumple |
|---|---|
| La factura es de venta (no nació de un contrato de arrendamiento). | 409 FACTURA_CONTRATO_NO_SOPORTADA con warnings[]. No se evalúa el estado ni la pertenencia. |
| La factura la creó tu consumidor por API. | 403 RECURSO_AJENO. |
La factura está en borrador (estado_id = 5). |
409 FACTURA_NO_EDITABLE con el estado actual en el mensaje. |
| El payload cumple el contrato de entrada (mismos campos obligatorios que la creación). | 422 VALIDATION_ERROR con errors por campo. |
La edición no revalida la configuración del sistema. Ni ENABLE_FACT_ELECT ni el período contable de la fecha se vuelven a evaluar aquí: se validaron al crear el borrador y la fecha del documento no cambió de período por editarse.
2. La Petición (¿Qué debes enviarnos?)
El cuerpo es un objeto JSON con el mismo contrato de entrada de la creación: la referencia de idempotencia de esta edición y, al mismo nivel, la cabecera y los ítems del documento.
| Elemento | Valor |
|---|---|
| Método | PUT o PATCH (equivalen) |
Content-Type |
application/json |
Authorization |
Bearer <token> |
| Cuerpo | Objeto JSON con la cabecera y el arreglo items |
Cabecera de la factura
| Parámetro | Tipo | Requerido | Por defecto | Descripción |
|---|---|---|---|---|
referencia_externa |
texto | Sí | — | Identificador único de esta edición. Si falta: 422 con El campo "referencia_externa" es obligatorio. No reutilices la referencia de la creación: esa ya quedó consumida por otra operación. |
tercero_id |
entero | Sí, o el objeto tercero |
— | Tercero de la factura. Si envías tercero_id y tercero, gana tercero_id. |
tercero |
objeto | Sí, o tercero_id |
— | Datos del tercero para resolverlo o crearlo en la misma operación. Sin ninguno de los dos: Debe indicar tercero_id o el objeto tercero. |
fecha |
texto AAAA-MM-DD |
Sí | — | Fecha de la factura. Debes enviarla aunque no la cambies: el payload es el estado completo del documento. Formato inválido: La fecha de la factura es obligatoria y debe tener el formato AAAA-MM-DD. |
dias_vencimiento |
entero | Sí, salvo que uses fecha_vencimiento o pago_total: true |
— | Entero mayor o igual a 1. Con pago_total: true se ignora. |
fecha_vencimiento |
texto AAAA-MM-DD |
No | — | Alternativa a dias_vencimiento: nuby deriva los días. Si envías ambas y se contradicen: La fecha de vencimiento no coincide con los días de vencimiento enviados. |
resolucion_id |
entero | No | la resolución que ya tiene la factura | Si lo omites, nuby conserva la resolución del documento. Si lo envías, debe existir, estar activa y ser de venta. Cambiarla reescribe la resolución del documento: hazlo solo si sabes lo que implica. |
moneda |
texto | No | COP |
Moneda del documento. |
tasa_cambio |
numérico | No | null |
Tasa de cambio aplicable. Solo tiene sentido con moneda extranjera. |
vendedor_id |
entero | No | null |
Usuario de nuby que figura como vendedor. |
centro_id |
entero | No | null |
Centro de costo del documento. |
observaciones |
texto | No | null |
Nota libre del documento. |
pago_total |
booleano | No | false |
Si es verdadero, la factura queda de contado y dias_vencimiento se ignora. |
formas_de_pago |
arreglo de objetos | Sí, cuando pago_total es true |
[] |
Con pago_total: true debes enviar al menos una: Debe agregar al menos una forma de pago cuando pago_total es verdadero. |
anticipos[] |
arreglo | No | [] |
Anticipos a aplicar. Acepta objetos ([{"documento_id": 5}]) o una lista plana ([5]). |
items |
arreglo de objetos | Sí | — | Líneas del documento. Al menos una: Debe enviar al menos un ítem en items. nuby reemplaza el detalle con lo que envíes. |
Ítems (items[])
La edición reutiliza el mismo contrato de ítem de la creación: producto_id, cantidad y valor_unitario son obligatorios y se validan todos antes de tocar el documento; el resto de campos (descuentos, impuestos, retenciones, descripcion, descuentos_id, otros_impuestos, tercero_aplica_id, valor_unitario_ori) son opcionales. Si un ítem es inválido, la factura no se modifica.
producto_id→ entero mayor que 0. producto_id debe ser un entero mayor que 0.cantidad→ entero mayor que 0. cantidad debe ser un entero mayor que 0.valor_unitario→ numérico y distinto de cero. valor_unitario debe ser numérico y distinto de 0.
El detalle se reemplaza, no se mezcla. Envía todas las líneas que debe tener el documento. Una línea que no envíes deja de existir en la factura, y los totales se recalculan con las líneas enviadas.
El factura_id del path manda
Si tu cuerpo trae un factura_id, nuby lo descarta: la factura que se edita es la del path. Es una protección deliberada contra errores de tu integración, porque un identificador equivocado en el cuerpo cambiaría el documento equivocado.
{
"referencia_externa": "INV-2026-0001-EDIT-1",
"tercero_id": 4821,
"fecha": "2026-10-21",
"dias_vencimiento": 15,
"items": [
{ "producto_id": 340, "descripcion": "Administración octubre 2026", "cantidad": 2, "valor_unitario": 1500 }
]
}
Enviado a PUT /invoices/10231, edita la factura 10231 y la respuesta también dice factura_id: 10231.
Campos que siguen prohibidos
La edición valida los mismos guardas que la creación, en este orden:
| Grupo | Campos | Respuesta |
|---|---|---|
| Datos de contrato o movimiento | movimiento_id, contrato_id, procesado, documento_respaldo_id, inmobiliaria |
422 FACTURA_CONTRATO_NO_PERMITIDA con warnings[]. Enviarlos en null, "" o 0 no es un error. |
| Campos protegidos | estado_id, consecutivo, cufe, dian_id, estado_dian, creado_por, tipo_factura |
422 CAMPO_NO_PERMITIDO. Se evalúa la presencia de la clave: enviarla en null también se rechaza. |
{
"success": false,
"status": 422,
"message": "El payload intenta fijar el campo estado_id, que solo el sistema puede asignar en una factura de venta.",
"error_code": "CAMPO_NO_PERMITIDO",
"errors": {
"estado_id": [
"El campo estado_id solo lo puede asignar el sistema."
]
}
}
Ejemplos de petición
Cambiar plazo y observaciones (documento completo)
PUT /service/v2/public/invoices/10231
Authorization: Bearer <token>
Content-Type: application/json
{
"referencia_externa": "INV-2026-0001-EDIT-1",
"tercero_id": 4821,
"fecha": "2026-10-21",
"dias_vencimiento": 15,
"moneda": "COP",
"observaciones": "Administración octubre 2026 (ajustada)",
"pago_total": false,
"items": [
{
"producto_id": 340,
"descripcion": "Administración octubre 2026",
"cantidad": 1,
"valor_unitario": 1200000,
"por_iva": 19,
"iva": 228000
}
]
}
Reemplazar el detalle con dos líneas (PATCH)
{
"referencia_externa": "INV-2026-0001-EDIT-2",
"tercero_id": 4821,
"fecha": "2026-10-22",
"fecha_vencimiento": "2026-11-21",
"items": [
{ "producto_id": 340, "descripcion": "Administración octubre 2026", "cantidad": 1, "valor_unitario": 1000000 },
{ "producto_id": 512, "descripcion": "Servicio de aseo octubre 2026", "cantidad": 2, "valor_unitario": 150000 }
]
}
Pasar el documento a contado
{
"referencia_externa": "INV-2026-0001-EDIT-3",
"tercero_id": 4821,
"fecha": "2026-10-21",
"pago_total": true,
"formas_de_pago": [
{ "tipo_pago_id": 1, "valor": 1190000 }
],
"items": [
{ "producto_id": 340, "cantidad": 1, "valor_unitario": 1000000, "por_iva": 19, "iva": 190000 }
]
}
3. La Respuesta (¿Qué te entregaremos?)
Si la edición es válida, nuby regraba el documento, recalcula totales y saldo, vuelve a escribir la contabilidad y responde 200:
{
"success": true,
"status": 200,
"message": "La factura de venta se actualizó correctamente.",
"body": {
"factura_id": 10231,
"estado_id": 5,
"consecutivo": -1,
"prefijo": "FE",
"valor_total": 3570,
"valor_saldo": 3570
},
"idempotent_replay": false
}
Estructura principal de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
success |
booleano | true cuando la edición se aplicó. |
status |
entero | Código HTTP replicado en el cuerpo. Aquí siempre 200. |
message |
texto | La factura de venta se actualizó correctamente. |
body |
objeto | Estado del documento después de la edición (tabla siguiente). |
idempotent_replay |
booleano | false en una edición nueva; true cuando la respuesta se recuperó de una solicitud anterior. En un replay la marca también aparece dentro de body. |
Campos de body
| Campo | Tipo | Descripción |
|---|---|---|
factura_id |
entero | Identificador de la factura editada: siempre el del path. |
estado_id |
entero | Estado del documento después de editar. Aquí 5 (Borrador). |
consecutivo |
entero | Marcador de "sin numerar" (cero o negativo). La edición no consume consecutivo real. |
valor_total |
numérico | Total recalculado por nuby con los ítems que acabas de enviar. |
valor_saldo |
numérico | Saldo recalculado. Es el valor que se tendrá en cuenta al emitir. |
No des por hecho el valor de consecutivo. Compara siempre contra estado_id y contra la presencia de cufe: mientras estado_id = 5 el documento no está numerado y su consecutivo es un marcador interno que no debes mostrar al cliente final.
Qué se recalcula exactamente
- Detalle: se reemplaza con los
itemsenviados. - Totales y saldo: nuby recalcula
valor_totalyvalor_saldocon los impuestos, descuentos y retenciones del detalle. - Contabilidad: el documento contable se vuelve a escribir con el nuevo detalle.
- Resolución: se conserva la que tenía la factura cuando no envías
resolucion_id.
4. Idempotencia y reintentos
Reintento de una edición ya completada (replay)
Si repites la petición con la misma referencia_externa, la misma factura y el mismo contenido, nuby devuelve la respuesta guardada con idempotent_replay: true. No se vuelve a escribir el documento:
{
"success": true,
"status": 200,
"message": "La factura de venta se actualizó correctamente.",
"body": {
"factura_id": 10231,
"estado_id": 5,
"consecutivo": -1,
"prefijo": "FE",
"valor_total": 3570,
"valor_saldo": 3570
},
"idempotent_replay": true
}
La clave de idempotencia incluye el factura_id: la misma referencia aplicada a otra factura responde 409 IDEMPOTENCY_PAYLOAD_MISMATCH, no un replay. Y la misma referencia con otro contenido también es 409: La referencia externa ya fue usada con un payload distinto.
Reglas de reintento
| Situación | Qué hace nuby con la referencia | Qué debes hacer |
|---|---|---|
Rechazo 422 de negocio |
Libera la referencia | Corrige el payload y reintenta con la misma referencia. |
409 FACTURA_NO_EDITABLE |
No consume la referencia | No insistas: la factura ya no es editable. Consulta su estado con Ver Detalle de una Factura. |
Fallo 500 |
Deja la solicitud fallida | Reintenta con la misma referencia. |
Éxito 200 |
Deja la solicitud completada | Una edición nueva necesita una referencia nueva. |
5. Seguridad y Posibles Errores
¡Tu pase tiene fecha de caducidad!
Por medidas de seguridad, el token que te entregamos solo dura 1 hora. Una vez transcurrido ese tiempo, el pase expira y el sistema bloquea el acceso. Cuando esto ocurra, tu sistema simplemente debe volver a consumir el servicio de Login para pedir un pase nuevo y reintentar con la misma referencia_externa.
{
"statusCode": 401,
"error": {
"type": "SERVER_ERROR",
"description": "JWT Token expired."
}
}
Ese 401 lo emite el middleware de autenticación, no el servicio de facturas, así que su forma es distinta: no trae success ni error_code.
Orden de evaluación: es contrato, no detalle interno
nuby evalúa en este orden exacto. Importa porque determina qué error recibes cuando hay varios problemas a la vez:
| # | Qué se evalúa | Rechazo |
|---|---|---|
| 1 | Scope del consumidor | 403 RECURSO_NO_AUTORIZADO / 403 CONSUMIDOR_NO_AUTENTICADO |
| 2 | El factura_id del path es un entero positivo |
400 VALIDATION_ERROR |
| 3 | El cuerpo es un objeto JSON | 400 VALIDATION_ERROR |
| 4 | Alcance: ¿es una factura de contrato? | 409 FACTURA_CONTRATO_NO_SOPORTADA |
| 5 | Pertenencia: ¿la creó tu consumidor? | 403 RECURSO_AJENO |
| 6 | referencia_externa presente |
422 VALIDATION_ERROR |
| 7 | Idempotencia de la solicitud | 409 IDEMPOTENCY_IN_PROGRESS / 409 IDEMPOTENCY_PAYLOAD_MISMATCH |
| 8 | Estado: la factura sigue en borrador | 409 FACTURA_NO_EDITABLE |
| 9 | Validación del payload | 422 (VALIDATION_ERROR, CAMPO_NO_PERMITIDO, FACTURA_CONTRATO_NO_PERMITIDA, …) |
El alcance gana sobre la pertenencia y sobre el estado. Una factura de contrato responde 409 FACTURA_CONTRATO_NO_SOPORTADA aunque además sea de otro consumidor o esté fuera de borrador: nuby no evalúa nada más. Y el estado gana sobre la validación: una factura ya emitida responde 409 aunque tu payload también tenga errores.
Tabla de códigos HTTP, causas y mensajes
| HTTP | error_code |
Causa y mensaje exacto |
|---|---|---|
400 |
VALIDATION_ERROR |
El factura_id del path no es un entero positivo. Debe indicar un factura_id entero mayor que 0 para operar sobre la factura. |
400 |
VALIDATION_ERROR |
El cuerpo no es un objeto JSON. El cuerpo de la solicitud debe ser un objeto JSON. |
400 |
— | El encabezado Authorization no tiene el formato Bearer {token}. JWT Token not send. |
401 |
— | Falta el encabezado Authorization: JWT Token required. |
401 |
— | El token no existe: JWT Token not found. |
401 |
— | El token expiró: JWT Token expired. Vuelve a consumir Login. |
403 |
CONSUMIDOR_NO_AUTENTICADO |
El token no corresponde a un cliente OAuth habilitado. El cliente autenticado no está autorizado para consumir este endpoint. |
403 |
RECURSO_NO_AUTORIZADO |
Al cliente le falta invoices:update. El consumidor no tiene habilitado el recurso solicitado. |
403 |
RECURSO_AJENO |
La factura la creó la interfaz de nuby u otro consumidor. La factura indicada no pertenece al consumidor autenticado. |
404 |
FACTURA_NO_ENCONTRADA |
nuby tiene registrada la pertenencia de ese identificador pero la factura ya no existe. La factura {factura_id} no existe. |
409 |
FACTURA_CONTRATO_NO_SOPORTADA |
La factura nació de un contrato de arrendamiento. Incluye warnings[] con factura_id. Mensaje: La factura {factura_id} fue generada desde un contrato de arrendamiento. Esta API solo admite facturas de venta; las facturas de contrato se gestionan en el flujo de contratos de nuby. |
409 |
FACTURA_NO_EDITABLE |
La factura no está en borrador. Mensaje: La factura {factura_id} no está en borrador (estado {estado_id}). Solo se pueden editar facturas en estado borrador. |
409 |
IDEMPOTENCY_IN_PROGRESS |
Hay una solicitud en curso con esa referencia. La solicitud con esta referencia externa está en proceso. |
409 |
IDEMPOTENCY_PAYLOAD_MISMATCH |
La referencia ya se usó con otro contenido o sobre otra factura. La referencia externa ya fue usada con un payload distinto. |
422 |
VALIDATION_ERROR |
Falta referencia_externa (El campo "referencia_externa" es obligatorio.) o el payload no cumple el formato (El payload no es válido.). Trae errors por campo. |
422 |
FACTURA_CONTRATO_NO_PERMITIDA |
El payload trae datos de contrato con valor. Incluye warnings[]. Mensaje: El payload intenta crear o modificar una factura con datos de contrato ({campo}). Esta API solo crea facturas de venta; las facturas de contrato se generan desde el flujo de contratos. |
422 |
CAMPO_NO_PERMITIDO |
El payload intenta fijar un campo del sistema. El payload intenta fijar el campo {campo}, que solo el sistema puede asignar en una factura de venta. |
422 |
RESOLUCION_CONTRATO_NO_PERMITIDA |
El resolucion_id enviado es de contrato. La resolución {resolucion_id} se usa para facturas de contrato. Esta API solo admite resoluciones de facturación de venta. |
422 |
RESOLUCION_NO_DISPONIBLE |
La resolución enviada no existe o no está activa para facturación. No hay una resolución de facturación de venta activa disponible para crear la factura. |
422 |
DATOS_FACTURA_INVALIDOS |
El motor de facturación rechazó el documento. El message trae la causa concreta (por ejemplo, datos fiscales incompletos del tercero). |
500 |
INTERNAL_ERROR |
Fallo técnico, con mensaje genérico a propósito: el detalle queda en el log de nuby. |
500 |
IDEMPOTENCY_STORAGE_ERROR |
No se pudo registrar la solicitud para controlar duplicados. No se pudo registrar la solicitud de idempotencia. |
Forma exacta de warnings[] al rechazar una factura de contrato
Es un arreglo con un solo elemento, y su forma es fija: code, factura_id y scope.
{
"success": false,
"status": 409,
"message": "La factura 990 fue generada desde un contrato de arrendamiento. Esta API solo admite facturas de venta; las facturas de contrato se gestionan en el flujo de contratos de nuby.",
"error_code": "FACTURA_CONTRATO_NO_SOPORTADA",
"warnings": [
{ "code": "FACTURA_CONTRATO_NO_SOPORTADA", "factura_id": 990, "scope": "solo_facturas_de_venta" }
]
}
Y esta es la forma de la advertencia cuando el payload trae datos de contrato (sin factura_id, porque todavía no hay documento):
{
"success": false,
"status": 422,
"message": "El payload intenta crear o modificar una factura con datos de contrato (movimiento_id). Esta API solo crea facturas de venta; las facturas de contrato se generan desde el flujo de contratos.",
"error_code": "FACTURA_CONTRATO_NO_PERMITIDA",
"errors": {
"movimiento_id": [
"El payload no puede incluir el campo movimiento_id en una factura de venta."
]
},
"warnings": [
{ "code": "FACTURA_CONTRATO_NO_PERMITIDA", "scope": "solo_facturas_de_venta" }
]
}
Un factura_id que no existe suele responder 403 RECURSO_AJENO. La pertenencia se evalúa antes de la existencia: si nuby no tiene registrado que tu consumidor creó ese identificador, no puede distinguir "no es tuya" de "no existe" y responde 403. El 404 FACTURA_NO_ENCONTRADA aparece cuando la pertenencia sí está registrada pero la factura ya no existe.
6. Ejemplos de integración
Cada ejemplo envía el documento completo y usa una referencia_externa nueva por edición.
cURL
TOKEN="TU_TOKEN_AQUI"
INSTANCIA="mi-inmobiliaria.nuby.app"
FACTURA_ID=10231
curl -X PUT "https://${INSTANCIA}/service/v2/public/invoices/${FACTURA_ID}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"referencia_externa": "INV-2026-0001-EDIT-1",
"tercero_id": 4821,
"fecha": "2026-10-21",
"dias_vencimiento": 15,
"moneda": "COP",
"observaciones": "Administración octubre 2026 (ajustada)",
"pago_total": false,
"items": [
{
"producto_id": 340,
"descripcion": "Administración octubre 2026",
"cantidad": 1,
"valor_unitario": 1200000,
"por_iva": 19,
"iva": 228000
}
]
}'
PHP
<?php
$instancia = 'mi-inmobiliaria.nuby.app'; // Reemplaza con tu dirección web completa
$token = 'TU_TOKEN_AQUI'; // Token obtenido del servicio Login
$facturaId = 10231; // factura_id devuelto al crear el borrador
$url = "https://{$instancia}/service/v2/public/invoices/{$facturaId}";
$payload = [
'referencia_externa' => 'INV-2026-0001-EDIT-1',
'tercero_id' => 4821,
'fecha' => '2026-10-21',
'dias_vencimiento' => 15,
'moneda' => 'COP',
'observaciones' => 'Administración octubre 2026 (ajustada)',
'pago_total' => false,
'items' => [
[
'producto_id' => 340,
'descripcion' => 'Administración octubre 2026',
'cantidad' => 1,
'valor_unitario' => 1200000,
'por_iva' => 19,
'iva' => 228000,
],
],
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
"Authorization: Bearer {$token}",
]);
$respuesta = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
echo 'Error de conexión: ' . curl_error($ch); // reintenta con la MISMA referencia
} else {
$info = json_decode($respuesta, true);
if ($httpCode === 200) {
echo "Borrador actualizado: factura_id={$info['body']['factura_id']}"
. " total={$info['body']['valor_total']}"
. " saldo={$info['body']['valor_saldo']}\n";
if (!empty($info['idempotent_replay'])) {
echo "Nota: fue un reintento idempotente, no se regrabó el documento.\n";
}
} else {
echo "Rechazo HTTP {$httpCode}: {$info['message']}\n";
echo "error_code: " . ($info['error_code'] ?? 'N/A') . "\n";
if (!empty($info['warnings'])) {
print_r($info['warnings']);
}
}
}
curl_close($ch);
?>
Python
import requests
# 1. Configura tus datos de acceso
instancia = 'mi-inmobiliaria.nuby.app' # Reemplaza con tu dirección web completa
token = 'TU_TOKEN_AQUI' # Token obtenido del servicio Login
factura_id = 10231 # factura_id devuelto al crear el borrador
# 2. Prepara la dirección y el cuerpo
url = f"https://{instancia}/service/v2/public/invoices/{factura_id}"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
}
payload = {
"referencia_externa": "INV-2026-0001-EDIT-1",
"tercero_id": 4821,
"fecha": "2026-10-21",
"dias_vencimiento": 15,
"moneda": "COP",
"observaciones": "Administración octubre 2026 (ajustada)",
"pago_total": False,
"items": [
{
"producto_id": 340,
"descripcion": "Administración octubre 2026",
"cantidad": 1,
"valor_unitario": 1200000,
"por_iva": 19,
"iva": 228000
}
]
}
# 3. Envía la petición y procesa la respuesta
try:
response = requests.put(url, json=payload, headers=headers, timeout=30)
print(f"Código de estado HTTP: {response.status_code}")
if response.status_code == 200:
info = response.json()
body = info.get("body", {})
print(f"Borrador actualizado: factura_id={body.get('factura_id')}")
print(f"Total: {body.get('valor_total')} | Saldo: {body.get('valor_saldo')}")
if info.get("idempotent_replay"):
print("Nota: reintento idempotente, no se regrabó el documento.")
else:
info = response.json()
print(f"Mensaje: {info.get('message')}")
print(f"error_code: {info.get('error_code')}")
for aviso in info.get("warnings") or []:
print(f" advertencia: {aviso}")
except Exception as e:
# Ante un timeout, reintenta con la MISMA referencia_externa.
print(f"Ocurrió un error de conexión: {e}")
JavaScript (Fetch API)
// 1. Configura tus datos de acceso
const instancia = 'mi-inmobiliaria.nuby.app'; // Reemplaza con tu dirección web completa
const token = 'TU_TOKEN_AQUI'; // Token obtenido del servicio Login
const facturaId = 10231; // factura_id devuelto al crear el borrador
const url = `https://${instancia}/service/v2/public/invoices/${facturaId}`;
const payload = {
referencia_externa: 'INV-2026-0001-EDIT-1',
tercero_id: 4821,
fecha: '2026-10-21',
dias_vencimiento: 15,
moneda: 'COP',
observaciones: 'Administración octubre 2026 (ajustada)',
pago_total: false,
items: [
{
producto_id: 340,
descripcion: 'Administración octubre 2026',
cantidad: 1,
valor_unitario: 1200000,
por_iva: 19,
iva: 228000
}
]
};
// 2. Función para editar el borrador
async function editarBorrador() {
try {
const response = await fetch(url, {
method: 'PUT', // PATCH tiene la misma semántica
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(payload)
});
console.log(`Código de estado HTTP: ${response.status}`);
const info = await response.json();
if (response.status === 200) {
const body = info.body || {};
console.log(`Borrador actualizado: factura_id=${body.factura_id}`);
console.log(`Total: ${body.valor_total} | Saldo: ${body.valor_saldo}`);
if (info.idempotent_replay) {
console.log('Nota: reintento idempotente, no se regrabó el documento.');
}
} else {
console.log(`Mensaje: ${info.message}`);
console.log(`error_code: ${info.error_code}`);
(info.warnings || []).forEach((aviso) => console.log(' advertencia:', aviso));
}
} catch (error) {
// Ante un timeout, reintenta con la MISMA referencia_externa.
console.error(`Ocurrió un error de conexión: ${error}`);
}
}
// 3. Ejecutamos la función
editarBorrador();
Power Query M (Excel / Power BI)
Power Query no puede elegir el verbo de la petición. Web.Contents con Content envía siempre POST, y este servicio solo acepta PUT y PATCH. Por eso la edición desde Excel o Power BI se hace a través de una función de tu backend (o de una función de Azure) que reciba el JSON y lo reenvíe a nuby con PUT. El ejemplo muestra el cuerpo exacto que debes enviar a esa función y cómo leer el resultado.
let
// 1. Configura la dirección de TU función, que reenvía con PUT a nuby
urlFuncion = "https://tu-servicio.example.com/facturas/10231/editar",
token = "TU_TOKEN_AQUI", // Token obtenido del servicio Login
// 2. Construye el cuerpo JSON del contrato de edición
cuerpo = Json.FromValue([
referencia_externa = "INV-2026-0001-EDIT-1",
tercero_id = 4821,
fecha = "2026-10-21",
dias_vencimiento = 15,
moneda = "COP",
observaciones = "Administración octubre 2026 (ajustada)",
pago_total = false,
items = {
[
producto_id = 340,
descripcion = "Administración octubre 2026",
cantidad = 1,
valor_unitario = 1200000,
por_iva = 19,
iva = 228000
]
}
]),
// 3. Envía el cuerpo a tu función (ella añade el verbo PUT y el token)
response = Web.Contents(urlFuncion, [
Headers = [
#"Content-Type" = "application/json",
#"Authorization" = "Bearer " & token
],
Content = cuerpo
]),
// 4. Decodifica la respuesta JSON que devuelve nuby
jsonResponse = Json.Document(response),
body = jsonResponse[body],
// 5. Convierte el resultado en tabla
tabla = Table.FromRecords({body}),
expandido = Table.ExpandRecordColumn(tabla, "Column1",
{"factura_id", "estado_id", "consecutivo", "prefijo", "valor_total", "valor_saldo"},
{"FacturaID", "EstadoID", "ConsecutivoSinNumerar", "ValorTotal", "ValorSaldo"})
in
expandido
Siguiente paso: cuando el borrador esté correcto, emítelo con Emitir Factura a la DIAN. Recuerda que una vez emitida la factura ya no se puede editar.