Notas débito y crédito [BETA]
Bienvenido al módulo de notas de tu inmobiliaria. En esta sección centralizamos las conexiones para que tus plataformas externas ajusten o anulen una factura de venta sin volver a digitar el documento en la interfaz de nuby.
¿Qué puedes lograr en este módulo?
1. Ajustar la cartera: tu aplicación puede crear una nota de crédito para reducir el saldo de una factura o una nota de débito para aumentarlo. nuby calcula el IVA, las retenciones y el descuento de cada ítem, recalcula el total y actualiza el saldo de la factura en el acto. Si una nota de crédito deja el saldo en cero, la factura queda anulada por nota crédito.
2. Consulta sincronizada: podrás listar las notas de una factura o de un rango de fechas, filtrar por tipo de nota y obtener el detalle con sus ítems, impuestos y el estado y saldo resultantes de la factura.
3. Control sin sorpresas: la nota nace en borrador y la emisión a la DIAN la realiza un usuario en nuby. La API solo opera con facturas de venta activas: las facturas de contrato se rechazan de forma explícita, y cada operación es idempotente para que un reintento no duplique el documento.
Explora las opciones a continuación para automatizar los ajustes de tu facturación y mantener tu cartera conciliada.
Listar Notas [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 devuelve una lista paginada de notas de crédito y débito registradas en nuby, con sus datos principales. Puedes filtrar por factura, por tipo de nota y por rango de fechas.
Úsalo para conciliar las notas de una factura con tu plataforma, para construir un historial de ajustes o para verificar qué notas se crearon en un período.
¿Para qué sirve este servicio?
Úsalo cuando necesites recorrer las notas registradas en nuby. Los casos de uso más frecuentes son:
- Conciliar las notas de una factura con tu plataforma, filtrando por
factura_id. - Construir un historial de ajustes separando las notas de crédito de las de débito con
tipo. - Cerrar un período contable revisando las notas creadas entre dos fechas con
desdeyhasta. - Recorrer resultados largos por páginas con
pageypage_size. - Obtener el
nota_idpara consultar el detalle solo de las notas que vas a mostrar.
1. El Endpoint (La dirección web)
Método y dirección
| Dato | Valor |
|---|---|
| Método | GET |
| Recurso | /notes/list |
| Cuerpo de la petición | No se envía cuerpo |
GET https://{{instancia}}/service/v2/public/notes/list
¿Qué debo colocar en {{instancia}}?
La dirección web principal con la que ingresas a tu plataforma, incluyendo la terminación .nuby.app o .arrendasoft.co.
Importante: no incluyas https:// ni barras diagonales (/) al final. La dirección se compone así: https:// + tu instancia + /service/v2/public/notes/list.
Encabezados requeridos
| Encabezado | Valor |
|---|---|
Authorization |
Bearer <token>, con el token obtenido en el servicio Login. |
Permiso requerido (scope): catalogs:read
Este servicio es de solo lectura y exige el permiso catalogs:read. Si falta, la respuesta es 403 con RECURSO_NO_AUTORIZADO.
Si aún no tienes un token, consulta la página Login del libro API nuby V2.
2. La Petición (¿Qué debes enviarnos?)
Todos son opcionales y se envían en la dirección web. Sin filtros, recibes la primera página con las últimas 50 notas.
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
factura_id |
entero | No | — | Devuelve solo las notas de esa factura. |
tipo |
texto | No | — | credito o debito. |
desde |
fecha | No | — | Fecha inicial en formato AAAA-MM-DD (inclusive). |
hasta |
fecha | No | — | Fecha final en formato AAAA-MM-DD (inclusive). |
page |
entero | No | 1 |
Número de página, empezando en 1. |
page_size |
entero | No | 50 |
Registros por página. Máximo 1000. |
Importante: page, page_size y factura_id deben ser enteros positivos; desde no puede ser mayor que hasta. Un valor inválido produce 400.
Los filtros se combinan. Si envías factura_id y tipo, deben cumplirse ambos.
Ejemplos de dirección
GET /notes/list?factura_id=456
GET /notes/list?tipo=credito&desde=2026-10-01&hasta=2026-10-31
GET /notes/list?page=2&page_size=20
Validaciones de parámetros
Los parámetros se validan antes de ejecutar la consulta. Si uno no cumple su regla, la respuesta es 400 VALIDATION_ERROR y no se devuelve ningún dato:
| Parámetro | Regla | Mensaje 400 |
|---|---|---|
page |
Entero positivo. 0, valores negativos o texto no son válidos. |
El parámetro "page" debe ser un número positivo. |
page_size |
Entero positivo, con 1000 como máximo. |
El parámetro "page_size" debe ser un número positivo. |
factura_id |
Entero positivo. Si apunta a una factura de contrato, la respuesta es 409 FACTURA_CONTRATO_NO_SOPORTADA. |
VALIDATION_ERROR con el mensaje del parámetro. |
tipo |
Solo admite credito o debito. |
VALIDATION_ERROR: el valor no está permitido. |
desde, hasta |
Fecha en formato AAAA-MM-DD. |
VALIDATION_ERROR con el mensaje del parámetro. |
| Rango de fechas | desde no puede ser mayor que hasta. |
VALIDATION_ERROR por rango inválido. |
Cuándo usar cada filtro
| Filtro | Caso de uso | Precisión |
|---|---|---|
factura_id |
Conciliar todas las notas de una factura concreta. | Exacta: devuelve solo las notas de esa factura. |
tipo |
Separar los ajustes que reducen el saldo de los que lo aumentan. | Exacta: credito o debito. |
desde / hasta |
Cerrar un período contable o revisar los ajustes de un mes. | Por rango de fechas, con ambos extremos incluidos. |
factura_id + tipo |
Ver solo las notas de crédito o solo las de débito de una factura. | Exacta: los filtros se combinan con AND. |
page / page_size |
Recorrer un resultado largo sin traer todo de una vez. | No filtra: solo controla el tamaño y la posición de la página. |
3. La Respuesta (¿Qué te entregaremos?)
{
"status": 200,
"message": null,
"body": [
{
"nota_id": 789,
"factura_id": 456,
"tipo": "credito",
"consecutivo": 1024,
"fecha": "2026-10-21",
"valor_total": 59500,
"estado_id": 5,
"clasificacion": 1,
"concepto_id": 6,
"observaciones": "Ajuste acordado"
}
],
"pagination": {
"page": 1,
"page_size": 50,
"total": 1,
"total_pages": 1
},
"filters_applied": {
"factura_id": 456,
"tipo": "credito"
}
}
Variantes de respuesta
El envelope cambia según los filtros enviados y según haya o no coincidencias. Estos son los tres casos que debes manejar en tu integración:
Con filtros
{
"status": 200,
"message": null,
"body": [
{
"nota_id": 789,
"factura_id": 456,
"tipo": "credito",
"consecutivo": 1024,
"fecha": "2026-10-21",
"valor_total": 59500,
"estado_id": 5,
"clasificacion": 1,
"concepto_id": 6,
"observaciones": "Ajuste acordado"
}
],
"pagination": { "page": 1, "page_size": 50, "total": 1, "total_pages": 1 },
"filters_applied": { "factura_id": 456, "tipo": "credito" }
}
Sin filtros
Cuando no envías ningún filtro, filters_applied es null y recibes la primera página de todas las notas de venta, ordenadas por fecha descendente.
{
"status": 200,
"message": null,
"body": [
{
"nota_id": 789,
"factura_id": 456,
"tipo": "credito",
"consecutivo": 1024,
"fecha": "2026-10-21",
"valor_total": 59500,
"estado_id": 5,
"clasificacion": 1,
"concepto_id": 6,
"observaciones": "Ajuste acordado"
},
{
"nota_id": 790,
"factura_id": 457,
"tipo": "debito",
"consecutivo": 1025,
"fecha": "2026-10-20",
"valor_total": 20000,
"estado_id": 5,
"clasificacion": 1,
"concepto_id": 8,
"observaciones": "Cargo adicional"
}
],
"pagination": { "page": 1, "page_size": 50, "total": 2, "total_pages": 1 },
"filters_applied": null
}
Lista vacía
No es un error: el servicio responde 200, body vacío y un mensaje informativo. En este ejemplo sí se enviaron filtros, por eso filters_applied no es null.
{
"status": 200,
"message": "No se encontraron notas con los filtros aplicados.",
"body": [],
"pagination": { "page": 1, "page_size": 50, "total": 0, "total_pages": 0 },
"filters_applied": {
"tipo": "debito",
"desde": "2026-09-01",
"hasta": "2026-09-30"
}
}
Campos de cada nota
| Campo | Descripción |
|---|---|
nota_id |
Identificador interno de la nota. Úsalo para consultar el detalle. |
factura_id |
Factura anotada. |
tipo |
credito o debito. |
consecutivo |
Número de la nota. |
fecha |
Fecha de la nota. |
valor_total |
Valor total de la nota. |
estado_id |
5 = borrador. |
clasificacion |
1 = vinculante. |
concepto_id |
Concepto aplicado a la nota. |
observaciones |
Observaciones registradas. |
Campos de paginación
| Campo | Significado | Caso sin resultados |
|---|---|---|
page |
Número de página devuelta, empezando en 1. |
Conserva la página solicitada (1 por defecto). |
page_size |
Cantidad de registros por página efectivamente aplicada. | Conserva el tamaño solicitado (50 por defecto). |
total |
Total de notas que cumplen los filtros. | 0. |
total_pages |
Total de páginas disponibles para ese resultado. | 0. Úsalo como condición de fin al recorrer páginas. |
filters_applied y message
- Sin filtros,
filters_appliedesnull. - Con filtros, es un objeto con los filtros usados.
- Con resultados,
messageesnull. - Sin resultados,
messageesNo se encontraron notas con los filtros aplicados.ybodyes[].
Campos del envelope
| Campo | Contenido | Cuándo cambia |
|---|---|---|
status |
200 en una lectura exitosa. |
Los errores usan su propio código HTTP y agregan error_code. |
message |
Texto informativo o null. |
Deja de ser null cuando la lista viene vacía. |
body |
Arreglo de notas con los campos del listado. | Es [] cuando no hay coincidencias. |
pagination |
Objeto con page, page_size, total y total_pages. |
total y total_pages pasan a 0 sin resultados. |
filters_applied |
Objeto con los filtros usados, o null. |
Es null solo cuando no enviaste ningún filtro. |
Una lista vacía no es un error. Se responde 200; revisa pagination.total para confirmar que no hay coincidencias.
Cómo interpretar el resultado
| Señal en la respuesta | Qué significa | Qué hacer |
|---|---|---|
body con notas y message: null |
La consulta encontró coincidencias. | Procesa cada nota y, si hay más páginas, avanza con page. |
body: [] y total: 0 |
Ninguna nota cumple los filtros enviados. | Revisa los filtros; no lo trates como un fallo técnico. |
filters_applied: null |
No se aplicó ningún filtro. | Confirma que enviaste los parámetros en la dirección web. |
400 VALIDATION_ERROR |
Un parámetro no cumple su regla y la consulta no se ejecutó. | Corrige el parámetro que indica el mensaje y repite. |
409 FACTURA_CONTRATO_NO_SOPORTADA |
El factura_id pertenece a una factura de contrato. |
Gestiona esas notas desde el flujo de contratos; no reintentes igual. |
Orden: las notas se devuelven por fecha descendente y, a igualdad de fecha, por identificador descendente. El orden es estable entre páginas.
4. Seguridad y Posibles Errores
Reglas y exclusiones
La API solo expone notas de facturas de venta:
- El listado nunca incluye notas de facturas de contrato, aunque no envíes
factura_id. - Si filtras por una factura de contrato, la respuesta es
409 FACTURA_CONTRATO_NO_SOPORTADAconwarnings[].
La nota se devuelve con el estado que tiene en nuby. Las notas creadas por la API están en borrador hasta que un usuario las emite en la interfaz.
Errores del contrato de la operación
| HTTP | error_code |
Cuándo ocurre y cómo resolverlo |
|---|---|---|
400 |
VALIDATION_ERROR |
Algún parámetro tiene un valor inválido. Revisa page, page_size, factura_id, tipo, desde o hasta. |
401 |
— | Falta el token o expiró. Vuelve a consumir Login. |
403 |
RECURSO_NO_AUTORIZADO |
Al cliente OAuth le falta catalogs:read. |
409 |
FACTURA_CONTRATO_NO_SOPORTADA |
Filtraste por una factura de contrato. Incluye warnings[]. |
500 |
INTERNAL_ERROR |
Fallo interno al consultar. Reintenta; si persiste, contacta a soporte. |
Errores frecuentes al paginar
| Situación | Qué ocurre | Cómo corregir |
|---|---|---|
Envías page=0 o page=abc |
400 VALIDATION_ERROR; la consulta no se ejecuta. |
Usa un entero desde 1. |
Envías page_size=0 o un valor negativo |
400 VALIDATION_ERROR. |
Usa un entero positivo, con 1000 como máximo. |
| Recorres páginas hasta recibir una vacía | Puedes hacer una petición de más o detenerte antes de tiempo. | Recorre hasta pagination.total_pages. |
Esperas que una lista vacía traiga filters_applied: null |
filters_applied refleja los filtros enviados, no el resultado. |
Revisa body y pagination.total para detectar la lista vacía. |
Atención: una factura de contrato no es un error de parámetro: el servicio la rechaza explícitamente para no exponer notas de ese flujo.
5. Ejemplos de integración
cURL
TOKEN="TU_TOKEN_AQUI"
INSTANCIA="tu-inmobiliaria.nuby.app"
# Notas de una factura
curl -G "https://${INSTANCIA}/service/v2/public/notes/list" \
--data-urlencode "factura_id=456" \
-H "Authorization: Bearer ${TOKEN}"
# Notas de crédito de octubre, página 1 de 20
curl -G "https://${INSTANCIA}/service/v2/public/notes/list" \
--data-urlencode "tipo=credito" \
--data-urlencode "desde=2026-10-01" \
--data-urlencode "hasta=2026-10-31" \
--data-urlencode "page=1" \
--data-urlencode "page_size=20" \
-H "Authorization: Bearer ${TOKEN}"
PHP
<?php
$instancia = 'tu-inmobiliaria.nuby.app';
$token = 'TU_TOKEN_AQUI';
$query = http_build_query([
'factura_id' => 456,
'page' => 1,
'page_size' => 50,
]);
$url = "https://{$instancia}/service/v2/public/notes/list?{$query}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
]);
$respuesta = curl_exec($ch);
$codigo = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$datos = json_decode($respuesta, true);
echo "HTTP: {$codigo}\n";
echo "Total: " . ($datos['pagination']['total'] ?? 0) . "\n";
foreach ($datos['body'] as $nota) {
echo $nota['nota_id'] . ' - ' . $nota['tipo'] . ' - ' . $nota['valor_total'] . "\n";
}
Python
import requests
instancia = "tu-inmobiliaria.nuby.app"
token = "TU_TOKEN_AQUI"
url = f"https://{instancia}/service/v2/public/notes/list"
headers = {
"Authorization": f"Bearer {token}",
}
pagina = 1
total_paginas = 1
while pagina <= total_paginas:
respuesta = requests.get(
url,
headers=headers,
params={"tipo": "credito", "desde": "2026-10-01", "hasta": "2026-10-31",
"page": pagina, "page_size": 50},
timeout=30,
)
if respuesta.status_code != 200:
print(respuesta.status_code, respuesta.json())
break
datos = respuesta.json()
total_paginas = datos["pagination"]["total_pages"]
for nota in datos["body"]:
print(nota["nota_id"], nota["tipo"], nota["valor_total"])
pagina += 1
JavaScript (Fetch API)
const instancia = 'tu-inmobiliaria.nuby.app';
const token = 'TU_TOKEN_AQUI';
const params = new URLSearchParams({
factura_id: '456',
page: '1',
page_size: '50'
});
const url = `https://${instancia}/service/v2/public/notes/list?${params}`;
const respuesta = await fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await respuesta.json();
if (respuesta.ok) {
console.log(`Página ${data.pagination.page} de ${data.pagination.total_pages}`);
data.body.forEach(n => console.log(n.nota_id, n.tipo, n.valor_total));
} else {
console.error(`${respuesta.status} ${data.error_code}: ${data.message}`);
}
Power Query M (Excel / Power BI)
Power Query consulta una página por vez. El paso pagination te permite parametrizar la página y combinar varias con Table.Combine si necesitas el historial completo.
let
// 1. Configura tus datos de acceso
instancia = "tu-inmobiliaria.nuby.app",
token = "TU_TOKEN_AQUI",
// 2. Prepara la dirección con los filtros y la página
url = "https://" & instancia & "/service/v2/public/notes/list?factura_id=456&page=1&page_size=1000",
// 3. Envía la petición GET
response = Web.Contents(url, [
Headers = [
#"Authorization" = "Bearer " & token
]
]),
// 4. Decodifica el envelope
jsonResponse = Json.Document(response),
body = jsonResponse[body],
pagination = jsonResponse[pagination],
// 5. Convierte el listado en tabla
tabla = Table.FromList(body, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
expandido = Table.ExpandRecordColumn(tabla, "Column1",
{"nota_id", "factura_id", "tipo", "consecutivo", "fecha", "valor_total",
"estado_id", "clasificacion", "concepto_id", "observaciones"},
{"NotaID", "FacturaID", "Tipo", "Consecutivo", "Fecha", "ValorTotal",
"EstadoID", "Clasificacion", "ConceptoID", "Observaciones"}),
tipos = Table.TransformColumnTypes(expandido, {
{"NotaID", Int64.Type},
{"FacturaID", Int64.Type},
{"Consecutivo", Int64.Type},
{"Fecha", type date},
{"ValorTotal", type number}
}),
totalPaginas = pagination[total_pages]
in
tipos
6. Buenas prácticas y límites conocidos
- Filtra por
factura_idcuando busques las notas de un documento concreto. - Usa
desdeyhastaen lugar de paginar meses completos. - Recorre hasta
total_pages, no hasta una página vacía. - No uses la lista como detalle: los ítems y el saldo de la factura están en la página Buscar Nota por Código.
- Guarda el
nota_idpara evitar búsquedas repetidas.
Límites del listado
- Solo expone notas de facturas de venta; las de contrato nunca aparecen.
- No devuelve los ítems ni el saldo de la factura: eso está en el detalle de la nota.
- El estado corresponde al que tiene la nota en nuby; las creadas por la API están en borrador.
Si necesitas crear una nota, revisa la página Crear Nota.
Buscar Nota por Código [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 devuelve el detalle completo de una nota de crédito o débito a partir de su identificador interno en nuby (nota_id): cabecera, ítems con sus impuestos y retenciones, y el estado y saldo actuales de la factura anotada.
Úsalo después de crear una nota, o cuando ya guardaste su nota_id y necesitas verificarla sin recorrer el listado.
¿Para qué sirve este servicio?
Úsalo cuando necesites ver el detalle de una nota sin recorrer el listado. Los casos de uso más frecuentes son:
- Verificar una nota recién creada. La creación ya devuelve el
nota_id; con este servicio confirmas su contenido. - Mostrar el detalle del ajuste en tu plataforma: cabecera e ítems con sus impuestos y retenciones.
- Auditar los montos que calculó nuby ítem por ítem: IVA, retenciones y descuento.
- Confirmar el estado y el saldo de la factura anotada antes de emitir el borrador en la interfaz de nuby.
1. El Endpoint (La dirección web)
Método y dirección
| Dato | Valor |
|---|---|
| Método | GET |
| Recurso | /notes/{nota_id} |
| Cuerpo de la petición | No se envía cuerpo |
GET https://{{instancia}}/service/v2/public/notes/{nota_id}
¿Qué debo colocar en {{instancia}}?
La dirección web principal con la que ingresas a tu plataforma, incluyendo la terminación .nuby.app o .arrendasoft.co.
Importante: no incluyas https:// ni barras diagonales (/) al final. La dirección se compone así: https:// + tu instancia + /service/v2/public/notes/ + el identificador de la nota.
Encabezados requeridos
| Encabezado | Valor |
|---|---|
Authorization |
Bearer <token>, con el token obtenido en el servicio Login. |
Permiso requerido (scope): catalogs:read
Este servicio es de solo lectura y exige el permiso catalogs:read. Si falta, la respuesta es 403 con RECURSO_NO_AUTORIZADO.
Si aún no tienes un token, consulta la página Login del libro API nuby V2.
2. La Petición (¿Qué debes enviarnos?)
Este servicio no requiere cuerpo. Solo necesitas el encabezado de autorización y el identificador de la nota en la dirección.
Parámetros de la ruta (path)
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
nota_id |
entero | Sí | Identificador interno de la nota. Debe ser un número: la dirección solo acepta dígitos. Va en la dirección, después del segmento /notes/. |
¿De dónde saco el nota_id?
- De la creación de la nota (
POST /notes): la respuesta lo devuelve enbody.nota_id. - Del listado (
GET /notes/list): cada fila delbodylo trae ennota_id.
Ejemplos de petición
GET https://tu-inmobiliaria.nuby.app/service/v2/public/notes/789
3. La Respuesta (¿Qué te entregaremos?)
Si la nota existe y pertenece a una factura de venta, el sistema devuelve el envelope y el cuerpo con el detalle de la nota:
{
"status": 200,
"message": null,
"body": {
"nota_id": 789,
"factura_id": 456,
"tipo": "credito",
"consecutivo": 1024,
"fecha": "2026-10-21",
"resolucion_id": 3,
"valor_total": 59500,
"estado_id": 5,
"moneda": "COP",
"tasa_cambio": 1,
"observaciones": "Ajuste acordado",
"clasificacion": 1,
"tercero_id": 123,
"concepto_id": 6,
"items": [
{
"producto_id": 159,
"descripcion": "Ajuste plan X",
"cantidad": 1,
"valor_unitario": 50000,
"por_iva": 19,
"iva": 9500,
"por_retencion": 0,
"retencion": 0,
"por_reteiva": 0,
"reteiva": 0,
"por_reteica": 0,
"reteica": 0,
"por_descuento": 0,
"descuento": 0
}
],
"factura_estado_id": 1,
"factura_saldo": 150000
},
"pagination": null,
"filters_applied": null
}
Nota sobre el envelope: es una consulta de un solo registro, así que pagination y filters_applied siempre vienen en null.
Cabecera de la nota
| Campo | Tipo | Puede ser null |
Descripción |
|---|---|---|---|
nota_id |
entero | No | Identificador interno de la nota. |
factura_id |
entero | No | Factura anotada. |
tipo |
texto | No | credito o debito. |
consecutivo |
entero | No | Número de la nota. |
fecha |
fecha | No | Fecha de la nota en formato AAAA-MM-DD. |
resolucion_id |
entero | Sí | Resolución heredada de la factura; puede ser null si la factura no tiene resolución. |
valor_total |
número | No | Valor total de la nota, calculado desde el detalle. |
estado_id |
entero | No | 5 = borrador. |
moneda |
texto | No | Moneda heredada de la factura (por ejemplo, COP). |
tasa_cambio |
número | No | Tasa heredada de la factura; es 1 cuando la moneda es la local. |
observaciones |
texto | No | Observaciones registradas; puede venir vacía si no se enviaron. |
clasificacion |
entero | No | 1 = vinculante. |
tercero_id |
entero | Sí | Tercero de la factura; puede ser null si la factura no lo tiene. |
concepto_id |
entero | No | Concepto aplicado a la nota. |
Campos de cada ítem
Cada ítem trae la base y los porcentajes junto con los montos ya calculados por nuby:
| Campo | Tipo | Descripción | Puede ser null |
|---|---|---|---|
producto_id |
entero | Producto o servicio del ítem. | No |
descripcion |
texto | Descripción del ajuste. | No (puede venir vacía) |
cantidad |
entero | Cantidad del ítem. | No |
valor_unitario |
número | Base unitaria enviada por el consumidor. | No |
por_iva |
número | Porcentaje de IVA aplicado. | No |
iva |
número | IVA calculado: base × por_iva / 100. |
No |
por_retencion |
número | Porcentaje de retención en la fuente. | No |
retencion |
número | Retención calculada sobre la base. | No |
por_reteiva |
número | Porcentaje de reteIVA. | No |
reteiva |
número | ReteIVA calculado sobre el IVA. | No |
por_reteica |
número | Porcentaje de reteICA. | No |
reteica |
número | ReteICA calculado sobre la base. | No |
por_descuento |
número | Porcentaje de descuento. | No |
descuento |
número | Descuento calculado sobre la base. | No |
Cómo se compone el valor total
| Monto | Fórmula | Se calcula sobre |
|---|---|---|
iva |
base × por_iva / 100 |
La base del ítem (cantidad × valor_unitario). |
retencion |
base × por_retencion / 100 |
La base del ítem. |
reteiva |
iva × por_reteiva / 100 |
El IVA calculado. |
reteica |
base × por_reteica / 100 |
La base del ítem. |
descuento |
base × por_descuento / 100 |
La base del ítem. |
El total del ítem suma la base y el IVA, y resta la retención, el reteIVA, el reteICA y el descuento. El valor_total de la nota es la suma de los totales de sus ítems, en la moneda original y con redondeo a dos decimales.
Estado y saldo de la factura
factura_estado_id indica el estado actual de la factura (1 activa, 6 anulada por nota crédito) y factura_saldo su saldo después de las notas.
Variantes de respuesta
El mismo endpoint devuelve una nota de crédito, una de débito o un rechazo de factura de contrato. Los tres casos cambian el contenido del body y, en el último, el estado HTTP:
Nota de crédito
{
"status": 200,
"message": null,
"body": {
"nota_id": 789,
"factura_id": 456,
"tipo": "credito",
"consecutivo": 1024,
"fecha": "2026-10-21",
"resolucion_id": 3,
"valor_total": 59500,
"estado_id": 5,
"moneda": "COP",
"tasa_cambio": 1,
"observaciones": "Ajuste acordado",
"clasificacion": 1,
"tercero_id": 123,
"concepto_id": 6,
"items": [
{
"producto_id": 159,
"descripcion": "Ajuste plan X",
"cantidad": 1,
"valor_unitario": 50000,
"por_iva": 19,
"iva": 9500,
"por_retencion": 0,
"retencion": 0,
"por_reteiva": 0,
"reteiva": 0,
"por_reteica": 0,
"reteica": 0,
"por_descuento": 0,
"descuento": 0
}
],
"factura_estado_id": 1,
"factura_saldo": 150000
},
"pagination": null,
"filters_applied": null
}
La nota de crédito reduce el saldo de la factura. Si el saldo llega a cero, factura_estado_id pasa a 6.
Nota de débito
{
"status": 200,
"message": null,
"body": {
"nota_id": 790,
"factura_id": 456,
"tipo": "debito",
"consecutivo": 1025,
"fecha": "2026-10-21",
"resolucion_id": 3,
"valor_total": 20000,
"estado_id": 5,
"moneda": "COP",
"tasa_cambio": 1,
"observaciones": "Cargo adicional acordado",
"clasificacion": 1,
"tercero_id": 123,
"concepto_id": 8,
"items": [
{
"producto_id": 159,
"descripcion": "Cargo adicional octubre",
"cantidad": 1,
"valor_unitario": 20000,
"por_iva": 0,
"iva": 0,
"por_retencion": 0,
"retencion": 0,
"por_reteiva": 0,
"reteiva": 0,
"por_reteica": 0,
"reteica": 0,
"por_descuento": 0,
"descuento": 0
}
],
"factura_estado_id": 1,
"factura_saldo": 170000
},
"pagination": null,
"filters_applied": null
}
La nota de débito aumenta el saldo. La factura conserva el estado 1 mientras siga ACTIVA.
Nota de una factura de contrato (409)
Cuando la nota pertenece a una factura de contrato, nuby no devuelve sus datos: responde 409 con el código estable y la advertencia estructurada. El scope siempre es solo_facturas_de_venta.
{
"success": false,
"status": 409,
"message": "La factura 456 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": 456,
"scope": "solo_facturas_de_venta"
}
]
}
Estados que puedes recibir
| Campo | Valor | Significado |
|---|---|---|
estado_id |
5 |
Borrador. Es el estado de toda nota creada por esta API. |
factura_estado_id |
1 |
Factura ACTIVA; el saldo todavía es mayor que cero. |
factura_estado_id |
6 |
Factura anulada por nota crédito porque el saldo llegó a cero. |
Campos que no expone el detalle
El detalle se limita a la nota y al estado y saldo de la factura. Estos datos no forman parte de la respuesta y debes consultarlos en otro servicio:
| Dato | Por qué no aparece | Dónde consultarlo |
|---|---|---|
| Nombre y documento del tercero | Solo se devuelve tercero_id. |
Buscar Tercero por Código. |
| Número, fecha y valor total de la factura | La nota solo informa factura_estado_id y factura_saldo. |
Ver Detalle de una Factura. |
| Ítems de la factura original | El arreglo items[] corresponde a la nota, no a la factura. |
Ver Detalle de una Factura. |
Emisión DIAN de la nota (cufe, dian_id) |
La API crea y consulta el borrador; la emisión es un paso posterior en nuby. | Interfaz de nuby. |
| Contactos, archivos y documentos del tercero | No forman parte de esta API. | Módulo de terceros de nuby. |
Importante: los campos sin valor vienen en null, no se omiten. Verifica null antes de usar un dato en tu interfaz.
4. Seguridad y Posibles Errores
| HTTP | error_code |
Cuándo ocurre y cómo resolverlo |
|---|---|---|
401 |
— | Falta el token o expiró. Vuelve a consumir Login. |
403 |
RECURSO_NO_AUTORIZADO |
Al cliente OAuth le falta catalogs:read. |
404 |
NOTA_NO_ENCONTRADA |
No existe una nota con ese identificador. Verifica el nota_id o búscala en Listar Notas. |
409 |
FACTURA_CONTRATO_NO_SOPORTADA |
La nota pertenece a una factura de contrato; esa información no se expone por esta API. Incluye warnings[]. |
500 |
INTERNAL_ERROR |
Fallo interno al consultar. Reintenta; si persiste, contacta a soporte. |
Ejemplo de nota inexistente (404)
{
"success": false,
"status": 404,
"message": "No se encontró la nota con el código solicitado.",
"error_code": "NOTA_NO_ENCONTRADA"
}
Ojo con el identificador: nota_id es el número interno de la nota, no el consecutivo ni el número de la factura. Para buscar las notas de una factura usa /notes/list?factura_id=....
Errores frecuentes y solución
| Código | Causa | Qué hacer |
|---|---|---|
401 |
No enviaste el token o ya expiró. | Vuelve a consumir Login y repite la consulta con el token nuevo. |
403 RECURSO_NO_AUTORIZADO |
Al cliente OAuth le falta el permiso catalogs:read. |
Solicita el scope a tu administrador en nuby. |
404 NOTA_NO_ENCONTRADA |
El nota_id no existe en nuby. |
Verifica el identificador o búscalo con Listar Notas. |
409 FACTURA_CONTRATO_NO_SOPORTADA |
La nota pertenece a una factura de contrato. | Gestiona el ajuste en el flujo de contratos y revisa warnings[]. |
500 INTERNAL_ERROR |
Fallo interno al consultar. | Reintenta; si persiste, contacta a soporte con el nota_id consultado. |
5. Ejemplos de integración
cURL
TOKEN="TU_TOKEN_AQUI"
INSTANCIA="tu-inmobiliaria.nuby.app"
NOTA_ID=789
curl "https://${INSTANCIA}/service/v2/public/notes/${NOTA_ID}" \
-H "Authorization: Bearer ${TOKEN}"
PHP
<?php
$instancia = 'tu-inmobiliaria.nuby.app';
$token = 'TU_TOKEN_AQUI';
$notaId = 789;
$url = "https://{$instancia}/service/v2/public/notes/{$notaId}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
]);
$respuesta = curl_exec($ch);
$codigo = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$datos = json_decode($respuesta, true);
if ($codigo === 200) {
$nota = $datos['body'];
echo "{$nota['tipo']} {$nota['consecutivo']} - total {$nota['valor_total']}\n";
echo "Factura {$nota['factura_id']}: estado {$nota['factura_estado_id']}, saldo {$nota['factura_saldo']}\n";
foreach ($nota['items'] as $item) {
echo "{$item['descripcion']} - {$item['cantidad']} x {$item['valor_unitario']}\n";
}
} else {
echo "HTTP {$codigo}: " . $datos['message'] . "\n";
}
Python
import requests
instancia = "tu-inmobiliaria.nuby.app"
token = "TU_TOKEN_AQUI"
nota_id = 789
url = f"https://{instancia}/service/v2/public/notes/{nota_id}"
headers = {
"Authorization": f"Bearer {token}",
}
respuesta = requests.get(url, headers=headers, timeout=30)
if respuesta.status_code == 200:
nota = respuesta.json()["body"]
print(f"{nota['tipo']} {nota['consecutivo']} — total {nota['valor_total']}")
print(f"Factura {nota['factura_id']}: estado {nota['factura_estado_id']}, saldo {nota['factura_saldo']}")
for item in nota["items"]:
print(item["descripcion"], item["cantidad"], item["valor_unitario"])
else:
print(respuesta.status_code, respuesta.json())
JavaScript (Fetch API)
const instancia = 'tu-inmobiliaria.nuby.app';
const token = 'TU_TOKEN_AQUI';
const notaId = 789;
const url = `https://${instancia}/service/v2/public/notes/${notaId}`;
const respuesta = await fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await respuesta.json();
if (respuesta.ok) {
const nota = data.body;
console.log(`${nota.tipo} ${nota.consecutivo} — total ${nota.valor_total}`);
console.log(`Factura ${nota.factura_id}: estado ${nota.factura_estado_id}, saldo ${nota.factura_saldo}`);
nota.items.forEach(i => console.log(i.descripcion, i.cantidad, i.valor_unitario));
} else {
console.error(`${respuesta.status} ${data.error_code}: ${data.message}`);
}
Power Query M (Excel / Power BI)
El detalle se modela como una tabla de cabecera y otra de ítems. Power Query consulta una vez y expande ambas partes del body:
let
// 1. Configura tus datos de acceso
instancia = "tu-inmobiliaria.nuby.app",
token = "TU_TOKEN_AQUI",
notaId = "789",
// 2. Prepara la dirección de la petición
url = "https://" & instancia & "/service/v2/public/notes/" & notaId,
// 3. Envía la petición GET
response = Web.Contents(url, [
Headers = [
#"Authorization" = "Bearer " & token
]
]),
// 4. Decodifica la respuesta JSON
jsonResponse = Json.Document(response),
body = jsonResponse[body],
// 5. Convierte la cabecera en tabla
cabecera = Table.FromList({body}, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
columnas = Table.ExpandRecordColumn(cabecera, "Column1",
{"nota_id", "factura_id", "tipo", "consecutivo", "fecha", "valor_total",
"estado_id", "concepto_id", "factura_estado_id", "factura_saldo"},
{"NotaID", "FacturaID", "Tipo", "Consecutivo", "Fecha", "ValorTotal",
"EstadoID", "ConceptoID", "FacturaEstadoID", "FacturaSaldo"}),
tipos = Table.TransformColumnTypes(columnas, {
{"NotaID", Int64.Type},
{"FacturaID", Int64.Type},
{"Consecutivo", Int64.Type},
{"Fecha", type date},
{"ValorTotal", type number},
{"FacturaSaldo", type number}
}),
// 6. Expande los ítems en una tabla aparte
items = Table.FromList(body[items], Splitter.SplitByNothing(), null, null, ExtraValues.Error),
itemsExpandidos = Table.ExpandRecordColumn(items, "Column1",
{"producto_id", "descripcion", "cantidad", "valor_unitario", "por_iva", "iva",
"por_retencion", "retencion", "por_reteiva", "reteiva",
"por_reteica", "reteica", "por_descuento", "descuento"},
{"ProductoID", "Descripcion", "Cantidad", "ValorUnitario", "PorIVA", "IVA",
"PorRetencion", "Retencion", "PorReteIVA", "ReteIVA",
"PorReteICA", "ReteICA", "PorDescuento", "Descuento"})
in
tipos
6. Buenas prácticas y límites conocidos
Recomendaciones
- Consulta el detalle solo cuando lo necesites. La creación ya devuelve el
nota_idy el saldo resultante. - Usa
factura_saldoyfactura_estado_idpara reflejar el estado real de la cartera sin consultar la factura. - Trata el
404como un caso de negocio, no como una falla técnica: el identificador no existe en nuby. - Recuerda que los campos ausentes vienen en
null; no supongas cadenas vacías.
Límites
- Solo devuelve notas de facturas de venta; las de contrato se rechazan con
409. - El detalle no incluye la emisión DIAN de la nota: la API solo crea y consulta el borrador.
- No incluye datos del tercero más allá de su identificador: para eso usa los servicios de terceros.
El detalle no admite paginación ni filtros: el nota_id identifica un único registro. Si necesitas recorrer varias notas, usa Listar Notas y consulta el detalle solo de las que vayas a mostrar. Así evitas una petición por cada nota del período.
Para crear una nota, revisa la página Crear Nota. Para listar notas, revisa Listar Notas.
Crear Nota [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 registra una nota de crédito o una nota de débito sobre una factura de venta de nuby. La nota se guarda en borrador y actualiza el saldo de la factura en el acto: una nota de crédito lo reduce y puede anular la factura si llega a cero; una nota de débito lo aumenta.
Es el servicio que necesitas cuando tu plataforma debe ajustar o anular una factura emitida desde nuby, sin volver a digitar el documento en la interfaz.
¿Para qué sirve este servicio?
Úsalo cuando necesites ajustar una factura de venta desde tu plataforma sin volver a digitar el documento en nuby. Los casos de uso más frecuentes son:
- Anular una factura por nota de crédito. Cuando el ajuste iguala el saldo, la factura queda anulada por nota crédito.
- Registrar un cargo adicional posterior con una nota de débito, por ejemplo un cobro que no estaba incluido en la factura original.
- Replicar en nuby los ajustes que originas en tu plataforma, con una
referencia_externaque evita crear la nota dos veces. - Dejar la nota lista para emitir. La API crea el borrador; un usuario lo emite a la DIAN desde nuby.
1. El Endpoint (La dirección web)
Método y dirección
| Dato | Valor |
|---|---|
| Método | POST |
| Recurso | /notes |
| Cuerpo de la petición | Sí: el JSON de la nota que se describe en La Petición |
POST https://{{instancia}}/service/v2/public/notes
¿Qué debo colocar en {{instancia}}?
La dirección web principal con la que ingresas a tu plataforma, incluyendo la terminación .nuby.app o .arrendasoft.co.
Importante: no incluyas https:// ni barras diagonales (/) al final. La dirección se compone así: https:// + tu instancia + /service/v2/public/notes.
Encabezados requeridos
| Encabezado | Valor |
|---|---|
Content-Type |
application/json |
Authorization |
Bearer <token>, con el token obtenido en el servicio Login. |
Permiso requerido (scope): notes:create
El cliente OAuth debe tener habilitado notes:create. Si el token es válido pero falta el permiso, la respuesta es 403 con el código RECURSO_NO_AUTORIZADO.
Si aún no tienes un token, consulta la página Login del libro API nuby V2.
2. ¿Qué hace este servicio?
La nota se crea reutilizando la misma lógica de la interfaz de nuby: se asigna su consecutivo, se guarda su detalle y se recalcula el saldo de la factura. La API no reimplementa numeración, contabilidad ni DIAN.
Qué SÍ hace
- Crea la nota en estado borrador y le asigna consecutivo.
- Reduce el saldo de la factura si es nota de crédito, o lo aumenta si es nota de débito.
- Deja la factura en estado 6 (anulada por nota crédito) cuando el saldo queda en cero.
- Resuelve por ti el concepto y los datos que hereda de la factura.
Qué NO hace
- No envía la nota a la DIAN. La nota queda en borrador; la emisión la realiza un usuario en nuby.
- No edita ni anula notas existentes.
- No crea notas no vinculantes. Toda nota creada por esta API queda vinculada a su factura.
- No anota facturas de contrato (inquilino o propietario): esas se gestionan en el flujo de contratos.
Importante: la factura debe estar en estado ACTIVA (facturada). Una factura en borrador o anulada no admite notas y la API responde 409 FACTURA_NO_ANOTABLE.
3. La Petición (¿Qué debes enviarnos?)
Campos obligatorios
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
referencia_externa |
texto | Sí | Identificador único de tu operación. Es la llave de idempotencia: un reintento no crea una segunda nota. |
factura_id |
entero | Sí | Identificador interno de la factura de venta que se va a anotar. |
tipo |
texto | Sí | credito para reducir el saldo o debito para aumentarlo. |
items[] |
arreglo | Sí | Al menos un ítem con producto_id, cantidad entero mayor que cero y valor_unitario distinto de cero. |
fecha es opcional: si no la envías, se usa la fecha de hoy en formato AAAA-MM-DD.
Campos opcionales
| Grupo | Campos |
|---|---|
| Cabecera | concepto_id, observaciones, por_iva, por_retencion, fecha_ini, fecha_fin |
| Ítem | descripcion, por_iva, por_retencion, por_reteiva, por_reteica, por_descuento |
Datos que hereda de la factura
No necesitas enviarlos y, de hecho, no se aceptan si difieren: resolucion_id, tercero_id, moneda y tasa_cambio se toman de la factura. Si envías una moneda o una tasa distinta, la respuesta es 422 CAMPO_NO_PERMITIDO: el saldo se calcula sin conversión y un valor distinto lo descuadraría.
Campos que el consumidor no puede fijar
La nota hereda de la factura los datos que garantizan la coherencia del saldo y de la numeración. Si envías uno de estos campos con valor, la respuesta es 422 CAMPO_NO_PERMITIDO y la nota no se crea:
| Campo | Por qué no lo puedes fijar | Respuesta |
|---|---|---|
clasificacion |
El servidor fija 1 (vinculante). La API no crea notas no vinculantes. |
422 CAMPO_NO_PERMITIDO |
tercero_id |
Se hereda de la factura anotada. Un tercero distinto descuadraría la cartera. | 422 CAMPO_NO_PERMITIDO |
resolucion_id |
Se hereda de la factura; la nota no tiene resolución propia. | 422 CAMPO_NO_PERMITIDO |
tipo_factura |
Lo determina la factura anotada, que debe ser de venta. | 422 CAMPO_NO_PERMITIDO |
consecutivo |
Lo asigna nuby al crear la nota. | 422 CAMPO_NO_PERMITIDO |
estado_id |
La nota nace en 5 (borrador); la emisión es un paso posterior en nuby. |
422 CAMPO_NO_PERMITIDO |
dian_id, cufe |
La emisión a la DIAN no forma parte de esta API. | 422 CAMPO_NO_PERMITIDO |
valor_total |
Se recalcula desde los ítems y sus porcentajes. | 422 CAMPO_NO_PERMITIDO |
creadopor |
Lo registra nuby con el consumidor autenticado. | 422 CAMPO_NO_PERMITIDO |
nota_id |
Lo asigna nuby; enviarlo sugiere una edición que la API no soporta. | 422 CAMPO_NO_PERMITIDO |
moneda, tasa_cambio |
Se heredan de la factura. Solo se toleran si coinciden exactamente con ella. | 422 CAMPO_NO_PERMITIDO si difieren |
Montos calculados: iva, retencion, reteiva, reteica, descuento |
El servidor los deriva de valor_unitario y de los porcentajes del ítem. |
Se ignoran; el total se recalcula |
Concepto de la nota
Si omites concepto_id, nuby aplica el concepto por defecto del tipo. El concepto se resuelve contra los target y codigo de la maestra:
| Tipo de nota | target |
codigo |
Cuándo aplica |
|---|---|---|---|
| Nota de crédito | 1 |
'2' |
Se omite concepto_id y tipo es credito. Corresponde a la anulación de la factura electrónica. |
| Nota de débito | 2 |
'4' |
Se omite concepto_id y tipo es debito. Corresponde al concepto "Otros". |
Si envías un concepto_id, nuby no aplica el default: valida que el concepto exista y que su target corresponda al tipo de nota. Si no existe o no corresponde, la respuesta es 422 CONCEPTO_NO_DISPONIBLE y no se crea la nota.
Montos: tú envías tasas, nuby calcula
Por cada ítem envía valor_unitario y los porcentajes. El servidor calcula el IVA, las retenciones y el descuento, y recalcula el total de la nota a partir del detalle. Si envías montos ya calculados, se ignoran.
| Monto | Fórmula (porcentaje sobre 100) | Ejemplo con base 50000 |
|---|---|---|
iva |
base × por_iva |
50000 × 19 % = 9500 |
retencion |
base × por_retencion |
50000 × 0 % = 0 |
reteiva |
iva × por_reteiva |
9500 × 0 % = 0 |
reteica |
base × por_reteica |
50000 × 0 % = 0 |
descuento |
base × por_descuento |
50000 × 0 % = 0 |
El total del ítem se obtiene al sumar la base y el IVA, y restar la retención, el reteIVA, el reteICA y el descuento. Con el ejemplo anterior, el total de la nota es 59500. Todos los montos se calculan en la moneda original de la factura y se redondean a dos decimales.
Importante: el IVA de un ítem requiere que el producto tenga configurado su tipo de IVA en nuby. Si no lo tiene, la operación falla y no se crea la nota.
Ejemplo: nota de crédito total
Cuando el total de la nota de crédito iguala el saldo de la factura, el saldo queda en 0 y la factura pasa al estado 6 (anulada por nota crédito). La respuesta lo confirma en factura_estado_id y factura_saldo.
{
"referencia_externa": "NOTE-000046",
"factura_id": 456,
"tipo": "credito",
"fecha": "2026-10-21",
"observaciones": "Anulación total acordada",
"items": [
{
"producto_id": 159,
"descripcion": "Anulación total factura 456",
"cantidad": 1,
"valor_unitario": 100000,
"por_iva": 19,
"por_retencion": 0,
"por_reteiva": 0,
"por_reteica": 0,
"por_descuento": 0
}
]
}
La nota calcula iva = 100000 × 19 % = 19000 y un total de 119000. Si el saldo de la factura es exactamente 119000, la respuesta devuelve factura_saldo: 0 y factura_estado_id: 6.
Ejemplo: nota de débito
La nota de débito aumenta el saldo. Úsala para cobrar un ajuste posterior, por ejemplo un cargo no incluido en la factura original. La factura debe seguir ACTIVA.
{
"referencia_externa": "NOTE-000047",
"factura_id": 456,
"tipo": "debito",
"fecha": "2026-10-21",
"observaciones": "Cargo adicional acordado",
"items": [
{
"producto_id": 159,
"descripcion": "Cargo adicional octubre",
"cantidad": 1,
"valor_unitario": 20000,
"por_iva": 0,
"por_retencion": 0,
"por_reteiva": 0,
"por_reteica": 0,
"por_descuento": 0
}
]
}
Si el saldo era 119000, después de esta nota de débito será 139000. La factura conserva el estado 1 (ACTIVA).
Ejemplo completo
{
"referencia_externa": "NOTE-000045",
"factura_id": 456,
"tipo": "credito",
"concepto_id": null,
"fecha": "2026-10-21",
"observaciones": "Ajuste acordado",
"items": [
{
"producto_id": 159,
"descripcion": "Ajuste plan X",
"cantidad": 1,
"valor_unitario": 50000,
"por_iva": 19,
"por_retencion": 0,
"por_reteiva": 0,
"por_reteica": 0,
"por_descuento": 0
}
]
}
4. Cómo afecta el saldo de la factura
Escenario → resultado en la factura
| Escenario | Resultado en la factura |
|---|---|
| Nota de crédito parcial | El saldo se reduce en el valor de la nota. La factura sigue ACTIVA (1). |
| Nota de crédito total | El saldo llega a 0 y la factura queda en estado 6 (anulada por nota crédito). |
| Nota de débito | El saldo aumenta en el valor de la nota. La factura sigue ACTIVA (1). |
| Factura en borrador | 409 FACTURA_NO_ANOTABLE. No se crea la nota ni se modifica el saldo. |
| Factura de contrato | 409 FACTURA_CONTRATO_NO_SOPORTADA con warnings[]. No se crea la nota ni se modifica el saldo. |
| Factura inexistente | 404 FACTURA_NO_ENCONTRADA. Verifica el factura_id antes de reintentar. |
La nota se crea en borrador y aun así afecta el saldo, porque nuby recalcula la cartera al guardarla. La emisión a la DIAN es un paso posterior y no cambia el saldo.
Ojo con el estado de la factura: solo se anotan facturas ACTIVAS. Si la factura ya fue anulada o está en borrador, la respuesta es 409 FACTURA_NO_ANOTABLE.
5. La Respuesta (¿Qué te entregaremos?)
Si la nota se crea correctamente, la respuesta es un JSON con el resultado de la operación y el estado en que queda la factura anotada:
{
"success": true,
"status": 201,
"message": "Nota creada en borrador.",
"body": {
"nota_id": 789,
"tipo": "credito",
"consecutivo": 1024,
"estado_id": 5,
"factura_id": 456,
"factura_estado_id": 1,
"factura_saldo": 150000
},
"idempotent_replay": false
}
Estructura de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
nota_id |
entero | Identificador interno de la nota. Guárdalo para consultarla después. |
tipo |
texto | credito o debito. |
consecutivo |
entero | Número asignado a la nota. |
estado_id |
entero | 5 = borrador. |
factura_estado_id |
entero | Estado de la factura después de la nota (1 activa, 6 anulada por nota crédito). |
factura_saldo |
número | Saldo de la factura después de la nota. |
idempotent_replay |
booleano | false en la primera respuesta; true cuando se recupera una solicitud anterior. |
Escenario de reintento (replay)
Si repites la petición con la misma referencia_externa y el mismo contenido, nuby no crea una segunda nota: devuelve la respuesta original con idempotent_replay en true.
{
"success": true,
"status": 201,
"message": "Nota creada en borrador.",
"body": {
"nota_id": 789,
"tipo": "credito",
"consecutivo": 1024,
"estado_id": 5,
"factura_id": 456,
"factura_estado_id": 1,
"factura_saldo": 150000
},
"idempotent_replay": true
}
Para ver el detalle completo de la nota, consulta la página Buscar Nota por Código.
6. Idempotencia y reintentos
La escritura exige referencia_externa. nuby recuerda qué respondió para esa referencia, así que un reintento no duplica la nota.
| Situación | Qué responde nuby |
|---|---|
| Primera vez que se usa la referencia | Procesa y responde 201. |
| Misma referencia y mismo contenido | La respuesta original con idempotent_replay: true. |
| Misma referencia con otro contenido | 409 IDEMPOTENCY_PAYLOAD_MISMATCH. |
| Solicitud en proceso | 409 IDEMPOTENCY_IN_PROGRESS. |
| Solicitud fallida | Se permite reintentar, incluso con datos corregidos. |
Importante: un error 422 de validación no consume la referencia. Puedes corregir el payload y reintentar con la misma referencia_externa.
Token renovado: el contenido interno que agrega la autenticación no forma parte de la comparación de idempotencia. Puedes renovar el token y reintentar sin conflicto.
7. Seguridad y Posibles Errores
| HTTP | error_code |
Cuándo ocurre y cómo resolverlo |
|---|---|---|
400 |
VALIDATION_ERROR |
El cuerpo no es un objeto JSON. Envía JSON válido con Content-Type: application/json. |
401 |
— | Falta el token o expiró. Vuelve a consumir Login. |
403 |
RECURSO_NO_AUTORIZADO |
Al cliente OAuth le falta notes:create. |
404 |
FACTURA_NO_ENCONTRADA |
El factura_id no existe en nuby. |
409 |
FACTURA_CONTRATO_NO_SOPORTADA |
La factura es de contrato; se gestiona en el flujo de contratos. Incluye warnings[]. |
409 |
FACTURA_NO_ANOTABLE |
La factura no está ACTIVA (borrador, anulada o pagada). |
409 |
IDEMPOTENCY_IN_PROGRESS / IDEMPOTENCY_PAYLOAD_MISMATCH |
Reintento con la misma referencia en curso o con contenido distinto. |
422 |
VALIDATION_ERROR |
Falta un obligatorio o un dato no es válido. Revisa errors por campo. |
422 |
CAMPO_NO_PERMITIDO |
Intentaste fijar un campo del sistema o una moneda/tasa distinta a la factura. |
422 |
FACTURACION_ELECTRONICA_DESHABILITADA |
La facturación electrónica está apagada en nuby. No se puede crear la nota por API. |
422 |
CONCEPTO_NO_DISPONIBLE |
El concepto_id no existe o no corresponde al tipo de nota. |
422 |
DATOS_NOTA_INVALIDOS |
Los datos no pasaron la validación de nuby. Revisa el mensaje. |
500 |
INTERNAL_ERROR |
Fallo interno. La solicitud queda fallida y es reintentable; si persiste, contacta a soporte. |
Errores de validación frecuentes
| Situación | Mensaje / error_code |
Cómo corregir |
|---|---|---|
Falta referencia_externa o viene vacía |
422 VALIDATION_ERROR |
Envía una referencia única y no vacía. Sin ella no se aplica idempotencia. |
Falta factura_id, tipo o items[] |
422 VALIDATION_ERROR |
Incluye la factura, un tipo válido (credito o debito) y al menos un ítem. |
Un ítem no tiene producto_id, o su cantidad o valor_unitario no es válido |
422 VALIDATION_ERROR |
Usa cantidad entero mayor que cero y valor_unitario distinto de cero. |
| La factura no existe | 404 FACTURA_NO_ENCONTRADA |
Verifica el factura_id con el detalle o el listado de facturas de venta. |
| La factura es de contrato | 409 FACTURA_CONTRATO_NO_SOPORTADA |
Anula o ajusta desde el flujo de contratos. Revisa warnings[].code, factura_id y scope. |
| La factura no está ACTIVA | 409 FACTURA_NO_ANOTABLE |
Solo se anotan facturas en estado 1. Emite o descarta el borrador antes de anotar. |
| La facturación electrónica está apagada | 422 FACTURACION_ELECTRONICA_DESHABILITADA |
Pide a tu administrador que habilite ENABLE_FACT_ELECT = 1 en nuby. |
| El concepto no existe o no corresponde al tipo | 422 CONCEPTO_NO_DISPONIBLE |
Omite concepto_id para usar el default, o envía uno del target correcto. |
| Enviaste un campo reservado al sistema | 422 CAMPO_NO_PERMITIDO |
Quita clasificacion, tercero_id, resolucion_id, consecutivo, estado_id, valor_total, nota_id y los demás de la lista. |
| La moneda o la tasa no coincide con la factura | 422 CAMPO_NO_PERMITIDO |
Omite moneda y tasa_cambio, o envía exactamente los de la factura. |
| El producto no tiene configurado su tipo de IVA o los porcentajes no son válidos | 422 DATOS_NOTA_INVALIDOS |
Revisa la configuración del producto en nuby y envía porcentajes válidos por ítem. |
Atención: cuando la respuesta es 409 o 422, nuby no deja una nota a medias. La operación se revierte completa.
8. Ejemplos de integración
cURL
TOKEN="TU_TOKEN_AQUI"
INSTANCIA="tu-inmobiliaria.nuby.app"
curl -X POST "https://${INSTANCIA}/service/v2/public/notes" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${TOKEN}" \
-d '{
"referencia_externa": "NOTE-000045",
"factura_id": 456,
"tipo": "credito",
"fecha": "2026-10-21",
"observaciones": "Ajuste acordado",
"items": [
{
"producto_id": 159,
"descripcion": "Ajuste plan X",
"cantidad": 1,
"valor_unitario": 50000,
"por_iva": 19,
"por_retencion": 0,
"por_reteiva": 0,
"por_reteica": 0,
"por_descuento": 0
}
]
}'
PHP
<?php
$instancia = 'tu-inmobiliaria.nuby.app';
$token = 'TU_TOKEN_AQUI';
$url = "https://{$instancia}/service/v2/public/notes";
$payload = [
'referencia_externa' => 'NOTE-000045',
'factura_id' => 456,
'tipo' => 'credito',
'fecha' => '2026-10-21',
'observaciones' => 'Ajuste acordado',
'items' => [
[
'producto_id' => 159,
'descripcion' => 'Ajuste plan X',
'cantidad' => 1,
'valor_unitario' => 50000,
'por_iva' => 19,
'por_retencion' => 0,
'por_reteiva' => 0,
'por_reteica' => 0,
'por_descuento' => 0,
],
],
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
"Authorization: Bearer {$token}",
]);
$respuesta = curl_exec($ch);
$codigo = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP: {$codigo}\n";
print_r(json_decode($respuesta, true));
Python
import requests
instancia = "tu-inmobiliaria.nuby.app"
token = "TU_TOKEN_AQUI"
url = f"https://{instancia}/service/v2/public/notes"
payload = {
"referencia_externa": "NOTE-000045",
"factura_id": 456,
"tipo": "credito",
"fecha": "2026-10-21",
"observaciones": "Ajuste acordado",
"items": [
{
"producto_id": 159,
"descripcion": "Ajuste plan X",
"cantidad": 1,
"valor_unitario": 50000,
"por_iva": 19,
"por_retencion": 0,
"por_reteiva": 0,
"por_reteica": 0,
"por_descuento": 0,
}
],
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
}
respuesta = requests.post(url, json=payload, headers=headers, timeout=30)
if respuesta.status_code == 201:
datos = respuesta.json()
print(f"Nota {datos['body']['nota_id']} — saldo factura: {datos['body']['factura_saldo']}")
else:
print(respuesta.status_code, respuesta.json())
JavaScript (Fetch API)
const instancia = 'tu-inmobiliaria.nuby.app';
const token = 'TU_TOKEN_AQUI';
const url = `https://${instancia}/service/v2/public/notes`;
const payload = {
referencia_externa: 'NOTE-000045',
factura_id: 456,
tipo: 'credito',
fecha: '2026-10-21',
items: [{
producto_id: 159,
descripcion: 'Ajuste plan X',
cantidad: 1,
valor_unitario: 50000,
por_iva: 19,
por_retencion: 0,
por_reteiva: 0,
por_reteica: 0,
por_descuento: 0
}]
};
const respuesta = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(payload)
});
const data = await respuesta.json();
if (respuesta.ok) {
console.log(`Nota ${data.body.nota_id} — saldo factura: ${data.body.factura_saldo}`);
} else {
console.error(`${respuesta.status} ${data.error_code}: ${data.message}`, data.errors ?? {});
}
Power Query M (Excel / Power BI)
Power Query construye el cuerpo con Json.FromValue y lo envía con Web.Contents. La respuesta se convierte en una tabla de una fila. Si la petición falla, Web.Contents genera una excepción que puedes capturar con try ... otherwise.
let
// 1. Configura tus datos de acceso
instancia = "tu-inmobiliaria.nuby.app",
token = "TU_TOKEN_AQUI",
// 2. Prepara la dirección de la petición
url = "https://" & instancia & "/service/v2/public/notes",
// 3. Construye el cuerpo JSON
cuerpo = Json.FromValue([
referencia_externa = "NOTE-000045",
factura_id = 456,
tipo = "credito",
fecha = "2026-10-21",
observaciones = "Ajuste acordado",
items = {
[
producto_id = 159,
descripcion = "Ajuste plan X",
cantidad = 1,
valor_unitario = 50000,
por_iva = 19,
por_retencion = 0,
por_reteiva = 0,
por_reteica = 0,
por_descuento = 0
]
}
]),
// 4. Envía la petición POST
response = Web.Contents(url, [
Headers = [
#"Content-Type" = "application/json",
#"Authorization" = "Bearer " & token
],
Content = cuerpo
]),
// 5. Decodifica la respuesta JSON
jsonResponse = Json.Document(response),
body = jsonResponse[body],
// 6. Convierte el resultado en tabla
tabla = Table.FromList({body}, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
expandido = Table.ExpandRecordColumn(tabla, "Column1",
{"nota_id", "tipo", "consecutivo", "estado_id", "factura_id", "factura_estado_id", "factura_saldo"},
{"NotaID", "Tipo", "Consecutivo", "EstadoID", "FacturaID", "FacturaEstadoID", "FacturaSaldo"})
in
expandido
9. Buenas prácticas y límites conocidos
Recomendaciones
- Verifica el estado de la factura antes de anotar: debe estar ACTIVA.
- Usa una referencia externa por operación y no la reutilices para otra nota.
- Guarda el
nota_idy elfactura_saldode la respuesta: te ahorran una consulta posterior. - Envía los porcentajes por ítem: los de cabecera no se usan.
- Trata el
422como reintentable tras corregir: la referencia no se consume.
Límites
- La nota queda en borrador: la emisión a la DIAN no forma parte de esta API.
- No hay edición ni anulación de notas por API.
- No se admiten notas no vinculantes.
fecha_iniyfecha_finse aceptan pero no se persisten en la nota.- No se valida el rango 0–100 de los porcentajes; envía valores válidos.
Para consultar notas ya registradas, revisa las páginas Listar Notas y Buscar Nota por Código.