Ir al contenido principal

Obtener el PDF de una Factura [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 enlace seguro de impresión del PDF de una factura de venta. El enlace lo genera nuby: apunta al visor público del documento y lleva un hash cifrado que lo protege. La ruta interna del archivo nunca se expone.

El enlace tiene una vigencia de 30 días. Después de esa fecha deja de funcionar, así que pídelo de nuevo cuando lo necesites en lugar de guardarlo como definitivo.

¿Para qué sirve este servicio?
Úsalo cuando necesites entregar el comprobante sin construir rutas ni manipular archivos. Los casos de uso más frecuentes son:

  • Enviar la factura al cliente. Adjunta el enlace en un correo, un mensaje o tu portal de autogestión.
  • Archivar el comprobante en tu sistema documental. Guarda el enlace junto al factura_id y renuévalo cuando venza.
  • Imprimir o descargar el PDF desde el navegador del usuario final, sin instalar nada.
  • Mostrar un botón "Ver factura" en tu aplicación, generando el enlace en el momento de la consulta.
  • Evitar descargas masivas de archivos. Tu integración nunca necesita credenciales de almacenamiento: nuby resuelve el documento.

1. El Endpoint (La dirección web)

Método y dirección
Dato Valor
Método GET
Recurso /invoices/{factura_id}/pdf
Cuerpo de la petición No se envía cuerpo
GET https://{{instancia}}/service/v2/public/invoices/{factura_id}/pdf
¿Qué debo colocar en {{instancia}}?

Es la dirección web principal con la que ingresas a tu plataforma, incluyendo la terminación .nuby.app o .arrendasoft.co. Si entras a nuby escribiendo inmobiliaria.nuby.app en el navegador, esa es tu instancia.

Importante: no incluyas https:// ni barras diagonales (/) al final. La dirección se compone así: https:// + tu instancia + /service/v2/public/invoices/ + el identificador + /pdf.

Encabezados requeridos
Encabezado ¿Obligatorio? Valor
Authorization Sí Bearer <token>, con el token obtenido en el servicio Login.
Content-Type No application/json si tu cliente lo envía. Como la petición no lleva cuerpo, el servicio no lo necesita.
Permiso requerido (scope): catalogs:read

Este servicio es de solo lectura y exige el permiso catalogs:read en el cliente OAuth. Si el token es válido pero al cliente le falta el permiso, la respuesta es 403 con el código RECURSO_NO_AUTORIZADO.

¿Por qué catalogs:read y no un permiso de facturas? El libro publicado de la API documenta el permiso genérico read para los endpoints de consulta anteriores (los que ya existían). Los endpoints nuevos del ciclo administrativo usan el permiso por recurso, y en tu cliente OAuth los permisos de consulta disponibles son catalogs:read, invoices:create, invoices:update, notes:create y third-parties:create. Como no existe un permiso de lectura propio de facturas, esta consulta se autoriza con catalogs:read.

Si aún no tienes un token, consulta la página Login del libro API nuby V2.

Requisitos previos de esta operación
  • La factura debe existir en nuby: si no, la respuesta es 404 FACTURA_NO_ENCONTRADA.
  • La factura debe ser de venta. Si se generó desde un contrato de arrendamiento, se rechaza con 409 FACTURA_CONTRATO_NO_SOPORTADA y warnings[].
  • El cliente OAuth del token debe tener el permiso catalogs:read.

2. La Petición (¿Qué debes enviarnos?)

Este servicio no requiere cuerpo. Solo necesitas el encabezado de autorización y el identificador de la factura en la dirección.

Dato Valor
Método GET
Content-Type application/json (opcional: la petición no tiene cuerpo)
Authorization Bearer <token>, token obtenido al consumir el servicio Login
Parámetros de la ruta (path)
Parámetro Tipo Requerido Por defecto Descripción
factura_id integer Sí — Identificador interno de la factura. Debe ser un entero mayor que 0. Va en la dirección, antes del segmento /pdf.

Si el identificador no es un entero positivo (por ejemplo /invoices/abc/pdf o /invoices/0/pdf), la respuesta es 400 con el mensaje "Debe indicar un factura_id entero mayor que 0 para operar sobre la factura." y el detalle del campo en errors.factura_id:

{
  "success": false,
  "status": 400,
  "message": "Debe indicar un factura_id entero mayor que 0 para operar sobre la factura.",
  "error_code": "VALIDATION_ERROR",
  "errors": {
    "factura_id": [
      "Debe indicar un factura_id entero mayor que 0 para operar sobre la factura."
    ]
  }
}
¿De dónde saco el factura_id?
  • De la creación de la factura (POST /invoices): la respuesta lo devuelve en body.factura_id.
  • Del listado (GET /invoices/sales/list): cada fila del body lo trae en factura_id.
  • Del detalle (GET /invoices/{factura_id}): lo devuelve en body.factura_id, junto con un pdf_link equivalente al de este servicio.

No es el número de factura. factura_id es el identificador interno de nuby; no es el consecutivo ni el número que aparece impreso. Para encontrar el factura_id correcto, usa el listado de facturas de venta.

Ejemplos de petición
GET https://mi-inmobiliaria.nuby.app/service/v2/public/invoices/10231/pdf
GET https://mi-inmobiliaria.nuby.app/service/v2/public/invoices/10230/pdf
Encabezados de una petición completa
GET /service/v2/public/invoices/10231/pdf HTTP/1.1
Host: mi-inmobiliaria.nuby.app
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...

Orden de respuesta: nuby primero revisa si la factura es de contrato (409), después si existe (404) y solo entonces genera el enlace (200). Por eso una factura de contrato nunca produce un enlace.

3. La Respuesta (¿Qué te entregaremos?)

Si el token es válido y la factura existe, el sistema devuelve un JSON con el identificador, el enlace seguro y la fecha en que el enlace deja de ser válido:

{
  "status": 200,
  "message": "",
  "body": {
    "factura_id": 10231,
    "pdf_link": "https://<dominio-publico-pdf>?hash=...",
    "expira_en": "2026-11-20"
  },
  "pagination": [],
  "filters_applied": []
}
Estructura principal de la respuesta
Campo Tipo Descripción
status integer Código del estado de la respuesta. 200 cuando la consulta fue exitosa.
message string Cadena vacía cuando la consulta fue exitosa.
body object Enlace y vigencia del PDF. En un error, este campo no existe.
pagination array Siempre una lista vacía []: esta consulta no pagina.
filters_applied array Siempre una lista vacía []: esta consulta no tiene filtros.
Campos del cuerpo (body)
Campo Tipo Descripción
factura_id integer Identificador de la factura consultada. Es el mismo valor que enviaste en la dirección.
pdf_link string Enlace seguro de impresión, listo para abrir en el navegador. Se sirve desde el dominio público de PDF de nuby e incluye un hash cifrado que valida el visor público. Contiene el identificador de la factura y una fecha límite; no contiene rutas internas de archivos.
expira_en string Fecha (AAAA-MM-DD) en la que el enlace deja de ser válido. Se calcula como la fecha de la consulta más 30 días.
El enlace lo genera nuby

Tu integración no construye la dirección del PDF ni conoce la ubicación del archivo: nuby cifra el identificador de la factura y una fecha límite, y devuelve el enlace listo para abrir en el navegador. La ruta interna nunca se expone: el visor público valida el hash y sirve el documento.

No intentes armar el enlace por tu cuenta. El hash es una firma que solo genera nuby; cualquier enlace construido a mano será rechazado por el visor. Tampoco reutilices un hash de otra factura: el identificador va dentro de la firma.

Vigencia de 30 días

nuby calcula el enlace con una vigencia de 30 días contados desde la fecha de la consulta. expira_en te dice el último día en que funciona; a partir del día siguiente, el visor deja de servir el documento.

No guardes el enlace como definitivo. Si lo almacenas más de 30 días, dejará de funcionar. Guarda el factura_id y vuelve a pedir el enlace cuando lo necesites: es una llamada barata y siempre devuelve un enlace vigente.

Cómo se usa el enlace
  • En el navegador: abre pdf_link tal como llega; el visor público valida el hash y muestra el PDF.
  • En un correo o un chat: envía el enlace completo, sin recortarlo: si se pierde parte del hash, el visor lo rechaza.
  • En tu portal: genera el enlace en el momento en que el usuario hace clic en "Ver factura", así nunca muestras un enlace vencido.

4. 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 sistema te bloquea el acceso con un error 401 y tu sistema debe volver a consumir el servicio de Login para pedir un pase nuevo.

Esta respuesta la produce el middleware de autenticación heredado (el mismo de toda la API) y no el contrato de la operación. Así se ve:

{
  "statusCode": 401,
  "error": {
    "type": "SERVER_ERROR",
    "description": "JWT Token expired."
  }
}

Ojo con la forma: los errores del middleware usan statusCode y error.description, no success ni error_code. El valor de type depende del manejador de errores de la versión desplegada (el libro publicado muestra SERVER_ERROR y el manejador actual responde UNAUTHENTICATED); lo estable es statusCode y la description.

Situación HTTP error.description exacta
No se envió el encabezado Authorization 401 JWT Token required.
El encabezado no tiene el formato Bearer {token} 400 JWT Token not send.
El token no fue encontrado en el sistema 401 JWT Token not found.
El token expiró 401 JWT Token expired.
Errores del contrato de la operación

Los errores que produce la operación misma traen success: false, el código HTTP, un message legible y un error_code estable:

{
  "success": false,
  "status": 404,
  "message": "La factura 990 no existe.",
  "error_code": "FACTURA_NO_ENCONTRADA"
}
Código HTTP error_code Cuándo ocurre y cómo resolverlo
400 VALIDATION_ERROR El factura_id de la dirección no es un entero positivo. Mensaje: Debe indicar un factura_id entero mayor que 0 para operar sobre la factura. Corrige el identificador.
401 — No enviaste el encabezado Authorization, o el token no existe o expiró. Vuelve a consumir el servicio Login. La respuesta tiene la forma del middleware (statusCode + error.description).
403 CONSUMIDOR_NO_AUTENTICADO El token es válido, pero no corresponde a un cliente OAuth habilitado para consumir la API. Mensaje: El cliente autenticado no está autorizado para consumir este endpoint.
403 RECURSO_NO_AUTORIZADO Al cliente OAuth le falta el permiso catalogs:read. Mensaje: El consumidor no tiene habilitado el recurso solicitado. Solicita el ajuste a tu administrador en nuby.
404 FACTURA_NO_ENCONTRADA No existe una factura con ese identificador. Mensaje: La factura {factura_id} no existe. Verifica el factura_id o búscalo en el listado. La respuesta no incluye warnings.
409 FACTURA_CONTRATO_NO_SOPORTADA La factura se generó desde un contrato de arrendamiento. Incluye warnings[] con el factura_id y el alcance. No se genera ningún enlace para esas facturas.
500 INTERNAL_ERROR Fallo técnico al leer la factura. Mensaje: No fue posible leer la factura de venta. Intente nuevamente o contacte al administrador del sistema. Reintenta; si persiste, contacta a soporte.
Forma exacta del rechazo 409

Una factura de contrato se rechaza con el código FACTURA_CONTRATO_NO_SOPORTADA, un mensaje que explica el motivo y una advertencia que identifica la factura y el alcance del endpoint:

{
  "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"
    }
  ]
}
Campo de warnings[] Tipo Significado
code string Siempre FACTURA_CONTRATO_NO_SOPORTADA. Es el valor que debes evaluar en tu código.
factura_id integer Identificador de la factura rechazada.
scope string Siempre solo_facturas_de_venta: confirma que el rechazo es por alcance, no por permisos.

El enlace es la credencial del documento. Quien tenga el enlace puede abrir el PDF durante su vigencia, así que trátalo como información sensible: no lo publiques en sitios abiertos ni lo registres en logs de acceso. Si se filtra, deja de ser válido a los 30 días sin que tengas que hacer nada.

Trata el 404 como un caso de negocio, no como una falla técnica: significa que el identificador no existe en nuby. No lo reintentes con el mismo valor.


5. Ejemplos de integración

Aquí tienes ejemplos listos para que tus desarrolladores los adapten a tu plataforma. Todos obtienen el enlace y tratan los dos rechazos de negocio más frecuentes (404 y 409).

cURL
TOKEN="TU_TOKEN_AQUI"
INSTANCIA="mi-inmobiliaria.nuby.app"
FACTURA_ID=10231

curl "https://${INSTANCIA}/service/v2/public/invoices/${FACTURA_ID}/pdf" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${TOKEN}"
PHP
<?php
$instancia = 'mi-inmobiliaria.nuby.app'; // Tu dirección web completa
$token     = 'TU_TOKEN_AQUI';            // Token obtenido del servicio Login
$facturaId = 10231;

$url = "https://{$instancia}/service/v2/public/invoices/{$facturaId}/pdf";

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    "Authorization: Bearer {$token}",
]);

$respuesta = curl_exec($ch);
$httpCode  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$info = json_decode($respuesta, true);

if ($httpCode === 200) {
    $enlace = $info['body']['pdf_link'];

    echo "Enlace del PDF: {$enlace}\n";
    echo "Válido hasta: {$info['body']['expira_en']}\n";

    // Guarda el identificador, no el enlace: el enlace vence a los 30 días.
    // file_put_contents('factura_pdf.txt', $enlace);
} elseif ($httpCode === 404) {
    echo "La factura no existe en nuby: {$info['message']}\n";
} elseif ($httpCode === 409) {
    echo "Factura de contrato, no soportada por esta API: {$info['message']}\n";
} else {
    echo "Error HTTP {$httpCode}: {$info['message']}\n";
}
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

# 2. Envía la petición
url = f"https://{instancia}/service/v2/public/invoices/{factura_id}/pdf"
headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {token}",
}

respuesta = requests.get(url, headers=headers, timeout=30)

# 3. Procesa la respuesta
if respuesta.status_code == 200:
    body = respuesta.json()["body"]
    print(f"Enlace del PDF: {body['pdf_link']}")
    print(f"Válido hasta: {body['expira_en']}")
elif respuesta.status_code in (404, 409):
    error = respuesta.json()
    print(f"Rechazo de negocio {error['error_code']}: {error['message']}")
else:
    print(f"Error HTTP {respuesta.status_code}: {respuesta.text}")
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

// 2. Función que obtiene el enlace del PDF
async function obtenerEnlacePdf(facturaId) {
  const url = `https://${instancia}/service/v2/public/invoices/${facturaId}/pdf`;

  const response = await fetch(url, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
  });

  const info = await response.json().catch(() => ({}));

  if (!response.ok) {
    console.error(`Error HTTP ${response.status} ${info.error_code ?? ''}: ${info.message ?? ''}`);
    return null;
  }

  return info.body;
}

// 3. Consume el enlace y ábrelo en una pestaña nueva
obtenerEnlacePdf(10231).then((body) => {
  if (!body) {
    return;
  }

  console.log(`Enlace: ${body.pdf_link}`);
  console.log(`Válido hasta: ${body.expira_en}`);

  // window.open(body.pdf_link, '_blank', 'noopener');
});
Power Query M (Excel / Power BI)
let
    // 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
    facturaId = "10231",                    // Identificador interno de la factura

    // 2. Prepara la dirección de la petición
    url = "https://" & instancia & "/service/v2/public/invoices/" & facturaId & "/pdf",

    // 3. Envía la petición GET
    response = Web.Contents(url, [
        Headers = [
            #"Content-Type" = "application/json",
            #"Authorization" = "Bearer " & token
        ]
    ]),

    // 4. Decodifica la respuesta JSON y toma el cuerpo
    jsonResponse = Json.Document(response),
    body = jsonResponse[body],

    // 5. Convierte el enlace y su vigencia en una tabla de una fila
    tabla = Table.FromRecords({[
        FacturaID = body[factura_id],
        PdfLink = body[pdf_link],
        ExpiraEn = Date.FromText(body[expira_en])
    ]})
in
    tabla

Sobre Power Query M: la columna PdfLink queda como texto; Excel y Power BI no descargan el PDF al actualizar la consulta, solo guardan el enlace. Si necesitas el archivo, abre el enlace desde el navegador mientras esté vigente.

6. Buenas prácticas y límites conocidos

Recomendaciones
  • Pide el enlace justo cuando lo necesites. Así siempre está vigente y no tienes que renovarlo.
  • Guarda el factura_id, no el enlace. El identificador no expira; el enlace sí.
  • Revisa expira_en antes de enviar el enlace a un tercero. Un enlace vencido se ve como un error del visor, no de tu integración.
  • Genera el enlace en el momento del clic si lo vas a mostrar en un portal: evitas enlaces muertos en la interfaz.
  • Trata el enlace como información sensible. Quien lo tenga puede abrir el documento mientras esté vigente.
  • No registres datos personales en tus logs. Para trazar la consulta te basta con el factura_id.
Límites de este servicio
  • Solo devuelve el enlace. No devuelve el archivo ni el contenido del PDF.
  • No expone la ruta interna del archivo. Solo el enlace público con su hash cifrado.
  • Solo facturas de venta. Las de contrato se rechazan con 409 FACTURA_CONTRATO_NO_SOPORTADA y no producen enlace.
  • El enlace vence a los 30 días. No hay renovación automática: se pide de nuevo.
  • No permite elegir formato ni plantilla. El PDF es el comprobante que nuby genera para la factura.
  • No pagina. Es una consulta de un solo documento: pagination y filters_applied vienen como listas vacías.
  • No requiere que la factura esté emitida. Un borrador también produce enlace, pero el documento refleja su estado actual (sin CUFE mientras no se haya enviado a la DIAN).

¿Necesitas más datos que el enlace? El detalle de la factura devuelve estado, valores, CUFE y el detalle de ítems, e incluye el mismo pdf_link. Si buscas el identificador de otra factura, usa el listado de facturas de venta.

¿Necesita ayuda adicional?

Nuestro equipo de soporte está disponible para asistirle:

Escríbenos a nuestro correo:

📞 (604) 444-7417 Opción 2

📧 soporte@nuby.ai

Escríbenos a nuestra línea de Whatsapp:

Whatsapp

Síguenos en nuestras redes sociales: